]> MSBuild reverting to Debug for dependencies 🌐:aligrant.com

MSBuild reverting to Debug for dependencies

Alastair Grant | Friday 28 February 2020

I have recently spent some quality time with MSBuild, specifically an Azure DevOps Server agent (TFS) and some .NET projects.

What MSBuild does

MSBuild allows building of projects without running Visual Studio interactively, it's at the core of most .NET build systems.  MSBuild builds projects (e.g. .csproj), and in Visual Studio we collect those together to form a Solution (.sln).  MSBuild supports Solution files in so far as it will create a project that acts like a solution behind the scenes.  You can view this project file by adding an environment variable called MSBuildEmitSolution and setting to a value of 1.  This will output a *.metaproj file that contains what is to be built.

Complex project dependencies

MSBuild will chain build dependencies, consider this scenario:

  • Solution A
    • Project 1
    • Project 2 (has a reference to Project 1)

This scenario is simple, the solution contains everything needed for a build, Project 2 is dependent on Project 1, and MSBuild will handle this.  This is very simple though, and in a business with lots going on, you might see something more like this:

  • Solution A
    • Project 1
    • Project 2 (has a reference to Project 1)
  • Solution B
    • Project 3 (has a reference to Project 2)

In this scenario, we have a third project that is in a solution of its own, with a reference outside the solution.  Providing that the reference is a "project reference" (not just a DLL added), and MSBuild can find said project, then this will work too.  When Project 3 is built, it'll automatically build Project 2 and Project 1 for you.

The issue of lost configuration settings

But you might encounter an issue here, and the reason for this article.  If you're building your Release configuration, you may encounter an issue where Project 2 is built with the Debug configuration.  This can be bad as you get debug assemblies being compiled and outright dangerous if you use compiler statements to change behaviour based on compiler flags.

It is this problem, the dependent project being built as debug when the configuration is set to release that I encountered.  The configuration that is used for the build is a parameter passed into MSBuild /p:configuration=release, but it seems that this doesn't get passed onto the dependencies.  I spent a bit of time with MSBuild tasks to see if I could override this behaviour, but didn't get anywhere far.

The answer comes in a largely undocumented "feature" of MSBuild (or more specifically, the default build "target" configuration), in that it will clear any global or inherited parameters when it moves onto building dependencies.   In this situation, it falls back to the default behaviour for that project.  If you edit the *.*proj file directly, you can see that if no $(Configuration) value is provided, then the project will default to Debug.

The fix

There are three ways of approaching this:

  1. The first obvious approach is to chuck all projects into one super-Solution; this method is probably the cleanest from a MSBuild perspective, but when dealing with a large number of unrelated solutions, fairly laborious.
  2. Edit the *.*proj file and change the default configuration to Release; the problem with this approach is that it requires you remember to do it for each existing and new project.
  3. The final approach - and the one I've used - is to use the poorly documented /p:ShouldUnsetParentConfigurationAndPlatform=false parameter (what a name).  Which is fairly obviously the root of the problem.  I'm not entirely sure why the default behaviour is to clear configuration, but there we go.
Breaking from the voyeuristic norms of the Internet, any comments can be made in private by contacting me.