August 20, 2007

Nant Integration

Surround SCM
Surround SCM integrates with Nant. The integration is packaged with NAnt-Contrib 0.85 and later. Without this package the integration will not be possible. Refer to the Nant documentation for the definintion of the available Surround SCM tasks. These contain definintion and examples on how to implement these. [toc]

A Simple Nant Example

The following takes the "Simple" example that comes with Nant to show how Surround SCM tasks can be added to retrieve the latest version from Surround SCM before the build and also how to create a snapshot branch to capture this build.

About the Program Being Built

The program being build comes with Nant as one of the samples. It is made up of a single file, Simple.cs, and it contains only the following:
public class Simple
{
    static void Main()
     {
        System.Console.WriteLine("Hello, World!");
     }
}
The sample also comes with a sample build file. This build file is ready to be used to compile Simple.cs. You should get this working before adding any Surround SCM tasks. Otherwise it will be very difficult to troubleshoot any issues you may encounter later. Please refer to the Nant documentation if you are unable to use the provided build file to build Simple.cs. For this example, I copied both the Simple.cs and Simple.build files from the Examples folder under the Nant installation to a folder called "nantest" on the root of my C: drive. I also created a mainline branch called "Nant Example", created a repository "nantest" and added Simple.cs to it. I also set the working directory to the "C:nantest" directory.
Figure 1 - Location of program in Surround SCM

About the Build File

The original build file contains the following:
<?xml version="1.0"?>
<project name="Simple" default="run">
    <property name="debug" value="true"/>

    <target name="clean" description="remove all generated files">
        <delete file="bin/Simple.exe" if="${file::exists('bin/Simple.exe')}" />
        <delete file="bin/Simple.pdb" if="${file::exists('bin/Simple.pdb')}" />
    </target>

    <target name="build" description="compiles the source code">
        <mkdir dir="bin" />
        <csc target="exe" output="bin/Simple.exe" debug="${debug}">
            <sources>
                <include name="Simple.cs" />
            </sources>
        </csc>
    </target>

    <target name="run" depends="build">
        <exec program="bin/Simple.exe" />
    </target>
</project>
Unlike Ant, the Surround SCM tasks do not have to be defined at the top of the build file. You simply insert them where ever you would like for them to take place. To keep things simple, I did not want to add another target, so I added both Surround SCM tasks to the same target. First, I added the get task to get the latest version for Nant to compile and build.
      <sscmget
         serverconnect="localhost:4900"
         serverlogin="administrator:"
         file="/"
         branch="Nant Example"
         repository="Nant Example/nantest"
         destdir="C:nantest"
         recursive="true"
         quiet="false"
         writable="false"
         overwrite="true"
         force="true"

      />
I placed the above section before it builds the debug version of Simple.cs, and afterwards, added the section to create a snaphot to capture this build.
        <sscmbranch
             serverconnect="localhost:4900"
             serverlogin="administrator:"
             branch="Simple 1.0"
             repository="Nant Example/nantest"
             parent="Nant Example"
             comment="Snapshot created by Nant"
             type="snapshot"
         />
Note that I have hard coded the snapshot branch name. I was not able to find an easy to use property to come up with a unique name that makes sense for the snapshot branch. The DateTime function unfortunatly returns a value that contains colons (:) and these can not be used for branch names in Surround SCM. I did find some examples on how to define your own functions, so I am sure you could write a custom function to come up with an alpha-numeric date/time value to use for a branch name. Another idea would be to store a version number on a separate file and have a function read that number, increment it to be used for the branch name, and then replace that version number in the separate file. With the example above, you are required to change the branch name each time you run the build, otherwise you will get an error that a branch by that name already exists. The final file looks like this:
<?xml version="1.0"?>
<project name="Simple" default="run">

    <property name="debug" value="true"/>

    <target name="clean" description="remove all generated files">
        <delete file="bin/Simple.exe" if="${file::exists('bin/Simple.exe')}" />
        <delete file="bin/Simple.pdb" if="${file::exists('bin/Simple.pdb')}" />
    </target>

    <target name="build" description="compiles the source code">
        <mkdir dir="bin" />
        <sscmget
         serverconnect="localhost:4900"
         serverlogin="administrator:"
         file="/"
         branch="Nant Example"
         repository="Nant Example/nantest"
         destdir="C:nantest"
         recursive="true"
         quiet="false"
         writable="false"
         overwrite="true"
         force="true"

         />
        <csc target="exe" output="bin/Simple.exe" debug="${debug}">
            <sources>
                <include name="Simple.cs" />
            </sources>
        </csc>
        <sscmbranch
             serverconnect="localhost:4900"
             serverlogin="administrator:"
             branch="Simple 1.0"
             repository="Nant Example/nantest"
             parent="Nant Example"
             comment="Snapshot created by Nant"
             type="snapshot"
         /> 

    </target>

    <target name="run" depends="build">
        <exec program="bin/Simple.exe" />
    </target>
</project>
This works well. I tested that the get did in fact work by removing the Simple.cs file from the working directory. I called Nant, and the file was retrieved from Surround SCM, built, and then branched. A look at the Surround SCM client shows the snapshot branch:
Figure 2 - Snapshot branch created by the Nant build
One improvement I made afterwards was to also check in the .exe built. Note that this can only be done after at least one build has run. I added the "bin" folder to the repository in Surround SCM to the mainline branch along with the Simple.exe and Simple.pdb files. I then added the check in task right before the branch task, this way the new snapshot branch would include the executable specific to the build being captured.
     <sscmcheckin
             serverconnect="localhost:4900"
             serverlogin="administrator:"
             file="/"
             branch="Nant Example"
             repository="Nant Example/nantest/bin"
             comment="Check In By Nant"
             writable="true"
     />
This, however, presented a new problem. I originally had set my get task to do a get recursively. Since I only had one file and no subfolders, it wasn't a big deal. But since I now had a "bin" repository with the .exe and .pdb files, the get was placing the files there, eventhough the build file had just deleted them. This caused for Nant to thing that a new build was not needed. I had made a change to the Simple.cs file to ensure that a new build was being created, but the output of the .exe was still the same. I realized that since the get was getting the .cs, .exe and .pdb files and giving them the same timestampt, Nant thought that a new build was not needed (it appears that Nant compares timestampt between the source code and the compiled file to determine if a build is in fact needed -same thing was noted when working with Ant). So I removed the "recursive" option from the get and also added the "writable=true" flag to the get and check in tasks to also avoid any issues caused by a ready only file. So the new build file now looks like this:
<?xml version="1.0"?>
<project name="Simple" default="run">

    <property name="debug" value="true"/>

    <target name="clean" description="remove all generated files">
        <delete file="bin/Simple.exe" if="${file::exists('bin/Simple.exe')}" />
        <delete file="bin/Simple.pdb" if="${file::exists('bin/Simple.pdb')}" />
    </target>

    <target name="build" description="compiles the source code">
        <mkdir dir="bin" />
        <sscmget
         serverconnect="localhost:4900"
         serverlogin="administrator:"
         file="/"
         branch="Nant Example"
         repository="Nant Example/nantest"
         destdir="C:nantest"
         recursive="false"
         quiet="false"
         writable="true"
         overwrite="true"
         force="true"
         />

        <csc target="exe" output="bin/Simple.exe" debug="${debug}">
            <sources>
                <include name="Simple.cs" />
            </sources>
        </csc>

        <sscmcheckin
             serverconnect="localhost:4900"
             serverlogin="administrator:"
             file="/"
             branch="Nant Example"
             repository="Nant Example/nantest/bin"
             comment="Check In By Nant"
             writable="true"
        />

        <sscmbranch
             serverconnect="localhost:4900"
             serverlogin="administrator:"
             branch="Simple 4.0"
             repository="Nant Example/nantest"
             parent="Nant Example"
             comment="Snapshot created by Nant"
             type="snapshot"
         />
    </target>

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

Using the Integration

Now that I have everything set up correctly, I can show how to run it and the output to expect. One thing to note is that I have changed the message output from the program to "Hello, Everyone!", just to ensure that a new build is made. I opened my command prompt (Start > Run, typed "cmd, clicked "OK"). Then I changed directories to "C:nantest" and called Nant. Here is the output from my terminal window:
C:nantest> ..nant-0.86-nightly-2007-04-15binnant
NAnt 0.86 (Build 0.86.2661.0; nightly; 4/15/2007)
Copyright (C) 2001-2007 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/nantest/Simple.build
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: run

build:

  [sscmget] get from Nant Example/nantest:
  [sscmget]   C:nantestSimple.cs
      [csc] Compiling 1 files to 'C:nantestbinSimple.exe'.
[sscmcheckin] check in to Nant Example/nantest/bin:
[sscmcheckin]   C:nantestbinSimple.exe (version 6)
[sscmcheckin]   C:nantestbinSimple.pdb (version 6)

run:

     [exec] Hello, Everyone!

BUILD SUCCEEDED

Total time: 1 seconds.
And a look in the Surround SCM client shows that the .exe and .pdb files in the "bin" directory have been updated and also a new snapshot branch was created.

Issues Encountered

Got Error Message About Not Recognizing the Surround SCM Tasks At first, I thought that the contrib.nant package was included with the installation of Nant. I was wrong. I had to download this package separately and copy EVERY file to the bin directory of Nant. Got Error that Branch Already Exists This will happen if you use a hard coded name for the snapshot branch and you forget to change the name before each build. New Build Wasn't Created Eventhough I Made Changes to the Source file This occurred when I was doing a recursive get, which was retrieving the .exe and .pdb files with the same timestamp as the source files (see explanation above). Once I removed the "recursive" flag from the get command, a new build was created.