Have MSBuild only build targets that require building - msbuild

So long story short, we've got a situation of having multiple solutions with assembly references between them. Used to be, we had to go and build these solutions in the correct order to have our dependencies built correctly.
I'm trying to make things easier by using MSBuild tasks to first restore nuget on the dependent solution, then build the dependencies for you, ie:
<Target Name="BeforeBuild" Condition="'$(Configuration)'=='Debug'">
<Message Importance="high" Text="Kicking off internal dependency build for $(MSBuildProjectName) in Configuration=$(Configuration), Platform=$(Platform), OutDir=$(OutDir)" />
<!--Build the dependencies-->
<Message Importance="high" Text="Restoring MyDependencyProject NuGet External Packages..." />
<Exec Command="..\..\bin\nuget.exe restore ..\MyProject\MyDependencyProject\MyDependencyProject.sln" />
<Message Importance="high" Text="Building MyDependencyProject dependency..." />
<MSBuild Projects="..\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject.csproj" Targets="Build" Properties="Configuration=$(Configuration)" />
<Message Importance="high" Text="Building MyDependencyProject2 dependency..." />
<MSBuild Projects="..\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject2.csproj" Targets="Build" Properties="Configuration=$(Configuration)" />
</Target>
This works great! What used to be 7(!) separate solutions we had to open and build, is now condensed to 2 (we build our C++ COM libraries, then build our C# libraries).
One problem - while this is easier, it is very slow. I've been going over the diagnostic output from MSBuild, and it seems that the way I've set this up is forcing the dependencies to be rebuilt every single time. If I had set Targets="Rebuild" I would expect this, but I thought Targets="Build" would build ONLY IF a project was out of date (or had a file set to):
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
in the project file). I can not find any message in the diagnostic output to explain why the MSBuild target is rebuilding these dependencies, even if the dependencies have not been touched.
Why does MSBuild do a full rebuild of dependency projects that have not been altered with the above tasks?
Edit - More information
I see the following:
Target "IncrementalClean: (TargetId:324)" in file "C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets" from project "c:\Source\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject.csproj" (target "CoreBuild" depends on it):

Related

.Net Core MS Build Package Creation Lifecycle

MSBuild quite powerful and important tool, but sometimes configuration of it is the same hard as making a spaceship or flying to Mars. Answers on next questions can help makes it a little bit more usefull and developer friendly:
- How the MSBuild create a Package(zip)?
- Does it use some temporary folders or direct from builds output ?
- Why can the output folder content differ from a package content
- What events can be used to add some custom logic?
The answers to this question could help me with the next long story short:
I'm working with a .Net Core solution, that has the dependency on some other solutions DLLs files( that are not referenced directly, but required to be in the solution folder). On post-build event CL there is the option added as an entry point "\TaskEP" that is a starting point of the next pipeline :
<PropertyGroup>
<PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
TaskEP;
Task2;
Task3;
$(PipelineCopyAllFilesToOneFolderForMsdeployDependsOn);
</PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
<RunPostBuildEvent>Always</RunPostBuildEvent>
Tasks are mostly done for add extra DLLs to package and in general looks like :
<Target Name="TaskEP" Condition="$(SomeConditions) == 'True'">
<Message Importance="high" Text="TaskEP: Copying some files..." />
<ItemGroup>
<ItemsName Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" />
<FilesForPackagingFromProject Include="%(ItemsName.Identity)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
Basically, for me, this instruction to MSBuild to add "Extra.dll" to output package.
Pubxml is quite usual :
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<_PackagePathShortened Condition="'$(_PackagePathShortened)' == ''">SuperWebSite</_PackagePathShortened>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PackageLocation>..\..\..\..\BIN\WEB\Release\Publish\Super.WebApplication.zip</PackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<TargetFramework>netcoreapp2.1</TargetFramework> ....
And one instruction that replaces the long long path in the package to Short and defined one:
<Target Name="AddReplaceRuleForAppPath" BeforeTargets="BeforePublish">
<Message Text="Adding replace rules for application path '$(PublishIntermediateOutputPath)' replace with '$(_PackagePathShortened)'" Importance="high" />
<EscapeTextForRegularExpressions Text="$(PublishIntermediateOutputPath)">
<Output PropertyName="_PackagePathRegex" TaskParameter="Result" />
</EscapeTextForRegularExpressions>
<!-- Add a replace rule for VSMSDeploy resp. MSdeploy to update the path -->
<ItemGroup>
<MsDeployReplaceRules Include="replaceFullPath">
<Match>$(_PackagePathRegex)</Match>
<Replace>$(_PackagePathShortened)</Replace>
</MsDeployReplaceRules>
</ItemGroup>
I also duplicated the same "Copy" tasks here in .pubxml file , play with different events "AfterBuild", "BeforePublish" but seems that all of the just ignoring. I can see the "Extra" DLLs files in the build output directory and all Info Messages on publishing, but not in the final "Super.WebApplication.zip" file !
The solution I found to copy extra files in .Net Core is taken from this documentation
<ItemGroup>
<DotnetPublishFiles Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" >
<DestinationRelativePath>bin\x86\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</DotnetPublishFiles>
</ItemGroup>

MSBuild properties as variables in VSTS build definitions

Is there a way to get my MSBuild properties (from the .props file) as variables in a build definitions in Visual Studio Team Services?
I would like it if it has a built-in way but I am ok with an extension.
This can be done by creating an msbuild project (or target in an existing project) that logs messages containing the needed properties in the form of VSTS logging commands. However MSBuild can not "enumerate" defined properties, so this only works for well-known properties - which is probably good to avoid accidental collisions (even environment variables are "properties" inside MSBuild).
Given a sample some.props file:
<Project>
<PropertyGroup>
<PackageId>a.package.id</PackageId>
<Version>1.2.3</Version>
<Description>I am your test project</Description>
</PropertyGroup>
</Project>
And an example emitvars.proj:
<Project>
<Import Project="some.props" />
<Target Name="Build">
<Message Importance="high" Text="##vso[task.setvariable variable=PackageId]$(PackageId)" />
<Message Importance="high" Text="##vso[task.setvariable variable=Version]$(Version)" />
<Message Importance="high" Text="##vso[task.setvariable variable=Description]$(Description)" />
</Target>
</Project>
This project file can then be "built" in an MSBuild task (or dotnet msbuild on linux machines using .NET Core tooling):
For demonstration purposes, I added a PowerShell task that uses these variables:
The build then uses the variables in the script as expected. note that the log lines setting the variables might not be displayed in the build log.

MSBuild target in csproj do a clean before every build

I have a C# project (vs2013) and for this project, i want to do always a clean solution before the build. Now i can (again and again) choose clean project before building, but this should be possible using a Target tag in the .csproj file. I started looking on the internet and came up with a solution that did not work:
<Target Name="Clean"
BeforeTargets="Build">
<Message Text="### Start Clean before build"
Importance="high" />
<Exec Command="CALL "$(MSBuildBinPath)\msbuild.exe" "$(MSBuildProjectFullPath)" /t:Clean" />
<Message Text="### Finished Clean before build"
Importance="high" />
</Target>
But this results in an infinite loop, starting msbuild over and over again.
You've defined the target recursively. Don't name it "Clean", name it "PreBuildClean" or something similar.
Also you don't have to call out to msbuild externally using Exec. You can use the CallTarget task to invoke a target directly.
<Target Name="PreBuildClean"
BeforeTargets="Build">
<Message Text="### Start Clean before build"
Importance="high" />
<CallTarget Targets="Clean" / >
<Message Text="### Finished Clean before build"
Importance="high" />
</Target>
The Rebuild target is just Clean followed by Build, so I'm not sure why you'd need this, unless it is only for one specific project in the solution.

Issue with using MSBuild to build and copy all outputs to a common folder

We are trying to write a msbuild script that will build the solution and copy over all the compiled binaries and dependencies over to a specific output folder. While the build script that we have does build and copy over the binaries to a common folder, but we are not getting the dependencies copied.
This probably has to do with the way we have used the msbuild task to build the solution and we are accepting the targetoutputs of the task into an itemgroup and iterating over the item group to copy all the compiled dlls and exes over to a common folder. But this is not including the dependency dlls which gets placed into the individual bin folder of each project.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ParentSolutionFile />
</PropertyGroup>
<ItemGroup>
<Assemblies Include="*.dll, *.exe" />
</ItemGroup>
<Target Name="BuildAll">
<CombinePath BasePath="$(MSBuildProjectDirectory)" Paths="Source\Solutions\xxx.sln">
<Output TaskParameter="CombinedPaths" PropertyName="ParentSolutionFile" />
</CombinePath>
<Message Text="$(ParentSolutionFile)" />
<MSBuild Projects="$(ParentSolutionFile)">
<Output TaskParameter="TargetOutputs" ItemName="Assemblies" />
</MSBuild>
<Message Text="%(Assemblies.Identity)" />
<Copy SourceFiles="%(Assemblies.Identity)" DestinationFolder="$(MSBuildProjectDirectory)\Binary" OverwriteReadOnlyFiles="True" SkipUnchangedFiles="True" />
</Target>
What will be the preferred way to copy over all the binaries along with the necessary dependencies to a common output folder?
Does not overriding OutputPath do the trick alone?
<MSBuild Projects="$(ParentSolutionFile)" Properties="OutputPath=$(MSBuildProjectDirectory)\Binary">
<Output TaskParameter="TargetOutputs" ItemName="Assemblies" />
</MSBuild>
And leave out the copy task alltogether?
The build process will place the final result in the directory represented by OutputPath - at least if you are building c# projects. For C/C++ the internal structure and variable names are completely different.
Thus, in theory, you could pass the OutputPath in the MsBuild-task that builds the solution.
<MsBuild Projects="$(ParentSolutionFile)"
Properties="OutputPath=$(MSBuildProjectDirectory)\Binary"/>
However, the csproj-files will overwrite that value unconditionally with the following code:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
I have solved this by injecting my own build system in each and every csproj-file.
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\buildsystem.targets" />
The path is relative to the csproj-file. An absolute path is fine too, or a variable. The trick is to make it work on all dev machines as well as the build agents.
Now, in buildsystem.targets, simply redefine OutputPath as much as you like. Again, the trick is to ensure you get the same - or at least a well defined - location regardless of who builds it (dev, build agent) and regardless how the build was initiated (VS, command line).
A simple way of handling the differences is to import conditionally.
<Import Project="..\..\..\build\buildsystem.targets"
Condition="'$(BuildingInsideVisualStudio)'!='true'"/>
That will give you no changes if initiating the build from VS and whatever changes you code for if you build from command line.
--Jesper

How to call the a unit test target in a project from a 'solution' project

I'm trying to get Team City to build my .NET solution and run my nUnit tests.
I know I can modify the individual projects and tell them always run the unit tests. I don't want the unit tests to run when I click "build" in visual studio, but I do want the unit tests to run when Team City kicks off a msbuild task.
I tried "msbuild solutionname.sln" and gave team city the targets of "BUILD" and my custom build tag of "TEST". However, msbuild can't find any specified target when invoked against a sln solution. So, I ran msbuild to convert my solution into a project which has a target like this:
<Target Name="Build">
<MSBuild Projects="#(BuildLevel0)" >
</Target>
I naively thought I could write a new task like this:
<Target Name="BuildAndTest">
<CallTarget Targets="Build"/> <!-- This builds everything in solution -->
<CallTarget Targets="Test"/> <!-- DOES NOT WORK. This target exists in project that gets built by this solution -->
</Target>
The nunit target looks like this:
<Target Name="Test" DependsOnTargets="Build" Condition=" '$(Configuration)' == 'Release'">
<NUnit Assemblies="$(OutputPath)\Tsa.BaseTest.dll" ContinueOnError="false" ToolPath="C:\Program Files\NUnit 2.5.2\bin\net-2.0\" DisableShadowCopy="true" OutputXmlFile="$(OutputPath)\nunit-results.xml" />
</Target>
As you can see, it references OutputPath, which only the project knows--the solution doesn't have reference to $OutputPath, else I'd just put all the test targets into the "solution project".
Any suggestions on how I can get this to work?
I think you're making this a lot harder than it needs to be. TeamCity has built-in support for running NUnit unit tests after the build - you don't need to modify the MSBuild file at all. Just set up your Build Configuration (I think it's under Runner) to specify the version of NUnit and which assemblies are test assemblies.
NOTE: I checked and we have this under Runner: sln2008 (section NUnit Test Settings) in TeamCity Enterprise Version 4.5.4, but I don't see anything on the JetBrains site that states that it's specific to Enterprise. It may require a version upgrade, though. See TeamCity Testing Frameworks.
This is what finally worked. It is ignored by visual studio, msbuild will run this section correctly, and team city will as well, although it replaces the Target with its own an runtime (according to the build log).
TeamCity will "automatically" run nunit tests and display the results, only in the sense that it will do so after manually editing the msbuild file, doing numerous manual teaks and telling TeamCity where each assembly is and where each output file is.
<Project (snip) DefaultTargets="BuildAndTest" (snip)>
<Target Name="BuildAndTest">
<CallTarget Targets="Build" />
<CallTarget Targets="TestBase" />
</Target>
<Target Name="TestBase" DependsOnTargets="Build">
<NUnit Assemblies="Tsa.BaseTest\bin\RELEASE\Tsa.BaseTest.dll" ContinueOnError="false" ToolPath="C:\Program Files\NUnit 2.5.2\bin\net-2.0\" DisableShadowCopy="true" OutputXmlFile="$(SolutionDir)\Tsa.BaseTest\bin\RELEASE\nunit-results.xml" />
</Target>
</Target>
</Project>