Engineering

Let's Code - GitVersioning: Native Code Support

Tonight I'm doing something completely different. I'm not working on WiX. In fact, I'm not even working on one of my own projects.

Last week, I mentioned that I was considering switching the WiX versioning scheme to one based on git history. I found two projects that tackled versioning via git history. However, neither of them supported native code (think .vcxproj). That means it’s time to roll up our sleeves and enhance one of these projects. Sound good? Yeah. Let’s code!

Picking the project

As noted above, when doing my research, I found two solutions to versioning via git history. One of them is called GitVersion. This is the project I looked at first. It is a very nice looking project with lots of documentation and several interesting integrations. However, the more I dug into the documentation the more concerned I got.

I want every build to create a unique version number. At relatively rare times, I will update the major or minor version and the build number can start over again. That’s it. Very simple requirements.

Maybe I can get that with GitVersion but after reading the documentation for a while I didn’t immediately see what set of configuration was going to give that. I especially didn’t want to be concerned about what the version would be based on the branch the code was committed to.

Fortunately, I came across NerdBank.GitVersioning (or NB.GV or as I often call it “GitVersioning”). The documentation for the project isn’t laid out as cleanly but the stated goals of the project immediately caught my attention:

  1. Prioritize absolute build reproducibility. Every single commit can be built and produce a unique version.
  2. No dependency on tags. Tags can be added to existing commits at any time. Clones may not fetch tags. No dependency on tags means better build reproducibility.
  3. No dependency on branch names. Branches come and go, and a commit may belong to any number of branches. Regardless of the branch HEAD may be attached to, the build should be identical.
  4. The computed version information is based on an author-defined major.minor version and an optional unstable tag, plus a shortened git commit ID.

Sound familiar? Yep. That’s exactly what I was looking for and articulated better than I do. So, does it support native code? Nope. But issue #94 says they wish it did. And since no one has made progress on the issue since December I guess I’ll do it.

Clone

Normally, I’d be creating a branch in one of my existing repositories. But this is a new project for me. So, I click “Fork” in the GitHub UI and clone my copy of GitVersioning.

git clone https://github.com/robmen/Nerdbank.GitVersioning.git GitVersioning
git checkout -b 94-nativebinaries

I’m primarily focused on MSBuild integration so I start with the .targets file: src\Nerdbank.GitVersioning.NuGet\build\Nerdbank.GitVersioning.targets. It’s pretty simple.

The GetBuildVersion target calculates all the version information (aka: magic) and the results are stored in a bunch of properties. The GenerateAssemblyVersionInfo target does the work to write the assembly information into a file and include that file in the compilation.

Either I teach the Nerdbank.GitVersioning.Tasks.AssemblyVersionInfo task called by the GenerateAssemblyVersionInfo target to write out .rc files. Or I create a new task and target. After digging through the AssemblyVersionInfo code, I decide the AssemblyVersionInfo code was complex enough dealing with C# and VB. Adding something completely different would be too much. I’ll create the new target and task.

So, let’s get to work.

Commit

I copy the GenerateAssemblyVersionInfo target and name it GenerateNativeVersionInfo. I’m so creative sometimes. Then I moved the VersionSourceFile property into the GenerateAssemblyVersionInfo target and create a copy of it in my new GenerateNativeVersionInfo target but change the extension from $(DefaultLanguageSourceExtension) to .rc. I didn’t know $(DefaultLanguageSourceExtension) existed (kinda’ cool). Unfortunately, it won’t work for our case because it is “.cpp” and we’re generating a .rc. It doesn’t technically matter but it would be pretty confusing to see a .cpp file being compiled by rc.exe.

Now to write the task that does the actual work. Continuing along with my original creative streak I copy the AssemblyVersionInfo.cs file and name it NativeVersionInfo.cs. Then I gut all the code that processes .cs files and start adding functionality to create a VERSIONINFO resource.

One of the nice things the AssemblyVersionInfo tasks does is inject a static class with the all version information into your assembly. This provided direct access to the version information so your C# code could display the version information or what not.

In the native code I wanted to do the same by putting all the version information in #define statements. Thus I added a standard VERSIONINFO resource that looked like the following:

VS_VERSION_INFO VERSIONINFO
  FILEVERSION     NBGV_FILE_MAJOR_VERSION,NBGV_FILE_MINOR_VERSION,NBGV_FILE_BUILD_VERSION,NBGV_FILE_REVISION_VERSION
  PRODUCTVERSION  NBGV_PRODUCT_MAJOR_VERSION,NBGV_PRODUCT_MINOR_VERSION,NBGV_PRODUCT_BUILD_VERSION,NBGV_PRODUCT_REVISION_VERSION
  FILEFLAGSMASK   0x3FL
#ifdef _DEBUG
  FILEFLAGS       0x1L
#else
  FILEFLAGS       0x0L
#endif
  FILEOS          0x4L
  FILETYPE        NBGV_FILE_TYPE
  FILESUBTYPE     0x0L
BEGIN
  BLOCK ""StringFileInfo""
  BEGIN
    BLOCK NBGV_VERSION_BLOCK
    BEGIN
      VALUE ""CompanyName"", NGBV_COMPANY
      VALUE ""FileDescription"", NGBV_TITLE
      VALUE ""FileVersion"", NBGV_FILE_VERSION
      VALUE ""InternalName"", NGBV_INTERNAL_NAME
      VALUE ""OriginalFilename"", NGBV_FILE_NAME
      VALUE ""ProductName"", NGBV_PRODUCT
      VALUE ""ProductVersion"", NBGV_INFORMATIONAL_VERSION
      VALUE ""LegalCopyright"", NBGV_COPYRIGHT
    END
  END

  BLOCK ""VarFileInfo""
  BEGIN
    VALUE ""Translation"", NBGV_LCID, NBGV_CODEPAGE
  END
END

All those NBGV_ values are #defines. My new NativeVersionInfo task writes out the #defines and that standard VERSIONINFO resource to the output .rc file.

I won’t actually finish the scenario where the #defines are included in the final built binary. I’m thinking in the future if I add the $(IntermediateOutputPath) to the C++ compilers INCLUDES then you could do:

#include "MyProject.Version.rc"

And all the NBVG_ #define variables would be available. Or something like that.

It looks easy now that we’re done but I spent a lot of time wandering through all the existing code trying to understand what the best path through. I also spent a lot of time futzing with the VERSIONINFO resource until I finally got that to look good.

Anyway, this is enough for tonight.

git add src\Nerdbank.GitVersioning.NuGet\build\Nerdbank.GitVersioning.targets
git add src\Nerdbank.GitVersioning.Tasks\NativeVersionInfo.cs
git commit

    > Support GitVersioning in .vcxprojs
    >
    > Generate a .rc file and include it into the build pipeline for native
    > code in a fashion similar to the `GenerateAssemblyVersionInfo` task.
    >
    > Fixes #94

git push

Then back to my fork on GitHub and push the button to send a pull request.

Pull request

Now we wait for someone to accept my PR.

Until next time! Keep coding. You know I am.