Make a target run once at the Solution level in MSBuild - msbuild

I need a set of tasks that need to be executed exactly once for the entire solution. This will run tasks that will modify each project to run a separate set of tasks for each project. We had done this earlier using a separate project to the solution which had the solution level tasks, but we want to move away from that. Has anyone done this or does anyone have any suggestions on how to implement this?

Since Solution files are not in MSBuild format they are not easily extended or customized. If you want more control over the build process you would have to create a "driver" msbuild file which would replace your solution file. Inside this driver file you would build all the projects that you needed and perform some additional tasks. You would do this using the MSBuild task. Here is a sample showing how to build more than 1 project.
<Project ...>
<ItemGroup>
<Projects Include="proj01.csproj"/>
<Projects Include="proj02.csproj"/>
<Projects Include="proj03.csproj"/>
</ItemGroup>
<Target Name="BuildAll">
<MSBuild Projects="#(Projects)" BuildInParallel="true" />
</Target>
</Project>
So in your case you would just execute the tasks before you build the projects. Also note that I specified the value true for the BuildInParallel indicating that MSBuild can try and build more than one project at once.

An alternative solution is to have a single target that dispatches to an MSBuild invoked target with as many Global properties removed as possible. My team have a target in the InitialTargets of a Directory.Build.props Import'ed props file - something like:
<Target Name="Prebuild">
<MSBuild Projects="$(MSBuildThisFileFullPath)"
Targets="PrebuildWorker"
RemoveProperties="Configuration;Platform;TargetFramework;BuildProjectReferences" />
</Target>
Since MSBuild appears to synchronize parallel builds on the {project file, global properties, target} set, then by removing all of the properties you can synchronize the build and run it once for all projects being built. The downside: you have to maintain the RemoveProperties attribute - MSBuild doesn't have a way to remove all global properties. If something in the build issues a new MSBuild task with a custom property specified, then you'll get a second instance of the Target invoked.
And - of course - your build will be synchronized on this target. You could try hooking the target up by setting, say, CompileDependsOn to depend on the Prebuild target, to allow independent progress in the build. But to have the target run early and ubiquitously using InitialTargets seems like the better option.

Related

Set msbuild OutputPath conditioned on project name

I'm trying to improve a build-server setup for .NET solutions. The build is done using msbuild solution.sln /p:OutputPath="$pwd/build" and an additional Directory.Solution.targets to work around a quirk of msbuild (from related question, see its content below). Now we've decided to move our tests to separate projects so that the test dlls and their dependencies don't land in the OutputPath. Usually this wouldn't be a problem, but for historical reasons every single project in the solution has its OutputPath set to ..\..\. This is why I'm overriding it when calling msbuild. But now I need to only override it for some of the projects - and I can't figure out how.
The project structure right now looks like this.
app/
sources/
Complete.sln
Proj1/
proj1.csproj
Proj1.Test/
proj1.test.csproj
Building the solution from Visual Studio by a developer results in this (only showing new files), which is fine.
app/
proj1.exe
sources/Proj1.Tests/bin/Release/
proj1.exe
proj1.tests.dll
But building it with msbuild Complete.sln /p:OutputPath="$pwd/build" /t:BuildAll results in following.
app/build/
proj1.exe
proj1.tests.dll
Is there a way without changing solution or project files to get the following?
app/build/
proj1.exe
app/tests/
proj1.exe
proj1.tests.dll
I.e. I want to set the OutputPath for every *.Tests.csproj to /tests, and for every other project to /build.
Alternatively, is there a way to call msbuild Complete.sln in such a way it builds either all the projects from that solution with names that don't end with "Tests" or only those that do?
The Directory.Solution.targets mentioned above (using it for reasons) looks like following.
<Project>
<Target Name="SetSkip">
<ItemGroup>
<ProjectReference Update="*">
<SkipNonexistentProjects>Build</SkipNonexistentProjects>
</ProjectReference>
</ItemGroup>
</Target>
<Target Name="BuildAll" DependsOnTargets="SetSkip">
<CallTarget Targets="Build"/>
</Target>
</Project>
UPDATE since the number of files grows, for easier testing I've created a repository.
You could try adding the following to Directory.Build.targets (I have not tested it, but it should work):
<PropertyGroup>
<OutputPath Condition="'$(BuildOutputPath)' != ''
and !$(MSBuildProjectName.EndsWith('.Test'))">$(BuildOutputPath)</OutputPath>
<OutputPath Condition="'$(TestOutputPath)' == ''
and $(MSBuildProjectName.EndsWith('.Test'))">$(TestOutputPath)</OutputPath>
</PropertyGroup>
Then invoke msbuild like so:
msbuild Complete.sln /p:TestOutputPath="$pwd\tests" /p:BuildOutputPath="$pwd\build"
There would be other options, like putting the required build or tests paths directly in the properties.

MSbuild style cop extensions task to run on solution

I need help in running stylecop task on all cs files included in solution.
Please let me know if its possible and how?
Right now I can run it on a file, but not on solution.
<CreateItem Include="$(RootPath)\**\*.cs">
<Output TaskParameter="Include" ItemName="StyleCopFiles"/>
</CreateItem>
<MSBuild.ExtensionPack.CodeQuality.StyleCop
TaskAction="Scan"
ShowOutput="true"
ForceFullAnalysis="true"
CacheResults="false"
SourceFiles="#(StyleCopFiles)"
logFile="$(OutDir)\StyleCopLog.txt"
SettingsFile="$(MSBuildStartupDirectory)\..\Settings.StyleCop"
ContinueOnError="false">
<Output TaskParameter="Succeeded" PropertyName="AllPassed"/>
<Output TaskParameter="ViolationCount" PropertyName="Violations"/>
<Output TaskParameter="FailedFiles" ItemName="Failures"/>
</MSBuild.ExtensionPack.CodeQuality.StyleCop>
Have you considered automatically running StyleCop rules as a part of your project build process? This won't run all rules at the solution level but at a project level. I prefer this approach because the rules will run whenever you build your project/solution and will display as Warnings in the Error List panel (double click to navigate to the offending line of code). Configuring this on a project by project basis may seem like a pain but we have a different set of StyleCop rules for our Unit Test projects, and this allows us to configure them individually.
Also, You won't have to explicitly add a MSBuild task to your build script because building the projects will automatically execute the StyleCop rules.
It's also worth noting that I'm using the NuGet Package: StyleCop.MSBuild (version 4.7.17.1) and using a relative path to reference the package from within my .csproj file like this:
<Project>
<Import Project="..\Packages\StyleCop.MSBuild.4.7.17.1\tools\StyleCop.targets" />
</Project>
http://stylecop.codeplex.com/wikipage?title=Running%20StyleCop%20in%20VS2005%20or%20VS%20Express&referringTitle=Documentation
You can also set conditions on when you want the rules to run. If the condition evaluates to false StyleCop will not run. We use the condition to suppress StyleCop when running Unit Tests
<Project>
<Import Project="..\Packages\StyleCop.MSBuild.4.7.17.1\tools\StyleCop.targets" Condition="'$(NCrunch)'!='1'" />
</Project>
In order to configure StyleCop rules, you will need to install StyleCop_v4.7.17.0.msi. We only define a single Settings.StyleCop file (Parent Settings File) for our entire codebase.
http://stylecop.codeplex.com/wikipage?title=Sharing%20StyleCop%20Settings%20Across%20Projects&referringTitle=Documentation

How make MSBuild build custom target specified in csproj building sln?

I am facing an issue with MSBuild I can't overcome it by myself. As a result I rely on community's wisdom.
The real situation I'm having troubles with
I have a soluiton file containing several projects with dependencies to other projects in same solution. I'd like to append a custom target to one of the project's csproj file and build it from the command line. It will allow me to make all the necessary output binaries for this project for further processing during the building of the custom target. But the main thing is that I can't figure out how to do it, googling doesn't help either.
Simplification
To make thing simplier I decided to make a new C# console project, add a simple custom target to the project's file and try to make it build. Still no success! Here what I've done so far:
Created a solution app with a default console project coreapp. This gaves me at least two files:
app.sln
coreapp\coreapp.csproj
Modified coreapp.csproj with addition of my custom target inside of the Project tag
<Target Name="SampleTarget">
<Message Text="This is a SampleTarget" />
</Target>
Run on the command line the following command
%windir%\Microsoft.NET\framework\v3.5\msbuild.exe app.sln /t:coreapp:SampleTarget
or even
%windir%\Microsoft.NET\framework\v3.5\msbuild.exe app.sln /t:coreapp.csproj:SampleTarget
Results
No luck, facing the error
MSB4057: The target "coreapp.csproj:SampleTarget" does not exist in the project.
I suspect that MSBuild thinks somehting fundamentally different from what I want it to think...
BEsides that, I also tried to set on the same command line the environment variable MSBuildEmitSolution=1 to force msbuild dump a temporary solution file it creates while processing the solution. In this file, indeed, no such target. However I guess it isn't the reason because I asked msbuild to build coreapp.proj where target SampleTarget really resides.
The question is how to build SampleTarget in this simplified scenario using solution file since potencially it can contain dependencies for the project containing this SampleTarget target?
I'd be greatful for any sort of help or firection for further investigation!
Instead of inserting a custom target in your project file, you could try creating a new standalone msbuild file, which would:
build the solution file (which builds projects)
defines your extra target
Call it app-custom-Debug.msbuild , for example.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WorkingFolder>$(MSBuildProjectDirectory)</WorkingFolder>
<Configuration>Debug</Configuration>
<SolutionFile>app.sln</SolutionFile>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="Compile" />
<Target Name="Compile">
<Message Text="=== COMPILING $(Configuration) configuration ===" />
<MSBuild Projects="$(SolutionFile)"
Properties="Configuration=$(Configuration)" />
</Target>
<Target Name="SampleTarget">
<Message Text="This is a SampleTarget" />
</Target>
</Project>
Then you execute:
msbuild.exe app-custom-Debug.msbuild /t:SampleTarget
One option is to tie your SampleTarget to the standard Build targets via overriding the appropriate DependsOn property. In this case you could tell BeforeBuild that it DependsOn SampleTarget or you do the same thing with AfterBuild. This will ensure that MSBuild processes your target prior to the standard target indicated.

Preventing MSBuild from building a project in a .sln without using Solution Configurations

I want to inhibit the building of certain projects within a solution from building (within a TeamCity Build Configuration in order to optimize the speed of my Commit Build feedback if you must know).
I'm aware of the Solution Configurations mechanism but don't want to have to force lots of .sln files to end up with every permutation of things I want to be able to switch off. I have Convention based rule where I want to say "If I'm doing the Commit Build, I dont want to do the final installer packaging". (And I don't want to break it out into a separate solution).
I'd prefer not to use a solution involving find and replace in the .sln file or in a .proj file created via [MsBuildEmitSolution][1]. I'm aware of questions here which cover the out of the box solution and this slightly related question.
I see MSBuild /v:diag is saying:
2>Target "Build" in file "Z.sln.metaproj" from project "Z.sln" (entry point):
Using "MSBuild" task from assembly "Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "MSBuild"
Global Properties:
BuildingSolutionFile=true
CurrentSolutionConfigurationContents=<SolutionConfiguration>
<ProjectConfiguration Project="{C83D035D-169B-4023-9BEE-1790C9FE22AB}" AbsolutePath="X.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
<ProjectConfiguration Project="{15E7887D-F1DB-4D85-8454-E4EF5CBDE6D5}" AbsolutePath="Y.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
</SolutionConfiguration>
So the question is:
Is there a neat way of me getting to do an XPath replace or similar to have the effect of changing BuildProjectInSolution="True" to BuildProjectInSolution="False" for Project Y above
Failing that, is there a relatively simple edit I can do within a .ccproj (An Azure 1.4 Package) or a .csproj (a general project) file to cause the effects (including triggering of dependent projects) of the project being enabled within a commandline msbuild Z.sln solution build to be nullified?
Not sure it qualifies as neat, but you can set CustomAfterMicrosoftCommonTargets to import an msbuild file to over-ride the BuildDependsOn property, pointing it to your own custom build task. Basically, by setting CustomAfterMicrosoftCommonTargets you get msbuild to import an msbuild file containing the following:
<PropertyGroup>
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
<BuildDependsOn>MyBuild</BuildDependsOn>
</PropertyGroup>
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)" />
<Target Name="MyBuild">
<CallTarget Targets="OldBuild" Condition="<IfIWantThis>" />
</Target>
Edit
You can use the following MyBuild target to Include/Exclude projects based on regular expressions passed in as IncludeInBuild and ExcludeFromBuild properties. (If you want complex regexes, you may fall foul of MSBuild special character escaping, but this works well enough for simple matching)
> msbuild /p:ExcludeFromBuild="Tests|Install|Azure"
<Target Name="MyBuild">
<CallTarget Targets="OldBuild" Condition="('$(IncludeInBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(IncludeInBuild),
System.Text.RegularExpressions.RegexOptions.IgnoreCase))'=='True') AND
('$(ExcludeFromBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(ExcludeFromBuild),
System.Text.RegularExpressions.RegexOptions.IgnoreCase))'=='False')" />
</Target>
You could always pass the particular projects you want to build as parameters to the MSBuild.
The MSBuild command line would look like this:
MSBuild /t:<Project Name>:Rebuild;<Another Project Name>:Rebuild
In TeamCity, you would put <Project Name>:<Target Action> in the target field in the MSBuild runner.
I add a system parameter under Parameters
Name: system.ExcludeFromBuild
Kind: System property (system.)
Value: path to your csproj

Incremental build support in Biztalk 2009 and 2010 .btproj projects?

While chasing incremental build time improvements, I found that .btproj files and thus all other projects that depend on these are rebuilt (partly) on each incremental build. Tracking this all the way to BizTalkCommon.targets, I found that it does a 2 pass compilation of the assembly - but only the first pass respects already built artifacts, thus breaking the incremental part of the dependency chain. The offending target can be seen in BizTalkCommon.targets (line 228):
<!-- Delete the assembly and rerun the build process -->
<Target Name="SecondPass"
Condition="$(SecondBuild)!=true and $(TempAssemblyOnly)!=true">
<Delete Files="#(IntermediateAssembly)" />
<MSBuild Projects="$(MSBuildProjectFile)" Properties="SecondBuild=true"/>
</Target>
I realize that there's a reason for the 2 pass build, but simply cannot believe it wouldn't be possible to specify appropriate in- and outputs for the target to handle incremental builds correctly.
Does anyone know if there's a patch for the .targets file, or if there's another good reason that incremental builds aren't supported?
You can enable incremental compilation of MSBuild BizTalk project with a couple of very simple changes. Basically, you need to override two targets that are defined in the BizTalkCommon.targets file.
Those targets can be overriden in your own .btproj files and do not require modifying the original .targets file that ships with BizTalk.
How To
First Create you own .targets file to host your customizations, for instance BizTalkCustom.targets :
<Import Project="$(MSBuildExtensionsPath)\Microsoft\BizTalk\BizTalkC.targets" />
<!-- Rerun the build process (second pass) -->
<Target Name="SecondPass" Condition="$(SecondBuild)!=true and $(TempAssemblyOnly)!=true and #(XLang)!=''">
<MSBuild Projects="$(MSBuildProjectFile)" Properties="SecondBuild=true" />
</Target>
<!-- Compile XLang/s orchestration -->
<Target
Name="CompileODX"
Condition="$(SecondBuild)==true"
Inputs="#(XLang);$(MSBuildAllProjects);$(ClrTypesAssembly)"
Outputs="$(BuildDone)">
<!-- Delete previously generated C# files from XLang compilation -->
<Delete Files="#(IntermediateAssembly)" />
<Delete Files="#(CSharpOutputFromXLang)" />
<XLangTask XLangItems="#(XLang)"
ProjectReferences="#(ReferencePath)"
WarningLevel="$(WarningLevel)"
BpelCompliance="$(BpelCompliance)"
DefineConstants="$(DefineConstants)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
TempAssembly="$(ClrTypesAssembly)"
OutputDirectory="$(XLangOutputPath)">
</XLangTask>
</Target>
Then, replace the last Import statement in your .btproj file:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MyCustomExtensions)\BizTalkCustom.targets" />
How doe it work
BizTalk Server projects need somehow to be compiled in two passes. The first pass compiles schemas, maps and pipelines, whereas the second pass compiles orchestrations.
You'll notice that the overriden targets are very very similar than the original ones, defined inside the BizTalkCommon.targets file. In fact, I made two simple changes:
The first change involves modifying the SecondPass Target and adding an extra test in the Conditionattribute. This test is usefull to prevent the second pass from occurring if your project does not even have Orchestrations.
Unfortunately, if your project contains Orchestrations, the original SecondPass Target deletes the intermediate assemblies and then proceed to compile the Orchestrations. However, the CompileODX Target does not need to run if all files are already up to date. Therefore, the second change involves moving the Delete Task from the SecondPass Target to the CompiledODX Target.
That's all there is to it.
This is something my team ran into a while back and simply backed off customizing the build files and went with the BizTalk deployment framework instead, located here. BizTalk does lots of "funny" things from a VS level, since 2009 was the first version BizTalk didn't use an external build process. But I'm not sure why the second pass is needed, except maybe from a designer perspective.