Abstract
I want to address two topics in this article, one educational and the other
practical. My educational reason is to show you how to do something
non-trivial utilizing the XML and
XSL .NET Framework classes. In doing this I'm going to show
you one approach to solving the practical problem of updating version numbers
in automated nightly builds; in particular of C++
and C# projects. Because of the use of XSL the solution
is open ended, so you can use for any language or project, with only
a little extra work.
Version Resources
If you've been a Microsoft Windows developer long enough, you've had to solve
the problem of updating your binaries version numbers after each major build of
your product. Incrementing the version number after each build gives you
an excellent way to localize problems to particular builds. Also, having
good version numbers is essential for your installer to be able to install
over or alongside existing copies of your product.
If you practice good software development principles and try and do a full build
automatically every day, there's a good chance you'll have figured out some
automated way to do the updates. If not, or if you are not happy with
your existing solution, today is your lucky day!
Details
The tool consists of a single executable called MKVER2.EXE
.
If you're looking at the source, the important code is all contained in MainClass
.
The data flow through the application looks like this:
An XML file with an extension PVD (for Product Version Data) contains all the
relevant version information for all the binary files in your
product. This file is the first input into MKVER2. You'll
also specify the name of the output file, in the diagram it's AssemblyInfo.cs
.
The algorithm I have implemented is that we use the extension of the
output file to search for a corresponding XSL transform file with the filename
of version.extension.xsl
. Hence, AssemblyInfo.cs
causes us to use version.cs.xsl
as the second input to
MKVER2.
These templates are looked for in the following various locations in order (1)
the directory specified on the command line, (2) the directory specified by the
MKVER_TEMPLATES
environment variable, (3) a Templates
sub-directory of the current directory, (4) a Templates
sub-directory
of the directory where MKVER.EXE
is located.
Take a look at the format of a PVD file and you'll see it's pretty
straightforward. One thing that might take some explaining are the <VersionInfo>
elements:
<VersionInfo lang="1033" charset="1200">
...
...
</VersionInfo>
The idea here is that all language specific pieces of version information, such
as copyrights, descriptions, trademarks, etc. are contained within these
elements. Using command line parameters you can determine which language
strings end up in your version resources. Take a look at the first part
of version.cs.xsl
:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" version="4.0" encoding="utf-8"/>
<xsl:param name="lang"/>
<xsl:param name="charset"/>
<xsl:param name="name"/>
<xsl:template match="/VersionData">
As you can see we make use of XSL parameters to pass this information into the
transform. Later on in the transform we use these parameters as input to
the XPath query that retrieves a specific VersionInfo
element:
<xsl:apply-templates select=
"VersionInfo[@lang=$lang and @charset=$charset]" mode="FileVersion"/>
The mode
attribute is used because I have two FileVersion
templates, one for the product VersionInfo
and one for the files
VersionInfo
. The following code shows the guts of the MKVER2
algorithm in MainClass.cs
:
try
{
inputFile = Path.GetFullPath((string)inputFile);
document.Load(inputFile);
navigator = document.CreateNavigator();
if (!IsCorrectRevision(1))
return;
UpdateProductVersion();
AddNodes();
if (trace)
{
XmlTextWriter xw = new XmlTextWriter(BpConsole.Out);
xw.Formatting = Formatting.Indented;
document.WriteContentTo(xw);
xw.Flush();
BpConsole.WriteLine();
}
TransformDocument();
}
catch (Exception e)
{
WriteMessage(MessageType.Error, e.Message);
}
First we grab the input document and load it into an XmlDocument
object.
A quick check for the expected revision attribute and we proceed to the
UpdateProductVersion()
function to increment/set the new version
number.
What is the function of AddNodes()
? Well, sometimes
in the output we are going to want some data that is derived from the
input data, but is not in the correct format to make it easily
accessible. For example, let's say your version number is currently
1.0.0.100. The 100 might refer to your current build number. Let's
say you want to automatically send an e-mail stating "Build 100
Completed". How do you get at the "100" part of the version number?
The solution I came up with was to have MKVER2 pre-calculate certain pieces of
information and add them as new elements into the original XML. This
makes things much easier in the XSL transformations.
In order that someone who doesn't have access to the source code can
easily see what these new elements are available, I added the -trace
command line option, which dumps the new XML data to the screen. One
downside of the approach of dynamically adding elements is that it creates a
strong coupling between MKVER and the format of the input data. We look
for certain elements to base our new nodes on. Hence the revision check
mentioned earlier.
The final step is to call TransformDocument()
to actually
do the XSL transformation. This creates our output file.
Using the Tool
How do you use the tool? Well you you've noticed MKVER2 uses MKVER2 to do
it's own versioning! Included in the sample source code solution file are
a couple of Makefile projects. The one called Version
simply
invokes a batch file to update the version of MKVER2 whenever a full Release
configuration build is performed. The batch file is written using
4NT, and shows how I update the version number of MKVER2 and check the
results into a local Perforce version
control database. Unless you have 4NT and Perforce you won't be able to
run it, but you'll be able to follow the basic steps easily enough.
One thing I've discovered about C# projects that I'm not thrilled about is that
there does not appear to be any way at all to do the equivalent of C++
includes. Unless you build from the command line you can't even have a C#
project in VS.NET that includes files in directories other than the project
directory. What this basically means is you'll have to go through
each project and update the version information separately. Note also
that this is why you have to specify a -name
command line
parameter, so that your C# output file only contains version information for
one project.
Yes, I know that .NET projects can automatically create version numbers for
themselves, if you use an attribute like [assembly:
AssemblyVersion("1.0.*")]
. But this essentially generates a
random number for the version number, which I feel is about as useful a poke in
the eye with a sharp stick.
For C++ projects, you'll have things easier. You can create an
#include
file in a shared location and generate just that one
file. You can set a preprocessor macro in each C++ project and select the
version information that's relevant to that project. Hence, no need to
specify a -name
parameter to MKVER2 (unless you want to that
is). Check out version.h.xsl
for more information, or just
run MKVER2 on the supplied version.pvd
to see some
example output. I've included a version.rc2 file that you can
include in your C++ projects RC file to make use of the #define's
generated in the header file. To add this RC2 to your project copy it
into the project directory, open your RC file and select Edit Resource
Includes... Then in the "Compile-time directives:" editor add the
line #include "version.rc2"
.
You can also use MKVER2 to generate any other type of output you like containing
the version information. I've include some XSL files for generating BAT
and HTML files for your versioning pleasure.
Conclusion
OK, if you read this article to learn how to program in XSL or .NET your
probably a bit disappointed by now. I suggest you store this article away
for future reference. However, if you have already mastered the basics of
XML and XSL and were looking for a power sample to really show you some of the
things you can usefully do with it, you should be happier. I'm blown
away by how easy it is to do XML related stuff in .NET. I've written a
fair amount of MSXML code using the COM interfaces in the past, and I'm never
going back!
As far as the version utility goes, I hope it's useful to you. If someone
writes XSL transforms for VB.NET, Java/J# or any other versionable project and
sends them to me, I'd be happy to add them to the MKVER2 download on
this page. Finally, if you haven't found it yet and you are looking for a
knock your socks off application of XSL with the .NET Framework check out
NDoc.