16

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bwerks
  • 8,651
  • 14
  • 68
  • 100

1 Answers1

30

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..

Peter McEvoy
  • 2,816
  • 19
  • 24
  • Yeah, you're right--ProjectDir is set (directly from MSBuildProjectDir, in fact) in Microsoft.Common.Targets. That much I have found in investigating this. I guess my own confusion stems from the fact that I don't yet understand the sequence of common.targets, csharp.targets, the csproj file in question, etc. 2. I got ProjectName from the pre-build macro page from the project's property pages--I'll need to investigate whether MSBuildProjectName is available to team build. Where does MSBuildProjectName come from? Any idea how it's supplied? – bwerks Apr 16 '10 at 15:36
  • 1
    3. Apparently the maligned and ineffectual IsDesktopBuild has been replaced with $(BuildingInsideVisualStudio), but I only assume this having seen the property in my build log. As for TeamBuildOutDir, you're right--it's not set in a normal VS build, so you react really just based on its existence. I actually got the idea from http://blogs.msdn.com/aaronhallberg/archive/2007/06/07/preserving-output-directory-structures-in-orcas-team-build.aspx. – bwerks Apr 16 '10 at 15:37
  • 3
    +1: I just spent all afternoon beating my head over `$(ProjectName)` not being set yet. That'll teach me not to check Stack Overflow first... – James McNellis Aug 09 '10 at 20:39
  • +1000. This workaround prevents some truly ugly MSBuild markup for me. – Reinderien Nov 26 '12 at 19:56
  • Is there a time *not* to use MSBuildXXX? I just tried it for pre and post build batch commands and was getting empty strings. I've asked a related question here: http://stackoverflow.com/questions/33768753/what-is-the-difference-between-projectdir-and-msbuildprojectdir. – DuckMaestro Nov 18 '15 at 01:35
  • I faced a similar issue when porting a .NET project to the SDK project style, where I was using the $(ProjectName) variable to call an external script. As already explained the problem is that the Microsoft.Common.Targets is implicitly included after the last line of the SDK-style project. See also https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk. Obviously the behavior in old fashioned style projects must be that $(ProjectName) is available somewhere before the substitution of properties in post-build events actions... – Ste Feb 12 '22 at 14:19