Drive `version` in `mkDerivation` from Git describe etc

Is it somehow possible to drive the version of a derivation from a an own provided function which migth use echo "${$(git describe --tag --abbrev=0)#v*}+$(git describe --always --abbrev=10)" which outputs something like 0.0.1+a88b148b49 which can be used as version = ... in mkDerivation ?

Is there any tooling possible for this?

I fiddled around with .verison file which is written before nix build ... this works, however the file needs to be checked in which is what I dont want.

Then there is something like fetchgitLocal <path> which make a derivation with the Git folder, but that needs --impure and I could not get it to work…

Remember you are calling a function named mkDerivation that takes a number of arguments - one of which is version. So it’s an input to the builder, not a product of the source.

Typically you know which version you are getting and you therefore pass version to mkDerivation and again to your fetcher because typically those can be referenced by version.

But what exactly is the problem you are trying to solve here? I get that you want, but I don’t understand why.

1 Like

It is possibly via the import from derivation feature. This feature has a lot of gotchas though, so you should rather just pin those values directly.

And IFD is very much discouraged.

You say “its an input to the builder and no the source” so we have semantically two different versions the “build” version and the version of the source which is commonly just a Git ref guarded by a SHA256 hash when doing fetchGit etc. if I understand correctly. Thats ok, but:

I wanted to solve a tooling issue such that I can execute nix build ". #default" inside the repo and the version to the builder is derived from from a git tags in the form vX.Y.Z where I add the commit sha to have X.Y.Z+abd3124e4. That thing I would like to use as a semantic version in mkDerivation. I do not want to hardcode it into the .nix file. Is this somehow not agreeabale with the way Nix works. This seems somewhat a tooling issue which would be very nice to overcome somehow:

I can read a file as version = builtins.readFile ./.version which needs to be there (and temporarily added to Git with git add), which solves this somehow, but not fully with Nix…

Note that the “build version” is just a freeform string. It has no inherent meaning. In some cases, it is interpreted outside of the package using a version comparison on the package’s version to change some other component’s behaviour but its primary purpose is to inform a human about a derivation’s contents, just like pname.

If there is no outside use of it, you could set it to foo and everything would still work the same.

Do you mean to infer the “exported” version of the software contained in the same repo as the Nix packaging from the git tag of the repo’s current HEAD?

This approach has multiple issues:

  • There can be any number >= 0 of git tags on any given commit. Which one should be used? What if there is none?
  • Rather than being dependant on the state of simple files inside of a directory, your build output would be dependant on the state of a complex version control system too. The same exact code doing the exact same thing would be represented as entirely different things depending on whether you fetched the git tags or not. Is that truly what you want?

Yes, yes indeed. Nix’ set of interactions with the outside world during evaluation are intentionally limited because it should be as pure as possible. Evaluating the same code should produce the same output.

This is core to the functional model and allows for functionality which a lot of the Nix ecosystem assumes to exist such as memoisation.


I’d use this version file as the source of truth. Git tags should be created automatically based on this file, not the other way around. Tooling which automatically creates a tag on the commit which changes this version file should be rather easy to create.


I still don’t fully understand what you’re trying to achieve here. Why do you need the git hash in the version of the Nix derivation?

1 Like

Correct.

The repository is a library repo, lets say with some C++ library etc. I have a flake.nix file inside the repository which will build a derivation for this repository (src = ./. for mkDerivation) containing the library.

You say, when I want to set the version in mkDerivation from the repos git tag, Nix depends then on the state of the Git, but doesnt it do that already in some sense:

  • It wants files to be staged/commited (in the index) for the build to work.
  • Every nix build .#default takes the whole repository (src = ./. for mkDerivation) and makes a derivation out of it, so yes it depends on the filesystem or better on the checkout Git SHA implicitly.

All I wanted to achieve is to link the version to some semantic version I give for the repository at a current commit hash. This is best done with git tags.
Since the version from Nix perspective is anyway meaningless and only for user semantic sugar, I can also set it to something which is aligned with the repos version at the current commit.
I know I can create a version file and put that in to the repository, but thats cumbersome. I’d rather work with tags. (also because a correct checkedin version file with build-meta = <commit-hash> is impossible)

What consistency concerns:
If the commit is on a Git tag vX.Y.Z (no there are not multiple Git version tags on the same commit) then the version is X.Y.Z, if its not on a Git tag, version is the Git SHA1 (or some more elaborate version bump from the last sem. version e.g. X.Y.(Z+1)-rc1.<commits-ahead>+<commit-sha> which is a local developers Nix build…

Add: I guess my request should probably be done externally, write the .version file, let it be read by Nix for the build and thats it. I agree that this is not reproducible only with the files at a current commit since it needs the .version file to be written first, which is against NIX philosophy, but its only for the version which is anyways meaningless, I think considering this I can sacrify some inconsistency, right?..

I don’t think it’s possible without a impure/non-reproducible way

I’m not sure how your project is structured. But for example in zig2nix, I have CI job that writes versions.json to the git repo having links and shasums of every zig release, I also write there the current HEAD Of ziglang/zig for source git builds. https://github.com/Cloudef/zig2nix/blob/ae9254b3965ada4574ef19f4fa341c01c7d7345b/versions.json
https://github.com/Cloudef/zig2nix/blob/ae9254b3965ada4574ef19f4fa341c01c7d7345b/flake.nix#L362-L388

For another project I do similar thing for some binary deps it needs https://github.com/Cloudef/mach-flake/blob/7615f558e16aae99ccce41822ae711de1deb94ae/mach-binaries.json

For bemenu I simply have VERSION file in the repo that I update in every release https://github.com/Cloudef/bemenu/blob/a3f19bab9b81dc9516fab7d54e75ead373a2aaf6/VERSION It’s required anyways, since many packagers grab source tarballs and won’t have the .git

This is not a Nix thing but specifically a flakes thing. Regular nix evals don’t care or even know about git.

AFAIUI, it’s intended to prevent “undeclared” state from sneaking into your eval. (I vehemently disagree with this approach.)

Those are two different kinds of state.

One is a tree of plain-text files interpretable by Nix aswell as humans.
The other is a structured store of data and, more importantly, metadata.

A regular Nix evaluation does not depend on any one Git SHA, it only cares about the files in the checkout. It doesn’t care what the commit title, hash, tag or whatever else is. If it’s the same content, it evaluates to the same derivation. Beyond that, even if you changed the content, it is often possible to still result in the same derivation if the code is semantically unchanged (i.e. changing a constant’s name).
This property is extremely important because it enables caching/memoisation.

If you were to encode some metadata into the derivation like i.e. the current time, evaluating the same code again at a different time would yield a different result; breaking this caching entirely.
It’s the same for git revs. If you amended your commit title, tagged your HEAD, changed the README, removed a blank line or renamed a constant, you’d always get a different derivation despite the code being unchanged.


I once again ask you: What do you want to achieve with the git hash being encoded into your result?

I’m not the author of this question, but I would guess that if the version is completely irrelevant (including for debugging etc), why does nix even include the version in the name? I personally like it as I can easily see which version of a package is installed by just looking at the name of the derivation with readlink -f $(which myprogram). One might argue that I could use foo --version, but:

  1. some programs do not come with a --version command
  2. this version will often mostly just be hardcoded in the code; notably it will not change with the name of the commit.

Notably, if you want this information to be commit-precise, you need somehow to provide this information to the derivation, just to make sure that something like yourprogram --version works as expected. I can also imagine some people wanting to provide a About help menu that lists precisely the current version/commit used to build a derivation to help with the debugging process.

Yes, thats some kind of the motivation, I was going down this route: @Atemu thanks for the explanation. I think its more clear now.

The explanation why things are as they are get a bit more subtle as @tobiasBora stated with the version information passed through to the builder.
I use a cmake flag to set the semantic version obtainer from ${version} of mkDerivation.
I am wondering what people do if they have this problem. Its a chicken-egg problem: what is ground truth, version or the git tag. From a tooling perspective I like the git tag ground truth first, but its incompatible with Nix packaging, so the only way is to check a .version file into the repo and use this, and build the tooling around that, so ground truth is the .version file, git tags should follow this file. End of story. I think…