ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Using NAnt to Build .NET Projects

by Jeffrey McManus
01/27/2003

With Visual Studio .NET, you can easily build and compile .NET projects that contain any number of subprojects -- collections of interdependent web pages, executables, DLL assemblies, and so forth -- with a single menu command. But relying on a single programmer hitting the "compile" button doesn't always work for large and complicated projects. What if you don't care to install VS.NET on every machine you own? Wouldn't it be nice if you had a way to automate the software build process so nobody ever has to hit the compile button? There are many benefits to an automated build process, but to make it happen, you've got to have a build tool.

Build tools solve problems associated with the process of compiling software. Simple software projects written by small development teams may not need a build tool -- you fire up the compiler, it builds your code into a binary executable, and you're done. But modern software is typically componentized, with each project dependent on one or more subprojects. The set of dependent components upon which your project relies may be written by many different people, who may check in different versions of their code at different times.

If one component fails to compile, or an out-of-date version of a component is used in a build, it can throw your whole project off track. Developers of complex projects typically use build tools to help manage this aspect of team development.

It's common for compilers to spew forth error messages if there's something in your code that causes compiler errors. But in a project that is comprised of several binary executables and several more dependent components, it may be difficult to pin down exactly where the failure took place. Ideally, you want a tool that builds the external dependencies required by your application, providing logs and notifications when something goes kablooey.

Enter Ant. The original Ant tool was originally created by the Apache Jakarta Project. It was created to overcome a number of frustrating aspects of existing build tools (such as the make tool that's commonly used on Unix and elsewhere). One imporant shortcoming addressed by Ant is the fact that build tools are commonly bound to a particular operating system, development environment, or language. Ant, in contrast, is designed to be platform-independent. To facilitate platform independence, the file you use to tell Ant how to compile your project is in XML format. This means that there are no operating system dependencies (aside from your development framework of choice -- Java, .NET, or whatever -- and Ant itself).

In addition to its cross-platform goodness, Ant build files are declarative. This means that you can accomplish a great deal without having to write code -- most of the heavy lifting is done by declarations you include in the XML file. (If your build process is complicated enough that you need to execute code, Ant gives you the flexibility to do that as well, by writing code to extend the tool.) And because the build file is in XML, you can use your favorite editor to create it.

Ant Comes to the .NET Framework

NAnt is a .NET implementation of Ant. It is written in C#, but is designed to work with any .NET language (the NAnt distribution contains examples for C#, VB.NET, and JScript.NET). You can even combine projects written in different .NET languages in one build, so if you need to build a VB.NET client application that has several dependent assemblies written in both VB.NET and C#, NAnt can deal with it easily. If that isn't enough, NAnt can even run multiple compilers, so if you want to use Microsoft's tools alongside the Mono C# compiler, NAnt can handle that, too.

To use NAnt, it helps to have a handle on how the command-line .NET compilers work. In this article, I'll use csc, the C# compiler, for the examples, but you can just as easily use vbc or another compiler -- or several compilers at the same time, if you prefer.

Hello, NAnt

The first step to using NAnt is to download it from the NAnt web site. The most recent "stable" build of NAnt as of this writing is 0.7.9.0, but the more recent nightly builds are solid, as well. Importantly for developers who are using NAnt as part of a continuous integration and build process, the most recent builds of NAnt contain integration with the excellent NUnit 2.0 unit testing tool. Because NUnit was significantly re-engineered from version 1 to version 2, if you use NUnit 2 you'll want to use a recent build of NAnt to take advantage of its features.

To see how NAnt works, let's take a look at one of the simplest possible scenarios -- building a single binary executable written in C# that runs in the console. Here is the code for the application:

  public class HelloWorld {
    static void Main() {
      System.Console.WriteLine("Hello world.");
    }
  }

Of course, you could just compile your simple project using the C# command line compiler. The command to do this is simplicity itself:

  csc *.cs
The output of this command is the executable binary HelloWorld.exe. To do the same thing using NAnt, you first create an XML file that ends in the extension .build. Here's an NAnt build file called default.build that does the same thing as the single command line above:
<?xml version="1.0"?>
  <project name="Hello World" default="build" basedir=".">
    <target name="build">
      <csc target="exe" output="HelloWorld.exe">
        <sources>
          <includes name="HelloWorld.cs"/>
        </sources>
      </csc>
    </target>
  </project>

Listing 1. Basic NAnt build script to create a single executable

Once you've created a build file, to build your project, you simply execute the command line:

  nant

As long as the current directory contains the .build file, and the NAnt executable itself is in your current PATH, that's all you need to type. NAnt will parse the contents of default.build and perform the tasks specified in the file.

Of course, using a tool like NAnt for a project comprised of a single class like this is massive overkill. But what if you were interested in first building the executable, and then running it? Or what if you want to build one or more dependencies and then build the main executable? This is where a build tool like NAnt can save loads of time.

An NAnt build file is primarily comprised of targets, tasks, and dependencies. A task is a single action you want NAnt to perform. Examples of tasks supported by NAnt include running a compiler, copying or deleting files, sending email, even zipping up sets of files (a complete list of tasks supported by NAnt is here).

A target represents a set of tasks you want NAnt to perform. Targets enable you to group tasks together logically. So if you want NAnt to delete the contents of the \bin directory, compile five executables, and copy the resulting binaries somewhere, those actions could be grouped together into a single target.

You can think of a dependency as a relationship between two targets. For example, in Listing 1 there is a single target. Its name is build; it runs the compiler against a single source file. Setting the default attribute of the <project> tag to build causes the build target to be processed by NAnt.

Within the csc task is a subnode called <sources> that indicates the source code file to be compiled.

Adding Dependencies

Now let's add a second target, one that will execute HelloWorld.exe after it is compiled. The new version of the build file looks like this:

<?xml version="1.0"?>
<project name="Hello World" default="run" basedir=".">
<target name="build">
<csc target="exe" output="HelloWorld.exe"> <sources>
<includes name="HelloWorld.cs"/>
</sources> </csc> </target> <target name="run" depends="build"> <exec program="HelloWorld.exe"/> </target>
</project>

Listing 2. Build script containing two dependent targets

The new run target contains a single action, exec, which executes a program. It also contains a dependency on the build target. This means that before the run target will be executed, the build target must also be executed, and it must execute successfully. Notice that in the project node, we also changed the default attribute to point to the run target instead of the build target. Because run depends on build, this ensures that the application will be built before it is run.

If the build target fails for some reason (most likely due to an error in your code caught by the compiler), the run target won't be executed. You can test this by intentionally inserting a syntax error in your HelloWorld code and then running NAnt again on your modified version of default.build. NAnt will display the compiler's error messages to the console so you can see what went wrong.

Creating Clean Builds

NAnt won't run the compiler if you have a compiled binary that's newer than the the source code -- in other words, it doesn't compile anything it doesn't have to. In addition, if multiple dependencies exist in a build file (that is, two or more components both depend on another component that is to be built), NAnt is smart enough to only build the dependent component once. This efficient method can make building large projects compile much more quickly. But in some cases you may want to have a way to say "build everything regardless of what I may have" -- in other words, wipe out all existing compiled binaries and do a clean build. For this reason, many build files contain a clean target so developers have the option of wiping out everything they've built and starting with a clean slate. Here's what a build file with a clean target looks like:


<?xml version="1.0"?>
<project name="Hello World" default="run" basedir=".">
  <target name="build">
    <mkdir dir="bin" />
    
    <csc target="exe" output="bin\HelloWorld.exe">
      <sources>
        <includes name="HelloWorld.cs"/>
      </sources>
    </csc>
  </target>

  <target name="clean">
    <delete dir="bin" failonerror="false"/>
  </target>	  

  <target name="run" depends="build">
    <exec program="bin\HelloWorld.exe"/>
  </target>
</project>

Listing 3. Build script containing a clean target

You probably don't want to run the clean target every time you build, just every once in a while. To execute the clean target, use the command line:

  nant clean

This causes NAnt to execute only the clean target (in other words, your project won't actually be built, it'll only clean out the contents of \bin). You can also see that in this version of the build script we've added a mkdir action to create a separate \bin subdirectory, into which compiled binaries are deposited. To clean the contents of the \bin subdirectory and build your project, simply use the command line

  nant clean build

Integration with Unit Testing

NAnt really shines in situations when you need to integrate your build process with other processes, such as email notifications or automated unit testing. A complete discussion of the NUnit unit testing framework is way beyond the scope of this article, but suffice it to say that NAnt plays very nicely with it. Listing 4 shows an example of a build file that builds an application and executes NUnit as a seamless part of the build process.

<?xml version="1.0"?>
<project name="NUnit Integration" default="test">
  <property name="build.dir" value="\dev\src\myproject\" />

  <target name="build">
    <csc target="library" output="account.dll">
      <sources>
        <includes name="account.cs" />
      </sources>
    </csc>
  </target>

  <target name="test" depends="build">
    <csc target="library" output="account-test.dll">
      <sources>
        <includes name="account-test.cs" />
      </sources>
      <references>
        <includes name="nunit.framework.dll" />
        <includes name="account.dll" />
      </references>
    </csc>
  
    <nunit2>
      <test assemblyname="${build.dir}account-test.dll" />
    </nunit2>
  </target>
</project>

Listing 4. Building components and integrating with NUnit 2.0

This build file first specifies the location of the project file in the form of a property. It's useful, but not required, to put values that might change or be reused in a property variable; properties are usually included at the top of the build file but can be supplied at the command line instead, if needed. In this case, specifying the project file as a property is useful because that information needs to be passed to NUnit later in the build process.

Next, the build file builds both the component (account.dll) and the test fixture (account-test.dll), in order. Both builds include the target="library" attribute so the compiler knows to build a component assembly rather than an .exe. You can also see in the listing that the test fixture references two dependent assemblies -- your business logic component and the NUnit framework -- using the references node. This is required any time you build a project that depends on an external library.

Finally, after the test fixture and project have been built, the build script calls NUnit, passing in the name of the assembly that contains your tests and generating an output file in XML format that lets you know the results of the tests that were run.

One note regarding NUnit integration -- be sure to use a recent snapshot of NAnt if you're using NUnit 2.0! Because of significant changes in the most recent version of NUnit, the current "stable" build won't work with NUnit 2.0 at all. More recent versions of NAnt are quite stable and support NUnit 2.0 well.

Hopefuly this article will serve as a useful introduction to NAnt. To learn more about what NAnt can do, check out NAnt's documentation, particularly the task reference, which contains a concise list of the tasks NAnt supports. There's also a mailing list, if you need additional help; more info on that is on the NAnt web site. Happy building!

The sample code can be downloaded here.

Jeffrey McManus is eBay's Senior Manager of Developer Relations and is the author of several books on software development.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.