Embedding mercurial revision information in Visual Studio c# projects automatically

Solution 1:

I've just released a small open-source MSBuild task to do exactly what you need:

  • It puts your Mercurial revision number into your .NET assembly version
  • You can tell from the version if an assembly has been compiled with uncommitted changes
  • Does not cause unnecessary builds if the revision hasn't changed
  • Not dependent on Windows scripting
  • Nothing to install - you just add a small DLL to your solution, and edit some files in your project

http://versioning.codeplex.com

Solution 2:

I think I have an answer for you. This will be a bit involved, but it gets you away from having to do any batch files. You can rely on MSBuild and Custom Tasks to do this for you. I've used the extension pack for MSBuild (Available at CodePlex) - but the second task you need is something you could just as easily write yourself.

With this solution, you can right click on the DLL and see in the file properties which Mercurial Version the DLL (or EXE) came from.

Here are the steps:

  1. Get the MBBuildExtension Pack OR Write Custom Task to overwrite AssemblyInfo.cs
  2. Create a Custom Build Task in its own project to get the Mercurial Id(code below).
  3. Edit project files that need the Mercurial Id to use Custom Task (code below).

Custom Task to Get mercurial id: (This would need to be tested well and perhaps better generalized...)

using System;
using System.Diagnostics;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;


namespace BuildTasks
{
    public class GetMercurialVersionNumber : Task
    {
        public override bool Execute()
        {
            bool bSuccess = true;
            try
            {
                GetMercurialVersion();
                Log.LogMessage(MessageImportance.High, "Build's Mercurial Id is {0}", MercurialId);
            }
            catch (Exception ex)
            {
                Log.LogMessage(MessageImportance.High, "Could not retrieve or convert Mercurial Id. {0}\n{1}", ex.Message, ex.StackTrace);
                Log.LogErrorFromException(ex);
                bSuccess = false;
            }
            return bSuccess;
        }

        [Output]
        public string MercurialId { get; set; }

        [Required]
        public string DirectoryPath { get; set; }

        private void GetMercurialVersion()
        {
            Process p = new Process();
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WorkingDirectory = DirectoryPath;
            p.StartInfo.FileName = "hg";
            p.StartInfo.Arguments = "id";
            p.Start();

            string output = p.StandardOutput.ReadToEnd().Trim();
            Log.LogMessage(MessageImportance.Normal, "Standard Output: " + output);

            string error = p.StandardError.ReadToEnd().Trim();
            Log.LogMessage(MessageImportance.Normal, "Standard Error: " + error);

            p.WaitForExit();

            Log.LogMessage(MessageImportance.Normal, "Retrieving Mercurial Version Number");
            Log.LogMessage(MessageImportance.Normal, output);

            Log.LogMessage(MessageImportance.Normal, "DirectoryPath is {0}", DirectoryPath);
            MercurialId = output;

        }
    }

And the modified Project File: (The comments may help)

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!--this is the import tag for the MSBuild Extension pack. See their documentation for installation instructions.-->
  <Import Project="C:\Program Files (x86)\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks" />
  <!--Below is the required UsingTask tag that brings in our custom task.-->
  <UsingTask TaskName="BuildTasks.GetMercurialVersionNumber" 
             AssemblyFile="C:\Users\mpld81\Documents\Visual Studio 2008\Projects\LambaCrashCourseProject\BuildTasks\bin\Debug\BuildTasks.dll" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>9.0.30729</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{D4BA6C24-EA27-474A-8444-4869D33C22A9}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>LibraryUnderHg</RootNamespace>
    <AssemblyName>LibraryUnderHg</AssemblyName>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Xml.Linq">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Data.DataSetExtensions">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Class1.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

  <Target Name="Build" DependsOnTargets="BeforeBuild">
    <!--This Item group is a list of configuration files to affect with the change. In this case, just this project's.-->
    <ItemGroup>
      <AssemblyInfoFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs" />
    </ItemGroup>
    <!--Need the extension pack to do this. I've put the Mercurial Id in the Product Name Attribute on the Assembly.-->
    <MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyInfoFiles="@(AssemblyInfoFiles)"
                                                  AssemblyProduct="Hg: $(MercurialId)"
                                                  />
    <!--This is here as an example of messaging you can use to debug while you are setting yours up.-->
    <Message Text="In Default Target, File Path is: @(AssemblyInfoFiles)" Importance="normal" />
  </Target>  

  <Target Name="BeforeBuild">
    <!--This is the custom build task. The Required Property in the task is set using the property name (DirectoryPath)-->
    <BuildTasks.GetMercurialVersionNumber DirectoryPath="$(MSBuildProjectDirectory)">
      <!--This captures the output by reading the task's MercurialId Property and assigning it to a local
          MSBuild Property called MercurialId - this is reference in the Build Target above.-->
      <Output TaskParameter="MercurialId" PropertyName="MercurialId" />
    </BuildTasks.GetMercurialVersionNumber>
  </Target>
  <!--<Target Name="AfterBuild">
  </Target>-->

</Project>

Last Note: The build tasks project only needs to be built once. Don't try to build it every time you do the rest of your solution. If you do, you will find that VS2008 has the dll locked. Haven't figured that one out yet, but I think the better thing to do is build the dll as you want it, then distribute ONLY the dll with your code, ensuring that the dll's location is fixed relative to every project you need to use it in. That way, no one has to install anything.

Good luck, and I hope this helps!

Audie

Solution 3:

Have you considered using a string resource instead of a C# language string constant? String resources can be edited/replaced in the output binaries post-build using tools intended for localization.

You would emit your mercurial version number to a text file that is not used by the C# build, then using a post-build operation replace the version resource with the actual value from the emitted text file. If you strong-name sign your assemblies, the resource string replacement would need to happen before the signing.

This is how we handled this issue at Borland years ago for Windows products. The world has become more complicated since then, but the principle still applies.

Solution 4:

Im my .hgrc I have this:

[extensions]                                                                                                                                                               
hgext.keyword =                                                                                                                                                            
hgext.hgk =                                                                                                                                                                

[keyword]                                                                                                                                                                  
** =                                                                                                                                                                       

[keywordmaps]                                                                                                                                                              
Id = {file|basename},v {node|short} {date|utcdate} {author|user} 

And in my source file (Java) I do:

public static final String _rev = "$Id$";

After commit $Id$ gets expanded into something like:

public static final String _rev = "$Id: Communication.java,v 96b741d87d07 2010/01/25 10:25:30 mig $";