18

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="&lt;?xml version='1.0' encoding='utf-8'?&gt;
&lt;Project DefaultTargets='Build' ToolsVersion='4.0' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
  &lt;Target Name='Build'&gt;
    &lt;MSBuild
      Projects='$(SlnPath)'
      Properties='
        SolutionDir=$(MSBuildProjectDirectory)\%3b
        Configuration=%(ProjectConfiguration.Configuration)%3b
        Platform=%(ProjectConfiguration.Platform)
      '
      Targets='$(BuildCommand)'
    /&gt;
  &lt;/Target&gt;
&lt;/Project&gt;"
    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.

Amber
  • 507,862
  • 82
  • 626
  • 550
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    As for _why_ this is important: I can cut at least 37% from my build times if I can get the builds to run in parallel. (I tested this by running 12 instances of msbuild myself, from the command line, and saw a 37% increase in build performance--4:09 vs. 6:40. The downside of this is that it breaks unified logging of the builds; each build emits its own log.) – James McNellis Oct 20 '12 at 03:55

1 Answers1

22

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>
Jason Malinowski
  • 18,148
  • 1
  • 38
  • 55
  • If both configurations then go onto perform another task, is it possible to nest it under each configuration such that the next tasks for each configuration are also executed in parallel? E.g it won't wait for release & debug to finish, then do task A for release, and task A for debug, but rather as soon as Debug is done start A for debug, and as soon as release is done start A for release? – paulm Feb 08 '13 at 10:08
  • 1
    If I understand your question correctly: this approach starts the two builds off as entirely independent operations. Each will complete as fast as it can, with no synchronization between the two. Imagine you just launched two console windows, and typed msbuild in each. – Jason Malinowski Feb 08 '13 at 16:49
  • So if you wished to execute another command only when these had been built then this is not possible? – paulm Aug 16 '13 at 08:41
  • Of course it's possible...just add more stuff in the Build target. – Jason Malinowski Aug 16 '13 at 17:35
  • But such a query is best asked as a new question rather than a comment in an unrelated one. – Jason Malinowski Aug 16 '13 at 17:35
  • Note: to get this to work, it might be necessary to pass `/maxcpucount:n` to the msbuild process else it will still build everything in series. – stijn Nov 07 '17 at 08:49