Lessons In MSBuild - Targets Are Called Only Once

   Submit to Reddit      

By design, targets in a MSBuild process are only called once. This is an important concept to understand, as it is unusual for the typical developer.

I had a situation where I wanted to call the same target from a few places in my MSBuild project - but I wanted to pass in different property values. I didn't want to repeat myself (DRY principle).

If <CallTarget ...> is used then the target is only called once within that process.

I found a sneaky way to work around this by examining the file C:\Program Files\MSBuild\Microsoft\VisualStudio\TeamBuild\ Microsoft.TeamFoundation.Build.targets

Instead of using <CallTarget ...>, use <MSBuild ...> which spawns off a new MSBuild process, thus allowing the target to be called each time.

<MSBuild Projects="$(MSBuildProjectFile)" Targets="MyTarget" Properties="MyProperty1=blah;MyProperty2=$(MyVariable)\blah2" />

Lessons in MSBuild - Applying the Single Responsibility Principle

   Submit to Reddit      

As an experienced developer, I promote the use of good practices in object-oriented designs.

I was modifying a number of MSBuild and TFS Build project files for several related environments and I noticed that they both duplicated information and each had more than one responsibility - such as specifying the solution to build and compilation settings such as code analysis, configuring testing, and then perhaps performing some custom operations.

I wondered if I could apply the Single Responsibility Principle to the build project files.

Indeed, you can split your project files into multiple files, and from the main project you can import the other file by using the following syntax.

<Import Project="MyCommonTargets.proj" />

Lessons in TFS Build - BuildShadowTask Failed

   Submit to Reddit      

Recently I have been testing out the private accessor mechanism in .NET to test private methods. I know there is a large debate over whether private methods should be tested directly or whether they should only be tested via public methods. But, this post is not about whether to test private methods or not.

In this particular application, there are a large number of Visual Studio projects, so File References are used everywhere instead of Project References so that the solution file is as small as possible, containing only the projects that are under development.

I recently modified the TFS build definition to run the tests as part of the TFS build, and then the build failed with the following log information.

Using "BuildShadowTask" task from assembly "C:\Program Files\MSBuild\Microsoft\VisualStudio\v9.0\TeamTest\Microsoft.VisualStudio.TestTools.BuildShadowsTask.dll".

Task "BuildShadowTask"

C:\Program Files\MSBuild\Microsoft\VisualStudio\v9.0\TeamTest\Microsoft.TeamTest.targets(18,5): error : Could not load file or assembly '[A.File.Referenced.Assembly.Name.Here], Version=, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

Done executing task "BuildShadowTask" -- FAILED.

Done building target "ResolveTestReferences" in project "[My.Test.Project.Name.Here.csproj]" -- FAILED.

When using the accessor mechanism to test private methods in a .NET language, the TFS build target "ResolveTestReferences" is called as part of the TFS Build process to build the xyz_Accessor.dll. [This build target resides in "C:\Program Files\MSBuild\Microsoft\VisualStudio\v9.0\TeamTest\Microsoft.TeamTest.Targets".]

It appears that in order for this build target to be successful, the test project must contain:

  • At least one Project Reference (as opposed to a File Reference); and
  • Copy Local set to True on the reference to the assembly for which the Accessor will be created.

In order to work around the first problem, I created a blank Visual Studio project that I called "AccessorFix" which I include in my test solution. I then added a Project Reference from each test project that uses accessors to the AccessorFix project.

Now the TFS build of the shadow accessors completes successfully! Go figure.

Lessons In .NET - Assembly Binding Configuration

   Submit to Reddit      

The Microsoft .NET Framework 2.0 SDK Configuration tool is a GUI for the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config file.

However, I was working on a machine that didn't have the SDK installed and I didn't want to install it on that machine. I needed to redirect an assembly by adding an <assemblyBinding> element, so I looked up the specification of the element at http://msdn.microsoft.com/en-us/library/twy1dw1e.aspx and went directly to the machine.config file in a text editor tool.

Regarding the appliesTo attribute, the documentation says the following:

... If no appliesTo attribute is specified, the <assemblyBinding> element applies to all versions of the .NET Framework.

I wanted my binding to apply to all the versions of the .NET framework, and I thought I would specify that in the XML by specifying appliesTo="", as below.

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" appliesTo="">

In all applications that I am familiar with - and even in ASP.NET - an attribute that has a value of the empty string behaves like the attribute has not been specified.

However, if you manually add an assembly binding section, do NOT specify appliesTo="", because that will actually prevent the assembly bindings applying to any .NET version at all!

Even after installing and using the tool to remove/fix the inner details of all assembly bindings, the actual <assemblyBinding> element was not removed and the manually added appliesTo="" remained - and this would cause future havoc.

Moral of the story: do NOT specify appliesTo="" in an assemblyBinding element!!