Introduction
In C# land, Visual Studio automatically creates an AssemblyInfo.cs for each project and places version info in that file as AssemblyVersion
and AssemblyFileVersion
attributes. The version is in the format major.minor.build.revision, which despite being a string in .NET land is really based on the original FILEVERSION
struct, which defines each of the four dotted fields as a U16. (I'm unsure if Windows still cares about this format, but you can be sure that something will still try to parse the string into four U16's and will thus barf if you use a non-U16 field, e.g., "1.2.3.abc"). One standard trick offered by Visual Studio is to use asterisk(s) in the version string(s) in AssemblyInfo.cs, and Visual Studio will auto-convert the asterisked fields to auto-incrementing numbers. While this helps in uniquely identifying builds, these automatic numbers are unrelated to anything and are thus less than ideal.
My company tracks versions in this standard major.minor.build.revision format, and we increment the 'revision' field with each build. The other day, one of our QA guys asked if this revision field had any relation to the project's SVN revision, and I was sad to answer that it did not. He asked if it were possible to do so, and after a bit of thinking, I realized that it would actually be pretty simple to do so; this article describes my solution.
Background
This article and tool assumes the use of SVN. It could easily be adapted to other source-control apps (e.g.: CVS) with a minor bit of tweaking, but for simplicity (and because we don't use CVS :) I've omitted that here.
This article also assumes that your SVN installation offers web-based access. We use ViewVC, so this article is based on ViewVC's page format. If you use a different Web front end, you'll likely need to tweak the sample code to get it to work with your environment.
I had originally planned to integrate an SVN library (e.g.: SharpSVN) to programmatically pull repo revisions, but then realized that pulling a ViewVC page and parsing its HTML was much easier to implement for this tool. If you're more hardcore than me, you may want to attempt using a lib, but for my purposes, the HTML parsing method was much faster and easier to get up and running.
Using the Code
There's nothing really novel about any of the code behind this - I assume you've used File.ReadAllLines()
before - so I've intentionally omitted code samples from this article. What I really wanted to present was my idea for a solution and a sample app to demonstrate it.
That said, here's an outline of my implementation:
As mentioned above, the attached sample (AssemblyRevisionFromSvn - VS2010/C#) is designed as a pre-build tool. It's a console app that accepts two command-line args:
/u=
specifies the SVN URL that contains the revision-tag you wish to use. I encase the value in quotes to ensure that the commandline parser doesn't mistake the URL's forward-slashes as switch chars.
/t=
specifies the target file, which is almost certainly (but not necessarily) named <project>\Properties\AssemblyInfo.cs; this is the file containing the attributes you wish to update. I intentionally do not encase this value in quotes because Visual Studio automatically does this for you, so if you supply quotes, the result will be a double-quoted param that breaks stuff in bad ways.
So the pre-build commandline might look like this:
AssemblyRevisionFromSvn.exe /u="http://svn/viewvc/MyProject/trunk/"
/t=$(SolutionDir)\Properties\AssemblyInfo.cs
(This assumes that AssemblyRevisionFromSvn.exe is in your search path.)
AssemblyInfo.cs contains the AssemblyVersion and AssemblyFileVersion
attributes that need updating:
[assembly: AssemblyVersion("1.2.3.4")]
[assembly: AssemblyFileVersion("1.2.3.4")]
We're interested in that last field, shown above as 4
. We want it to be the current SVN revision, e.g., 108
, and we want it to automatically be updated to the current SVN revision prior to each build.
The ViewVC page - where we'll get the revision from - looks like this; we want the Directory Revision value:
Therefore, putting it all together is pretty straightforward:
- Retrieve the
/t
and /u
command-line args.
- Fetch the ViewVC URL's HTML using
HttpWebRequest
.
- Parse out the ViewVC revision value.
- Load the target file.
- Replace all desired attribute values with the rev from ViewVC.
- Save the target file.
(Naturally, we validate everything and throw errors as needed.)
Points of Interest
Because my versioning standard requires four fields (major.minor.build.revision), if a version string contains fewer then four fields, I'll add the missing fields with zeros. Thus 1.*
becomes 1.*.0.108
(108
being the SVN revision). If your original version-string contains an asterisk, the fixed-up string (e.g.: 1.*.0.108
) will cause a compiler error:
Error emitting 'System.Reflection.AssemblyVersionAttribute'
attribute -- 'The version specified '1.*.0.108' is invalid'
The solution is to not mix asterisks with AssemblyRevisionFromSvn
:)
One minor annoyance of this method is that, because it alters AssemblyInfo.cs for each build, your SVN project tree will always show this file as changed (TortoiseSVN shown here):
I haven't yet attacked this issue, but even without being "solved", it's a minor price to pay for the convenience and consistency provided by the tool. Possible solutions I've considered:
- Stash the original version string in a comment or
#ifdef
, then re-run the tool as a post-build step to restore the original value
- Run the tool as post-build and simply revert the file
Given that I just invented this tool, it's too soon to tell if the annoyance requires a solution. If and when I do something about it, I'll post an update.
I'd be interested to hear (comments) if anyone else uses (or has considered using) SVN revisions in their assembly versions, and if so, how they went about keeping things in sync.