Category Archives: CSharp

MsBuild woes

Why

Do you use WCF? Do you generate your datacontracts by means of postbuild events? Have you tried msbuild and seen a “cannot build XXXXX.csproj because DataContracts.cs was not found” error? Then keep reading.

What

MsBuild is the “next generation” nmake for Visual Studio. It’s been available since .NET 2.0 and has been bundled with Visual Studio since MSVC2008.

Same project files…

You don’t need to feed msbuild special project files (such as the Makefiles nmake required), it is smart enough to understand Visual Studio solutions.

… yet slightly different behavior

MsBuild does not behave 100% like Visual Studio (devenv), specifically in regards to:

  1. Dependencies
  2. When events are run
  3. When byproducts and referenced projects are copied

In general, this will affect you whenever you use postbuild events to generate source code or binaries which are required immediately.

In the project I am involved now, this is especially important when generating datacontracts. We use a Visual Studio post-build event.

Dependencies

There are two kinds of ways to express project-to-project dependencies:

  • Project to project references (Add Reference->Project tab) [this mechanism also handles gathering the referenced output file as well]. For .csproj, this is persisted as a <ProjectReference> item in the project file.
  • Explicitly specified dependencies (Solution properties->Project Dependencies) [with this mechanism you would usually also add a File Reference to the referenced output yourself]. This writes the “ProjectSection(ProjectDependencies)” section in the .sln.

There seems to be a long standing bug which makes msbuild not take all the dependencies into account.

As a consequence, a solution may build fine in Visual Studio (devenv) but not build at all with msbuild. I would say this bug is fixed in VS2010SP1, but I cannot tell 100% sure because I have not performed enough testing.

In addition to that, never add a dependency as a reference-to-DLL if that DLL is part of of your solution (.sln). Both MsBuild and DevEnv will choke when you switch from Debug to Release, because you would be setting a reference to a Debug DLL from a Release project, or viceversa (unless you use $(ConfigurationName) in your path, of course, but people rarely remember to do that).

When events are run

MsBuild runs the postbuild event before copying the byproducts to the output path

Visual Studio (devenv) runs the postbuild event after copying the byproducts to the output path

This can be solved by writing an AfterBuild event manually in the .csproj, but it is quite inconvenient because there is no GUI in Visual Studio to add, edit or remove AfterBuild events. You are alone with your text editor.

When byproducts and references are copied

As said above, msbuild runs the postbuild event before copying the byproducts. No only that: msbuild runs the postbuild event before copying the referenced projects (DLLs) to the output path.

Visual Studio (devenv), on the other hand, runs the postbuild event after copying the byproduct and its referenced projects to the output path.

In summary

DevEnv

  1. Compile
  2. Copy byproducts (DLLs, executables, etc)
  3. Copy byproducts’ referenced project output (DLLs, etc)
  4. Run postbuild event

MsBuild

  1. Compile
  2. Run postbuild event
  3. Copy byproducts (DLLs, executables, etc)
  4. Copy byproducts’ referenced project output (DLLs, etc)

Workaround

If you need the compilation byproducts in the post-build event (as is generally the case when generating datacontracts in the postbuild event), you will need a workaround.

You would think it’d be possible to tell MsBuild to behave like devenv, right? Wrong. It is not possible.

The only possible workaround I have found is to manually copy the result of the compilation to the output path in the postbuild event. Something like this:

call “%VS100COMNTOOLS%\vsvars32.bat”

if exist “$(ProjectDir)Temp” del /s /f /q “$(ProjectDir)Temp”

copy $(ProjectDir)\obj\$(ConfigurationName)\$(TargetName).dll .

svcutil /t:metadata /dataContractOnly /directory:$(ProjectDir)Temp\DataContract $(TargetName).dll

svcutil /t:code /dataContractOnly /r:$(ProjectDir)..\MyProject.Data.DataContract\bin\$(ConfigurationName)\MyProject.Data.dll /directory:$(ProjectDir)..\$(ProjectName).DataContract /out:DataContracts.cs $(ProjectDir)Temp\DataContract\*.xsd

(where “.” is the output path, the solution generally runs from there; usually you will only need to copy & paste this line to your postbuild event).

Not nice, but it works.