- Nix 66.5%
- Go 33.5%
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| go.mod | ||
| main.go | ||
| README.md | ||
go-vcs
A minimal Go project demonstrating how to embed VCS metadata (commit hash, build date, version) into a Go binary using Nix flakes and -ldflags.
Quick start
nix build --print-build-logs
The build log will print:
=== Embedded VCS Data ===
Version: 2026.05.19-df7f7b9
Commit: df7f7b94702c4d70a6aac7e82791e1ccb19a90f0
BuildDate: 20260519170620
=== Go Build Info ===
Go Version: go1.26.2
Project structure
| File | Purpose |
|---|---|
main.go |
Go binary with Version / Commit / BuildDate vars populated via -ldflags |
flake.nix |
Nix flake that builds with buildGoModule, injecting VCS data into ldflags |
go.mod |
Minimal Go module declaration |
How it works
1. Go side — linker variables
main.go declares three package-level variables with safe defaults:
var (
Version = "dev"
Commit = "unknown"
BuildDate = "unknown"
)
At compile time, the Go linker overwrites these via -ldflags -X:
-X main.Version=v1.2.3
-X main.Commit=abc1234
-X main.BuildDate=2026-05-19
The binary also calls runtime/debug.ReadBuildInfo() to show Go's own embedded VCS settings for comparison.
2. Nix side — extracting git metadata
flake.nix reads three values from self.sourceInfo, which is populated automatically when the flake lives inside a git checkout:
| Nix variable | Source | Example output |
|---|---|---|
gitShortRev |
self.sourceInfo.shortRev |
df7f7b9 |
gitRev |
self.sourceInfo.rev |
df7f7b94702c4d70a6aac7e82791e1ccb19a90f0 |
gitDate |
self.sourceInfo.lastModifiedDate |
20260519170620 |
The version string is derived by combining the date (parsed from YYYYMMDDHHMMSS into YYYY.MM.DD) with the short revision:
packageVersion =
let
y = builtins.substring 0 4 gitDate;
m = builtins.substring 4 2 gitDate;
d = builtins.substring 6 2 gitDate;
in "${y}.${m}.${d}-${gitShortRev}";
These are passed to buildGoModule as ldflags:
packages.default = pkgs.buildGoModule {
pname = "go-vcs";
version = packageVersion;
src = ./.;
ldflags = [
"-s" "-w" # strip debug info
"-X main.Version=${packageVersion}"
"-X main.Commit=${gitRev}"
"-X main.BuildDate=${gitDate}"
];
# ...
};
3. Verifying at build time
doInstallCheck = true runs the built binary during the build:
doInstallCheck = true;
installCheckPhase = ''
echo "--- Running go-vcs binary to verify embedded VCS data ---"
$out/bin/go-vcs
'';
This means nix build --print-build-logs prints the embedded VCS data directly in the build log — no need to run the binary separately.
Important notes
Clean git tree required
self.sourceInfo only returns real values when the flake is evaluated from a clean git tree. If you have uncommitted changes, you'll see:
Version: yyyy.mm.dd-dirty
Commit: dirty
BuildDate: yyyymmddhhmmss
Always commit (and add flake.lock) before building.
Zero dependencies
This example uses zero third-party Go dependencies, so vendorHash = null with proxyVendor = true. For real projects with dependencies, you'll need to provide a real vendor hash (and update it whenever go.mod/go.sum changes).
The values are baked in at compile time
Because the values come from -ldflags, they become static strings in the ELF binary. They survive stripping (-s -w) and don't depend on the git repo being present at runtime. This is different from Go 1.18+'s vcs.revision / vcs.time build settings, which are only available when the binary is built inside a git worktree.
Adapting for your own project
- Copy the
ldflagspattern fromflake.nix. - In your Go code, declare the same package-level vars (
Version,Commit,BuildDate). - Replace
vendorHash = nullwith a real hash from yourgo.sum. - Make sure your repo is clean before running
nix build. - The
installCheckPhaseis optional — it's just a convenient way to see the VCS data during the build.