Custom NAnt Tasks Calling Other Tasks

We use NAnt to automate our build process, and right now I'm working on refactoring the build that my group uses for continuous integration. One of the things I noticed is that when our product builds, the main build file "includes" a few other build files and executes targets from them. One of the build files that gets included only ever has one target called, and it does a bunch of internal work to get a lot of things done. It looks a lot like this:

Build script flow

Notice how there's only one entry point in the external build file and the tasks inside call other tasks, some of which are common, almost like NAnt "functions" that pass parameters by setting properties. Not only that, but part of the build output for the product is to include this build script so other products can include it and use it in the same way. I don't know about you, but this says "custom NAnt task" to me.

In converting this to a custom NAnt task, I found that part of what the script was doing was calling other custom NAnt tasks. Not wanting to replicate all of the functionality of these other custom NAnt tasks, I figured I'd write my custom task to call the other tasks programmatically.

Interestingly enough, this isn't as straightforward as you might think, and NAnt documentation on this is, well, light. You can't just create the task object and call it, you actually have to give the created task some context about the environment it's working in. You do this by calling the CopyTo method on the task object. By and large, the way it looks is this:

using System;

using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;

namespace MyCustomNAntTasks {
  [TaskName("customtask")]
  public class MyCustomTask : Task {
    protected override void ExecuteTask() {
      this.Log(Level.Info, "Starting custom task...");
      
      // Create and execute a <tstamp /> task
      this.Log(Level.Verbose, "Executing a tstamp task...");
      TStampTask tstamp = new TStampTask();
      this.CopyTo(tstamp);
      tstamp.Execute();

      // Create and execute a <sysinfo /> task
      this.Log(Level.Verbose, "Executing a sysinfo task...");
      SysInfoTask sysinfo = new SysInfoTask();
      this.CopyTo(sysinfo);
      sysinfo.Execute();
      
      this.Log(Level.Verbose, "Executing custom work...");
      // TODO: Insert your custom task's work here
      
      this.Log(Level.Info, "Custom task complete.");
    }
  }
}

That seems to work well for most tasks. Some tasks require more initialization than just CopyTo, like the setting of properties or what-have-you, so you'll need to set that stuff up for things to work since you don't get the validation benefits that you get when NAnt parses the build script and tells you when you're missing required values.

One task I haven't gotten to work like this is the <csc /> task - for some reason, I haven't figured out how to properly add references to the task to get it to work. Instead of calling <csc />, I ended up writing a quick method using the Microsoft.CSharp.CSharpCodeProvider to compile things directly.

Minor update: I actually ended up having to use an <exec /> task to build the code rather than the Microsoft.CSharp.CSharpCodeProvider because I didn't see a way with the code provider to specify a target framework, whereas you can call the framework-specific csc.exe based on NAnt project settings and the correct framework will be used.

Print | posted @ Friday, May 05, 2006 12:06 PM

Comments on this entry:

Gravatar # Re: Custom NAnt Tasks Calling Other Tasks
by RobvK at 8/11/2006 4:47 AM

You my friend are a lifesaver! I've been trying to get something very similar to work all morning and I think I may finally get to the solution.

I also tried loading the external build file as a new project (e.g. NAnt.Core.Project project = new NAnt.Core.Project("&lt;buildfile&gt;")). This worked ok and I could get to the project name etc, but for some reason it wouldn't load the targets inside the file. Event looping through the Targets property of the newly created project wouldn't return any targets.

If you ask me this SDK is a bit buggy, dodgy at best. Anyway, thank you for this post. Makes me wonder how on earth you figured out that CopyTo initialises your task! :)
Gravatar # Re: Custom NAnt Tasks Calling Other Tasks
by Travis at 8/11/2006 8:59 AM

Glad this helped you out. There was actually a lot of effort that went into figuring out how it all worked, much of it done through Reflector (much quicker to investigate that way than loading up the NAnt source in Visual Studio).

What they've got going is, well, not necessarily buggy, but definitely under-documented. I also haven't seen too many quality articles out there on writing custom tasks. The articles I have seen generally seem to cover the same thing - writing the absolute simplest of custom tasks and leaving anything complex to the reader.

If you think the NAnt API is bad, try writing custom plugins for CruiseControl .NET. Holy crap. NAnt tasks are a piece of cake to both write and debug compared to wading through the CruiseControl .NET "dependency injection framework"-based API and approximately zero documentation.
Gravatar # re: Custom NAnt Tasks Calling Other Tasks
by Sameer at 8/15/2007 2:25 PM

awesome ... just the thing i was look for ... thanks.

Regards,
Sameer
Gravatar # re: Custom NAnt Tasks Calling Other Tasks
by Suresh at 1/14/2008 1:46 AM

I spent days and days to work out a way of calling custom task DLLs from nAnt custom tasks c# script.Nothing worked. Wow! Here you are,the genius, just a simple example shown and it works. Thanks a lot mate. You saved my day.
Just curious!, How did you work it out?
Gravatar # re: Custom NAnt Tasks Calling Other Tasks
by Travis Illig at 1/14/2008 7:41 AM

The secret was a little bit of Reflector and a lot of patience. Once I figured out the secret of CopyTo, worlds were opened.

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 1 and 5 and type the answer here: