Setting Up a Build Server - Part 3 (NAnt)

by mgordon 1. April 2008 13:21

So far, in this series,  we've installed and configured Subversion for source control and installed and partially configured CruiseControl.Net as a continuous integration server.  This time, we'll look at a utility called NAnt which provides a declarative way of specifying tasks that are to be performed - specifically, tasks to be completed during a build.

A NAnt script is an XML document containing nodes that the NAnt engine understands.  By carefully creating a NAnt script, we can have the tool automate many tasks for us at build time.  NAnt can be downloaded from the site,  here and the documentation is here.  NAnt is powerful, out of the box, but its functionality can be extended by also obtaining the NAnt Contrib library which contains additional tags you can use in your script file.

To install NAnt, Download the zip file containing the binary files and unzip the archive to a folder on your hard drive.  You'll probably want to make this folder as shallow as possible (c:\nant) since you'll be specifying this path in CruiseControl.Net setup and possibly on the command line for testing you scripts.  As an alternative, you can place the code anywhere and add the path to the bin folder beneath the root folder to the path variable. 

Next, download the NAntContrib library and unzip the contents of the downloaded archive to a different folder.  Now, copy the contents of the bin folder beneath where you unzipped NAntContrib into the bin folder beneath where you placed NAnt.  You should now be ready to start building scripts.

NAnt looks for its instructions in a file with a .build extension.  As I said before, this file contains XML.  I won't try to teach you all there is to know about NAnt (I don't know it all, anyway), but I'll try to give you enough of the basics to get you stared on a firm foundation.  The root node of a build file looks like this.

<?xml version="1.0" encoding="utf-8" ?>
<project name="Rtc.Fx" default="CopyFiles" basedir="." xmlns="http://nant.sf.net/release/0.85/nant.xsd">
</project>

The root node is a <project> node and specifies the project name and a default task.  The build file is broken up into separate tasks and the default attribute indicates which task to begin with.  There may be certain values we'll want to use over and over in our build file or that we'll want to calculate.  We can work with these as properties.

  <property name="month" value="${datetime::get-month(datetime::now())}"/>
  <property name="day" value="${datetime::get-day(datetime::now())}"/>
  <property name="year" value="${datetime::get-year(datetime::now())}"/>
  <property name="hour" value="${datetime::get-hour(datetime::now())}"/>
  <property name="minute" value="${datetime::get-minute(datetime::now())}"/>
  <property name="second" value="${datetime::get-second(datetime::now())}"/>
  <property name="buildDirName" value="build_${month}-${day}-${year}_${hour}-${minute}-${second}"/>
  <property name="sourceDirName" value="source_${month}-${day}-${year}_${hour}-${minute}-${second}"/>

Insert these inside the root node.  As you can see, we're calling available functions to obtain values for parts of the current date and time.  These are stored in parameters have the name specified in the name attribute.  We've calculated names, here, for our source and destination folders.  Often, you'll want to save versions of code on your build server for a while and creating new folders with the date and time in the name is an easy way to identify when the snapshot was pulled down for a build.  Now let's actually do something.  Let's start by pulling the current version of our source code down for a compile.

<?xml version="1.0" encoding="utf-8" ?>
<project name="Rtc.Fx" default="CreateTags" basedir="." xmlns="
http://nant.sf.net/release/0.85/nant.xsd">
  <property name="month" value="${datetime::get-month(datetime::now())}"/>
  <property name="day" value="${datetime::get-day(datetime::now())}"/>
  <property name="year" value="${datetime::get-year(datetime::now())}"/>
  <property name="hour" value="${datetime::get-hour(datetime::now())}"/>
  <property name="minute" value="${datetime::get-minute(datetime::now())}"/>
  <property name="second" value="${datetime::get-second(datetime::now())}"/>
  <property name="buildDirName" value="build_${month}-${day}-${year}_${hour}-${minute}-${second}"/>
  <property name="sourceDirName" value="source_${month}-${day}-${year}_${hour}-${minute}-${second}"/>

  <target name="GetSolution"  description="Get Solution Files from Subversion">
    <mkdir dir="c:\build\source\Rtc.Crm\${sourceDirName}"/>
    <svn-checkout
      destination="C:\Build\source\Rtc.Crm\${sourceDirName}"
      uri="svn://localhost/rtc_repository/RTC/Rtc.Crm"
      quiet="true"
      username="uname"
      password="pword"
      />
  </target>
</project>

Tasks, in NAnt, are called targets.  More on why they maybe called this and how to control the flow of execution in a minute, but for now look at the target we added.  It has a name of GetSolution and an optional description.  Inside each target, we can specify zero or more actual action that need to take place.  In this example, a new directory is being created to place the source code into by using the mkdir tag.  It will have the name we calculated in the parameter, above.  In NAnt, a property(Variable, Function all, anything that returns a value) is accessed by surrounding it with ${}.  Once the directory is prepared for our source code, we'll ask Subversion to check our code out into the new folder by using the svn-checkout tag.  We specify a destination for the source code, the uri path inside the repository where the source code is located, a user name and a password for an account having access to the repository.  When this tag is encountered, the code will be checked out for us.  Now, let's specify another target that will build our code.

<?xml version="1.0" encoding="utf-8" ?>
<project name="Rtc.Fx" default="CreateTags" basedir="." xmlns="
http://nant.sf.net/release/0.85/nant.xsd">
  <property name="month" value="${datetime::get-month(datetime::now())}"/>
  <property name="day" value="${datetime::get-day(datetime::now())}"/>
  <property name="year" value="${datetime::get-year(datetime::now())}"/>
  <property name="hour" value="${datetime::get-hour(datetime::now())}"/>
  <property name="minute" value="${datetime::get-minute(datetime::now())}"/>
  <property name="second" value="${datetime::get-second(datetime::now())}"/>
  <property name="buildDirName" value="build_${month}-${day}-${year}_${hour}-${minute}-${second}"/>
  <property name="sourceDirName" value="source_${month}-${day}-${year}_${hour}-${minute}-${second}"/>

  <target name="GetSolution"  description="Get Solution Files from Subversion">
    <mkdir dir="c:\build\source\Rtc.Crm\${sourceDirName}"/>
    <svn-checkout
      destination="C:\Build\source\Rtc.Crm\${sourceDirName}"
      uri="svn://localhost/rtc_repository/RTC/Rtc.Crm"
      quiet="true"
      username="build"
      password="build"
      />
  </target>

  <target name="build" description="Build Core Solution" depends="GetSolution">
        <exec program="c:\program files\Microsoft Visual Studio 8\Common7\IDE\devenv.com" commandline="c:\build\source\Rtc.Crm\${sourceDirName}\Rtc.Crm.Sln /build Release"></exec>
  </target>
</project>

We've added a new target, now, called build.  Notice the depends attribute.  This attribute, in effect, says don't run this target until the GetSolution target has finished.  This is how you can control the flow of your targets.  If we make the build target the default (on the root node), NAnt will come to the build task and see that it can't execute it until GetSolution has run, so it puts build on hold and goes to execute GetSolution.  Once it's finished, the build target is executed.  In the build target, we're using the exec tag to build our code.  You could use compile tags in the NAnt libraries to compile your code, as an alternative (or MSBuild, the C# compiler or whatever), but I have found that it's hard, sometimes, to keep the .Net version and the NAnt version in sync.  For this reason...stability...I prefer to use the exec task and specify a command line to execute.

From here, you can use any of the provided tags to round out all the tasks you want to automate in your build.  You could copy the binaries just compiled to a deployment location, run NUnit tests, create a cd image file or whatever is needed.

Integrating NAnt into CruiseControl.Net

Last time, we left our CruiseControl config file looking like this...

<cruisecontrol>
<project name="MyProject" >
      <webURL>http://servername/ccnet/</webURL>
      <triggers>
        <intervalTrigger seconds="60" />
      </triggers>
      <modificationDelaySeconds>60</modificationDelaySeconds>
      <sourcecontrol type="svn">
        <executable>c:\program files\subversion\bin\svn.exe</executable>
        <workingDirectory>c:\build\source\ProjectName</workingDirectory>
        <trunkUrl>svn://localhost/repository/path/to/project</trunkUrl>
        <username>uname</username>
        <password>password</password>
      </sourcecontrol>
      <tasks>
        <nant>
          <executable>c:\nant\bin\nant.exe</executable>
          <baseDirectory>c:\build\source\ProjectName</baseDirectory>
          <buildFile>c:\build files\Project.build</buildFile>
          <targetList>
            <target>TargetName</target>
          </targetList>
          <buildTimeoutSeconds>600</buildTimeoutSeconds>
        </nant>

      </tasks>
      <publishers>
        <xmllogger />
        <statistics/>
      </publishers>
    </project>
</cruiseControl>

We can, now, complete the section in red.  The executable tag contains the path to the NAnt executable and the buildFile tag the path to the build file we just created.  The baseDirectory is where NAnt will do its work and the targetName is the name of the target within the build file to execute first.  You can also specify a timeout for the build.

So, the config file as it stand now will check Subversion every 60 seconds to see if there have been any changes checked into the repository.  If so, CCNet will pause for 60 seconds to give the developer enough time to complete checking in all their code and then initiate a build.  Now CruiseControl will look at all the tasks in the tasks section of the config file.  Here, there is one and that is to execute our NAnt script.

If you code the defaults when you installed CC.Net, you should be able to browse to http://buildServerName/ccnet and take a peak at the CCNet portal. Make sure the Cruise Control service is started!  The portal looks like this.

ccnet

From here, you can look at the progress for all your projects and builds.  Notice the link in the upper right to the documentation.  There is also a link on the far left to download CCTray, which is a utility that runs in the system tray and lets you know, at a glance, the health of your builds and double-clicking on the icon will open a utility that offers more functionality.

Next time, we'll look at how to get metrics from your subversion database as part of your build process.

Tags:

.Net | Productivity

Comments

4/7/2008 5:34:36 AM #

Just thought i'd congratulate you on your fantastic guide series! I'm only having one slight problem after getting this far, it seems as though my CC.Net gets the latest source code, and see's any updates to the SVN. However after getting the latest version it then says: (From the log file)

"2008-04-07 15:28:20,877 [AstraDirectXExample:INFO] 5 modifications detected.
2008-04-07 15:28:20,877 [AstraDirectXExample:INFO] Building: IntervalTrigger triggered a build (IfModificationExists)
2008-04-07 15:28:20,877 [AstraDirectXExampleLaughingEBUG] Starting process [c:\program files\subversion\bin\svn.exe] in working directory [c:\build\source\AstraDirectXExample] with arguments [update --revision 34 --username smansfield --password buster7 --non-interactive --no-auth-cache]
2008-04-07 15:28:21,877 [AstraDirectXExampleLaughingEBUG] U    AstraDirectXExample.ncb
2008-04-07 15:28:21,877 [AstraDirectXExampleLaughingEBUG] U    AstraDirectXExample.suo
2008-04-07 15:28:21,877 [AstraDirectXExampleLaughingEBUG] U    PaytableDisplayer\BasicWindowsApp.cpp
2008-04-07 15:28:22,002 [AstraDirectXExampleLaughingEBUG] Updated to revision 34.
2008-04-07 15:28:23,111 [AstraDirectXExample:INFO] Integration complete: Exception - 07/04/2008 15:28:23"

then continues to check for changes which it doesn't find.

Simon Mansfield United Kingdom

4/7/2008 5:55:43 AM #

Ok, figured out what was wrong, I accidentally left the first task to execute as "TargetName"! However now the NAnt Project.Build file is causing errors, and im not exactly sure what it all means; maybe you could go into it in more detail?

Simon Mansfield United Kingdom

Comments are closed

Powered by BlogEngine.NET 1.5.0.7
Theme by Extensive SEO