Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Version Resource Tool Using XML/XSLT and .NET

0.00/5 (No votes)
24 Oct 2002 1  
A tool for automatically updating version resources in C++, C# and other projects.

Sample Image

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:

VRT Data Flow

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);

        // load the version data

        document.Load(inputFile);

        // wrap it with an XmlNavigator

        navigator = document.CreateNavigator();
		
        if (!IsCorrectRevision(1))
            return;

        UpdateProductVersion();

        // Add dynamically generated nodes

        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here