NAnt

NAnt is my new best friend. NAnt is very easy to learn, very straight forward to use, and very powerful in it’s execution.

NAnt is an automated build tool. Why would you deviate from Visual Studio’s Build menu? Here’s why. .config files. I’ve got a dev config file, I’ve got a production config file, various customers like their own pre-packaged config files, and I like to be able to debug live data issues with their config files as well.

Well, I want that config file baked into the .msi installer I deliver to them. That’s all fine and good, but standing between the end of the .exe’s project build and the beginning of the .vdproj’s msi build is just awful. From time to time, I’d create an empty project that depended on the .exe project, and had a pre-build .bat file that it ran to swap in the config file. This was awkward at best.

Since jumping into .NET, I’ve migrated from just using Visual Studio’s F5 to build things to having a batch file that builds a release verison. It just calls devenv.exe, passing in the solution and configuration names, and directing output to a log file. The glaring problem though is that it’s difficult to stop the process mid-take if things go wrong. Embedding the correct config file is also a matter of great intensity.

Enter NAnt. NAnt is to .NET as Ant is to Java. (And NAnt is to MSBuild as Macintosh is to Windows, or Palm is to Windows CE, or Netscape is to IE – Microsoft knows a great idea when it sees it, and then promptly steals it.)

To install it, do this:

  • Download NAnt and NAntContrib

    • NAntContrib is extra functions and tasks that aren’t part of the NAnt core, such as SVN, MSBuild, Zipping, etc.
  • Unzip them both

  • Copy everything from NAntContrib’s folders into the corresponding NAnt directories.

  • Put the new package somewhere permanent (like c:\Program Files)

  • Put NAnt’s bin folder in your Path environment variable It isn’t absolutely necessary to put NAnt’s bin directory in your path, but it’ll make it MUCH easier to work with. With nant.exe in your path, you can just type “nant” rather than C:\Program Files\Nant\bin\nant.exe.

To add NAnt’s bin directory to your path do this:

  • Right-click on My Computer
  • Choose Properties
  • Choose Advanced
  • Push Environment Variables
  • Locate Path in the bottom (system) list
  • Click edit
  • This is a semi-colon delimited list of folders (Yes, one of the most stupidest delimiters in the world)
  • Add ; and NAnt’s bin folder’s path to the end
  • Push OK a dozen times
  • No restart is necessary
  • You’ll need to close and reopen any command prompts to see the effect.

From time to time, I’ll copy the whole thing into my text editor of choice, use find-n-replace to change ; into new line, and browse.

Now that you’ve got it installed, time for a little build action. The syntax is incredibly elegant. The NAnt data file is an xml file with a .build extension. The root level element is <project>. Various targets (functions), and properties (variables) are in it. A target contains various tasks to do. Tasks can call other functions, set properties, or do something. Tasks can also require a separate task to have run first. When you call nant.exe, you pass in the build file (or it uses the one in the current directory), and you specify the targets to execute. For example: nant debug will run the “debug” task in the .build file in the current directory.

The NAnt zip file has some great HelloWorld .build files to cruise through. Great bedtime reading. Go read it, go experiment. I’ll wait. You done? Good.

Ok, now from the simple to the complex. The standard NAnt way of building .net code is either a task to call csc (e.g. turn your project file into an NAnt file, and synchronize them back and forth – yeah, I don’t like that either), or the <solution> task, passing in the .sln and configuration. Well, <solution> doesn’t work with Visual Studio 2005. Alas, there’s an <msbuild> task in NAntContrib. Pass <msbuild> the .sln filename, the configuration, and we’re good. Well, almost.

First, the good. Starting in Visual Studio 2005, .csproj files are actually msbuild files. MSBuild is for all intents and purposes MSAnt. MSBuild knows how to build a project or a solution created from Visual Studio. No need to mess with NAnt on the project level.

Now the not-so good news. MSBuild doesn’t support .vdproj projects – “Setup and Deployment Projects”. Microsoft’s official answer? call devenv from the command line. The down side? Now you need Visual Studio installed on the build machine. Ok, I can live with that.

About this step in the process, I did 1000 iterations of various things. Here’s the design I finally settled on:

  • A property for the config file to use

  • Set the default config file up front, outside any targets

  • A target per client, changing the config file property

  • A clean target that cleans out the /bin/ and /obj/ folders

  • A target that given a solution file and a configuration, builds it

  • A target per configuration: e.g. 1 for Debug, 1 for Release, etc.

  • A target that

    • calls the clean target
    • calls the “build a solution” target once for each solution in the project.
    • copies the final built pieces to a “build contents” directory
    • copies the config file into the “build contents” directory
  • A target that exec’s devenv, passing in the .vdproj responsible for building the installer

    • I tweaked this .vdproj to get its contents from the “build contents” directory and to remove it from any of the .sln files Download a sample of this strategy (and accompanying source) here.

Now to build my entire software product, from a command line, I type:

nant -l:logfile Client Configuration Msi

For example:

nant -l:build.log Microsoft Release Msi

will build the release version of my product, and include the Microsoft config file, and write the build status to the file build.log. Most excellent.

And should anything go wrong with the actual software build, that task will fail, and the Msi will never get attempted. Righteous.

A couple tricks I learned along the way:

Joshua has a nice blog post about building msi’s through config files by exec-ing deveng. (Search the page for MSI.) William Caputo has an extremely excelent post on how to resolve the path to Visual Studio. Yes, I could assume it’s in C:\Program Files, but this is hardly robust. His technique is incredible. Both techniques are in the sample code.

“**” is the regular expression for “in any directory from here down, recursively”. That’s very nice for cleaning out old build data. name=”**/bin/${Configuration}/**” and name=”**/obj/${Configuration}/**” and we’ve successfully targeted all built data for all projects within this solution (assuming we started in the root of the directory tree).

Intellisense for .build files. This is an incredibly cool topic for a subsequent blog. I found the xml syntax incredibly easy though, and syntax highlighting was just gravy once I got it to work.

Rob