MSBuild (RSS)

MSBuild

Creating custom MSBuild tasks

Let's pick up where we left off.

If you go to the MSBuild wiki, you'll see that the topic  "How do I write a task?" is still blank. There is an MSDN article (in a 3 part series) that explains this, so I'll start from another perspective: I have some existing NAnt tasks and I want to use these in MSBuild.

Let's start with a simple one: Version (actually, it's a nantcontrib task, but that's the same, right?). In NAnt this task reads a version number from a file, increments it and uses it as a NAnt property.

Change the using statements

using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Util;

To their MSBuild equivalents:

using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

Also, don't forget to set the namespace to something more appropriate.

Next, you should rename the task not to include the 'Task' suffix. In NAnt, the taskname you use in the buildfile is set with a custom attribute on the class [TaskName("name")], but MSBuild uses the classname. The good way to do this imo, is to leave off the 'Task' suffix, as this is the way the standard MSBuild tasks are named.

You will see something like the above with the public properties exposed by the task. In both NAnt and MSBuild, you can set/get these in xml. NAnt uses the technique of marking these properties wit the [TaskAttribute("name")] custom attribute, and MSBuild just uses the correct name of the property. So remove the attributes on the properties.

On some properties, in the NAnt version you will see the attribute [StringValidator(AllowEmpty=false)]. Replace this with the MSBuild [Required] attribute.

NAnt also contains a package called "StringUtils". Unless you want to re-use this, you're going to have to remove any references to this package, e.g.

StringUtils.ConvertEmptyToNull(value)

becomes

(string.IsNullOrEmpty(value)) ? null : value;

Also, the BuildException class is NAnt-specific. Replace these with generic Exceptions for the time being.

Next we come to the body of the implementation. Replace the NAnt ExecuteTask() method with its MSBuild Execute() equivalent. The first difference you'll notice is that the MSBuild version returns a boolean value, true if the task succeeded, so you'll need a mechanism (combined with try/catch) that determines the outcome.

Replace the logging mechanism with the MSBuild Log class:

Log(Level.Info, "Build number '{0}'.", buildNumber);

becomes

Log.LogMessage(BuildEventImportance.Low, "Build number '{0}'.", _buildNumber);

The rest of the implementation is the same, the compiler will help you with any NAnt-specific things (mostly BuildExceptions).

When you look at the code that comes with this post, you'll see that I added a method called "WriteVersionInfoFile" and that this is executed if the corresponding property is set to true in the buildfile.  This is used to create a VersionInfo.cs file that you can use in your solution to represent the version information for the assemblies. If you want all assemblies in a solution to have the same version number, you need to remove all AssemblyVersion attributes from the AssemblyInfo.cs files, and put this information in a solution file called "VersionInfo.cs". You can then add this file as a link to all projects in the solution (not as file, because then it is copied) and it will be used to set the version number for the assemblies.

A quick recap:

1. replace the "using" statements
2. remove the NAnt [TaskXXX] attributes and name the class and its properties correctly
3. replace the NAnt [StringValidator(XXX)] attributes with MSBuild or other equivalents
4. solve the StringUtils issue (either remove it or import it)
5. remove all references to BuildException
6. replace the logging mechanism

You can find the source here.

You can find the original 'VersionTask.cs' file in the nantcontrib source redistributable. I used the one from v0.84, but the 0.85RC1 version is the same.

Now for some licensing issues. I don't know if the above was entirely legal. I haven't removed the header stating that this class is 'free software under the GPL', but I've changed the name and the NAnt (c) statement. If this is in some way wrong, let me know.

[Edit] I forgot to mention how to start using this task. See the previous post on MSBuild for instructions on how to make this task available in MSBuild buildfiles. An example of the task in action is below:

<Version
   BuildType="increment"
   RevisionType="increment"
   Path="$(BuildNumberFile)"
   WriteVersionInfo="true"
   VersionInfoPath="$(VersionInfoFile)">
      <Output TaskParameter="BuildNumber" PropertyName="BuildNumber"/>
</Version>

Using the Microsoft SDC MSbuild tasks

Microsoft Research UK has released a number of 'extra' tasks for MSBuild on gotdotnet. You can find the workspace here. These tasks include a number of common things, like file management, source control etc... You can find a complete list of tasks at Jamie Cansdale's NunitAddin weblog here.

The mechanism for using custom tasks in MSBuild is with an <Import/> statement in your build file.

<Import Project="C:\WINDOWS\Microsoft.NET\Framework\v2.0.40607\Microsoft.Sdc.Tasks"/>

After that, you can use the tasks in your build file and msbuild will recognize them.

Of course, you still need to create the '.tasks' file that points the msbuild framework to the assembly that contains the task. This mechanism is widely used by the msbuild framework and in itself, its just a build file that uses the <UsingTask> task :-). It looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="
http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyName="Microsoft.Sdc.Tasks, Version=2.0.5000.0, Culture=neutral, PublicKeyToken=e24a7ed7109b7e39" TaskName="Microsoft.Sdc.Tasks.ActiveDirectory.Group.AddUser" />
  <UsingTask AssemblyName="Microsoft.Sdc.Tasks, Version=2.0.5000.0, Culture=neutral, PublicKeyToken=e24a7ed7109b7e39" TaskName="Microsoft.Sdc.Tasks.ActiveDirectory.Group.Create" />
  <UsingTask AssemblyName="Microsoft.Sdc.Tasks, Version=2.0.5000.0, Culture=neutral, PublicKeyToken=e24a7ed7109b7e39" TaskName="Microsoft.Sdc.Tasks.ActiveDirectory.User.Create" />
...
</Project>

Next, you put this file and the assembly in the .NET framework folder (c:\windows\microsoft.net\framework\v2.0.40607).

I was getting errors running the SDC tasks because they seemed to overlap tasks already present in the basic MSBuild framework (I don't know if this was related to my desktop install or something else). Just renaming the '.tasks' file that came with the SDC tasks to '.tasks2' and updating my import statement above made the errors go away. (spooky :))

By doing these three simple things, you can start using custom tasks in your build files:
1. create a '.tasks' file that lists the custom tasks in an assembly
2. copy the '.tasks' file and the assembly somewhere msbuild will find them (the framework dir is the easiest)
3. use an <import> statement to reference the tasks in your build file

How to use the SDC tasks:
When you extract the zipfile from the gotdotnet workspace, you should look in the following folder:
<zipfile>\Microsoft.Sdc.Tasks\MainTempGDN\Framework2.0

In this folder, you will find another zipfile, called 'GDN2.0.041008.124.zip'. Extract this zipfile and look in the following folder:
<new zipfile>\Microsoft.Sdc.Tasks\MainTempGDN\Install

This folder contains everything you need to get started (apart from the docs :-)). Copy everything in this folder to your .NET framework folder (the v2.0.40607 one). You might experience the same 'overlap' problem I explained above, but that can be solved easy :-).

You can find a sample build file that uses some of the SDC tasks (mostly file & folder mgmt) here.

Next time, I'll talk about how to create a custom task yourself, and how to port a Nant or NantContrib task to MSBuild. I needed it because I couldn't get the source control tasks in the SDC build to work, so I ported the Nant VSS tasks.

CruiseControl.NET and MSBuild

Hi all,

This is my first post to this blog, and I wanted to make it a more memorable one than 'this is a test' ;-)

This blog is (among other more worldly things I hope) going to be about automated builds. I'm currently working on a project that is in a proof-of-concept stage and is using Visual Studio 2005 Beta 1 and, by extension, the version of MSBuild that comes with it (v2.0.40607.16). In itself, that's not that unusual, but we also started out with the idea of automating builds and incorporating a number of good software practices or concepts in the build process (unit testing, static analysis, outputting reports to developers etc...).

If you start looking around the web for an automated build solution for .NET that can also function apart from Visual Studio, you'll probably end up with a combination of NAnt on the one side and CruiseControl.NET, Hippo.NET or Draco.NET on the other side. NAnt is the answer to scripting the real work behind your build process, and the other tools provide you with (a.o.) a way to schedule those builds (or execute on-demand builds) and a nice overview of past and current build results and reports.

What you don't get is a way to combine one of these tools with MSBuild. Of course, you can do this with NAnt by using the <exec> task to start msbuild.exe, but I wanted to cut out the middle man. Note: I do NOT want to start a religious war here and i don't consider MSBuild to be better than NAnt (or vice versa). Let's just say I wanted to make it more challenging by veering away from the path most travelled :-).

So here's a short overview and a sample of my experiences with running CruiseControl.NET (ccnet for short) with MSBuild instead of NAnt.

First of all, ccnet uses an xml configuration file to drive the build process: ccnet.config. This file is included in every 'server' folder in your ccnet setup. Next, it has a mechanism called 'builders' that delegate the actual build process to a more specialized tool. The <build> node in the ccnet.config xml file will be used to drive the builder.

Out of the box, a builder for NAnt (if you download the source, look for a file called NAntBuilder.cs) is included. When you look at the code, you'll see it's fairly easy to replace the arguments needed to run nant.exe with those needed to run msbuild.exe and hey-presto! - MSBuilder.cs is born.

The first thing you'll notice in the bowels of ccnet is that it uses a component called Exortech.Reflector to map builder properties onto xml. This means that you can decorate .NET properties with attributes that declare which xml nodes are going to be used to fill the properties with data.

This code:
[ReflectorProperty("buildArgs", Required = false)]
  public string BuildArgs;

Will be filled in by the <buildargs> node in the following xml:
    <build type="msbuild">
      <executable>C:\WINDOWS\Microsoft.NET\Framework\v2.0.40607\MSBuild.exe</executable>
      <baseDirectory>d:\temp\</baseDirectory>
      <buildArgs>@mybuild.response</buildArgs>
      <buildFile>mybuild.proj</buildFile>
      <targetList>
        <target>Build</target>
      </targetList>
      <buildTimeoutSeconds>300</buildTimeoutSeconds>
    </build>

What you basically have to do is figure out what information you need to pass onto the msbuild executable, translate this into appropriate xml, and provide properties in the 'builder' that read the xml.

Download the MSBuilder.cs file here.

The way you get ccnet to use your custom builder is either by adding it to the ccnet source code and rebuilding it, or by building it into a plugin. You can create a plugin as follows:
- create a VS.NET 2003(!) library project
- add your custom builder to the project
- add the appropriate references (exortech reflector and a number of 'core' ccnet dlls)
- name the output of the project 'ccnet.<insert name here>.plugin.dll
- place the plugin dll in the 'server' folder of your ccnet project

Next, you can replace the standard <build> node in the ccnet.config file with the above 'msbuild' <build> node, write a small msbuild script ('.proj' file) and you're off.

In short:
1. build the custom 'builder' into a plugin dll
2. put the plugin dll in the ccnet server folder
3. put the custom xml in ccnet.config
4. point the custom xml to an appropriate msbuild script
5. force the ccnet project to build through its website (or schedule it) and watch the magic happen :-)

Coming up next on this blog:
- using the Microsoft SDC MSBuild tasks to do lots of stuff (source control, file management, nunit, fxcop, ...)
- creating custom MSBuild tasks yourself (or stealing them from NAnt) (it's not really stealing as long as you re-release them under the (L)GPL, is it ?)
- Whatever you want to hear, as long as its about MSBuild and/or CCNET