Making MSBuild in NANT act like MSBuild in Visual Studio

Posted by Tom on 2011-12-21 09:45

So I was making some NANT scripts for an ASP.NET MVC project.

Rather than going through the rigmarole of maintaining references and dependencies in two places (the VS project file and the NANT scripts), this time round I started using the <msbuild> task from nantcontrib instead of invoking the compiler directly using the plain old <csc> task. After making sure I set my OutDir I run NANT and get a failed build while copying a file, which is odd because I don't want MSBuild doing anything of the sort. It builds fine from Visual Studio, but after a little investigation it turns out that MSBuild is attempting to copy a file which is in the project but is missing from the file system. It's trying to drop it, along with the rest of the project contents, into _PublishedWebsites which has attaching itself to my output directory, like a warty blemish or tumour on my otherwise pristine build.

By setting the verbosity level of MSBuild to Diagnostic in both NANT and Visual Studio I could diff the two outputs and see where the processes started to diverge. The output of the NANT ran to a svelte 26k lines, but a quick search on '_PublishedWebsites' turns up the part of the log which includes the failing build. The copy operation is happening within a _CopyWebApplication target. Time to do a quick search in the Visual Studio MSBuild log . . .

Aha! Here's the slippery little devil:

Target "_CopyWebApplication" skipped, due to false condition; (!$(Disable_CopyWebApplication) And '$(OutDir)' != '$(OutputPath)') was evaluated as (!False And 'bin\' != 'bin\').

But why? Why would you do that? What is the OutputPath even supposed to be? According to MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets . . .

<!--
============================================================
_CopyWebApplication

This target will copy the build outputs along with the content files into a 
_PublishedWebsites folder.

This Task is only necessary when $(OutDir) has been redirected to a folder 
other than ~\bin such as is the case with Team Build.

The original _CopyWebApplication is now a Legacy, you can still use it by 
setting $(UseWPP_CopyWebApplication) to true. By default, it now change to 
use _WPPCopyWebApplication target in Microsoft.Web.Publish.targets. It allow 
to leverage the web.config trsnaformation.
============================================================
-->

Fine. Whatever. Regardless of the rationale, if you set your OutDir and OutputPath properties to the same value in the NANT <msbuild> task:

<msbuild project="${dir.src}/${project::get-name()}/${project::get-name()}.csproj"
      target="Rebuild" verbosity="Minimal">
   <property name="Configuration" value="Release" />
   <property name="OutDir" value="../../${dir.build}/${project::get-name()}/bin/" />
   <property name="OutputPath" value="../../${dir.build}/${project::get-name()}/bin/" />
</msbuild>

Then your case of _PublishedWebsites should clear up in no time. (Alternatively you could set the Disable_CopyWebApplication property, but I'm attempting to get our NANT builds as close to our VS builds as possible.) As an addendum, once I knew what I was looking for I found quite a bit of confusion related to OutputPut, including a blog post from Mark Needham who also ran into problems related to OutputPath when tweaking OutDir and who turned up this nugget from MSDN:

OutputPath: This property is typically specified in the project file and resembles OutDir. OutputPath has been deprecated and OutDir should be used instead whenever possible.

At this point I stopped caring and reversed out of the rabbit hole.