MSBuildAllProjects Considered Harmful

For a long time now, a standard piece of advice to authors of MSBuild .targets and .props files is to make sure you add your file path to the property $(MSBuildAllProjects). This is because MSBuild checks the paths listed in that property when it wants to determine whether a project file has changed and needs to be rebuilt. So if you wanted projects that use your .targets or .props to be rebuilt when your file is updated or edited, you added your full path to that property.

However, there were three problems with this:

  1. Paths tend to be long and, especially with NuGet in the picture, there are now a lot of .targets and .props files that get mixed into a build. So the $(MSBuildAllProjects) property keeps getting bigger and bigger.
  2. MSBuild keeps around all versions of a property. So not only could the final value of the property be really long, if there were, say 30 files added in one at a time, there are also 30 copies of the property as it is being built up, multiplying the memory cost of the property.
  3. There is one instance of this property for each project in a solution. So if you have a lot of projects in a solution, you’re going to multiply things even further.

The end result was that on some larger solutions we’ve looked at, you end up with a non-trivial percentage of the managed heap devoted to just this one single property. And the thing is that 99% of the time we only really care about one file in that list — the file that was last modified, since that’s what MSBuild is going to check against.

So, starting in 16.0, MSBuild now will automagically prepend the last modified project/.targets/.props file to $(MSBuildAllProjects). So if the only reason you’re adding yourself to that property is to make sure that the project rebuilds when you’re touched and you only need to support 16.0+, you should stop adding yourself. We’re already doing this in our projects, and so should you, to help performance.

Leave a Reply