Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set git commit hash as dll version number in visual studio

I'm developing a project using visual studio 2013 and git.

I must distribute some libraries of the project so I'd like to set their version number with the current git commit hash, so I can be sure of which library version they are using.

Is there a way to put the hash as version number in automated way, I.e. with a pre-build event, instead of doing it manually every time?

like image 246
Jepessen Avatar asked Jan 12 '16 08:01

Jepessen


1 Answers

Here are some snippets of a possible implementation for native projects using resource files. The idea is to add a single property sheet to the project, which has a prebuild event which creates a .res file based on the git commit hash and which also adds this .res file as a resource. Here is the property sheet:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup>
    <VersionResourceOut>$(MSBuildProjectDirectory)\version.res</VersionResourceOut>
  </PropertyGroup>
  <ItemGroup>
    <Resource Include="$(VersionResourceOut)" />
  </ItemGroup>
  <Import Project="$(BuildToolsDir)tools\versionrc.targets" />
  <Target Name="CreateGitVersionResource" BeforeTargets="BuildGenerateSources">
    <CallTarget Targets="CreateGitVersionResInBuild" />
    <MakeSameWriteTime SourceFile="$(OutDir)$(TargetName)$(TargetExt)" DestFile="$(VersionResourceOut)"/>
  </Target>
</Project>

The $(BuildToolsDir)tools\versionrc.targets file is where the actual creation of the resource file is done. The complete implementation is rather lengthy because it also works for svn and allows a bunch of customisation - a bit much to post here so I'll just lay out the meat of it:

  • the commit hash is stored in an msbuild property, the command to get it is

    git --work-tree=$(GitVersionDir) --git-dir=$(GitVersionDir)\.git rev-parse --short HEAD
    

    where $(GitVersionDir) usually is set to $(MsbuildProjectDirectory) since we have most .vcxproj files in the source root.

  • I also like the build date to be included so the property which eventually goes into the FileDescription entry of the StringFileinfo block is

    <FileDesc>$(GitVersion) $([System.DateTime]::Now.ToString('HH:mm:ss dd/MM/yyyy'))</FileDesc>
    
  • the actual file/product version, company name and other fields are fetched from elsewhere. Usually we have a common header file defining all VRC_XXX macros needed by the RC file template (see below), and a per-project header file containing e.g. #define VRC_FILEDESC "Project Foo", and those headers are merged using ReadLinesFromFile/WriteLinesToFile tasks. Anyway the idea is to end up with a header file like

    #define VRC_FILEVERSION 4,4,1,0
    #define VRC_PRODUCTVERSION 4.4.1.0
    #define VRC_COMPANYNAME MyCompany
    #define VRC_PRODUCTNAME VRC_COMPANYNAME Libraries
    #define VRC_FILEDESC Project Foo
    #define VRC_FILEDESCRIPTION VRC_FILEDESC VRC_FILEDESCGIT
    

    whos path is stored in a $(VersionMainInclude) property.

  • all of this is fed to rc.exe to create the .res file. The full command is something like

    rc /d VRC_INCLUDE=$(VersionMainInclude)
       /d VRC_ORIGINALFILENAME=$(TargetName)$(TargetExt)
       /d VRC_FILETYPE=$(FileType)
       /d VRC_FILEDESCGIT=$(FileDesc)
       /d VRC_COPYRIGHT=VRC_COMPANYNAME \251 $([System.DateTime]::Now.ToString(`yyyy`))
       /fo $(VersionResourceOut) $(MsBuildThisFileDirectory)version.rc
    
  • Note the MakeSameWriteTime trick to set the modified time of the .res file the same as the output file, to assure the prebuild event doesn't trigger new builds each time it the .res file is generated. There might be better ways to do this, but this one works for me:

    <UsingTask TaskName="MakeSameWriteTime" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
      <ParameterGroup>
        <SourceFile Required="true" ParameterType="System.String"/>
        <DestFile Required="true" ParameterType="System.String"/>
      </ParameterGroup>
      <Task>
        <Code Type="Fragment" Language="cs">
          <![CDATA[
    System.IO.File.SetLastWriteTime( DestFile, System.IO.File.GetLastWriteTime( SourceFile ) );]]>
        </Code>
      </Task>
    </UsingTask>
    

This is the full .rc template used:

#include <winver.h>

#define stringize( x )        stringizei( x )
#define stringizei( x )       #x

#ifdef VRC_INCLUDE
  #include stringize( VRC_INCLUDE )
#endif

#ifdef _WIN32
  LANGUAGE 0x9,0x1
  #pragma code_page( 1252 )
#endif

1 VERSIONINFO
 FILEVERSION    VRC_FILEVERSION
 PRODUCTVERSION VRC_PRODUCTVERSION
 FILEFLAGSMASK  0x1L
 FILEFLAGS      VS_FF_DEBUG
 FILEOS         VOS__WINDOWS32
 FILETYPE       VRC_FILETYPE
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "040904E4"
    BEGIN
      VALUE "CompanyName",      stringize( VRC_COMPANYNAME )
      VALUE "FileDescription",  stringize( VRC_FILEDESCRIPTION )
      VALUE "FileVersion",      stringize( VRC_FILEVERSION )
      VALUE "LegalCopyright",   stringize( VRC_COPYRIGHT )
      VALUE "InternalName",     stringize( VRC_ORIGINALFILENAME )
      VALUE "OriginalFilename", stringize( VRC_ORIGINALFILENAME )
      VALUE "ProductName",      stringize( VRC_PRODUCTNAME )
      VALUE "ProductVersion",   stringize( VRC_PRODUCTVERSION )
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x409, 1200
  END
END
like image 192
stijn Avatar answered Sep 30 '22 16:09

stijn