Nerdbank.gitversioning: How to determine git-based version when the build directory has no .git directory?

Created on 13 Dec 2019  路  24Comments  路  Source: dotnet/Nerdbank.GitVersioning

We have the need to "sometimes" override the calculated version from Nerdbank. This happens when are building a docker image in an on premise build machine. Essentially, when building the image, the files are downloaded to the build agent (where git exists) and then copied into the image where they are built. However, because the files are copied to the image and then built there, the .git directory doesn't exist and, as expected, the versioning doesn't work. We need a way to override (for example: pass in the version to use with /p:Version=1.2.3) the version that would be calculated. Since also produce binaries that are used outside of the docker image, removing the package is not an option. Is there a way to override this? I even tried setting GenerateAssemblyVersionInfo to false, but that doesn't work either.

question

Most helpful comment

I've tried to override the GetBuildVersion and it worked. So before building a docker image you should get VersionOracle using of powershell core (because it is shipped with sdk and it is crossplatform) and generate props file. Then add Directory.Build.targets with the overriding of GetBuildVersion target.
I'm going to make blogpost and maybe will make PR with docs about it on weekend.

All 24 comments

There certainly _is_ a way to override the versions calculated by NB.GV. Just schedule a target to run after GetBuildVersion and override the properties it sets.

That said, the docker scenario came up on @csharpfritz's twitch stream a few days ago and I suggested that NB.GV does already have a feature that probably would help this docker scenario work. Specifically these two properties can be set:

https://github.com/AArnott/Nerdbank.GitVersioning/blob/1908e7d6afea8c0fc6d180d19a9028cd279da171/src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets#L75-L76

You'd have to 'mount' the git repo into the docker container. You do not have to build from that repo, but if you know the path to it while you're building in some other directory, then NB.GV can be redirected to scan git history using that other path rather than the project's own directory.

I was thinking of exploring this possibility on an upcoming twitch stream myself. If I do, I'll drop a link here.

@lkruger If you find something that works, please consider sharing here or as a doc pull request.

@AArnott afaik you can't mount volume during docker build, so your approach doesn't fit here.
I'm thinking of adding the ability to disable the entire GetBuildVersion.

Are you building a container or building within a container. For the former, I agree mounting doesn't work but NB.GV doesn't apply anyway. For the latter, you can definitely mount a volume because you're just running a container.

@AArnott Why doesn't NB.GV apply when building a container?

I want to build a container that contains my app. My Dockerfile is the one from the Visual Studio templates (just slightly modified paths etc). I'd like my app's assembly to be versioned correctly for logging etc..

  • Is there a way to achieve correct versioning without copying version.json and .git?
  • Given a CI build scenario where nbgv cloud ran before and the container is built in a later Azure Pipelines step, is there a way to just use the env variables created by nbgv cloud? I thought of passing them to the build but I'm not sure if that's possible (without nbgv in the container just overwriting them)?

It'd be really great if you could point me in the right direction and thanks for creating nbgv in the first place of course!

@georg-jung it does apply to docker containers. But most likely your .git folder is ignored by the .dockerignore file while building the container. Nbgv needs the .git folder to calculate the version.

Why doesn't NB.GV apply when building a container?

@georg-jung First off, we don't build containers. We build images. Containers are instances of images. And a dockerfile image is only known by its has and an optional tag. It isn't _versioned_, per se. Or at least, I've never seen one. Is it really the image you want to version? If not, then that's why I say that it doesn't apply.

But versioning is applicable to building a project (whether or not the project build happens in a container). If you're building inside a container, then your source code is presumably accessible within that container. NB.GV (and in fact nearly all software) doesn't even know it's in a container, so you can freely use NB.GV within a container and get a version calculated during your build.
Your source code might be within your container because some step (possibly in your Dockerfile) copied your source code in. Or it could be in your container because you mounted your repo as a volume into your container. If you mounted your entire volume, the .git folder should be present, which is necessary to calculate git history based versions.

But what often happens I think is what @Franklin89 said: your container doesn't actually have your entire repo mounted or copied inside it. Instead it's just a subset of your repo that may be missing version.json or (more likely) your .git folder. For the build within the container to calculate a version, you must have access to the .git folder, even if it's not strictly above the project directory being built on account of special volume mounting or copying you might do.
The GitRepoRoot property would need to be set to the actual root of the real repo (with whatever path applies in the context of the build). Then the ProjectPathRelativeToGitRepoRoot property must be set to the path of the project directory under that git repo, relative to the GitRepoRoot.

By far, I think the easiest way to build and version your software would be either build outside a container and then copy the binaries in (in case you want your app to run inside a container), or clone the repo within the container itself and build (in case you want to build your app inside a container).

Many thanks for the detailed explanation!

I am "kind of versioning" the image itself in the sense of tagging it with the version NB.GV caluclates. But that already works like I want it to, so that's not what I intended my question to be about.

What I was thinking about is versioning the .Net assemblies that are built as part of a multi-stage build inside a container and run inside another container, that's based on my "kind of versioned" image mentioned before. Now, that does work currently too and I'm already copying my complete repo incl. the .git folder during an earlier stage of my multi stage build. What I thought of was a way to avoid copying the .git folder/the unnecessary parts of my repo at all.

My build pipelines looks like this:

  1. Install and run nbgv cloud
  2. Restore, build & test project
  3. If we release from this branch, containerize:
    a. Restore and build again as part of a multistage build process
    b. Push the slim final image to my private registry

My consideration was whether it would not be a good idea and maybe even easy to implement, that just (1) does the version number calculation and the dotnet build NB.GV instances in (2) and (3a) could see there's an (or multiple) env variable already set and just use this value as if it was calculated by them.

While for me copying the .git folder works perfectly fine in pratice and I'm considering this more because I think it's interesting, I guess there are deployment scenarios where copying or even mounting the repo around isn't an option. That's why I thought this "take these env vars as your source of truth instead of .git" option could exist and I just didn't find it.

The version stamp on the assembly, and the version info that goes into the ThisAssembly class is indeed simply taken from MSBuild properties. So if you were to arrange to set those properties (possibly by env vars) and suppress the run of the GetBuildVersion target (possibly by defining your own empty one to override the one from this package) then you could do it today.

If there are just a couple small, tactful changes to this repo that would make your work easier, I'd be willing to consider a PR. I don't have capacity right now to invent such a change myself, particularly given its very small group of users.

Thanks for the advice. It's not really a priority to me too, I merely thought it might already be possible and I'm just missing the option. If I find some time I'll look into how this could be done and might create a PR if I get the feeling my understanding of the code base is well enough to contribute code in a sensible manner.

Thanks. If you don't need it, I suggest we leave it alone. No reason to write and maintain a feature with no customer. But I appreciate your willingness.

I've tried to override the GetBuildVersion and it worked. So before building a docker image you should get VersionOracle using of powershell core (because it is shipped with sdk and it is crossplatform) and generate props file. Then add Directory.Build.targets with the overriding of GetBuildVersion target.
I'm going to make blogpost and maybe will make PR with docs about it on weekend.

Alternatively to using VersionOracle directly, you can use nbgv get-version -f json and parse that in powershell. We could even add a -f props switch that would produce the .props file to be imported.
Just ideas.

I might want this in the near-ish future too. Not for docker, but because when building thousands of projects in a very large repo with a long history, GetBuildVersion can take a significant part of the build time (#31), so running it just once at the start of the build and applying it to the rest of the projects, while a bit hacky perhaps, may be worth it for very large repos.

Share a link to your blog post here, will ya @vchirikov ?

I have successfully mounted the .git folder in my Docker image:
ADD ./.git /.git

With this addition I get a version number with four parts, which I didn't get before (without ADD above I only get 1.0 from version.json file), but it doesn't seem to be a correct version number. When I run locally I get "1.0.211.7973" and I can verify this version number with nbgv get-commits 1.0.211.7973 and get the correct commit. When building the same commit with Docker in our CI server I get version number "1.0.0.7973", so the version part is only 0, but should have been 211. I don't understand why and I've got no idea how to troubleshoot. Any suggestions?
Thanks!

That's pretty strange, @equist. I wonder if an environment variable is throwing it off. What does nbgv get-version tell you when run in the docker container?

Make sure you run that tool within the project directory, or use the -p switch, if your version.json file is or ever was not in the repo root.

get-version gets the correct version:

Step 23/32 : RUN nbgv get-version
---> Running in ab267d37179c
Version: 1.0.219.51740
AssemblyVersion: 1.0.0.0
AssemblyInformationalVersion: 1.0.219+1ccaf7db9a
NuGetPackageVersion: 1.0.219-g1ccaf7db9a
NpmPackageVersion: 1.0.219-g1ccaf7db9a

The build result gets version number 1.0.0.51740.

The directory structure looks like this:
ROOT
Dockerfile
-- .git
-- src
-- src -- ProjektDir
-- src -- ProjektDir -- ProjectFile.csproj
-- src -- ProjektDir -- version.json

Relevant parts from Dockerfile, that runs the nbgv and dotnet build in the same working directory:

WORKDIR "/src/ProjectDir"
RUN dotnet tool install -g nbgv
RUN nbgv get-version
RUN dotnet build "ProjectFIle.csproj" -c Release -o /app/build

Anything else I can do to troubleshoot?

I found that an update for the package existed and after updating I got the correct version number. So sorry for wasting your time and big thanks for the help!

Share a link to your blog post here, will ya @vchirikov ?

@AArnott
I've finished writing. Sorry for being late. Blogpost

Example repo: https://github.com/vchirikov/nerdbankgitversioning-docker

p.s. blog is running on Blazor wasm, but if you have any problems with it you can see the original markdown file in repo.

Thanks for sharing, @vchirikov. I'm curious whether you needed to go to the trouble of finding the Nerdbank.GitVersioning and loading the library and libgit2... Wouldn't have installing and using the nbgv CLI tool been much easier? You can use that to produce a JSON file which powershell can trivially parse into an object with all the same properties that are on VersionOracle.

Hmm, I think you're right, I just haven't seen the nbgv 馃ぃ
Btw how to make json with nbgv ? I only see the nbgv get-version command

nbgv get-version -f json

We could even add a -f props switch that would produce the .props file to be imported.

How do you feel about a concept of a "cache" file like version.cache.json or similar (version.override.json) that would also be understood by the MSBuild tasks/targets - so that nbgv get-version -o json > version.cache.json or even nbgv cloud could be used to create this file and a build inside a container without the .git folder would build with that version and/or normal builds could potentially skip git lookups.

@dasMulli We may go into cache file territory. This is discussed over at #114.

Was this page helpful?
0 / 5 - 0 ratings