Friday, January 19, 2007

Boosting Your Assembly.cs Revision Number from NAnt

I know that others have blogged about this, esp. Scott Hanselman did, somewhere...I'm digressing already. I wanted the simplest possible solution: I wanted to leverage already existing NAnt or NAntContrib tasks, rather than write my own; I did not want to use the CruiseControl.NET label, since a new CC.NET installation starts from zero, as I found out after I hosed my own, and in any case I would want CC.NET ultimately to do no more than call a bunch of NAnt tasks, pass all the artifacts through some XSLT, and create the Web pages. What I ended up with was 1, a file called version.txt, containing only (at the moment) the text 3.0.0.1401; 2, a file called CommonAssemblyInfo.cs, including assembly-level metadata common to the entire application, e.g. [assembly: AssemblyVersionAttribute("3.0.0.1412")]; 3, the following NAnt task:

<target name="createAsmInfo" description="Overwrite the rev. #" >
<if test="${not property::exists('version.txt')}">
<property name="version.txt" value="version.txt" />
</if>
<if test="${not property::exists('common.assembly.info')}">
<property
name="common.assembly.info"
value="Applications/CommonAssemblyInfo.cs" />
</if>
<!-- I.e., increment the revision number and *not* the build number. -->
<version path="${version.txt}"
buildtype="NoIncrement"
revisiontype="Increment" />
<asminfo output="${common.assembly.info}" language="CSharp">
<imports>
<import namespace="System.Reflection" />
</imports>
<attributes>

<attribute type="AssemblyVersionAttribute"

value="${buildnumber.major}.${buildnumber.minor}.${buildnumber.build}.${buildnumber.revision}" />
<attribute type="AssemblyCopyrightAttribute" value="Copyright © 2006 MDi" />
<attribute type="AssemblyCompanyAttribute" value="MegaDyne, Inc." />
<attribute type="AssemblyProductAttribute" value="KillerApp" />
</attributes>
</asminfo>
<!-- The svn task is inadequate. It doesn't handle commits! -->
<exec program='${svn.exe}'
commandline='commit --non-interactive --username=${svn.username} --password=${svn.password} -m "Updated by CruiseControl.NET" ' />
<exec program='${svn.exe}' commandline='update' />
</target>


The <asminfo> task should really not overwrite existing metadata unless so instructed, but I haven't tested that yet. Note that I immediately commit the changed files back into the repository; this might be the wrong thing to do, unless the build succeeds. If it does fail, then CruiseControl.NET will be unable to update the source code from the repository; i.e., there files I've changed will be in conflict. This suggests that I really do all my work from NAnt, but I haven't gotten around to that yet. A further problem is that version.txt and CommonAssemblyInfo.cs will look new to CC.NET the next time it checks, triggering another build. CC.NET offers a "filter trigger" for this sort of thing, but I found the documentation incomprehensible, so I haven't been able to use it yet.


The one additional step is to remove the common metadata from AssemblyInfo.cs files throughout the solution, and add CommonAssemblyInfo.cs as a link. This turned out to be somewhat tricky for me. I have an old habit of configuring Windows Explorer to open files with a single click; a more experienced developer once told me that he was "more productive" with this option; I fell for it, and now I'm stuck with this wierd tic. Anyway, I would select "Add existing item" from a project's context menu, navigate to CommonAssemblyInfo.cs, click on it...and of course a copy would be added to the project. You won't have this problem! Anyway, I had to right-click on the file from the File Open dialog, select "Select" from the context menu, and only then would the little triangle be enabled:

That's pretty much it. You have to remember to change every project this way, but that's about it. Some (Scott Hanselman) prefer to edit the .csproj files themselves; I am probably revealing myself as a non-guru by this admission, but I hate doing that.