AtalaFileRegex: Putting it together
In my recent blog posts Writing NAnt tasks with nested types and Extending NAnt with your own type I went through adding nested types to custom task, and also making your own nested type to show how easy it is to get NAnt to do something it doesn't do already (it does so much, new tasks are rarely needed). In this case, I thought the generic Regex/Replace task in an entire file, or set of files would be nice.
I explained before that I wanted to make our builds parallel while maintaining their consistent version numbers. To do this, I needed to be able to hand out version numbers from CC.NET to my NAnt scripts. In an out-of-order fashion, I showed you the carriage but not the horse, but the point is the same. We'll assume NAnt has a build number coming in, somehow, so here I'll show you how I applied this to our build process.
Before I begin, I want to add to our task one small difference from the code I showed (but it's included in the posted source). I make a simple assumption in our builds that read only files have been gotten from version control and never edited. This is a safe assumption, since the source files should only be changed from the source control version by the build script. The next time the build script runs, we don't want it touching the version number (for continuous builds) because that causes a total rebuild of the project (nightly deletes the folder and starts from scratch). To get around this, I want the version part of the build script (using the AtalaFileRegex task) to only work on read only files (files that were freshly gotten from version control). To accomplish that, I added another boolean attribute to the AtalaFileRegex task called "onReadOnly." It's assumed false (that is, work on all files in the fileset). With this attribute, using the AtalaFileRegex task to apply version information to our assemblies only happens when the file has been updated in source control, or at least once per day in the nightly build. The result is our projects will be considered up-to-date when they should be!
I'm sure everyone is familiar with the AssemblyInfo.[cs,cpp] file. This file contains information used by the compiler to set assembly attributes, like version number, company name, trademark, etc. As part of my job, I cleaned up all of the AssemblyInfo files and separated out the fields that would be updated by the build server. I commented them to indicate to the editing engineers that certain fields will be edited by the build server (and that they don't have to maintain them manually).
So here's my (shaved down) task:
<AtalaFileRegex verbose="true" onReadOnly="true">
<regexs>
<regex value="\[assembly:\s?AssemblyVersion(Attribute)?\(.*\)\]"
replace="[assembly:AssemblyVersionAttribute("${Build.Version}")]" />
</regexs>
<fileset basedir="${Dir.Source}">
<include name="**\AssemblyInfo.cs" />
<include name="**\AssemblyInfo.cpp" />
</fileset>
</AtalaFileRegex>
Everybody love regex, so I'll break it down a bit (this one's pretty easy). The regex/replace will find any lines like:
[assembly: AssemblyVersion("1.23.45.67890")]
and replace it with a line like:
[assembly:AssemblyVersionAttribute("9.0.0.45000")]
Which will work for both the semi-colon terminated, and the non-semi-colon terminated lines (.cpp and .cs respectively). I'm sure you could put more checking in there, like ensuring there wasn't comment-type characters preceding the text, but this is a destructive regex; if it's in comments, who cares? It only matters on the one line where the compiler sees it.
Next time, I'll show you how I've used Cruise Control and Team Foundation Server to feed NAnt some interesting information.
Get the source!