Monday, January 20, 2014

Embedding GIT Commit Hash to Assembly Version

Have you ever tried to understand looking at your DLL what GIT branch it corresponds to? Have you been thinking if your commit is already in this DLL or not?

To easily find a GIT commit which your binary assembly relates to, it would be very handy to have the GIT commit hash in the assembly info. And having something like "1.0.0.0-e7f45a6" instead of just "1.0.0.0" in you file properties:



There are already several solutions to do that automatically (you were not planning to maintain it manually, right?) in the Internet, but they have some drawbacks. Automatic update of AssemblyInfo.cs with every build will create a real nightmare in commits log. Using GIT hooks is not bad but you need to have hooks on every developer's machine. So ideally, we need something that would dynamically modify assembly version on each build.

This solution appends commit hash to your assembly's version and puts it all together to AssemblyInformationalVersion attribute, so in your assembly file properties you would have something like "Product version: 1.0.0.0-e7f45a6".

Usage

1. Add MSBuildTasks using Nuget to your project(s). Anything from the version 1.4.0.65 would work.
2. Create file GitAssemblyVersion.targets in $(SolutionDir)\.build folder with content:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MSBuildCommunityTasksPath Condition=" '$(MSBuildCommunityTasksPath)' == '' ">$(SolutionDir)\.build</MSBuildCommunityTasksPath>
    <AssemblyInfoFile Condition=" '$(AssemblyInfoFile)' == '' ">$(MsBuildProjectDirectory)\Properties\AssemblyInfo.cs</AssemblyInfoFile>
    <GeneratedAssemblyInfoFile Condition=" '$(GeneratedAssemblyInfoFile)' == '' ">$(MsBuildProjectDirectory)\Properties\GeneratedAssemblyInfo.cs</GeneratedAssemblyInfoFile>
    <BuildDependsOn>
      ReadAssemblyVersion;
      SetAssemblyVersion;
      $(BuildDependsOn)
    </BuildDependsOn>
    <CleanDependsOn>
      $(CleanDependsOn);
      SetAssemblyVersionClean
    </CleanDependsOn>
  </PropertyGroup>
  
  <Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.targets" />
  
  <Target Name="ReadAssemblyVersion">
    <ReadLinesFromFile File="$(AssemblyInfoFile)">
      <Output TaskParameter="Lines" ItemName="ItemsFromFile"/>
    </ReadLinesFromFile>
    <PropertyGroup>
      <Pattern>;\s*\[assembly\s*:\s*AssemblyVersion.*?"(.*?)"</Pattern>
      <In>@(ItemsFromFile)</In>
      <AssemblyVersion>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)).Groups.get_Item(1))</AssemblyVersion>
    </PropertyGroup>
  </Target>
  <Target Name="SetAssemblyVersion">
    <ItemGroup>
      <Compile Include="$(GeneratedAssemblyInfoFile)" />
    </ItemGroup>
    <GitVersion LocalPath="$(SolutionDir)">
      <Output TaskParameter="CommitHash" PropertyName="CommitHash" />
    </GitVersion>
    <AssemblyInfo CodeLanguage="CS" OutputFile="$(GeneratedAssemblyInfoFile)" AssemblyInformationalVersion="$(AssemblyVersion)-$(CommitHash)" />
  </Target>
  <Target Name="SetAssemblyVersionClean" Condition="Exists($(GeneratedAssemblyInfoFile))">
    <Delete Files="$(GeneratedAssemblyInfoFile)" />
  </Target>
</Project>

3. Add this line somewhere to the end of you .csproj file(s):
<Import Project="$(SolutionDir)\.build\GitAssemblyVersion.targets" />
That's it!

How It Works

It dynamically creates GeneratedAssemblyInfo.cs file, extracts version from AssemblyVersion attribute, and adds current GIT commit hash before each build.