Exposing Git Information in Rust Binaries Built With Nix
When you have software out in the wild being used by lots of people, it’s inevitable that there will be pretty large spread across the versions of your software being used.
If that software is aimed towards technical folks and power users, or if you publish nightly releases, in addition to the spread across official version releases, you will also have many people running on any number of commit hashes.
It has been very valuable for me to have commit information available when a
user runs my software on Windows with the --version flag as this allows me to
quickly narrow down if a reported issue has already been addressed in the
current stable or nightly release.
I have done this using the excellent
shadow-rs crate, which queries
various git information at build time, and provides a convenient
CLAP_LONG_VERSION which can be used when building a CLI with
clap to provide detailed build information.
komorebi 0.1.38
branch:master
commit_hash:21cb5e1e
build_time:2025-06-10 00:26:27 +02:00
build_env:rustc 1.85.0 (4d91de4e4 2025-02-17),stable-x86_64-pc-windows-msvc
One of the things that I miss when developing for Windows is having
reproducible builds using a Nix flake, so naturally, one of the highlights of
porting my software to macOS has been finally being able to build my project
with nix build.
When building Rust projects I typically use
crane to do all of the heavy lifting,
including filtering my repository for the relevant files needed to compile the
project crates themselves.
Typically when filtering relevant files for any language when building with
Nix, the .git directory is excluded, which means that shadow-rs will not be
able to extract information about the current commit at build time.
komorebi 0.1.0
branch:
commit_hash:
build_time:1980-01-01 00:00:00 +00:00
build_env:rustc 1.90.0 (1159e78c4 2025-09-14),
Although I prefer to ship binaries built using nix build, users who compile
from source will often use cargo install, meaning that whatever path I take
for getting more detailed information returned from --version, it has to work
both when building using cargo directly and when using nix build.
Luckily, a Nix flake will expose its current git revision via
inputs.self.rev, and this can be set as a build-time environment variable in
flake.nix.
{
commonArgs = {
inherit src version;
strictDeps = true;
COMMIT_HASH = self.rev or (lib.removeSuffix "-dirty" self.dirtyRev);
buildInputs = [
pkgs.gcc
pkgs.libiconv
];
};
}
I was not, however, able to find a way to get the current git branch name from a Nix flake.
The next step is to modify the build.rs which was previously just calling
shadow-rs to behave accordingly when being called via nix build.
Running the shadow-rs builder and seeing if one of the git-sourced values
such as BRANCH was populated is one way to determine if we are inside of a
nix build.
If BRANCH was not populated, we can run the shadow-rs builder again with a
custom hook which will populate the commit_hash value of the --version
output using env!("COMMIT_HASH") - the environment variable used to expose
inputs.self.rev in flake.nix.
use std::fs::File;
use std::io::Write;
use shadow_rs::SdResult;
use shadow_rs::ShadowBuilder;
fn main() {
let shadow_build = ShadowBuilder::builder()
.hook(raw_hook)
.build()
.unwrap();
let is_flake_build = shadow_build
.map
.get("BRANCH")
.map_or_else(|| true, |entry| entry.v.is_empty());
if is_flake_build {
ShadowBuilder::builder().hook(flake_hook).build().unwrap();
}
}
const RAW_VERSION_CONST: &str = r##"pub const LONG_VERSION:&str = shadow_rs::formatcp!(r#"{}
branch:{}
commit_hash:{}
build_env:{}"#,PKG_VERSION, BRANCH, COMMIT_HASH, RUST_VERSION
);
"##;
fn raw_hook(mut file: &File) -> SdResult<()> {
writeln!(file, "{RAW_VERSION_CONST}")?;
Ok(())
}
const FLAKE_VERSION_CONST: &str = r##"pub const LONG_VERSION:&str = shadow_rs::formatcp!(r#"{}
commit_hash:{}
build_env:{}"#,PKG_VERSION, env!("COMMIT_HASH"), RUST_VERSION
);"##;
fn flake_hook(mut file: &File) -> SdResult<()> {
writeln!(file, "{FLAKE_VERSION_CONST}")?;
Ok(())
}
Note that both the RAW_VERSION_CONST and FLAKE_VERSION_CONST hooks will
generate a pub const LONG_VERSION. This ensures that it’s possible to
reference a single version = LONG_VERSION value when creating a CLI with
clap.
#[derive(Parser)]
#[clap(author, about, version = LONG_VERSION)]
struct Opts {
#[clap(subcommand)]
subcmd: SubCommand,
}
The end result is that binaries built both with cargo build and nix build
will now be able to return detailed version information.
With nix build:
❯ ./result/bin/komorebi --version
komorebi 0.1.0
commit_hash:4e8caf2b2a777cf8dbc75fbaa87d98406e6bc390
build_env:rustc 1.91.0 (f8297e351 2025-10-28)
With cargo build:
❯ ./target/debug/komorebi --version
komorebi 0.1.0
branch:master
commit_hash:4e8caf2b2a777cf8dbc75fbaa87d98406e6bc390
build_env:rustc 1.91.0 (f8297e351 2025-10-28)
If anyone has any ideas on how the branch name could also be exposed from a Nix flake, let me know!
Finally, a big thank you to both Isabel and Ivan for pointing me in the right direction and leading me to this solution.
If you have any questions or comments you can reach out to me on Bluesky and Mastodon.
If you’re interested in what I read to come up with software like komorebi, you can subscribe to my Software Development RSS feed.
If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.
If you would like early access to komorebi for Mac, you can sponsor me on GitHub.