Major.Minor.CCLabel.svn revision

June 7, 2007

A few days ago the team decided that we needed versioning for the application we’re working on. We wanted the version to include the build number from the build server and the svn revision number. We figured this would make it a lot easier to track bugs.

Frist off, how can you do this from NAnt ? Actually, getting the build number is easy since CruiseControl.NET passes a bunch of properties to NAnt, including CCNetLabel. If you’re using the default labeler than this is exactly the build number.

Fot the svn version number all the solutions we could find were sort of ugly hacks, so we decided to write a bunch of functions that you could call in nant and get the revision numbers. This again turned out to be fairly easy.

Here’s the code:

using System;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.ComponentModel;

using NAnt.Core;
using NAnt.Core.Types;
using NAnt.Core.Attributes;
using System.Collections.Generic;

namespace Nant.SvnFunctions
{
	///
	/// Provides methods for working with SVN
	///
	[FunctionSet("svn", "SourceControl")]
	public class SvnFunctions : FunctionSetBase
	{
		#region Public Instance Constructors

		public SvnFunctions(Project project, PropertyDictionary properties) : base(project, properties) { }

		#endregion Public Instance Constructors

		#region Public Instance Methods

		///
		/// Gets the revision number for the Subversion repository at the specified path
		///
		/// The path to a svn repository
		/// The revision number of the specified repository
		[Function("get-revision-number")]
		public static int GetRevisionNumber(string path)
		{
			return int.Parse(GetInfo(path)["Revision"]);
		}

		///
		/// Gets an url containing the root of the specified repository
		///
		/// The path to a svn repository
		/// The root url of the repository
		[Function("get-repository-root")]
		public static string GetRepositoryRoot(string path)
		{
			return GetInfo(path)["Repository Root"];
		}

		///
		/// Gets the url of the specified repository
		///
		/// The path to a svn repository
		/// The url of the repository
		[Function("get-repository-url")]
		public static string GetRepositoryUrl(string path)
		{
			return GetInfo(path)["URL"];
		}

		///
		/// Gets the name of the person who made the last change in the repository
		///
		/// The path to a svn repository
		/// The name of the last committer
		[Function("get-last-changed-author")]
		public static string GetLastChangedAuthor(string path)
		{
			return GetInfo(path)["Last Changed Author"];
		}

		///
		/// Gets the number of the latest revision in which the  has changed.
		///
		/// The path to check
		/// The latest revision number in which the file or directory specified in  has changed.
		[Function("get-last-changed-rev")]
		public static int GetLastChangedRev(string path)
		{
			return int.Parse(GetInfo(path)["Last Changed Rev"]);
		}

		#endregion Public Instance Methods

		#region Private Instance Methods

		private static Dictionary GetInfo(string path)
		{
			ProcessStartInfo pinfo = new ProcessStartInfo("svn.exe");
			pinfo.Arguments = "info " + path;
			pinfo.UseShellExecute = false;
			pinfo.RedirectStandardOutput = true;

			Process svnProcess;
			try
			{
				svnProcess = Process.Start(pinfo);
			}
			catch (Win32Exception win32Ex)
			{
				throw new Exception("Exception caught executing process: most probably 'svn.exe' was not found", win32Ex);
			}

			string output = svnProcess.StandardOutput.ReadToEnd();

			if (svnProcess.ExitCode == 9009 && output.Contains("is not a working copy"))
				throw new ArgumentException("The specified path is not a working copy", "path");
			else if (svnProcess.ExitCode != 0)
				throw new Exception("Failed while running 'svn.exe'. Error code " + svnProcess.ExitCode);

			svnProcess.Dispose();
			return ParseOutput(output);
		}

		private static Dictionary ParseOutput(string output)
		{
			Dictionary values = new Dictionary();
			Regex regex = new Regex("^(?[^:]*):(?.*)$", RegexOptions.Multiline);
			foreach (Match match in regex.Matches(output))
				values[match.Groups["key"].Value] = match.Groups["value"].Value.Trim();

			return values;
		}

		#endregion Private Instance Methods
	}
}

Compile this with a reference to NAnt.Core.dll and then load it in the build file like this:

<loadtasks assembly="../lib/Nant.SvnFunctions.dll"/>

You could alternatively dump it in the tasks folder in NAnt to make it available to all projects.

The functions exposed by it are:

  • svn::get-revision-number(path)
  • svn::get-repository-root(path)
  • svn::get-repository-url(path)
  • svn::get-last-changed-author(path)

Now to actually set the version numbers we use the asminfo nant task. This generates all the AssemblyInfo.cs files with the desired attributes set. In our case:

<asminfo output="src/AssemblyInfo.cs" language="CSharp">
    <attributes>
        <attribute
            type="System.Reflection.AssemblyVersionAttribute"
            value="3.0.${CCNetLabel}.${svn::get-revision-number('..')}"/>
    </attributes>
</asminfo>

So now we have pretty version number on all the dlls.

Note: none of us has a lot of experience with neither NAnt nor CruiseControl.NET. If there’s an easier or better way to do this please tell.

Update: Sean Chambers added username/password authentication support. Download here that repo’s dead

Tags: , ,

Leave a Reply