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:
- Prioritize absolute build reproducibility. Every single commit can be built and produce a unique version.
- 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.
- 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.
- 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.
Now we wait for someone to accept my PR.
Until next time! Keep coding. You know I am.