One aim of continuous integration should be to automate as much of the build process as possible. If something is left to human intervention then inevitably it will at some point be completed incorrectly or inconsistently or forgotten completely.
Part of a good build process is to update the version number of the application so that each build (triggered by changes in the source) will have a unique number and the source control system should label the source so that the code that created that build can be identified. This is my solution to updating the version number when using NAnt, Draco.Net and SourceGear Vault.
The build process consists of the following:
Draco.Net monitors the project folder in the SourceGear Vault repository.
A developer checks in some changes which causes Vault to update the folder version number (based on the changeset).
Draco.Net detects the changes and gets the latest version of the project to a temporary build folder
The NAnt .build file (part of the project) is started by Draco.Net
Within NAnt a custom function gets the folder version from Vault and sets the revision part of the version number based on it
The project folder in Vault is labeled with the build revision number
The assembly is compiled using the version number by overriding the AssemblyInfo.cs file (using the standard <adminfo> task)
The NUnit tests are run and reports produced, MSI files created etc.
Everything is pretty straightforward using the standard functions of NAnt, Draco.Net and the SourceGear Vault NAnt Tasks other than step #5 which required a custom NAnt function to return the folder tree version number. The code for this is below which I added to the Vault NAnt Tasks project (I'm not sure yet of the process for feeding this into the project as an update):
<?xml version="1.0"?>
<project name="NUnitAspInjector" default="build" basedir=".">
<!-- set by Draco.Net for automated builds -->
<property name="draco" value="false"/>
<property name="build.id" value="0"/>
<!-- project properties -->
<property name="project.name" value="NUnitAspInjector"/>
<!-- base project version for this trunk / branch -->
<property name="build.version.major" value="1"/>
<property name="build.version.minor" value="0"/>
<property name="build.version.build" value="5000"/>
<property name="build.version.revision" value="0"/>
<!-- if automated build then set revision number based on version in vault -->
<if test="${draco}">
<!-- vault parameters (set in Draco.Net project properties) -->
<!-- <property name="vault.url" value="http://localhost"/> <property name="vault.repository" value="default"/> <property name="vault.path" value="$/NUnitAspInjector/trunk"/> <property name="vault.username" value="build"/> <property name="vault.password" value="password"/> -->
<!-- set revision number based on tree version from vault -->
<property name="build.version.revision" value="${vault::getversion(vault.url, vault.repository, vault.path, vault.username, vault.password)}"/>
<!-- label the build -->
<!-- in case a build is forced, this avoids a duplicate label error -->
<vaultdeletelabel url="${vault.url}" username="${vault.username}" password="${vault.password}" repository="${vault.repository}" path="${vault.path}" labelstring="build ${build.version.revision}"/>
<!-- apply the label to the source folder -->
<vaultlabel url="${vault.url}" username="${vault.username}" password="${vault.password}" repository="${vault.repository}" path="${vault.path}" labelstring="build ${build.version.revision}"/>
</if>
<!-- set the full build version based on major.minor.build.revision -->
<property name="build.version" value="${build.version.major}.${build.version.minor}.${build.version.build}.${build.version.revision}"/>
<!-- output directory -->
<property name="build.outdir" value="C:\working\buildoutput\${project.name}\${build.version}"/>
<!-- target .NET framework and corresponding compiler directives -->
<property name="nant.settings.currentframework" value="net-1.1"/>
<property name="csc.define" value="NET11"/>
<target name="build">
<echo message="building: ${project.name} ${build.version}"/>
<echo message="build id: ${build.id}" if="${draco}"/>
<!-- build the solution in debugh mode so that the test are created -->
<!-- this is a shortcut until a proper test project is created -->
<solution solutionfile="src\NUnitAspInjector.sln" configuration="debug">
<webmap>
<map url="http://localhost/NUnitAspInjector.Sample/" path="src\NUnitAspInjector.Sample\"/>
</webmap>
</solution>
<!-- make a folder for the assembly -->
<mkdir dir="bin"/>
<!-- if automated build then create assembly version info -->
<if test="${draco}">
<attrib file="src\NUnitAspInjector\AssemblyInfo.cs" readonly="false"/>
<asminfo output="src\NUnitAspInjector\AssemblyInfo.cs" language="CSharp">
<imports>
<import name="System" />
<import name="System.Reflection" />
<import name="System.EnterpriseServices" />
<import name="System.Runtime.InteropServices" />
</imports>
<attributes>
<attribute type="ApplicationNameAttribute" value="${project.name}" />
<attribute type="AssemblyProductAttribute" value="${project.name}" />
<attribute type="AssemblyTitleAttribute" value="InteSoft ${project.name} ${build.version.major}.${build.version.minor} for .NET 1.1" />
<attribute type="AssemblyDescriptionAttribute" value="${project.name} for ASP.NET" />
<attribute type="AssemblyCompanyAttribute" value="InteSoft IT Ltd." />
<attribute type="AssemblyCopyrightAttribute" value="Copyright © 2005 InteSoft IT Ltd." />
<!-- the version stays at x.x.x.0 for binding -->
<attribute type="AssemblyVersionAttribute" value="${build.version.major}.${build.version.minor}.${build.version.build}.0" />
<!-- the information versions show the revision number -->
<attribute type="AssemblyFileVersionAttribute" value="${build.version}" />
<attribute type="AssemblyInformationalVersionAttribute" value="${build.version}" />
<attribute type="AssemblyDelaySignAttribute" value="false" />
<attribute type="AssemblyKeyFileAttribute" value="" />
<attribute type="AssemblyKeyNameAttribute" value="" />
<attribute type="CLSCompliantAttribute" value="true" />
<attribute type="ComVisibleAttribute" value="false" />
</attributes>
<references>
<include name="System.EnterpriseServices.dll" />
</references>
</asminfo>
</if>
<!-- now build the proper release version of our component -->
<csc target="library" output="bin\NUnitAspInjector.dll" debug="false" define="${csc.define};" optimize="true" rebuild="true">
<sources basedir="src\NUnitAspInjector">
<include name="AssemblyInfo.cs"/>
<include name="Gdi32.cs"/>
<include name="Handler.cs"/>
<include name="Html2Image.cs"/>
<include name="InjectorModule.cs"/>
<include name="InjectorWebFormTestCase.cs"/>
<include name="RenderAssertion.cs"/>
<include name="TestHostAttribute.cs"/>
<include name="TestInjectorAttribute.cs"/>
<include name="TestRenderAttribute.cs"/>
<include name="TestServer.cs"/>
<include name="cassini\ByteParser.cs"/>
<include name="cassini\ByteString.cs"/>
<include name="cassini\Connection.cs"/>
<include name="cassini\Host.cs"/>
<include name="cassini\Messages.cs"/>
<include name="cassini\Request.cs"/>
<include name="cassini\Server.cs"/>
</sources>
<resources prefix="NUnit.Extensions.Asp" basedir="src\NUnitAspInjector">
<include name="Html2Image.resx"/>
</resources>
<references>
<include asis="true" name="System.Web.dll"/>
<include asis="true" name="System.Web.Services.dll"/>
<include asis="true" name="System.Runtime.Remoting.dll"/>
<include asis="true" name="System.XML.dll"/>
<include asis="true" name="System.Windows.Forms.dll"/>
<include asis="true" name="System.Drawing.dll"/>
<include asis="true" name="lib\AForge.Imaging.dll"/>
<include asis="true" name="lib\AForge.Math.dll"/>
<include asis="true" name="lib\nmock.dll"/>
<include asis="true" name="lib\nunit.framework.dll"/>
<include asis="true" name="lib\NUnitAsp.dll"/>
<include asis="true" name="lib\Interop.SHDocVw.dll"/>
<include asis="true" name="lib\AxInterop.SHDocVw.dll"/>
<include asis="true" name="lib\Microsoft.mshtml.dll"/>
</references>
</csc>
<!-- copy this release version to the test app -->
<copy file="bin\NUnitAspInjector.dll" todir="src\NUnitAspInjector.Sample\bin" overwrite="true"/>
<!-- run the NUnit tests -->
<nunit2 failonerror="true">
<formatter type="Xml" usefile="true" extension=".xml" outputdir="${build.outdir}"/>
<test assemblyname="src\NUnitAspInjector.Sample\bin\NUnitAspInjector.Sample.dll" appconfig="src\NUnitAspInjector.Sample\web.config">
<categories>
<exclude name="IIS"/>
</categories>
</test>
</nunit2>
<!-- create NUnit report -->
<nunit2report out="NUnitReport.htm" todir="${build.outdir}" opendesc="true">
<fileset>
<include name="${build.outdir}\NUnitAspInjector.Sample.dll-results.xml"/>
</fileset>
</nunit2report>
<!-- copy this release version to the output folder -->
<copy file="bin\NUnitAspInjector.dll" todir="${build.outdir}" overwrite="true"/>
</target>
</project>
The key line is this:
<!-- set the revision number based on a build startdate -->
<property name="build.startdate" value="01 Oct 2005"/>
<script language="C#">
<imports>
<import namespace="System.Globalization"/>
<import namespace="System.Threading"/>
</imports>
<code>
<![CDATA[
public static void ScriptMain(Project project)
{
DateTime start = Convert.ToDateTime(project.Properties["build.startdate"]);
Calendar calendar = Thread.CurrentThread.CurrentCulture.Calendar;
int months = ((calendar.GetYear(DateTime.Today) - calendar.GetYear(start)) * 12) + calendar.GetMonth(DateTime.Today) - calendar.GetMonth(start);
int day = DateTime.Now.Day;
int revision = (months * 100) + day;
project.Properties["build.version.revision"] = revision.ToString();
}
]]>
</code>
</script>
However, I like the fact that the build number only changes when the source changes (due to checkins) rather than incrementing simply due to time. Also, with Vault, the version number of the folder ends up matching the build label applied to it which just seems neat. I like neat :)
That's it. Hope someone finds this of some use!