NAnt (A build tool) [CI]

About NAnt

NAnt is one of the oldest build/deployment tools that you can find online. It was created as a .NET build tool based on Ant, which is a build tool for Java. In NAnt’s sourceforge page you can see archived initial versions since 2001 and by looking for pages/blogs/questions about NAnt you could see that it was extremely popular in around 2006-2010. It is still popular, however, several new other tools have also appeared and are becoming very popular.

NAnt became popular because, contrary to its alternatives at that time, it uses xml files and not shell commands. This presented two main advantages 1) xml is easier to work with than native shell commands 2) it allowed NAnt to work on multiple platforms since xml commands were not based on the OS used.

It can run only with its executables. You do not need to install anything on the developers’ machines other than the .NET framework. So you would push these DLLs to the shared code repository and NAnt is ready to be used everywhere. After setting it up with XML, it runs with shell commands.

NAnt Structure

Project

Configuring NAnt requires a *.build where you would define a project, properties and targets. Each file should contain one project only. projectName.build:

<project name="projectName" default="defaultTargetName">…</project>

Properties

Inside a project tag, you would define your properties. These could be settings or variables. They are a way of storing values per build session. For example, you could have a property with the path of your code. So during the NAnt build process, you can tell the compiler to build the code inside your code folder by using the property you defined.

Defining a property: <property name="sourceCode" value="src/Project" />

Using that property: ${sourceCode}

Another really useful feature of properties is that you can create a separate file where you define all your project’s settings and instead of hard coding your settings in your configuration files, you can add a token (${propertyName}) which would then be replaced with the property value defined the separately. This feature is useful for storing environment-specific configurations between several machines.

In web.config.template: <add name="masterDB" connectionString="${masterDB.ConnectionString}" />

In dev1.properties (configuration of a developer): <property name="masterDB.ConnectionString" value="ActualDatabaseConnectionString" />

After running the related task, a new web.config file would be created with the actual property values.

Targets

Targets are like functions or tasks in which you specify the action to be done. There is a very wide list of actions that you could use in targets. Almost anything that you could do with shell commands (related to .NET development), you could do with NAnt targets. Please see the full list of tasks, functions and filters.

<target name="HelloWordMessage">
    <echo message="Hello World"/>
</target>

All the above target does is echo (write to console) the message specified.

You can create a target with a set of actions or tasks to be executed in order, and you can create several targets in a project which you can call in any order.

Combining the above

In a file with a .build extension, add the following to create a NAnt build file:

<project name="WelcomeProject" default="WelcomeMe">
    <property name="MyName" value="Tamer" />
    <property name="sourceCode" value="src/Project" />

    <target name="WelcomeMe">
       <echo message="Welcome, ${MyName}" />
    </target>
    <target name="cleanOutput">
        <delete>
            <fileset>
                <include name="${sourceCode}/bin/*.exe" />
                <include name="${sourceCode}/bin/*.pdb" />
            </fileset>
        </delete>
    </target>
</project>

Running the above file

In your solution’s root directory, create a new directory named ‘config’ for example. Inside it create another directory and name it your nant’s version e.g. nant-0.92 and put nant’s files inside it. Then create a new file in your solution’s root directory and name it nant.bat and add the following inside it:

@config\nant-0.92\bin\nant.exe -buildfile:projectName.build %*

Of course, you’ll have to edit the path to the nant.exe according to your structure and edit your nant build file name (projectName.build).

Now, if you open cmd to your solution’s root directory and type nant, the *.bat file will execute the nant.exe and look for a *.build file. Once it finds it, it retrieves the default target (specified in the default attribute in the project tag) and executes it, which is ‘WelcomeMe’ in this case. If you, however, need to execute another target (cleanOutput for example), all you have to do is add the target name after the nant command: nant cleanOutput

You can also tell a target to execute another one or a set of targets before it gets executed. This is achieved by using the ‘depends’ attribute which is quite descriptive. So I can tell our build file that the ‘WelcomeMe’ target ‘depends’ on ‘cleanOutput’. This way if I call ‘WelcomeMe’ target, NAnt automatically executes cleanOutput before executing ‘WelcomeMe’. You can specify a  set of space/comma-separated targets in the ‘depends’ attribute when a target depends on a number of targets.

File Building

.template

One of the cool features in NAnt is file building in which you can generate (build) configuration/settings files from templates. This is typically used with solutions that are shared among several developers and environment-specific settings exist. For example, a connection string or a file path should not be shared between developers because they probably wouldn’t work and shouldn’t work on any other environment.

Templates are just like any configuration files (e.g. web.config or App_Config/ConnectionStrings.config in case of Sitecore) except that they have a different extension. You can choose your own extension but let’s choose ‘.template’ for now. So my web.config would be named web.config.template.

Filling the template

Our web.config template should contain everything a regular web.config contains except that we will replace our environment-specific values with NAnt property tokens: ${...}

So let’s say I have an appSetting in my web.config for a custom log output named ‘customLogOutputPath’. Instead of having an actual path value, I can just put the NAnt property token: ${customLogOutputPath}:

<appSettings>
    <add key="customLogOutputPath" value="${customLogOutputPath}" />
</appSettings>

Creating property files

At this point, we should have our template files containing property tokens to be replaced with actual values. We will need to specify the actual values for those properties. We will do this by letting NAnt read the properties from a new folder inside the ‘config’ folder named ‘environments’ for example. This new folder would typically have several *.properties files: test.properties (all property values for the test machine environment), stage.properties (all property values for the stage environment), prod.properties (all property values for the production environment), {dev1,dev2,dev3, etc}.properties (all property values for each developer. dev1,2,3.. would be replaced by the name of the developer).

Inside the ‘config’ folder create a global properties file that controls NAnt execution in general (nothing related to business settings) a name something like globalConf.properties. Inside this you would have properties that you may need relating to NAnt or solution structure such as your web project path, external DLLs path, build configurations (release/debug) etc… We will also need this file to add a property representing our current environment, lets called it something like env.current. It’s value should be one of the name of one of the files inside the environments folder such as dev1, stage etc…

Next, we’ll have to tell NAnt to read from those property files in our *.build file. This is done by using the following tag:

<include buildfile="filePath" failonerror="true" />
//buildfile: path of the property file to read and build
//failonerror: whether to fail the entire nant operation if something goes wrong with reading and building this file

So at the beginning of our *.build file, we would add the above include tag and have the ‘buildfile’ attribute point to ‘config/globalConf.properties’. This would right away read that file and build it (by reading and building all its contents) and then continue to the next tag. So if the next tag was an echo with a message of ${env.current}, it should display the current environment which was read from the globalConf.properties file.

The next part is to tell NAnt to read the properties file specific to the current environment by adding another include tag with a buildfile value of ‘config/environments/${env.current}.properties’:

<include buildfile="config/environments/${env.current}.properties" failonerror="true" />

At this point, all of our properties should be loaded and ready to be used.

One cool point to note here is that you could add an include tag in each of the ${env}.properties files that builds a new defaultProps.properties files in that folder. This new file would contain the default values for the properties and the ${env}.properties files would only override the properties that need to be overridden.

<project>
  <include buildfile="defaultProps.properties" failonerror="true" />

  <property name="MasterDB.ConnectionString" value="..." />
  <property name="customLoggingPath" value="c:/.../..." />
  //all the other properties would still be inside the defaultProps.properties
</project>

Note on Source Control Systems: property files that contain environment specific properties (such as globalConfig.properties and the environments/${env}.properties) are best added to your source control system and then ignored. This way when a developer first checks out the solution, they would get pulled and then when he/she edits the globalConfig.properties to point to his/her environment’s property file or edits the ${env}.properties files, edits wouldn’t get committed and pushed to the other environments. Note that the defaultProps.properties should not be ignored since new settings/properties should go there and get pulled/updated from the source control on every pull.

Note on another approach: another approach to do the ${env}.properties files (especially when sensitive data may exist or when there are lots of developers in a project) is having the default.properties and a dev.properties.sample. Each developer would have to copy this .sample file, rename it by removing the .sample part and adding in his/here environment/specific properties. This file will not be tracked by the source control and its contents will not be shared with anyone.

File building target

In order to use them and build actual config files from their templates we’ll have to create a new target in our .build file that does exactly this:

<target name="buildFiles">
    //'File' is a keyword that tells the foreach to loop on every 'File'. 'currentFile' is a reference variable to the current file being manipulated
   <foreach item="File" property="currentFile">
   <in>
    <items>
     //In here you list your files to be included in the loop or as you can see, one include for all *.template files under the 'src' folder. ** means under 'src' no matter how deep in.
     <include name="src/**.template"/>
    </items>
   </in>
   <do>
    //In here your do your action for every 'currentFile'
    <echo message="${currentFile}"/>
    //The following tag creates a copy of the current *.template file but only after renaming it by removing the .template part of its name
    <copy file="${currentFile}" tofile="${string::replace(currentFile,'.template','')}" overwrite="true">//with overwriting any existing config file
     <filterchain>
       //'expandproperties' tells NAnt to process the current file by expanding its property tokens. In other words, replace the ${...} token with their actual values that were read from the the environments property files.
      <expandproperties/>
     </filterchain>
    </copy>
    <echo message="${currentFile} built"/>
   </do>
  </foreach>
 </target>

If your run nant buildFiles, the new target should get executed generating all the config files that have a template and replacing all property tokens. If you see an error, it is usually descriptive and straightforward to what the actual problem is.

NAnt-contrib

Users of NAnt felt that NAnt was missing many important tasks so they start a nant contribution package called nant-contrib where tasks that were missing from the original NAnt were added.

To install this package, create a new folder next to the base NAnt folder and name it nant-contrib (and add the version). Extract the contents of the package inside this folder. You only have to load it from your .build file by using the below command:

<loadtasks assembly="config\nant-contrib-0.92\bin\NAnt.Contrib.Tasks.dll" />

You should have a look at contrib’s Tasks,  Functions and Types.

Building/Compiling Projects

At this point if everything is clear and you understood how NAnt worked above, you should feel confident about doing anything in NAnt assuming you know what you need to do, if you were using cmd and file explorer only, and then refer to the documentation. For example, if you need to create a folder as one of your steps, you would search for the proper command and read how it is used. In this case mkdir is needed and its description is very useful and easy to understand.

One of the major tasks you will probably need is building or compiling .NET projects and solutions. There are several ways to do this. I will not dig deeper into this but just mention them:

  • <csc>:  Compiles C# programs. This method requires you to manual pass all build parameters such as the files to build, the resources to include, the warnings to ignore etc… This is probably the one that requires the most work but it allows you to set up the compilation just the way you need it. Following is the example that is presented in the documentation:
<csc target="exe" output="HelloWorld.exe" debug="true">
    <nowarn>
        <!-- do not report warnings for missing XML comments -->
        <warning number="0519" />
    </nowarn>
    <sources>
        <include name="**/*.cs" />
    </sources>
    <resources dynamicprefix="true" prefix="HelloWorld">
        <include name="**/*.resx" />
    </resources>
    <references>
        <include name="System.dll" />
        <include name="System.Data.dll" />
    </references>
</csc>
  • <msbuild> (Part of nant-contrib): Builds the specified targets in the project file using MSBuild. This method calls the msbuild API to build the project using Microsoft’s msbuild tool. msbuild is one of the most widely used tool for building projects via third party tools. So it is a good idea to read a lot about it.
<target>
    <loadtasks assembly="${nant::get-base-directory()}/../../nantcontrib-0.85/bin/NAnt.Contrib.Tasks.dll" />
    <msbuild project="${filepath.root}/yourproject.csproj" verbose="true">     
          <arg value="/p:Platform=${build.platform}" />
          <arg value="/t:Rebuild" />
          <arg value="/p:OutputPath=${build.dir}/bin/" />
    </msbuild>
</target>
  • <solution>: Compiles VS.NET solutions (or sets of projects), automatically determining project dependencies from inter-project references. 
<solution configuration="debug" solutionfile="HelloWorld.sln" />

Advantages

  • XML-based. All developers should be confident working with XML
  • Lots of tutorials, questions, answers, articles etc…
  • Free and open-source
  • Quite old and therefore stable and trust-worthy
  • File-building feature is one of its best features (which is quite easy to implement and use) that lots of dev companies still choose it
  • Has a community contribution package which most probably has everything you could think of
  • You can almost do anything that could be done with cmd

Disadvantages

  • XML-based!
  • No user-interface at all
  • Becoming old with very few updates and new releases
  • You need to run with it with shell commands (cmd)
  • Can’t set up automatic building, you need another tool to run NAnt on certain triggers, events or schedules
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s