Wednesday, July 06, 2005

Versioning Using NAnt

I've posted already on how much I'm getting out of Marc Holmes' Expert .NET Delivery using NAnt and CruiseControl .NET. (Marc's no relation to me, at least none that I know of.)
One of the topics within is versioning. Marc uses the version task from nant-contrib to increment build numbers held in a Build.Number file. This gets used in the same work task to create the assembly information via the asminfo task. I found one mistake in his example code, plus made one enhancement to it. Changes are below. (I should say I think I found a mistake. I haven't heard back from him yet.)
The mistake seems to be that the incremented version number isn't used when generating the AssemblyVersionAttribute. The enhancement I did was wrapping the Build.Number file into Source Safe, checking it out before incrementing the version, then checking it back in afterwards.
Below is the updated XML for the task. I'm not going to fill in all the NAnt properties created upstream from this task-- they're pretty obvious. Note that Marc's strategy uses the debug property to control whether the build number's incremented or not. You don't have to increment build numbers if you're just testing additions to the NAnt scripts. I may rename this property in the future since "debug" means different things to me.
Marc also uses a CommonAssemblyInfo file which he links to individual projects within a solution, a great trick for cutting duplication of redundant information. ("Duplication of redundant..." Clever, no?)
<target name="version1" description="Increments version number in CommonAssemblyInfo file.">
<property name="sys.version" value="0.0.0.0"/>
<ifnot test="${debug}">
<!-- Enhancement: check out Build.Number file from source control -->
<vsscheckout
username="${vss.username}"
password="${vss.password}"
localpath="${core.source}\"
recursive="false"
dbpath="${vss.dbpath}"
path="${vss.path}\${solution.name}.Build.Number"
/>
<version buildtype="Increment"
revisiontype="Increment"
path="${core.source}\${solution.name}.Build.Number"/>
<!-- Bugfix: store version task's output back to sys.version property if we're incrementing -->
<property name="sys.version" value="${buildnumber.version}" />
<!-- Enhancement: check Build.Number file back into source control -->
<vsscheckin
username="${vss.username}"
password="${vss.password}"
localpath="${core.source}\${solution.name}.Build.Number"
recursive="false"
writable="false"
dbpath="${vss.dbpath}"
path="${vss.path}\${solution.name}.Build.Number"
comment="Incrementing for version ${sys.version}"
/>
</ifnot>
<attrib file="${core.source}\CommonAssemblyInfo.vb" readonly="false" />
<asminfo output="${core.source}\CommonAssemblyInfo.vb" language="VB">
<imports>
<import name="System" />
<import name="System.Reflection"/>
</imports>
<attributes>
<attribute type="AssemblyVersionAttribute" value="${sys.version}" />
<attribute type="AssemblyProductAttribute" value="${solution.name}" />
<attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2005, ${company.name}"/>
<attribute type="AssemblyCompanyAttribute" value="${company.name}" />
<attribute type="AssemblyTrademarkAttribute" value="Trademark by ${company.name}" />
</attributes>
</asminfo>
<attrib file="${core.source}\CommonAssemblyInfo.vb" readonly="true" />
</target>
This approach may not work for everyone. In particular, the linked CommonAssemblyInfo file won't work with Web projects because of their pain-in-the-ass broken up directory structure. It also doesn't give granular control down to each assembly. That's OK, because Marc says frequently in his book that his topics aren't solutions for everyone, they're guides to get you started.
So there you have it.
UPDATE: Marc pointed out that sys.version is the property set by nant-contrib's <version> task -- in earlier versions of nant-contrib. I'm using 0.85RC3 which defaults to buildnumber.version. I should have checked that from the get-go!

I was using the code from Marc's book and the sys.version was never getting incremented. Did a google search, found your a link to your blog ..and discovered that buildnumber.version is what incremented ..

THANKS BRO!!!

I was wondering if you could answer my question on the versioning part of the build process. My question is why are we incrementing the version number before compiling. Its quite possible that the compilation may fail and why would one increment the version for failed builds.

You need to have a new version number for passing to the assembly. Otherwise how will the new assembly have the proper version number?

You do raise a good point about not incrementing for failed builds. You could set the "debug" property which doesn't increment the version number. However, then what do you do when you need to version the assembly? Rerun the build?

I suppose you could wrap in some sort of failover condition where the build number gets decremented if the build fails, but that's quite a bit of hassle. Frankly, I'd just let the version number increment and run with it.

My Env: 1) 3 .sln files each of which are referring to 10-12 .prj files (each of which are referring around 70-80 .cs files and other related proj. files)2) one web site project folder(ref-http://maordavid.blogspot.com/2007/06/aspnet-20-web-site-vs-web-application.html) not having a prj file at all.

My need : I would like to increment the version numbers in both the cases whenever I create a new full-build or patch-build with the only difference being the patch build should contain only the changed files and not all of the files, just because a new version number has been stamped on to all the files in a single project referring to one common assembly.cs file.

What I tried :I couldn't succeed because my patch build gets either all the files or none of them. But never just the changed files alone.

Appreciate if you could share some light on how to configure Nant for the above need. Even some pointers would help.

Not a Problem, Jim. An idea I have is to get a manifest-like file generated having file-hashes of all files during the product-build and get the patch build contain only the changes files based on the changed-hash data. Though it sounds like a work around, will try it to see if it works until I figure out an easier way.

About Me

I'm the owner/principal of Guidepost Systems. I help lots of great folks figure out what works and what doesn't in the world of delivering quality software -- something I'm very passionate about. I'm also a Father trying to remain sane while trying to build great software, herd my kids around, fix school lunches and handle the yardwork. (And roast great coffee!)