.NET Projects: Automating Builds with NAnt

Introduction

As a bit of history, NAnt is based on a Java build tool known as Ant. Ant is useful for automating the overall build process in the Java world. NAnt does the same for .NET. This applies to Windows Forms, ASP.NET websites, web services, and anything else that can be built on the .NET platform.

This article will build upon my previous one, using Subversion for .NET development projects. The principles illustrated are generally applicable whether or not you are using Subversion, and also to any other project than the one I use for illustration. As before, I will use my project for illustration purposes, but the procedures can be applied to any other project.

So what does NAnt do?

A build process tends to include numerous steps, including:

  • compile code
  • run unit tests
  • perform code analysis
  • generate documentation
  • create distributable packages

This article will cover the first step, code compilation. The remaining steps will be covered in future articles.

Setting up NAnt

Get the latest stable NAnt version - go to the NAnt homepage, click Releases in the sidebar, then choose the latest stable version. At the time of this writing, that was NAnt 0.85. To get a quick start, just get the binaries package. Download and save to a location of your preference (e.g. your desktop). Unzip the package.

Now, the software is just a bunch of compiled .dll/.exe files, so there is no actual installation like with packaged software. So you can simply copy the NAnt folder to a more permanent location, such as at the root of your C: drive, or in Program Files or such. This can work fine.

However, if you are using Subversion to version control your project code, you can include NAnt in the repository as well. This comes with a few advantages:

  • Including NAnt in the project repository means there is one less dependency to worry about. If NAnt were not included in the repository, each computer where the project is developed would need to have NAnt installed on it somewhere, and this might not always be the case. This way, a single checkout ensures that you have everything needed to build the project, right away, without needing to handle additional dependencies.
  • You would want to ensure that you were consistent with which version of NAnt you use for building your project. Since NAnt tends to make changes to the way it works with each release, you want your local copy to stay consistent to head off any breaking changes. This applies if you use more than one computer to work on your project, or if there are multiple people work on the same project. The NAnt version used in those cases should be consistent.
  • NAnt will be a bit easier to use, since you know where it will be located in relation to your project code.

All the above advantages are compelling in my opinion, so I will include NAnt in my repository. This way, if I am on a computer without any of the project files on it, I can retrieve the full trunk from the repository and do a build immediately.

Open Windows Explorer and navigate to your local working copy. Hopefully, you will have a nicely organized project structure, perhaps similar to what I illustrated in my previous article. This will probably include a tools folder. If not, create it. Create a nant folder inside, and copy all the NAnt files into this new folder.

If you are using Subversion to version control your project, you can go ahead and add and commit the nant folder and the files within. It’s a good idea to specify in the commit message what version of NAnt is being used, so you can easily check it later. The image below shows the nant folder in my working copy.

NAnt located within the tools folder

Creating the Build File

Move to the root of your project (if using my suggested project layout, this will be one level above the src directory) and create a text file. Change the name to your project name, and the extension to build. The build extension is the official one used for NAnt build files. Open the file in a plain text editor and paste the following skeleton content. Save and close the file.

<?xml version="1.0"?>
<project name="Wadmt" default="build">
    <property name="debug" value="true" />
    <property name="dir.libs" value="libs\" />

    <property name="nant.settings.currentframework" value="net-1.0" />

    <target name="build" description="compiles the application">
        <vbc target="library" output="Wadmt.DataAccess.dll" debug="${debug}">
            <sources>
                <include name="srcDataAccess***.vb" />
            </sources>
            <references>
                <include name="System.dll" />
                <include name="System.Data.dll" />
                <include name="System.Xml.dll" />
                <include name="${dir.libs}MySql.Data.dll" />
            </references>
        </vbc>
    </target>

</project>

A NAnt build file is XML-based, so it is an open format, but can be a bit cryptic. Various lines in the above script have the following meanings:

  • start with XML prolog, since this IS an XML file
  • a project tag encloses the contents of the file
    • name attribute can be set to name of project being built
    • default indicates which target should be run if none specified
  • custom properties can be set as constants to be used through the file
    • debug property can specify whether this build needs to include debugging information
    • dir.libs tells where required external libraries are located
      • note that within a NAnt build file, all paths are relative to the location of the build file, unless otherwise specified with a full path.
  • also overriding a built-in property, nant.settings.currentframework, to specify which version of the framework this project will be built against. This can be specified once in the NAnt configuration, but it will be assumed for all projects built by NAnt. Better to be explicit about it for each project.
    • whichever framework version is used, must have the matching SDK installed, since NAnt sends the actual compile work to the compilers included in the SDK.
  • can have multiple targets within each project; each target can be created to achieve some specific task
    • name of target
    • just a blurb to indicate what purpose the target serves
  • vbc is for the VB.NET compiler, use csc for C#.
    • target specifies the type of project, library is for class library
    • output is for where to put the compiled assembly (.dll)
    • debug uses the predefined custom property, and determines whether debugging information will be included in the build (.pdb)
  • sources specifies which files should be included for compilation. .vb for VB.NET, .cs for C#. Can use exclude tag if any files need to be excluded from the build. Triple asterisk (*) means wild card for filenames AND directories, so files in subdirectories of the project would be included.
  • references for any namespaces being used in the build. End the namespace with .dll, which is how NAnt will understand. Also specify external libraries here, as shown, from the libs folder.

NOTE: yes, this project is based on the version 1.0 of the .NET framework. I would like to upgrade it to (at least) 2.0 one of these days, but it would not be a quick task, so I will save that thought until I get my build/test/analyze process firmly in place. The way I am doing things now with NAnt reflect that I am using .NET 1.0, as with the advent of 2.0 there comes some additional functionality and flexibility in automating builds. That may well be the subject for a future article, but for now it’s .NET 1.0 all the way.

Build files can be edited with a plain text editor such as Notepad. Visual Studio can edit the build file as well, since it is essentially an XML file, but does not have any out-of-the-box intellisense. This can be achieved, however, by being sure to open the build file with the XML editor feature, and by adding an xmlns attribute to the opening project tag:

<project name="Wadmt" default="build" xmlns="http://nant.sf.net/release/0.85/nant.xsd">

This directs Visual Studio to the specified schema document so it knows how to help us with Intellisense. Very useful. And here is a shot of the end result:

Intellisense for build file in Visual Studio

When editing the build file in Visual Studio, you will receive the help provided by Intellisense, much like when programming VB.NET or C#. This is a very helpful feature since the build files are somewhat cryptic.

Back to the basic build script, you can edit the project’s name attribute as desired.

Running the Build

With the basic build script in place, open a command prompt window and navigate it to the same folder containing the build script. Type the following line and press ENTER:

toolsnantbinNAnt.exe -buildfile:wadmt.build build

This is just executing the NAnt executable located within the tools directory, specifying a build file to use in the process. Since no target is specified here, the default target specified in the build file is used. You could follow the parameter list with the name of a specific target, and only that target would be run.

Initial build output

You should get some visual output showing whether the build succeeded or failed. In the case of failure, errors will be shown. At this point, if errors related to the project source code need to be fixed, go ahead and make the corrections and re-run the build script.

When I first did this, I received quite a few errors, largely related to missing namespace references. This is because Visual Studio implicitly includes some namespaces for you, so you don’t have to do it yourself. But when compiling via NAnt, NAnt is not aware of this. So some namespace references needed to be added to several of my code files, and some specific namespaces needed to be added to the references list in the build script. The script (and output) shown is from after the errors were corrected.

Using a Shortcut

The build script execution line above is simple enough, but who wants to type that line each time they want to do a build? You can avoid that need by using a batch (.bat) file to run that command.

In the same folder as the build script, create a .bat file named “build” or something similar. Open it in a text editor, and paste the following command line into the file. Save and close the batch file.

cls
toolsnantbinNAnt.exe -buildfile:wadmt.build %*

The first line clears anything currently in the command line window. This helps keep the workspace tidy. The second line is similar to the command line previously shown, except for the extra characters on the end. They indicate that one can pass additional parameters to the batch command, which in turn passes them along to NAnt.

Returning to the command prompt, type the name of the batch file (no extension) and press ENTER. You should get output similar to what you received when running the NAnt tool directly.

Admittedly, this is another layer of commands, although a very small one. And it saves the extra typing and reduces the chance of typos. Anything that achieves this is a good thing, in my opinion.

Build files in project root folder

At this point, I have two extra files at the root of my working directory, as shown in the image above. One is the actual build script (wadmt.build) and the other is the shortcut batch file (build.bat). To do a quick build, simply run the “build” command in a command prompt at your project’s root directory.

Adding to the Build File

When doing a build, it is a good idea to have the resulting assemblies generated outside of your source tree. This keeps your source tree tidier, and makes the build artifacts easier to locate and package up.

To facilitate and automate this, another couple blocks can be added to the build script, one to delete the build directory, if it exists, and another to create it again. This ensures each build will be a clean one.

<?xml version="1.0"?>
<project name="wadmt" default="build" xmlns="http://nant.sf.net/release/0.85/nant.xsd">
...

    <property name="dir.build" value="build" />
...
    <target name="build" description="compiles the application">
        <delete dir="${dir.build}" if="${directory::exists(dir.build)}" />

        <mkdir dir="${dir.build}" />
        <mkdir dir="${dir.build}bin" />
...
  • added another custom property just inside project tag, to define the location of the build folder
  • delete the build folder if it already exists
  • recreate the build folder and the bin folder within

Just drop that into your build file and save. Now there’s another step to deal with.

The above build script snippet shows how simple it is to create directories as necessary. But NAnt needs to be instructed to create build artifacts directly in the build directory, rather than within the source tree. The following snippet demonstrates how:

<target name="build" description="compiles the application">
...
    <vbc target="library" output="${dir.build}binWadmt.DataAccess.dll" debug="${debug}">

Note the dir.build property being used in the output attribute. So the compiled assembly from this project will be created directly in the build folder specified in the previous section. This shows how useful custom properties can be: if there are common values being repeated all over the file, just extract those values into custom properties and refer to the property values as necessary.

As a side note, this build directory is intended to be temporary, since it is deleted and recreated on every build, and solely for the purpose of storing built/deployable files. Meaning, it does no need to be stored in the source repository. What’s the point? You should be able to do a single checkout and build the project right away.

So you have build artifacts directly generated in the builds folder. Sometimes there are related, non-source files to go with them. In the case of ASP.NET projects, there will likely be an assortment of pages (.aspx), controls (.ascx), stylesheets (.css), images (.gif, .jpg), and scripts (.js) that should be bundled with the build artifacts. Again, this is so that all final files are all in one place, and easy to deploy or package as necessary.

...
        <copy todir="${dir.build}">
            <fileset basedir="srcWeb">
                <include name="***.*" />
                <exclude name="***.vb*" />
            </fileset>
        </copy>

        <mkdir dir="${dir.build}Admin" />

        <copy todir="${dir.build}Admin">
            <fileset basedir="srcWebAdmin">
                <include name="***.*" />
                <exclude name="***.vb*" />
            </fileset>
        </copy>

        <copy todir="${dir.build}bin">
            <fileset basedir="${dir.libs}">
                <include name="*.dll" />
            </fileset>
        </copy>
...

This sample is within the build target, following a few vbc sections. The shown code block is responsible for gathering related, non-built files to be distributed with the compiled assemblies.

  • copy all files to within build root…3-* wild card includes folders
    • but exclude source files such as .vb
  • make an Admin directory
    • copy files over with similar rules to above
  • also copy over necessary external libraries, from the libs folder to the build bin folder

You can create folder structure in your build tree as necessary, like I have done. As with the build folder and artifacts, this chunk of code is very customizable, and the idea can easily be applied to other projects.

Now that the build script is fully fleshed out (for the moment anyway), you can add and commit it to your Subversion repository (assuming you are using one), along with the batch file. This is for the same reasons as including the NAnt tool in the repository. So you can do a single checkout and be able to run the build right away. I’m already enjoying this flexibility!

Following is a full, commented copy of my build script as it is currently.

<?xml version="1.0"?>

<project name="wadmt" default="build" xmlns="http://nant.sf.net/release/0.85/nant.xsd">
    <!-- specify whether the build should include debug information -->
    <property name="debug" value="true" />

    <!-- specify where external libraries are located -->
    <property name="dir.libs" value="libs\" />

    <!-- specify where the built assemblies and deployable files should go -->
    <property name="dir.build" value="build\" />

    <!-- override the default framework version for compilation -->
    <property name="nant.settings.currentframework" value="net-1.0" />

    <target name="build" description="compiles the application">
        <!-- delete the target build directory if it exists -->
        <delete dir="${dir.build}" if="${directory::exists(dir.build)}" />

        <!-- recreate the target build directory to ensure a clean build -->
        <mkdir dir="${dir.build}" />
        <mkdir dir="${dir.build}bin" />

        <!-- compile the DataAccess assembly -->
        <vbc target="library" output="${dir.build}bin\Wadmt.DataAccess.dll" debug="${debug}">
            <sources>
                <include name="src\DataAccess***.vb" />
            </sources>
            <references>
                <include name="System.dll" />
                <include name="System.Data.dll" />
                <include name="System.Xml.dll" />
                <include name="${dir.libs}MySql.Data.dll" />
            </references>
        </vbc>

        <!-- compile the BusinessLogic assembly -->
        <vbc target="library" output="${dir.build}bin\Wadmt.BusinessLogic.dll" debug="${debug}">
            <sources>
                <include name="src\BusinessLogic***.vb" />
            </sources>
            <references>
                <include name="System.dll" />
                <include name="System.Data.dll" />
                <include name="System.Web.dll" />
                <include name="System.Xml.dll" />
                <include name="${dir.build}bin\Wadmt.DataAccess.dll" />
                <include name="${dir.libs}Microsoft.Office.Interop.Owc.dll" />
            </references>
        </vbc>

        <!-- compile the Web assembly -->
        <vbc target="library" output="${dir.build}bin\Wadmt.Web.dll">
            <sources>
                <include name="src\Web***.vb" />
            </sources>
            <references>
                <include name="System.dll" />
                <include name="System.Data.dll" />
                <include name="System.Web.dll" />
                <include name="System.Xml.dll" />
                <include name="${dir.build}bin\Wadmt.DataAccess.dll" />
                <include name="${dir.build}bin\Wadmt.BusinessLogic.dll" />
                <include name="${dir.libs}WilsonMasterPages.dll" />
            </references>
        </vbc>

        <!-- copy related, deployable files over to the build directory...ignore source files -->
        <copy todir="${dir.build}">
            <fileset basedir="src\Web">
                <include name="***.*" />
                <exclude name="***.resx" />
                <exclude name="***.vb*" />
            </fileset>
        </copy>

        <!-- create necessary directories in the build directory -->
        <mkdir dir="${dir.build}Admin" />

        <!-- copy deployable files to the Admin directory -->
        <copy todir="${dir.build}Admin">
            <fileset basedir="src\Web\Admin">
                <include name="***.*" />
                <exclude name="***.resx" />
                <exclude name="***.vb*" />
            </fileset>
        </copy>

        <!-- include external libraries in the build bin folder -->
        <copy todir="${dir.build}bin">
            <fileset basedir="${dir.libs}">
                <include name="*.dll" />
            </fileset>
        </copy>
    </target>

</project>

Conclusion

Well, that’s a fair bit of material, but this article makes a good start on automating project builds with the help of NAnt.

As mentioned before, time spent writing a NAnt build script can easily be regained when the system automatically compiles code, runs unit tests, performs code analysis, generates documentation, and packages up the distributable files.

This article only covered the first step of the process, actually compiling code, as well as storing build artifacts and related files in a separate directory to ease future work with the results.

Future articles will cover the following topics:

  • run unit tests
  • perform code analysis
  • generate documentation
  • create distributable packages

These tasks can certainly be done manually, but are much more convenient when automated with NAnt. They do not need to be added all at once, and indeed it is a bit easier to add those steps in one at a time, which is what will be shown with my build file over the next few articles.

There’s plenty more to cover, so stay tuned!

Further Reading

There are many NAnt articles on the web, but these are some of the most helpful that I found so far.

Make a Comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>