When does MSBuild set the $(ProjectName) property?

bwerks picture bwerks · Apr 15, 2010 · Viewed 13k times · Source

I'm fairly new to MSBuild, and I've done some customization on a WPF project file that I'm building both in Visual Studio 2010 and TFS 2010. I've customized the output path as follows:

<OutputPath Condition=" '$(TeamBuildOutDir)' == '' ">$(SolutionDir)build\binaries\$(ProjectName)\$(Configuration)\$(Platform)</OutputPath>
<OutputPath Condition=" '$(TeamBuildOutDir)' != '' ">$(TeamBuildOutDir)binaries\$(ProjectName)\$(Configuration)\$(Platform)</OutputPath>

This allows me to build to a centralized binaries directory when building on the desktop, and allows TFS to find the binaries when CI builds are running.

However, it seems that in both cases, the $(ProjectDir) property is evaluating to '' at build time, which creates strange results. Doing some debugging, it appears as if $(ProjectName) is set by the time BeforeBuild executes, but that my OutputPath property is evaluating it prior to that point.

<ProjectNameUsedTooEarly Condition=" '$(ProjectName)' == '' ">true</ProjectNameUsedTooEarly>

The preceding property is in the same property group as my OutputPath property. In the BeforeBuild target, $(ProjectNameUsedTooEarly) evaluates to true, but $(ProjectName) evaluates to the project name as normal by that point.

What can I do to ensure that $(ProjectName) has got a value when I use it?

I just used Attrice's MSBuild Sidekick to debug through my build file, and in the very first target available for breakpoint (_CheckForInvalidConfigurationAndPlatform) all the properties seem to be set already. ProjectName is already set correctly, but my OutputPath property has already been set using the blank value of ProjectName.

Answer

Peter McEvoy picture Peter McEvoy · Apr 16, 2010

Hmm - bit of confusion going on there which I'll try to sort out

  1. Don't use $(ProjectDir) - use $(MSBuildProjectDir) - that's the location of your csproj in the source tree and is set by MSBuild.exe as a reserved property. I don't think $(ProjectDir) is available until after Microsoft.Common.Targets has been imported (which is done by Microsoft.Csharp.targets). Property evaluation is always carried out "in-place" within the file, and not when all the Imports have completed. This may explain why you are seeing the property as valid in the SideKick tool

  2. Likewise use $(MSBuildProjectName) (which I think will address your problem)

  3. I'm unsure about VS2010 and TFS2010 (as that uses MSBuild 4.0 and no doubt a new TeamBuild), but in 2008, it's pretty hard within a .csproj to figure out if your build was called from a command line/IDE build or from within TeamBuild. What I'm trying to say is that I don't think $(TeamBuildOutDir) is available within your csproj. I normally test $(TeamBuildConstants) property, as that property is passed down when teambuild calls your proj file. YMMV as I haven't played with 2010 yet..