I have a Visual Studio 2012 Solution with twelve Solution Configurations. Each Solution Configuration is independent (i.e., the outputs of each configuration are entirely unrelated).
The Question: How can I build all twelve configurations in one step, i.e. by running a single MSBuild command on the command line, and how can I get the configurations to be built in parallel?
As an example, if there were just two configurations, Release/AnyCPU and Debug/AnyCPU, I would want both of these to build at the same time, in parallel.
For completeness, the following is what I have tried; I don't yet have a solution to this problem.
To build all of the projects at once, I created a new project file with a Build target that runs the MSBuild task on the Solution file for each configuration:
<Target Name="Build">
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release;Platform=Win32" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release;Platform=x64" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release;Platform=ARM" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release(ZW);Platform=Win32" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release(ZW);Platform=x64" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Release(ZW);Platform=ARM" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug;Platform=Win32" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug;Platform=x64" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug;Platform=ARM" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug(ZW);Platform=Win32" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug(ZW);Platform=x64" Targets="$(BuildCommand)" />
<MSBuild Projects="cxxreflect.sln" Properties="SolutionDir=$(MSBuildProjectDirectory)\;Configuration=Debug(ZW);Platform=ARM" Targets="$(BuildCommand)" />
</Target>
This works great, except that each MSBuild task is invoked in sequence, so there's no parallelism (yes, there is parallelism within each configuration build, but I'd really like to get parallelism across configuration builds).
In an attempt to get the configurations to build in parallel, I tried to make use of the MSBuild task's BuildInParallel property. I wrote a pre-build task that generated project files for each configuration, then attempted to build all of these generated projects in parallel:
<Target Name="PreBuild" Outputs="%(ProjectConfiguration.Identity)" Returns="%(BuildProject.Identity)">
<Message Text="Cloning Solution for Configuration: %(ProjectConfiguration.Identity)" />
<PropertyGroup>
<BuildProjectPath>$(IntPath)\%(ProjectConfiguration.Platform)\%(ProjectConfiguration.Configuration)\build.proj</BuildProjectPath>
</PropertyGroup>
<ItemGroup>
<BuildProject Include="$(BuildProjectPath)" />
</ItemGroup>
<MakeDir Directories="$(IntPath)\%(ProjectConfiguration.Platform)\%(ProjectConfiguration.Configuration)" />
<WriteLinesToFile
File="$(BuildProjectPath)"
Lines="<?xml version='1.0' encoding='utf-8'?>
<Project DefaultTargets='Build' ToolsVersion='4.0' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
<Target Name='Build'>
<MSBuild
Projects='$(SlnPath)'
Properties='
SolutionDir=$(MSBuildProjectDirectory)\%3b
Configuration=%(ProjectConfiguration.Configuration)%3b
Platform=%(ProjectConfiguration.Platform)
'
Targets='$(BuildCommand)'
/>
</Target>
</Project>"
Overwrite="true" />
</Target>
<Target Name="Build" DependsOnTargets="PreBuild">
<Message Text="%(BuildProject.Identity)" />
<MSBuild Projects="%(BuildProject.Identity)" Properties="BuildCommand=$(BuildCommand)" BuildInParallel="true" />
</Target>
Unfortunately, while this does build all twelve configurations, it builds them serially. My guess is that when MSBuild performs dependency analysis on the set of projects to be built, it identifies that they all depend on the same Solution file, so it decides they cannot be built in parallel. (This is just a guess; I could be completely wrong. I know very little about MSBuild.)
I am also using the /m switch when building.
Take advantage of the MSBuild Task's BuildInParallel option, and pass all the projects in a single call. The examples here give the basic approach:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectToBuild Include="a1.sln">
<Properties>Configuration=Debug</Properties>
</ProjectToBuild>
<ProjectToBuild Include="a1.sln">
<Properties>Configuration=Release</Properties>
</ProjectToBuild>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectToBuild)" BuildInParallel="true" />
</Target>
</Project>
Related
I'm trying to automate the process of some projects output folder, at the moment I'm writing those lines for each project that my website uses:
<Target Name="BuildPlugin" BeforeTargets="Build">
<MSBuild Projects="..\Module.Products\Module.Products.csproj" Targets="Publish" Properties="Configuration=$(Configuration);PublishDir=$(OutputPath)/plugins/Module.Products" />
<MSBuild Projects="..\Module.Sales\Module.Sales.csproj" Targets="Publish" Properties="Configuration=$(Configuration);PublishDir=$(OutputPath)/plugins/Module.Sales" />
</Target>
Is there any way I can automate that to search for Module.* and use in all those places where the name repeats? This is a standalone project.
Yes! Just make Items and then iterate over them while leveraging their metadata (https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata). Something like this (apologies for any mistakes – typing this on a phone).
<Target Name="BuildPlugin" BeforeTargets="Build">
<ItemGroup>
<ProjectFiles Include="..\*\*.csproj" />
</ItemGroup>
<MSBuild Projects="%(ProjectFiles.Identity)" Targets="Publish" Properties="Configuration=$(Configuration);PublishDir=$(OutputPath)/plugins/%(ProjectFiles.Filename)" />
</Target>
I have two files that I want to configure by environment: App.config and ApplicationInsights.config. I have created the files App.Debug.config and ApplicationINsights.Debug.config and added the following tasks to the csproj file:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('ApplicationInsights.$(Configuration).config')">
<Message Text="Transforming app insights config file to $(OutputPath)\ApplicationInsights.config" Importance="high" />
<TransformXml Source="ApplicationInsights.config" Transform="ApplicationInsights.$(Configuration).config" Destination="$(OutputPath)\ApplicationInsights.config" />
</Target>
Both tasks work when they are the only task in the file, but when both are included only the second transform is executed. I have tried giving the tasks different Names, but to no avail. What can I do to get both tasks to run?
You have to give the two tasks different Names and then hook into the existing AfterCompile target:
<Target Name="SomeUniqueName1" AfterTargets="AfterCompile" …>
…
</Target>
<Target Name="SomeUniqueName2" AfterTargets="AfterCompile" …>
…
</Target>
The <UsingTask> only needs to be there once to define the imported TransformXml task.
I have some msbuild code that looks something like this:
<Target Name="Build">
<MSBuild
Projects="#(UnitTestProject)"
Properties="$(BuildProperties)">
<Output TaskParameter="TargetOutputs" ItemName="TestAssembly" />
</MSBuild>
</Target>
<Target Name="Test" DependsOnTargets="Build">
<ItemGroup>
<TestAssembly Remove="*.Example.dll" />
</ItemGroup>
<xunit Assemblies="#(TestAssembly)" />
</Target>
So I am building all of my unit test projects and caputuring the built dll's using the Output task on the TargetOutputs parameter. The problem is that one of the projects is calling a task that outputs some dll's that I don't want to actually run xunit against.
What's weird though is that the Remove="*.Example.dll" appears to not have any affect at all and xunit is trying to test the assembly anyway.
Why is Remove not working?
Actually I think I figured it out. It appears that the problem resides in the way the relative path is resolved in ItemGroups in the Target vs. outside of a Target. I need to be a little more explicit with my path and then it works. Basically I did this to get it to work:
<Target Name="Build">
<MSBuild
Projects="#(UnitTestProject)"
Properties="$(BuildProperties)">
<Output TaskParameter="TargetOutputs" ItemName="UnitTestOutput" />
</MSBuild>
<ItemGroup>
<TestAssembly Include="#(UnitTestOutput)" Exclude="$(RootTestPath)\**\*.Example.dll" />
</Target>
<Target Name="Test" DependsOnTargets="Build">
<xunit Assemblies="#(TestAssembly)" />
</Target>
I am creating a buildscript, where I'm outputting the TargetOutputs of an MSBuild, then wanting to call FXCop in a separate target, and using those outputs in the TargetAssemblies.
<Target Name="Build">
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDLLs"/>
</MSBuild>
<CallTarget Targets="FxCopReport" />
</Target>
<Target Name="FxCopyReport">
<Message Text="FXCop assemblies to test: #(TargetDLLs)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="FXCopReport.html"
TargetAssemblies="#(TargetDLLs)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
ApplyOutXsl="True"
FailOnError="False" />
</Target>
When I run this, in the FxCopyReport target, the Message of TargetDLLs in empty, whereas if I put this in the Build target, it populates.
How can I pass/reference this value?
There is a blog post by Sayed Ibrahim Hashimi (co-author of Inside MSBuild book), describing the issue you ran into, dating back in 2005. Essentially CallTarget task is behaving weird. I'm not sure if it is a bug or designed behavior, but the behavior is still the same in MSBuild 4.0.
As a workaround, use normal MSBuild mechanism of setting order of execution of targets in MSBuild, using attributes DependsOnTargets, BeforeTargets or AfterTargets.
I was able to figure this one out.
Essentially, after the MSBuild step, I created an ItemGroup, which I then referenced in the calling Target.
<Target Name="Build">
<Message Text="Building Solution Projects: %(Projects.FullPath)" />
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDllOutputs"/>
</MSBuild>
<ItemGroup>
<TestAssemblies Include="#(TargetDllOutputs)" />
</ItemGroup>
</Target>
<Target Name="FXCopReport">
<Message Text="FXCop assemblies to test: #(TestAssemblies)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="$(BuildPath)\$(FxCopReportFile)"
TargetAssemblies="#(TestAssemblies)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
Rules="$(FxCopExcludeRules)"
ApplyOutXsl="True"
FailOnError="True" />
<Message Text="##teamcity[importData id='FxCop' file='$(BuildPath)\$(FxCopReportFile)']" Condition="'$(TEAMCITY_BUILD_PROPERTIES_FILE)' != ''" />
</Target>
Looking at this article from MS, I have a question about the SolutionToBuild section.
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\path\MySolution.sln />
<SolutionToBuild Include="$(SolutionRoot)\Scribble\scribble.sln" />
<SolutionToBuild Include="$(SolutionRoot)\HelloWorld\HelloWorld.sln" />
<SolutionToBuild Include="$(SolutionRoot)\TestProject1\TestProject1.sln" />
</ItemGroup>
It says that the sequence of the build is determined by the order above. So, for example, MySolution would be built before scribble.
However, is this safe if scribble is dependant on MySolution? For example, MySolution outputs one or more dlls that are used by scribble. If MySolution and scribble are changed simultaneously, will the build wait for MySolution to be completely compiled before moving to the next project?
You can try to use additional metadata for item SolutionToBuild. Some work with recursion and voilà!
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\Scribble\levelone.sln">
<DependsOnSolutions>$(SolutionRoot)\Scribble\leveltwo.sln</DependsOnSolutions>
</SolutionToBuild>
<SolutionToBuild Include="$(SolutionRoot)\Scribble\leveltwo.sln">
<DependsOnSolutions>$(SolutionRoot)\Scribble\levelthree.sln;$(SolutionRoot)\TestProject1\TestProject1.sln</DependsOnSolutions>
</SolutionToBuild>
<SolutionToBuild Include="$(SolutionRoot)\Scribble\levelthree.sln" />
<SolutionToBuild Include="$(SolutionRoot)\TestProject1\TestProject1.sln" />
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="BuildSolution"
Properties="SolutionFullPath=%(SolutionToBuild.Identity)"/>
</Target>
<Target Name="BuildSolution">
<CreateItem Condition="'%(SolutionToBuild.Identity)'=='$(SolutionFullPath)'"
Include="%(SolutionToBuild.DependsOnSolutions)">
<Output TaskParameter="Include"
ItemName="DependentSolutions" />
</CreateItem>
<Message Text="Building solution $(SolutionFullPath)..." />
<Message Text="Solution $(SolutionFullPath) depends on %(DependentSolutions.Identity)..."
Condition="'#(DependentSolutions)'!=''"/>
<Message Text="Building dependent solutions..."
Condition="'#(DependentSolutions)'!=''"/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="BuildSolution"
Properties="SolutionFullPath=%(DependentSolutions.Identity)"
Condition="'#(DependentSolutions)'!=''"/>
<!-- <MSBuild Projects="$(SolutionFullPath)" /> -->
<Message Text="Building solution $(SolutionFullPath)... OK" />
</Target>
</Project>
How do you manage solution dependency? Aren't you referencing projects instead? I'm also puzzled about the 'simultaneous' changes on some of your solutions. Please clarify the nature of these changes.
So far, the answers to your questions are:
No. They may be compiled one after the other, but does it qualify for dependency?
Yes. If the sequence is mandatory, the builder will 'wait' until each solution is built (either with success or error) before moving to the next.