How to build dependent project first with msbuild - c#

I have just started looking into msbuild, because I want to make my own build scripts. For now I am able to create build scripts that compiles only one project, but how do I handle dependencies?
For example what if I have two projects that gets build with these two msbuild scripts?
projectA.xml
projectB.xml
How do I tell msbuild that when I am executing projectB.xml that it should first execute projectA.xml?
I have googled alot on this, but it does not seem to get anything that a starter like me understands. I would be more than happy with a link to an article describing this, or maybe just a small code example.
The reason why I want this control is because of a library I am building. The library consists of several projects. A developer should be able to pull the source code for the library down and build only the libraries that he wants.
Actually I want to be able to build .net modules from the different projects. That is why I want to be able to run a customized msbuild script.

If you create a solution with the two projects you can target the .sln file with msbuild, rather than directly building the projects, it should take care of project dependencies :)
But that's if you're using standard .csproj projects...
Ok I looked at a project I'm working on, and it's like this:
<ItemGroup>
<ProjectReference Include="..\SomeFolder\SomeProject.csproj">
<Project>{1A94B405-2D01-4A09-90D5-A5B31180A03B}</Project>
<Name>SomeProjectNamespace</Name>
</ProjectReference>
</ItemGroup>
And here's an MSDN page about references. Scroll down till you find ProjectReference...

I setup my build scripts so that I have a few common targets that do not do anything, but use DependsOnTargets to setup project dependencies and run the build.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- ************************************************************************************************ -->
<!-- Targets that run the builds -->
<!-- ************************************************************************************************ -->
<Target Name="AutoBuild" DependsOnTargets="BuildProject1;BuildProject2;BuildInstallers">
<OnError ExecuteTargets="NotifyFailure" />
</Target>
<Target Name="FullCompile" DependsOnTargets="BuildProject1;BuildProject2">
<OnError ExecuteTargets="NotifyFailure" />
</Target>
<!-- Build Project 1 -->
<Target Name="BuildProject1">
<!-- Use MSBuild task and point it to build project1.csproj, project1.sln or whatever your projects is -->
</Target>
<!-- Build Project 2 -->
<Target Name="BuildProject2">
<!-- Use MSBuild task and point it to build project2.csproj, project2.sln or whatever your projects is -->
</Target>
<Target Name="BuildInstallers">
<!-- Whatever logic you have for building installers -->
</Target>
</Project>

In MSBuild issue #2887 a similar situation is discussed. The thread also reveals a link to official ProjectReference Protocol.

You dont need to build using the sln. If you use project references in your csproj then the dependency order is taken care of by MSBuild.
Try it. Automajically.
You do not need to sort the dependency order in your msbuild script.

Related

Have a set of scripts run in C# Projects

I am a web developer moving to the C#/.NET world. In npm I usually write the scripts I will need for development (run unit tests/local server/compilation) in the package.json file. Its easy to access, and it is very helpful for CI/CD pipelines.
My question is, are there any alternatives similar to this in C#/.NET, or are there any industry standards for where and how to store these scripts needed for development/deployment.
For dependencies management, paket (https://fsprojects.github.io/Paket/) is used.
I do not work at a company has web technologies as its core business,
so there are maybe better solutions.
In .NET however project files are built with MSBuild (the build engine).
You can directly edit the project file(s) and add commands, copy files, etc.
For example in project.csproj i added a section to automate api documentation (swagger/openapi3) :
<!-- Enriches the swagger/openapi specification with documentation from xml -->
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- The following target generates the files according to the Nswag configuration file(s?) present in the project. -->
<!-- NOTE do not use the project file in the nswag configuration when using NSwag.Build, this causes a Build loop, spawning a bunch of dotnet processes. Use the assembly! See: https://github.com/RicoSuter/NSwag/issues/1445-->
<Target Name="NSwag" AfterTargets="Build">
<Copy SourceFiles="#(ReferencePath)" DestinationFolder="$(OutDir)References" />
<Message Text="Performing cmd: $(NSwagExe_Core30) run /variables:Configuration=$(Configuration),OutDir=$(OutDir) /runtime:NetCore30 /nobuild:true" />
<Exec Command="$(NSwagExe_Core30) run /variables:Configuration=$(Configuration),OutDir=$(OutDir) /runtime:NetCore30 /nobuild:true" ConsoleToMSBuild="true" />
<RemoveDir Directories="$(OutDir)References" />
</Target>
</Project>
Now this gets ran each time I build.
Maybe you only want certain operations to run when you actually publish the project? well you can do so with publish profiles:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/visual-studio-publish-profiles?view=aspnetcore-3.1#run-a-target-before-or-after-publishing
Additionally we also have a build server that does a lot of this for us. We for example don't package the application on each build, but the buildserver does this with a post-build script (in addition to various other tasks).
We use devops for this;
https://learn.microsoft.com/en-us/aspnet/core/azure/devops/cicd?view=aspnetcore-3.1
I think it would be helpful to get a good idea of what you want to do when, and pick a strategy accordingly.
Once again - There may be other ways but this is how we do it.

How to properly package a Nuget distributed custom tool that generates source code as part of the build

Thanks to this awesome article by Nate McMaster, I know how to package a .NET core console application as a Nuget package that automatically installs itself as a (pre, in this instance) build task.
To test if everything works, I simply had my custom tool write out a public C# class.
Here is the complete and runnable sample on Github.
However, the file that my custom tool adds isn't really part of the build (the first one that actually generates the file) and therefore the introduced class is not in the assembly after the first build (see Line 38 here). However, because the .NET core projects now automatically include all .cs files alongside the project, it builds the new class into the output on subsequent builds (see Line 57 here).
The generated files don't go away on clean, though and generally don't behave like something an MSBuild task would output. However, because the exec happens in a targets file, we ought to have access to all the machinery to make this happen. So my question is:
How do I correctly execute a custom build tool (console app) that needs to examine the project, its files and generate source code (preferably in obj/ as say <foo>.g.cs that gets compiled into the resulting assembly as part of a single build? Ideally, this generated file(s) shouldn't appear in the solution explorer, either.
Help!
When generating the intermediate file (CustomTool.g.cs) in the intermediate folder (you'll need to resolve it, see example in Refit library: https://github.com/reactiveui/refit/blob/5b4e14aaf8a1fcc27396b7c08171d100aba1b97d/Refit/targets/refit.targets#L11); you need to explicitly add it as a compile item.
Taking your example targets file (https://github.com/aniongithub/CustomTool/blob/master/CustomTool/RunCustomTool.targets#L13):
<Project>
<PropertyGroup>
<IntermediateOutputPath Condition="$(IntermediateOutputPath) == '' Or $(IntermediateOutputPath) == '*Undefined*'">$(MSBuildProjectDirectory)obj\$(Configuration)\</IntermediateOutputPath>
<!-- Command to invoke CustomTool -->
<CustomTool>dotnet "$(MSBuildThisFileDirectory)/netcoreapp2.2/CustomTool.dll"</CustomTool>
<!-- Other variables -->
<CustomVariable>"$(MSBuildProjectDir)"</CustomVariable>
</PropertyGroup>
<Target Name="CustomTool" BeforeTargets="CoreCompile" DependsOnTargets="PrepareForBuild">
<Exec Command="$(CustomTool) $(ProjectPath) $(IntermediateOutputPath)CustomTool.g.cs" />
<!-- add generated file as a compile item, otherwise it won't get picked up -->
<ItemGroup Condition="Exists('$(IntermediateOutputPath)\CustomTool.g.cs')">
<Compile Include="$(IntermediateOutputPath)\CustomTool.g.cs" />
</ItemGroup>
</Target>
</Project>

project.assets.json not found with msbuild

I have an ASP.NET Core application that i wish to build on a jenkins machine with MSBuild 15.
When i try to build i get the following error:
C:\Program Files\dotnet\sdk\2.1.502\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(198,
5): error NETSDK1004: Assets file 'C:\sync\Src\Util\myUtil\ob
j\project.assets.json' not found. Run a NuGet package restore to generate this file
I understand that i need to do nuget restore somehow, but i failed to make it work.
My build process:
Running a batch filed with the following command:
call "%VS150COMNTOOLS%VsDevCmd.bat"
MSBuild DailyBuild.proj /t:DailyBuild /p:VersionNumber=%2 /l:FileLogger,Microsoft.Build.Engine;logfile=Build.log
The DailyBuild.proj file look like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SourcesPath>$(MSBuildProjectDirectory)\..\..\</SourcesPath>
<CSCleanProperties>BuildType=Clean;Configuration=Release;IsFormalBuild=true</CSCleanProperties>
<CSBuildProperties>BuildType=ReBuild;Configuration=Release;PauseBuildOnError=false;PublishWebSites=true;VersionName=myProd-$(VersionNumber)</CSBuildProperties>
</PropertyGroup>
<Target Name="DailyBuildWithClean">
<MSBuild Projects="$(MSBuildProjectDirectory)\Make.proj" Targets="Clean" Properties="$(CSCleanProperties)"/>
<MSBuild Projects="$(MSBuildProjectDirectory)\Make.proj" Properties="$(CSCleanProperties)"/>
<MSBuild Projects="$(MSBuildProjectDirectory)\Make.proj" Targets="FormalBuild" Properties="$(CSBuildProperties)"/>
</Target>
<Target Name="DailyBuild">
<MSBuild Projects="$(MSBuildProjectDirectory)\Make.proj" Targets="SW;PreparePackFolder" Properties="$(CSBuildProperties)"/>
</Target>
</Project>
The Make.proj is a proj file containing definitions for many applications to be built, one of them is my ASP.NET Core app.
How do i fix this problem? thank you.
SOLUTION EDIT:
Thanks to solution by Martin Ullrich:
Added in the DailyBuild.proj the target Restore, also added in the Make.proj a target called restore as suggested
(IE:
<Target Name="Restore">
<MSBuild Projects="$(SourcesPath)\my.sln" Targets="Restore" />
</Target>
)
Add -r (-restore//Restore) to your MSBuild command to trigger a restore before the main build.
The restore parameter will build the Restore target, clear internal caches and then run the rest of the build as specified.
Since you build a custom MSBuild project, you then need to add a Restore target to it:
<Target Name="Restore">
<MSBuild Projects="$(SourcesPath)\my.sln" Targets="Restore" />
</Target>
(or alternatively add another Restore target on the make.proj file and forward it from there to the solution or individual projects that you need to be restored)
Be careful using Restore Nuget packages directly in MS build task configuration.
This option is deprecated, as mentioned here for Azure DevOps. (However, I am not sure how context-dependent that is.)
(Important) This option is deprecated. Make sure to clear this checkbox and instead use the NuGet Installer build step.
Source Link: MSBuild
However, I already used that step (in TFS), so this obviously would not fix it for me.
I tried removing the packages-folder in Source Control Explorer as mentioned here, but that did not fix it either.
Inspired by this, I upgraded the TFS NuGet Installer build step to use Nuget 4.0 (in "Advanced" options), and that did fix it. (Maybe in combination with the removal of the packages-folder?)
Simply rebuild your project in another location(directory/folder ) and run your solution it works 100%.

Force TFS buildserver to compile csproj files in a specific order

after applying the suggested changes from this answer my buildserver is unable to build the affected solutions.
The problem is that the .NET 4.0 and the .NET 4.5 projects are compiled after eachother.
As an example let's assume that I've 4 projects: a.csproj, b.csproj, c.csproj. All projects have a .NET 4.0 and a .NET 4.5 version, meaning that I've a.csproj, a_40.csproj, b.csproj, b_40.csproj, c.csproj and c_40.csproj. Some of those projects depend on another project inside the solution. So for example b*.csproj depends on a*.csproj. Also the output library of a.csproj and a_40.csproj have the same name: a.dll.
Now instead of going the sane way and compiling all 4.0 assemblies and then all 4.5 assemblies, TFS, as usual, chooses the insane way and builds everything in a completely randomized order while using the same output folder for all projects. Which of course fails.
I guess there are at least two solutions to that problem:
Instead of throwing everything in the same build folder, use one folder for each project or at least one per .NET version. (as Visual Studio does)
Force the TFS to build the projects in a specific sequence (all .NET 4.0 assemblies first for example).
The problem: I've no idea how to do that.
My question: Can someone show me how to make either of my two suggested solutions work or show me another solution to this problem?
Found the solution: https://stackoverflow.com/a/1027551/937093
By adding two build configurations to all affected solutions (one for building the .NET 4 projects and one for building the .NET 4.5 projects) and creating two builddefinitions in TFS I got the thing working the way I wanted it.
I don't use TFS as build machine (I prefer Jenkins or other CI tool), but all servers allows you to create your own MSBuild Script to do your builds. A .csproj IS a MSBuild script.
If you create your own script you can setup your preferred order. Anyway, using .sln usually solves the dependencies correctly.
If you want to dig deeper in MSBuild, take a look at the reference: http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx
A sample script would look like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TextToSay>This is a property</TextToSay>
</PropertyGroup>
<ItemGroup>
<Binaries Include="$(MSBuildProjectDirectory)\..\MSBuildTests\**\*.dll" />
</ItemGroup>
<UsingTask TaskName="GenerateDumbFiles" AssemblyFile="$(MSBuildProjectDirectory)\..\MSBuildTests\MSBuildTests.Tasks\bin\Debug\MSBuildTests.Tasks.dll" />
<Target Name="SaySomething">
<Message Text="$(TextToSay)" />
</Target>
<Target Name="Build">
<Delete Files="#(Binaries)" />
<MSBuild Projects="$(MSBuildProjectDirectory)\..\MSBuildTests\MSBuildTests.sln" />
<MakeDir Directories="$(MSBuildProjectDirectory)\..\dumbfiles" />
<GenerateDumbFiles Directory="$(MSBuildProjectDirectory)\..\dumbfiles" Prefix="DumbFile" Count="100" />
</Target>
<Target Name="TransformLog">
<XslTransformation XmlInputPaths="$(MSBuildProjectDirectory)\msbuild-output.xml" XslInputPath="$(MSBuildProjectDirectory)\msbuild.xsl" OutputPaths="$(MSBuildProjectDirectory)\log.html" />
</Target>
The MSBuild task is used to compile stuff in .NET.

MSBuild Post-Build

I've got an MSBuild script that is just about doing everything that I need it to do apart from my post-build step (see a previous question that I asked: MSBuild conditional Exec?).
What I'm looking to do is build many csproj files and optionally perform post-build steps if and only if the project was built. I don't want to perform my post-build step all the time or else the timestamp on my final output will be modified unnecessarily (and it makes the build process very time consuming for no reason).
In my MSBuild script I've got something like the following for each of my csproj files:
<Target Name="ProjectName">
<MSBuild Projects="PathToProject" Properties="Configuration=$(buildtype)" />
</Target>
Edit:
I think what I really want to do is detect when the CoreCompile task runs for each project. If there were some way to check for this in a condition?
Any ideas?
I'm new to MSBuild so maybe I'm on completely the wrong track!
Thanks,
Alan
You can also do it based on the configuration selected in your build process. For CI, you should always use "Release" or "Production" (you can define your own).
<Exec Condition="'$(ConfigurationName)'=='Release'" Command="your command goes here ..."/>
After much searching for a simple solution to this problem I didn't find one and ended up coming up with a solution of my own that works but may not be the best solution. However, I wanted to share it with anyone else that is having the same problem so that you can at least have a working solution and hopefully saving you a lot of head banging.
To recap, what I wanted to do was run a command line tool after my project was built but only if the assembly was updated (i.e. the timestamp changed). I didn't want to put this into the post-build section of every project because I only wanted the post-build to happen on our build server (not development machines).
I didn't find any way of doing this externally in my main .proj file and did end up altering the post-build section of each .csproj file. However, I prefixed it with an if condition something like this:
if '$(ExecuteCommand)' == 'true' command.exe
This means that the command will never be executed on the development machine but when I invoke the build from my .proj file I can set that flag to true like this:
<!-- Define common properties -->
<PropertyGroup>
<ExecuteCommand>true</ExecuteCommand>
</PropertyGroup>
<Target Name="YourTarget">
<!-- Build project -->
<MSBuild Projects="Path to project" Properties="ExecuteCommand=$(ExecuteCommand)" />
</Target>
As I said, I don't think it is the most graceful solution but it certainly works and will be sufficient for me for the time being. However, I'd still be interested to hear what the proper way of achieving this is so that I can improve my script.
Thanks,
Alan
If you can add the following to each of your projects:
<Target Name="DoStuffWithNewlyCompiledAssembly">
<Exec Command="command.exe" />
</Target>
... then you only need to add a property:
<Target Name="Name">
<MSBuild Projects="" Properties="TargetsTriggeredByCompilation=DoStuffWithNewlyCompiledAssembly" />
</Target>
This works because someone smart at Microsoft added the following line at the end of the CoreCompile target in Microsoft.[CSharp|VisualBasic][.Core].targets (the file name depends on the language and MSBuild/Visual Studio version).
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''"/>
So if you specify a target name in the TargetsTriggeredByCompilation property, your target will run if CoreCompile runs-- and your target will not run if CoreCompile is skipped (e.g. because the output assembly is already up-to-date with respect to the code).

Categories