I am trying to use Cake's built in MSBuild functionality to only build a specific Target (i.e. Compile). Using the example at: https://cakebuild.net/api/Cake.Common.Tools.MSBuild/MSBuildAliases/C240F0FB
var settings = new MSBuildSettings()
{
Verbosity = Verbosity.Diagnostic,
ToolVersion = MSBuildToolVersion.VS2017,
Configuration = "Release",
PlatformTarget = PlatformTarget.MSIL
};
settings.WithTarget("Compile");
MSBuild("./src/Cake.sln", settings);
But it seems to build all targets, where as i would like to only build a specific target, as detailed in: https://msdn.microsoft.com/en-us/library/ms171486.aspx
As per the documentation here:
https://cakebuild.net/api/Cake.Common.Tools.MSBuild/MSBuildSettingsExtensions/01F8DC03
The WithTarget extension method returns the same instance of the MSBuildSettings with the modifications, it doesn't interact with the current instance. As a result, where you have:
settings.WithTarget("Compile");
Is actually not doing anything. However, if you do this:
var settings = new MSBuildSettings()
{
Verbosity = Verbosity.Diagnostic,
ToolVersion = MSBuildToolVersion.VS2017,
Configuration = "Release",
PlatformTarget = PlatformTarget.MSIL
};
MSBuild("./src/Cake.sln", settings.WithTarget("Compile");
It should work how you intend it.
To help with this sort of thing, you can run Cake in diagnostic mode, to see exactly what command is being sent to the command line for execution. You can find more about that in this related question:
How to enable diagnostic verbosity for Cake
Related
I am trying to get some net core 2.1 projects to build on a new build server that we have. We have installed Visual studio tools for 2017 and 2019.
I am getting this error when it tried to build it via our TFS build process. We use cake scripts to build the code.
C:\Program Files\dotnet\sdk\6.0.102\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets(54,5): error MSB4186: Invalid static method invocation syntax: "[MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')". [MSBuild]::GetTargetFrameworkIdentifier Static method invocation should be of the form: $([FullTypeName]::Method()), e.g. $([System.IO.Path]::Combine(`a`, `b`)). [D:\Agents\EROS-006\_work\2\s\src\Cases.CommandHandlers\Cases.CommandHandlers.csproj]
Is it something to do with the csproj contents? We have this declared at the top as this bit of the message stands out
GetTargetFrameworkIdentifier Static method invocation should be of the form: $([FullTypeName]::Method())
The csproj version details:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.4</RuntimeFrameworkVersion>
<TargetLatestAspNetCoreRuntimePatch>True</TargetLatestAspNetCoreRuntimePatch>
</PropertyGroup>
...
I have searched for an answer but seem to point to mono related things, which we don't use at all, its a windows machine with the visual studio tools installed as mentioned above.
The cake build part looks like this
Task("Build")
.IsDependentOn("Version")
.Does(() =>
{
var settings = new DotNetCoreRestoreSettings()
{
Sources = packageSources
};
DotNetCoreRestore(settings);
if(useLatestMsBuild){
MSBuild(solution, new MSBuildSettings {
Configuration = configuration,
MaxCpuCount = maxcpucount,
ArgumentCustomization = args => args
.Append("/p:Version=" + versionInfo.InformationalVersion.Replace("/", "-"))
.Append("/p:AssemblyVersion=" + versionInfo.AssemblySemVer)
.Append("/p:FileVersion=" + versionInfo.AssemblySemVer)
});
}else{
MSBuild(solution, new MSBuildSettings {
Configuration = configuration,
MaxCpuCount = maxcpucount,
ToolVersion = MSBuildToolVersion.VS2017,
Restore = true,
ArgumentCustomization = args => args
.Append("/p:Version=" + versionInfo.InformationalVersion.Replace("/", "-"))
.Append("/p:AssemblyVersion=" + versionInfo.AssemblySemVer)
.Append("/p:FileVersion=" + versionInfo.AssemblySemVer)
});
}
});
Looking at further build steps in cake and powershell there was mention of it compiling with mono instead of the default. Updating cake to version 2.0 and rewiting it from the ground up seems to resolve the issue.
Installing Visual Studio Build Tools 2022 and latest nuget.exe added to the environmental variable PATH seemed to help. Ideally installing everything that the build server has on it and running the same scripts locally helped a lot.
my question does not target a problem. It is more some kind of "Do you know something that...?". All my applications are built and deployed using CI/CD with Azure DevOps. I like to have all build information handy in the create binary and to read them during runtime. Those applications are mainly .NET Core 2 applications written in C#. I am using the default build system MSBuild supplied with the .NET Core SDK. The project should be buildable on Windows AND Linux.
Information I need:
GitCommitHash: string
GitCommitMessage: string
GitBranch: string
CiBuildNumber: string (only when built via CI not locally)
IsCiBuild: bool (Detecting should work by checking for env variables
which are only available in CI builds)
Current approach:
In each project in the solution there is a class BuildConfig à la
public static class BuildConfig
{
public const string BuildNumber = "#{Build.BuildNumber}#"; // Das sind die Namen der Variablen innerhalb der CI
// and the remaining information...
}
Here tokens are used, which get replaced with the corresponding values during the CI build. To achieve this an addon task is used. Sadly this only fills the values for CI builds and not for the local ones. When running locally and requesting the build information it only contains the tokens as they are not replaced during the local build.
It would be cool to either have the BuildConfig.cs generated during the build or have the values of the variables set during the local build (IntelliSense would be very cool and would prevent some "BuildConfig class could not be found" errors). The values could be set by an MSBuild task (?). That would be one (or two) possibilities to solve this. Do you have ideas/experience regarding this? I did not found that much during my internet research. I only stumbled over this question which did not really help me as I have zero experience with MSBuild tasks/customization.
Then I decided to have a look at build systems in general. Namly Fake and Cake. Cake has a Git-Addin, but I did not find anything regarding code generation/manipulation. Do you know some resources on that?
So here's the thing...
Short time ago I had to work with Android apps namly Java and the build system gradle. So I wanted to inject the build information there too during the CI build. After a short time I found a (imo) better and more elegant solution to do this. And this was modifying the build script in the following way (Scripting language used is Groovy which is based on Java):
def getGitHash = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().replace("\"", "\\\"")
}
def getGitBranch = { ->
def fromEnv = System.getenv("BUILD_SOURCEBRANCH")
if (fromEnv) {
return fromEnv.substring("refs/heads/".length()).replace("\"", "\\\"");
} else {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().replace("\"", "\\\"")
}
}
def getIsCI = { ->
return System.getenv("BUILD_BUILDNUMBER") != null;
}
# And the other functions working very similar
android {
# ...
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
buildConfigField "String", "GitBranch", "\"${getGitBranch()}\""
buildConfigField "String", "BuildNumber", "\"${getBuildNumber()}\""
buildConfigField "String", "GitMessage", "\"${getGitCommitMessage()}\""
buildConfigField "boolean", "IsCIBuild", "${getIsCI()}"
# ...
}
The result after the first build is the following java code:
public final class BuildConfig {
// Some other fields generated by default
// Fields from default config.
public static final String BuildNumber = "Local Build";
public static final String GitBranch = "develop";
public static final String GitHash = "6c87e82";
public static final String GitMessage = "Merge branch 'hotfix/login-failed' into 'develop'";
public static final boolean IsCIBuild = false;
}
Getting the required information is done by the build script itself without depending on the CI engine to fulfill this task. This class can be used after the first build its generated and stored in a "hidden" directory which is included in code analysis but exluded from your code in the IDE and also not pushed to the Git. But there is IntelliSense support. In C# project this would be the obj/ folder I guess. It is very easy to access the information as they are a constant and static values (so no reflection or similar required).
So here the summarized question: "Do you know something to achieve this behaviour/mechanism in a .NET environment?"
Happy to discuss some ideas/approaches... :)
It becomes much easier if at runtime you are willing to use reflection to read assembly attribute values. For example:
using System.Reflection;
var assembly = Assembly.GetExecutingAssembly();
var descriptionAttribute = (AssemblyDescriptionAttribute)assembly
.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false).FirstOrDefault();
var description = descriptionAttribute?.Description;
For most purposes the performance impact of this approach can be satisfactorily addressed by caching the values so they only need to read once.
One way to embed the desired values into assembly attributes is to use the MSBuild WriteCodeFragment task to create a class file that sets assembly attributes to the values of project and/or environment variables. You would need to ensure that you do this in a Target that executes before before compilation occurs (e.g. <Target BeforeTargets="CoreCompile" ...). You would also need to set the property <GenerateAssemblyInfo>false</GenerateAssemblyInfo> to avoid conflicting with the functionality referenced in the next option.
Alternatively, you may be able to leverage the plumbing in the dotnet SDK for including metadata in assemblies. It embeds the values of many of the same project variables documented for the NuGet Pack target. As implied above, this would require the GenerateAssemblyInfo property to be set to true.
Finally, consider whether GitVersion would meet your needs.
Good luck!
I am trying to figure out which project is enabled/disabled in respective build configuration/platform setup. Where could I find this "project.BuildsInCurrentConfiguration" information please?
var properties = new Dictionary<string, string>
{
{ "Configuration", "Debug" },
{ "Platform", "x86"}
};
MSBuildWorkspace workspace = MSBuildWorkspace.Create(properties);
workspace.LoadMetadataForReferencedProjects = true;
Solution solution = workspace.OpenSolutionAsync("someSolution.sln").Result;
foreach (Project project in solution.Projects)
Console.Out.WriteLine($"{project.OutputFilePath} is enabled in this build setup: {project.BuildsInCurrentConfiguration}");
workspace.CloseSolution();
I would have thought I wouldn't be offered the projects that are not part of the picked configuration/platform, but solution.Projects shows me all of them regardless build setup.
I don't think Roslyn really has most of that information right now (I'm not sure if it ever would; but I would hope it would). I don't see anything related to a "configuration" for a project with the Roslyn APIs for example. That seems to be delegated to the DTE interfaces. You can get at platform type in a Roslyn project, so conceptually you could only get projects that would apply to a given type of build:
var rolsynProjects = solution.Projects
.Where(p => p.CompilationOptions.Platform == Platform.X86);
but, things like "DEBUG" configuration seem to only be available via DTE--which isn't that hard to get at. e.g.
var project = DTE.Solution.Projects
.Where(p=>p.FullName == rolsynProjects.First().FilePath).FirstOrDefault();
And from that VS project, you can get at its ConfigurationManager
I need to compile source code of big project dynamically and output type can be Windows Application or Class Library.
Code is nicely executed and its possible to make .dll or .exe files, but problem is that, when I'm trying to make .exe file - it's losing resources like project icon. Result file doesn't include assembly information to.
Any way to solve this? (Expected result should be the same, that manual Build function on project file in Visual Studio 2015).
Thank you!
var workspace = MSBuildWorkspace.Create();
//Locating project file that is WindowsApplication
var project = workspace.OpenProjectAsync(#"C:\RoslynTestProjectExe\RoslynTestProjectExe.csproj").Result;
var metadataReferences = project.MetadataReferences;
// removing all references
foreach (var reference in metadataReferences)
{
project = project.RemoveMetadataReference(reference);
}
//getting new path of dlls location and adding them to project
var param = CreateParamString(); //my own function that returns list of references
foreach (var par in param)
{
project = project.AddMetadataReference(MetadataReference.CreateFromFile(par));
}
//compiling
var projectCompilation = project.GetCompilationAsync().Result;
using (var stream = new MemoryStream())
{
var result = projectCompilation.Emit(stream);
if (result.Success)
{
/// Getting result
//writing exe file
using (var file = File.Create(Path.Combine(_buildPath, fileName)))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
}
We never really designed the workspace API to include all the information you need to emit like this; in particular when you're calling Emit there's an EmitOptions you can pass that includes, amongst other things, resource information. But we don't expose that information since this scenario wasn't hugely considered. We've done some of the work in the past to enable this but ultimately never merged it. You might wish to consider filing a bug so we officially have the request somewhere.
So what can you do? I think there's a few options. You might consider not using Roslyn at all but rather modifying the project file and building that with the MSBuild APIs. Unfortunately I don't know what you're ultimately trying to achieve here (it would help if you mentioned it), but there's a lot more than just the compiler invocation that is involved in building a project. Changing references potentially changes other things too.
It'd also be possible, of course, to update MSBuildWorkspace yourself to pass this through. If you were to modify the Roslyn code, you'll see we implement a series of interfaces named "ICscHostObject#" (where # is a number) and we get passed the information from MSBuild to that. It looks like we already stash that in the command line arguments, so you might be able to pass that to our command line parser and get the data back you need that way.
We are using TeamCity for continuous integration, our source control is Git, and we have 1 major repository that contains multiple .sln files (around 10).
All in all, this repository has about ~ 100 - 200 C# projects.
Upon a push to the master repository, TeamCity triggers a build that will compile all projects in the repository.
I'd like to be able to tell which projects were actually affected by a particular commit, and thus publish only those projects' outputs as artifacts of the current build.
For this, i've designed a solution to integrate NDepend into our build process, and generate a diff report between current and latest build outputs.
The outputs that were changed/added will be published as the build outputs.
I have little experience with NDepend; from what i've seen all of its true power comes from the query language that is baked into it.
I am wondering how (if possible) i can achieve the following:
Diff between a folder containing previous build's outputs and current folder of build outputs.
Have NDepend generate a report in a consumable format so i can determine the files that need to be copied.
Is this scenario possible? How easy/hard would that be?
So the simple answer is to do the Reporting Code Diff way as explained in this documentation. The problem with this basic answer is that, it pre-suppose two NDepend projects that always refers to the two same set of assemblies.
Certainly, the number and names of assemblies is varying in your context so we need to build two projects (old/new) on the fly and analyze them through NDepend.API.
Here is the NDepend.API source code for that. For a It-Just-Works experience, in the PowerTools source code (in $NDependInstallDir$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln) just call the FoldersDiff.Main(); method after the AssemblyResolve registration call, in Program.cs.
...
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolverHelper.AssemblyResolveHandler;
FoldersDiff.Main();
...
Here is the the source code that harnesses NDepend.API.
Note that so much more can be done, through the two codeBase objects and the compareContext object. Instead of just showing the 3 lists of assemblies added/removed/codeWasChanges, you could show API breakings changes, new methods and types added, modified classes and methods, code quality regression... For that, just look at default code rules concerning diff, that are based on the same NDepend.CodeModel API.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NDepend;
using NDepend.Analysis;
using NDepend.CodeModel;
using NDepend.Path;
using NDepend.Project;
class FoldersDiff {
private static readonly NDependServicesProvider s_NDependServicesProvider = new NDependServicesProvider();
internal static void Main() {
var dirOld = #"C:\MyProduct\OldAssembliesDir".ToAbsoluteDirectoryPath();
var dirNew = #"C:\MyProduct\NewAssembliesDir".ToAbsoluteDirectoryPath();
Console.WriteLine("Analyzing assemblies in " + dirOld.ToString());
var codeBaseOld = GetCodeBaseFromAsmInDir(dirOld, TemporaryProjectMode.TemporaryOlder);
Console.WriteLine("Analyzing assemblies in " + dirNew.ToString());
var codeBaseNew = GetCodeBaseFromAsmInDir(dirNew, TemporaryProjectMode.TemporaryNewer);
var compareContext = codeBaseNew.CreateCompareContextWithOlder(codeBaseOld);
// So much more can be done by exploring fine-grained diff in codeBases and compareContext
Dump("Added assemblies", codeBaseNew.Assemblies.Where(compareContext.WasAdded));
Dump("Removed assemblies", codeBaseOld.Assemblies.Where(compareContext.WasRemoved));
Dump("Assemblies with modified code", codeBaseNew.Assemblies.Where(compareContext.CodeWasChanged));
Console.Read();
}
internal static ICodeBase GetCodeBaseFromAsmInDir(IAbsoluteDirectoryPath dir, TemporaryProjectMode temporaryProjectMode) {
Debug.Assert(dir.Exists);
var dotNetManager = s_NDependServicesProvider.DotNetManager;
var assembliesPath = dir.ChildrenFilesPath.Where(dotNetManager.IsAssembly).ToArray();
Debug.Assert(assembliesPath.Length > 0); // Make sure we found assemblies
var projectManager = s_NDependServicesProvider.ProjectManager;
IProject project = projectManager.CreateTemporaryProject(assembliesPath, temporaryProjectMode);
// In PowerTool context, better call:
// var analysisResult = ProjectAnalysisUtils.RunAnalysisShowProgressOnConsole(project);
var analysisResult = project.RunAnalysis();
return analysisResult.CodeBase;
}
internal static void Dump(string title, IEnumerable<IAssembly> assemblies) {
Debug.Assert(!string.IsNullOrEmpty(title));
Debug.Assert(assemblies != null);
Console.WriteLine(title);
foreach (var #assembly in assemblies) {
Console.WriteLine(" " + #assembly.Name);
}
}
}