Friday, August 29, 2008

Creating my own custom NAnt tasks in C#

One of the things that I find myself doing when not crunching numbers as a performance enginerd is learning the ways of the Buildmiester. We use NAnt for deploying our code from the build server to a target test machine and UAT test environment.

During the process we use NAnt to delete and create vdirs under IIS with mkiisdir which is handy dandy and all but it doesn't fully get the job done as some of the vdirs we need should not belong to DefaultAppPool. And also unfortunately it appears that NAntContrib doesn't have all the tools that I need to delete and create a custom AppPool that I need to create with a special local username/password.

I found the command line code to create the AppPool that I want and I was just going to wrap the execution of the command line stuff with an <exec> task but I still couldn't find how to create a vdir on the command line and have it binding to my custom AppPool that I wanted.

This morning after Scrum another coder working on a sister project that uses MSBuild instead of NAnt suggested that I take a gander at the SDC Tasks for MSBuild up on CodePlex. After pulling down the latest and greatest SDC Tasks it appears that I can delete, then create my custom AppPool and then delete/create a customer vdir bound to my custom AppPool. How great is that?

Except that SDC Tasks was designed for MSBuild and we are already heavily into NAnt. Bummer, right? Well. I know that NAnt has a <msbuild> task but going from NAnt into MSBuild that would reference a custom MSBuild script and pass the params around seemed like a real PITA, not to mention a documentation nightmare. But then again, I really want this functionality of the SDC Tasks.

What do I do? What do I do?

After consulting the Great Google, I decided that it was time to "Man up, Nancy Boy!" as one of the female trainers at my gym would say (actually shout during a middle of a grueling spin class with the grunting and sweating and the pain!) and learn how to code my own custom NAnt task and build a custom wrapper for the SDC stuff so that I could invoke the functionality from within my NAnt script.

After consulting some blogs and some pain and suffering I've been able to create a basic useless NAnt task. I haven't written the wrapper code yet, but I think that'll be simple compared to actually getting a custom NAnt task up and running.

This is what I did to create my very own useless NAnt task.

First, I created a .NET Class project by the name of "NAntCustomAuswipeTasks." It seems to be very important that the assembly name postfix be "Tasks.dll." The second thing is that it is important to use class decorators for specifying various things that NAnt needs to get the job done. Two of those being "TaskName" and "TaskAttribute."

Another important thing that I learned is that when calling the task from inside the NAnt script that the task name is case sensitive to the decorator string.

Below is my code for my useless NAnt task:



   1:  using System;

   2:  using NAnt.Core;

   3:  using NAnt.Core.Attributes;

   4:  namespace NAntCustomAuswipeTasks {

   5:    [TaskName("auswipetask")]

   6:    public class AuswipeTask : Task {

   7:      private string customData;

   8:      [TaskAttribute("customdata", Required = true)]

   9:      [StringValidator(AllowEmpty = false)]

  10:      public string CustomData {

  11:        get { 

  12:          return customData; 

  13:        }

  14:        set { 

  15:          customData = value; 

  16:        }

  17:      }

  18:      protected override void ExecuteTask() {

  19:        Project.Log(Level.Info, "[AuwipeTask] The parameter passed is '" + customData + "'.");

  20:      }

  21:    }

  22:  }



Take notice of the TaskName, TaskAttribute and StringValidator decorators. Very important or otherwise your attempts to call your custom NAnt task will fail. The blog entry that I was learning on didn't have the TaskName decorator and so my call to my task was failing. Very frustrating.

After compiling into my assembly of "NAntCustomAuswipeTasks.dll" I copied the DLL into the same subdir where my NAnt build file resides. In my case, this is a folder off of my Desktop subdir where I am playing around checking to see if it really works. I'm using a default.build file right now just to make things simple:

8<--------------------------------
C:\Users\Auswipe\Desktop\NAntTask>dir
Volume in drive C has no label.
Volume Serial Number is A463-3DC5

Directory of C:\Users\Auswipe\Desktop\NAntTask

08/29/2008 11:44 PM <DIR> .
08/29/2008 11:44 PM <DIR> ..
08/29/2008 11:44 PM 404 default.build
08/29/2008 11:45 PM 4,608 NAntCustomAuswipeTasks.dll
2 File(s) 5,012 bytes
2 Dir(s) 332,392,656,896 bytes free
-------------------------------->8

8<--------------------------------


   1:  <?xml version="1.0"?>

   2:  <project name="Custom NAnt Task Test" default="test-task" basedir="." xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd">

   3:    <loadtasks assembly="NAntCustomAuswipeTasks.dll" />

   4:    <target name="test-task">

   5:      <echo message="Here goes nothing...." />

   6:      <auswipetask customdata="Killroy was here." />

   7:    </target>

   8:  </project>


-------------------------------->8

And here I am testing out my useless NAnt task:

8<--------------------------------
C:\Users\auswipe\Desktop\NAntTask>nant /f:default.build test-task
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/Users/auswipe/Desktop/NAntTask/default.build
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: test-task

[loadtasks] Scanning assembly "NAntCustomAuswipeTasks" for extensions.

test-task:

[echo] Here goes nothing....
[AuwipeTask] The parameter passed is 'Killroy was here.'.

BUILD SUCCEEDED

Total time: 0 seconds.

-------------------------------->8

Notice that I go ahead and specify the default.build file on the command line along with the target despite having the test-task as the defacto target of default.build and knowing full well that NAnt looks for default.build. Why'd I do that? Just because I hate seeing demonstrations of some technology where the author assumes that the readers know all the ins and out of the subject. Sure, I can get the same results with `nant` as I did with `nant /f:default.build test-task` but having been in the RTFM mode before, I can gleam more information from `nant /f:default.build test-task` and learn the rest later. But that is just my own opinion. YMMV, IANAL, ROFLWTFBBQ.

So, now that I've created my useless NAnt task successfully I can work on wrapping the SDC Tasks that I need to make my world (or at least my deploy) a better place. And the less time I'm spending cleaning up after a deploy changing settings is more time I can be sitting on my butt surfing the web, or something like that.

No comments: