Scott Dorman

ephemeral segment

  Home  |   Contact  |   Syndication    |   Login
  566 Posts | 9 Stories | 568 Comments | 51 Trackbacks

News


Post Categories

Image Galleries



Creative Commons License


Microsoft MVP


MCP Profile


Locations of visitors to this page

Subscribers to this feed

TwitterCounter for @sdorman

View blog authority

Add to Technorati Favorites

Windows Live Alerts

AddThis Social Bookmark Button

LinkedIn profile

Community Credit profile

The Code Project

Follow me on Twitter

Get Free Shots from Snap.com

Community Credit Hall of Fame

Get Feedghost

Xobni outlook add-in for your inbox

Windows Live Translator


Support This Site

Tag Cloud


Article Categories

Archives

Post Categories

Image Galleries

I have been working and talking a lot about MSBuild over the last few months. As part of that work, I have implemented several custom tasks for MSBuild. Most of those tasks were ones that I had written as part of an NAnt based build system while others were part of the NAntContrib project. There is a very good basic explanation of how to write a task on MSDN, so instead I will cover how to port a task from NAnt to MSBuild. To keep things simple, I'm going to focus on creating an MSBuild v3.5 task in C#.

The first step is to create a new C# .NET Framework 3.5 based Class Library. You then need to add the following references:

  • Microsoft.Build.Framework
  • Microsoft.Build.Utilities.v3.5

Once you do that, you can create a new class file for the new MSBuild task. After the class is created, you need to add the using statements for the MSBuild namespaces:

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

namespace MSBuildContrib.Tasks
{
    public class Attrib : Task
    {
    }
}

Make sure to derive your task from Microsoft.Build.Utilities.Task, which is very similar to the NAnt.Core.Task class. At this point, you will encounter the first difference between the two tasks. MSBuild tasks do not use attributes like NAnt tasks, so you don't need to decorate the class with the TaskName attribute.

The minimal task implementation is to override the public Execute method. This is has a different signature than the Execute method in an NAnt task. Instead of being a void function, it returns a boolean value. The class should now look like this:

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

namespace MSBuildContrib.Tasks
{
    public class Attrib : Task
    {
        public override bool Execute()
        {
        }
    }
}

The boolean value returned by the Execute task informs the MSBuild engine of the tasks success or failure. If the task succeeded, you should return true; otherwise you should return false. Ideally you would want to log any exceptions that are encountered in the Execute method and return false.

The next step is to create the properties that will be available in the build script. Unlike NAnt, which requires a TaskAttribute attribute, any public property is exposed to the build script. By default, all properties are optional so if you want a required property you will need to decorate it with the Required attribute.

While the lack of attributes makes things simpler it also means that the property return values are more restricted. MSBuild supports only the basic data types: System.Boolean, System.String, and any of the numeric types System.Int32, System.Int64, or System.Int16. It's at this point that you start to see the major difference between the two, since NAnt allowed you to use any of the .NET data types.

This restriction might seem like it is too limiting at first, but so far I haven't encountered any problems with it. In fact, it's the exact opposite. To expose a collection as a return type, NAnt requires you to use a FileSet or BuildElement datatype. On the other hand, MSBuild allows you to specify these as a String or Microsoft.Build.Framework.ITaskItem array.

Let's look at an example to make this a bit clearer. The NAnt AttribTask declares the following property:

private FileSet _fileset = new FileSet();

[BuildElement("fileset")]
public FileSet AttribFileSet 
{
    get { return _fileset; }
    set { _fileset = value; }
}

The corresponding property for the MSBuild Attrib task declares the property like this:

private ITaskItem[] inputFiles;

[Required]
public ITaskItem[] InputFiles
{
    get { return inputFiles; }
    set { inputFiles = value; }
}

Alternatively, it could have been declared like this:

private string[] inputFiles;

[Required]
public string[] InputFiles
{
    get { return inputFiles; }
    set { inputFiles = value; }
}

The only difference between these two declarations is that by using an ITaskItem array, you gain access to the additional item metadata that MSBuild makes available. If you don't need that metadata, declaring this as a string array is just fine.

The benefit to using arrays is that MSBuild will implicitly convert a semi-colon delimited list to the array. This means that you can call the task like this:

<Attrib InputFiles="File1.txt;File2.txt;File3.txt;File4.txt" />

And be able to iterate over the list of files using a foreach or a for loop:

for (int i = 0; i < this.inputFiles.Length; i++)
{
    // Do something with each file.
}

For NAnt properties that were returning a System.IO.FileInfo or System.IO.DirectoryInfo you can return a String or ITaskItem. Again, the only difference between declaring the property with an ITaskItem return type is that you gain access to the additional item metadata.

The other difference for properties is that MSBuild does not include equivalents for NAnt's validator attributes. If you need to validate the property input you must do so in the property setter or the Execute method.

At this point, the class should look like this:

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

namespace MSBuildContrib.Tasks
{
    public class Attrib : Task
    {
        private ITaskItem[] inputFiles;

        [Required]
        public ITaskItem[] InputFiles
        {
            get { return inputFiles; }
            set { inputFiles = value; }
        }

        public override bool Execute()
        {
            bool flag = true;
            return flag;
        }
    }
}

We are now ready to actually implement the Execute method. As I mentioned earlier, you generally want to try and minimize the exceptions that are thrown during the task execution and instead just log them as errors. If we take this into consideration when implementing the Execute method, we end up with a method that looks like this:

public override bool Execute() { bool flag = true; for (int i = 0; i < this.inputFiles.Length; i++) { flag &= this.UpdateAttributes(this.inputFiles[i].ItemSpec); }

 

return flag; }

This brings us to another difference between MSBuild and NAnt: logging task errors and messages. Unfortunately, this is an area that I think NAnt still does a considerably better job of doing. In NAnt, each task has a verbose property that controls the level of detail that is to be reported during task execution. Going beyond that, the logging levels are independent of the verbosity since the levels determine the type of message logged. MSBuild does not have a similar concept of verbosity at the task level, and the logging levels in MSBuild are actually verbosity levels and are tied to the /verbose command line switch. You also don't have a built-in exception that can be thrown, like the NAnt BuildException. Instead, you can either throw the exception or log it as a failure.

To see this in action, lets look at the UpdateAttributes method:

private bool UpdateAttributes(string path)
{
    bool flag = true;

    try
    {
        // Change the attributes
        Log.LogMessage(MessageImportance.Normal, "Attributs changed for {0}.", path);
    }
    catch (IOException ex)
    {
        Log.LogError("Failed to change attributes for {0}. {1}", path, ex.Message);
        flag = false;
    }
    catch (Exception ex)
    {
        if (ExceptionHandling.NotExpectedException(ex))
        {
            throw;
        }
        Log.LogError("Failed to change attributes for {0}. {1}", path, ex.Message);
        flag = false;
    }
    return flag;
} 

One of the nice features that NAnt tasks provided was the ability to set properties from within the task. MSBuild tasks don't allow you to do this, but you can create output properties. These output properties are captured in an Output element declared in the build script. It accomplishes the same thing with just a little bit of extra work. It also has the added benefit of not creating global properties that aren't used...just because the task declares an output property does not require it to be captured in the build script. Declaring an output property is as simple as decorating it with the Output attribute. The attribute name is a little misleading, however, since these properties are actually bi-directional. That is, a property decorated with an Output attribute can also be used as input. If you do create output properties, they should be set to a valid value by the time the Execute method completes.

To put all of this together in a simple to follow list:

  • MSBuild tasks do not use attributes to declare the task or the properties that are available to the build script.
  • Properties can only be the following data types:
  • Required properties are marked with the single Required attribute.
  • Tasks cannot set project properties directly, instead you must declare a property and decorate it with the Output attribute.
  • Task logging is different (and not as flexible).
  • There is not an equivalent to the BuildException exception, instead just throw a normal .NET exception.
  • The Execute method returns a boolean indicating the task success or failure.

The complete source code for the MSBuild Attrib task can be found in the MSBuildContrib project on CodePlex.

posted on Wednesday, January 09, 2008 9:37 AM

Feedback

# re: Porting an NAnt Task to MSBuild 1/12/2008 12:02 PM Scott
Our company makes extensive use of NAnt. I'm familiar with MSBuild as we've had to call out to MSBuild for certain build steps. However, I've never been compelled to port our NAnt to MSBuild, as there is nothing that drives us to do so.

In your opinion, what does MSBuild offer over NAnt? Why would one be compelled to convert over to MSBuild. I'd be interested in hearing your reasoning.

Best regards,
Scott

# re: Porting an NAnt Task to MSBuild 1/12/2008 6:58 PM Scott
If you already have a big investment in NAnt, you probably won't gain a whole lot right now by porting it to MSBuild. While they are very similar and the learning curve to move from one to the other is very minimal (mostly just learning the new syntax and task names), there isn't much you can do in MSBuild that can't also be done in NAnt.

A few benefits that MSBuild does have over NAnt are:

1. MSBuild is part of the .NET Framework runtime, so there are no additional dependencies to install.
2. MSBuild is (for 95% of .NET projects) the same technology and process that Visual Studio uses to compile projects. This means that you get the same experience from outside of the IDE as you do from inside.
3. In my experience porting an existing NAnt based build system to MSBuild, the resulting MSBuild script is actually more flexible, simpler to understand, and easier to refactor.

Overall, you need to make a judgement call if the time spent porting is worth spending. In my particular case, it was since I was starting a build system from scratch for someone else.

# re: Porting an NAnt Task to MSBuild 2/3/2009 6:22 PM Jonathan
That's assuming you aren't using any third party tools such as NUnit, xUnit.NET or NCover and NCoverExplorer.

There are all kinds of related and very useful tools and utilities that have NAnt tasks for them already.

.02 - Jonathan

# re: Porting an NAnt Task to MSBuild 2/3/2009 6:33 PM Scott
Jonathan,

That is true, there are a lot of third-party tools that have NAnt tasks but a lot of them also have MSBuild tasks as well and if the source code for the task is available you can always port it. I'm always looking for contributors to my MSBuildContrib project (http://www.codeplex.com/MSBuildContrib)...which, sadly, I've been neglecting lately. I should be able to get back to it soon.

Post Feedback

Title:
Name:
Email: (never displayed)
Url:
Comments: