Build process hangs when recursively building solu

2019-08-12 09:27发布

问题:

While trying to answer this SO question I encountered an issue which I cannot explain and would appreciate your input on.

Setup:

  1. Have solution consisting of several C++ projects (Test.sln),
  2. Add a brand new project to your solution (BuildInstaller.vcxproj),
  3. Open BuildInstaller.vcxproj in text editor and append following xml fragment right before closing </Project> tag:
<Target Name="Build">
  <MSBuild Projects="..\Test.sln" Properties="Configuration=Release;Platform=Win32" />
  <MSBuild Projects="..\Test.sln" Properties="Configuration=Release;Platform=x64" />
</Target>
  1. Above code overrides default Build target of the BuildInstaller project and everytime the project is being built, it builds its parent solution with Release configuration for both Win32 and x64 platforms,
  2. To prevent unbounded recursion, open Configuration Manager in Visual Studio and uncheck "Build" checkbox for BuildInstaller project for all combinations of Debug/Release and Win32/x64,
  3. Then, still in Configuration Manager, create a new configuration, e.g. Installer for which you should uncheck all the other project's Build checkbox and leave it checked for BuildInstaller only,
  4. Now build your solution for Installer configuration.

I would expect this build to finish successfully, but it simply hangs, even though BuildInstaller should not be built recursively as we are recursively building the Test.sln only for Release configuration.

I am not asking whether this is a good approach or how to work around it, I am just curious why the build hangs. Setting output window verbosity to Diagnostic was of no help to me.

I am using Visual Studio 2013 Ultimate.

回答1:

MSBuild has an internal protection about recursion in the projects. Normally your build will fail with error MSB4006 in a case if any sort of circular dependency is discovered in the build graph. That said, if I were to guess what might have caused the hang, and if it is related to recursion, I would have inclined on the side of .sln files. The reason is that the way MSBuild treats .sln files is quite peculiar. Any time it encounters .sln file, it converts it to intermediate representation that actual MSBuild engine can understand. That intermediate representation does not have any identifier similar to the project file, thus the circular dependency detection logic might not work correctly if .sln is in the loop.

To solve your particular problem, there are couple of ways. The easiest one is to remove BuildInstaller.vcxproj from Test.sln. The second is to modify BuildInstaller.vcxproj as follows:

First, create an ItemGroup, populated with all projects from the solution:

<ItemGroup>
    <AllMyProjects Include="..\Proj1\Proj1.vcxproj" />
    <AllMyProjects Include="..\Proj2\Proj2.vcxproj" />
    ...
    <!-- DO NOT ADD BuildInstaller project to prevent recursion!!! -->
</ItemGroup>

Then build the projects for every configuration:

<Target Name="Build">
    <MSBuild Projects="@AllMyProjects" Properties="Configuration=Release;Platform=Win32" />
    <MSBuild Projects="@AllMyProjects" Properties="Configuration=Release;Platform=x64" />
</Target>

The downside of the second approach is that you have to remember to maintain list of projects in sync between .sln and your installer project.