Category: .NET

  • Integrating the Subversion Revision into the Version Automatically with .NET

    In our final post in this series, we’ll be discussing how to integrate the Subversion information into the file version automatically with .NET. Building on these previous posts, I’ll be discussing how to do this for a .NET project using Visual Studio 2010 Professional and TortoiseSVN.

    The source code for this post is available here.

    Step 1: Add a Version Project

    I find adding a specific Version project to the solution is beneficial. The reason is that we are generating common version info to be used across the entire solution, however the first project in the build order needs to be the one to generate this info. Depending on your project dependencies there may not be an obvious or good place to do this. By adding a Version project and making all other projects depend on it we have an easy place to generate the necessary files.

    1. Add a project named Version to your Solution
    2. Add a reference to Version from all the other projects in your Solution

    Step 2: Add a GlobalAssemblyInfo Template

    Create a file named GlobalAssemblyInfo.cs.tmpl in the Properties folder of the Version project. Copy the code below into the template and make any desired customizations to match your environment.

    Notice the $WCNOW$, $WCNOW$ and $WCMODS?M:$ tokens. We’ll use these to place the necessary Subversion information in the file we generate using SubWCRev.exe.

    // This file contains common AssemblyVersion data to be shared across all projects in this solution.
    using System.Reflection;
    
    [assembly: AssemblyCompany("Zach Burlingame")]
    [assembly: AssemblyProduct("DotNetSVNAutoVersion")]
    [assembly: AssemblyCopyright("Copyright © Zach Burlingame 2011")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
    
    #if DEBUG
    [assembly: AssemblyConfiguration("Debug")]
    #else
    [assembly: AssemblyConfiguration("Release")]
    #endif
    
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version 
    //      Revision
    //      Build
    [assembly: AssemblyVersion("1.0.0.$WCREV$")]
    [assembly: AssemblyTitle("Built $WCNOW$ from r$WCREV$$WCMODS?M:$")] // This is visible as the "File Description" on the Details tab of the Explorer pane
    

    NOTE: Originally I also had set the AssemblyFileVersion in the template as well. However, for .NET projects, placing string data in this attribute will generate a warning (like the one below), unlike the FileVersionStr in native projects.

    [assembly: AssemblyFileVersion(“1.0.0.$WCREV$$WCMODS?M:$”)]
    Warning 1 Assembly generation — The version ‘1.0.0.118M’ specified for the ‘file version’ is not in the normal ‘major.minor.build.revision’ format Version

    Step 3: Using SubWCRev to Generate GlobalAssemblyInfo

    Next we need to add a pre-build step to our Version project to generate the GlobalAssemblyInfo.cs file from the template using the TortoiseSVN tool called SubWCRev.

    The extracted version info includes the revision number, the date time of the build and if there are any local modifications to the working copy. Note that in counter-intuitive fashion, the AssemblyTitle attribute is what sets the Description field when viewed from the Details tab of the Properties pane in Explorer. Meanwhile the AssemblyDescription field isn’t displayed at all and rather is only accessible through API calls against the binary. Why MS did this, I do not know…

    1. Right-click on the Version project
    2. Select Properties
    3. Select the Build Events tab
    4. In the Pre-build event command line box, enter:
      subwcrev.exe "$(SolutionDir)\" "$(ProjectDir)Properties\GlobalAssemblyInfo.cs.tmpl" "$(SolutionDir)Code\App\GlobalAssemblyInfo.cs"

    Step 4: Add a link to the GlobalAssemblyInfo.cs file in each project

    1. In Visual Studio, right-click on a project
    2. Select Add->Existing Item
    3. Browse to GlobalAssemblyInfo.cs
    4. Select the file
    5. Click the drop-down arrow next to Add and select Add As Link
    6. Move the link to your Properties folder (optional, but keeps things neat)

    Step 5: Update the AssemblyInfo.cs for each project

    In order to avoid duplicate annotations for assembly information, you must remove entries from the AssemblyInfo.cs file that appear in the GlobalAssemblyInfo.cs file. In our example here, this is what we end up with:

    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    // General Information about an assembly is controlled through the following 
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyDescription("")]  // This is not visible on the Details tab of the Explorer pane
    
    // Setting ComVisible to false makes the types in this assembly not visible 
    // to COM components.  If you need to access a type in this assembly from 
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
    
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("dad09178-814d-43f4-b76d-0fbe29a32544")]
    

    Other Posts in this Series

    1. Mapping Binaries in the Field to Source Code in the Repository
    2. Versioning a Native C/C++ Binary with Visual Studio
    3. Versioning a .NET Assembly with Visual Studio
    4. Integrating the Mercurial Revision into the Version Automatically with Native C/C++
    5. Integrating the Mercurial Revision into the Version Automatically with .NET
    6. Integrating the Subversion Revision into the Version Automatically with Native C/C++
    7. Integrating the Subversion Revision into the Version Automatically with .NET
  • Integrating the Mercurial Revision into the Version Automatically with .NET

    I’ve already shown how you can add the mercurial revision into the version automatically with native C/C++. However, there are some extra hurdles you have to jump to make this work for .NET. The problem is that you can’t use static variables or class data in the assembly attributes – you have to use constant literals or expressions. As a result, we can’t just generate a simple class and reference it in the AssemblyAttributes.

    Again I’ll be building on the previous posts and discussing how to do this with Visual Studio 2010 Professional and Mercurial using TortoiseHg, this time for a .NET application. Other versions of Visual Studio should work similarly and other Mercurial packages will work as well, as long as they provide command-line tools that are in the path of your IDE. We’ll be using a GlobalAssemblyInfo file to share common assembly attributes across projects in the same solution as discussed here.

    The source code for this post is available here.

    Step 1: Add a Version Project

    Although I didn’t do this for the native solution, I find adding a specific Version project to the solution is beneficial. The reason is that we are generating common version info to be used across the entire solution, however the first project in the build order needs to be the one to generate this info. Depending on your project dependencies there may not be an obvious or good place to do this. By adding a Version project and making all other projects depend on it we have an easy place to generate the necessary files.

    1. Add a project named Version to your Solution
    2. Add a reference to Version from all the other projects in your Solution

    Step 2: Add a GlobalAssemblyInfo Template

    Rather than generate the entire contents of the GlobalAssemblyInfo.cs file in the script, where it’s harder to find and edit when needed, we’ll use a template. Create a file named GlobalAssemblyInfo.cs.tmpl in the Properties folder of the Version project. Copy the code below into the template and make any desired customizations to match your environment.

    Notice the $REVISION$, $CHANGESET$ and $LOCAL_MODIFICATIONS$ tokens. We’ll use these to place the necessary Mercurial information in the file we generate.

    // This file contains common AssemblyVersion data to be shared across all projects in this solution.
    using System.Reflection;
    
    [assembly: AssemblyCompany("Zach Burlingame")]
    [assembly: AssemblyProduct("DotNetHgAutoVersion")]
    [assembly: AssemblyCopyright("Copyright © Zach Burlingame 2011")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
    
    #if DEBUG
    [assembly: AssemblyConfiguration("Debug")]
    #else
    [assembly: AssemblyConfiguration("Release")]
    #endif
    
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version
    //      Revision
    //      Build
    [assembly: AssemblyVersion("1.0.0.$REVISION$")]
    [assembly: AssemblyTitle("$CHANGESET$$LOCAL_MODIFICATIONS$")]   // This is visible as the "File Description" on the Details tab of the Explorer pane
    

    Step 3: Using WSH to Generate Global Assembly

    Next we need to add a file to our Version project named hg_version.jse. Personally, I like to add this file under the Properties filter of my project. Copy the code below in to the script file. This code does two things:

    1. Creates GlobalAssemblyInfo.cs
    2. Extracts the desired Mercurial version info from the working copy and places it in the header

    The extracted version info includes the full node identity, the revision number and if there are any local modifications to the working copy. Note that in counter-intuitive fashion, the AssemblyTitle attribute is what sets the Description field when viewed from the Details tab of the Properties pane in Explorer. Meanwhile the AssemblyDescription field isn’t displayed at all and rather is only accessible through API calls against the binary. Why MS did this, I do not know…

    var fso   = new ActiveXObject("Scripting.FileSystemObject");
    var shell = new ActiveXObject("WScript.Shell");
    var ForReading = 1, ForWriting = 2, ForAppending = 8;
    
    var projectDir = "../../";
    
    var hgRevNum               = shell.Exec("hg identify --num");
    var rev                    = hgRevNum.StdOut.ReadAll();
    var hg_revision            = String(rev).replace(/\n/g,"").replace(/\+/g,"");
    var hg_local_modifications = '';
    
    if( String(rev).replace(/\n/g, "").indexOf("+") != -1 )
    {
       hg_local_modifications = '+';
    }
    
    var hgChangeset  = shell.Exec("hg parents --template \"{node}\"");
    var changeset    = hgChangeset.StdOut.ReadAll();
    var hg_changeset = String(changeset).replace(/\n/g,"");
    
    var hgChangesetShort    = shell.Exec("hg parents --template \"{node|short}\"");
    var changeset_short     = hgChangesetShort.StdOut.ReadAll();
    var hg_changeset_short  = String(changeset_short).replace(/\n/g,"");
    
    var tmplFile = fso.OpenTextFile( projectDir + 'Properties/GlobalAssemblyInfo.cs.tmpl', ForReading, false );
    var strContents = tmplFile.ReadAll();
    tmplFile.Close();
    
    strContents = String(strContents).replace(/\$REVISION\$/g, hg_revision );
    strContents = String(strContents).replace(/\$LOCAL_MODIFICATIONS\$/g, hg_local_modifications );
    strContents = String(strContents).replace(/\$CHANGESET\$/g, hg_changeset );
    strContents = String(strContents).replace(/\$SHORT_CHANGESET\$/g, hg_changeset_short )
    
    var asmFile = fso.CreateTextFile( projectDir + '../GlobalAssemblyInfo.cs', ForWriting, true );
    asmFile.Write( strContents );
    asmFile.Close();
    

    Step 4: Add a Pre-build Event

    Add a pre-build event to the Version project to call the hg_version.jse script and generate GlobalAssemblyInfo.cs.

    1. Right-click on the Version project
    2. Select Properties
    3. Select the Build Events tab
    4. In the Pre-build event command line box, enter:
      cscript.exe “$(ProjectDir)\Properties\hg_version.jse”

    Step 5: Add a link to the GlobalAssemblyInfo.cs file in each project

    1. In Visual Studio, right-click on a project
    2. Select Add->Existing Item
    3. Browse to GlobalAssemblyInfo.cs
    4. Select the file
    5. Click the drop-down arrow next to Add and select Add As Link
    6. Move the link to your Properties folder (optional, but keeps things neat)

    Step 6: Update the AssemblyInfo.cs for each project

    In order to avoid duplicate annotations for assembly information, you must remove entries from the AssemblyInfo.cs file that appear in the GlobalAssemblyInfo.cs file. In our example here, this is what we end up with:

    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    // General Information about an assembly is controlled through the following
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyDescription("")]  // This is not visible on the Details tab of the Explorer pane
    
    // Setting ComVisible to false makes the types in this assembly not visible
    // to COM components.  If you need to access a type in this assembly from
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
    
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("dad09178-814d-43f4-b76d-0fbe29a32544")]
    

    Conclusion

    And there you have it! Now when you build your solution, all your project assemblies will have the latest Mercurial version information included in their meta-data automatically.

    Other Posts in this Series

    1. Mapping Binaries in the Field to Source Code in the Repository
    2. Versioning a Native C/C++ Binary with Visual Studio
    3. Versioning a .NET Assembly with Visual Studio
    4. Integrating the Mercurial Revision into the Version Automatically with Native C/C++
    5. Integrating the Mercurial Revision into the Version Automatically with .NET
    6. Integrating the Subversion Revision into the Version Automatically with Native C/C++
    7. Integrating the Subversion Revision into the Version Automatically with .NET
  • Versioning a .NET Assembly with Visual Studio

    We’re already discussed the value of including version information in your binaries. Last post we talked about Versioning a Native C/C++ Binary with Visual Studio. This time we are going to talk about how to do this with a .NET assembly with Visual Studio. I’ll be using C# and Visual Studio 2010 but the principals work in VB .NET and other versions of Visual Studio as well.

    First of all, credit where credit is due. Many of the details below are based on this stackoverflow question.

    The source code for this post is available here.

    Step 1: It’s Already Done!

    Whenever you create a new .NET project in Visual Studio a file named AssemblyInfo.cs is created for you under the Properties folder. The annotations in the AssemblyInfo.cs file define the version characteristics include those found in the file properties. By default you’ll get a file like this which sets the copyright to Microsoft, the version to a fixed 1.0.0.0 (more on that in a moment) and a title that matches the project name.

    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    // General Information about an assembly is controlled through the following
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle("ClassLibrary1")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyCompany("Microsoft")]
    [assembly: AssemblyProduct("ClassLibrary1")]
    [assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
    
    // Setting ComVisible to false makes the types in this assembly not visible
    // to COM components.  If you need to access a type in this assembly from
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
    
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("6e0a5113-8d8a-4177-8de4-1c18b5b2c446")]
    
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version
    //      Build Number
    //      Revision
    //
    // You can specify all the values or you can default the Build and Revision Numbers
    // by using the '*' as shown below:
    // [assembly: AssemblyVersion("1.0.*")]
    [assembly: AssemblyVersion("1.0.0.0")]
    [assembly: AssemblyFileVersion("1.0.0.0")]
    

    You can make changes directly to this file however after you have several projects in the same solution, it can get rather tedious to manually update this duplicative information across all your assemblies.

    Step 2: Add a Solution-wide GlobalAssemblyInfo File for Common Info

    To make life a little simpler moving forward and in keeping with that whole DRY thing you can use a single file to hold all the assembly information that is common across multiple projects. I generally organize my source tree such that there is a top-level Code directory, with separate sub-directories for each project. Therefore I find it most convenient to place a file named GlobalAssemblyInfo.cs in the Code directory, peer to all my project directories. You can place the file wherever you want, however.

    Place common version info inside such as:

    
    // This file contains common AssemblyVersion data to be shared across all projects in this solution.
    using System.Reflection;
    
    [assembly: AssemblyCompany("Zach Burlingame")]
    [assembly: AssemblyProduct("DotNetVersionAssembly")]
    [assembly: AssemblyCopyright("Copyright © Zach Burlingame 2011")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
    
    #if DEBUG
    [assembly: AssemblyConfiguration("Debug")]
    #else
    [assembly: AssemblyConfiguration("Release")]
    #endif
    
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version
    //      Revision
    //      Build
    //
    // You can specify all the values or you can default the Revision and Build Numbers by using the '*' as shown below:
    // [assembly: AssemblyVersion("1.0.*")]
    [assembly: AssemblyVersion("1.0.0.*")]
    //[assembly: AssemblyFileVersion("1.0.0.*")]
    

    Step 3: Add a link to the GlobalAssemblyInfo.cs file in each project

    1. In Visual Studio, right-click on a project
    2. Select Add->Existing Item
    3. Browse to GlobalAssemblyInfo.cs
    4. Select the file
    5. Click the drop-down arrow next to Add and select Add As Link
    6. Move the link to your Properties folder (optional, but keeps things neat)

    Step 4: Update the AssemblyInfo.cs for each project

    In order to avoid duplicate annotations for assembly information, you must remove entries from the AssemblyInfo.cs file that appear in the GlobalAssemblyInfo.cs file. In our example here, this is what we end up with:

    
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    // General Information about an assembly is controlled through the following
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle("DotNetVersionAssembly")]
    [assembly: AssemblyDescription("")]
    
    // Setting ComVisible to false makes the types in this assembly not visible
    // to COM components.  If you need to access a type in this assembly from
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
    
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("dad09178-814d-43f4-b76d-0fbe29a32544")]
    

    Step 5: Auto-incrementing the build number

    You might of noticed a couple of other changes I made in Step 3:

    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version
    //      Revision
    //      Build
    //
    // You can specify all the values or you can default the Revision and Build Numbers by using the '*' as shown below:
    // [assembly: AssemblyVersion("1.0.*")]
    [assembly: AssemblyVersion("1.0.0.*")]
    //[assembly: AssemblyFileVersion("1.0.0.*")]
    

    By replacing build value with an asterisk, Visual Studio (and more specifically, MSBuild) will auto-increment that number every time you build. If you replace both the revision and the build value with a single asterisk it will cause both the revision and build number to auto-increment. This is helpful since the maximum value of any single field is 65535. By auto-incrementing both fields, the revision field is only incremented when the build value overflows, giving you in effect 4 billion unique version numbers for a given Major.Minor combination. If you overflow that, then for the love of pete increment your Major version already!

    Also notice that I commented out the AssemblyFileVersion. The differences between the AssemblyVersion and AssemblyFileVersion are discussed here. The key in our example is that you can’t use asterisks to auto-increment the AssemblyFileVersion like you can the AssemblyVersion and you probably don’t want the two getting out-of-sync (at least in this versioning scheme). By not providing an AssemblyFileVersion, MSBuild will default to using the value from the AssemblyVersion.

    Results

    Now when you build your application, all the version info above will be defined in the assembly itself. Much of this information is available directly from the Details pane of the Properties window in Explorer.

    Version Info
    Version Info

    Final Thoughts

    Note that while it is possible to auto-increment the build and revision number of the assemblies, those numbers don’t correspond to anything in source control. I’ll be discussing how to make the BUILD number automatically correspond to the revision info from the working copy of the source code using Mercurial or Subversion in an upcoming post.

    Other Posts in this Series

    1. Mapping Binaries in the Field to Source Code in the Repository
    2. Versioning a Native C/C++ Binary with Visual Studio
    3. Versioning a .NET Assembly with Visual Studio
    4. Integrating the Mercurial Revision into the Version Automatically with Native C/C++
    5. Integrating the Mercurial Revision into the Version Automatically with .NET
    6. Integrating the Subversion Revision into the Version Automatically with Native C/C++
    7. Integrating the Subversion Revision into the Version Automatically with .NET