Skip to content

fix: derive the reported version from the release tag - #13

Open
jeroenrinzema wants to merge 1 commit into
mainfrom
fix/report-release-version
Open

jeroenrinzema wants to merge 1 commit into
mainfrom
fix/report-release-version

Conversation

@jeroenrinzema

Copy link
Copy Markdown
Collaborator

brainpod --version printed brainpod 0.1.0 for every release ever published, carrying no information about which build a user was running. Cargo.toml hardcoded version = "0.1.0" and releases are versioned only by git tag, so the tag never reached the crate.

How the version now travels

The release tag is the single source of truth, and it reaches the compiler through a VERSION file rather than Cargo.toml:

  1. release.yml strips the leading v from github.event.release.tag_name, validates it as semver, and writes it to VERSION.
  2. flake.nix reads VERSION and passes it into the derivation as BRAINPOD_VERSION.
  3. build.rs re-exports it so #[command(version = env!("BRAINPOD_VERSION"))] picks it up.

Cargo.toml drops to 0.0.0 because it is no longer the release version, with Cargo.lock kept consistent so --locked builds still resolve. Unstamped builds report 0.0.0-dev, plus the commit they were built from when git metadata is reachable, so a local binary can never be mistaken for a published one.

Why VERSION and not Cargo.toml

Rewriting Cargo.toml per release is the obvious approach, but the manifest is inside the crane source filter and is also read directly via cargoTomlContents, so every release would change cargoArtifacts and rebuild the whole dependency set on all four runners. VERSION sits outside that filter. Verified by evaluating the derivations before and after a version rewrite, on both a native and a cross target:

VERSION=0.0.0-dev VERSION=0.0.6
deps (aarch64-darwin) …-brainpod-cli-deps-0.0.0.drv identical
deps (x86_64-linux musl) …-brainpod-cli-deps-x86_64-unknown-linux-musl-0.0.0.drv identical
binary …-brainpod-cli-0.0.0-dev.drv …-brainpod-cli-0.0.6.drv

Only the crate itself rebuilds. This also keeps the hashFiles('flake.nix', 'flake.lock', 'Cargo.lock') Nix store cache key stable across releases, and avoids needing cargo-edit on the runners.

One note on build.rs

build.rs previously emitted no cargo:rerun-if-* directives, so Cargo's default applied: rerun the script whenever any file in the package changes. Adding rerun-if-env-changed for BRAINPOD_VERSION would have silently narrowed that to only the env var and stopped the protobufs from regenerating when a .proto changed, so cargo:rerun-if-changed=proto is emitted alongside it.

The regression gate

This bug shipped five times because nothing verified it. Both workflows now run the binary they just built and compare --version against what it should be — the tag in release.yml, the VERSION placeholder in build.yml. In the release workflow this runs after nix build and before the artifact is packaged or uploaded, so a mismatching binary never reaches a release.

A malformed or empty tag fails the job at its first step rather than shipping another wrong version. Rejected: v1.2, v1.2.3.4, release-1, empty. Accepted: v1.2.3, 1.2.3, v1.2.3-rc.1.

The check runs on all four matrix entries, including x86_64-darwin, which is built on an arm64 macos-14 runner: the same Rosetta path that lets Nix build that target also runs the resulting binary. If it were ever unavailable, the step fails loudly with a distinct "could not run" error rather than silently skipping.

Verified locally

  • nix flake check --all-systems --no-build passes.
  • nix build on a checkout with no tag → brainpod 0.0.0-dev. Git is unreachable inside the builder and .git is not in the source filter, so the placeholder is deterministic, which is what build.yml asserts.
  • Release path simulated by writing 0.0.6 to VERSION unstaged, exactly as the workflow does → brainpod 0.0.6, with only brainpod-cli-0.0.6.drv built and dependencies reused. Nix reads a tracked file's working-tree content, so the workflow needs no git add.
  • The aarch64-darwin build succeeds, so its postFixup assertion still holds; otool -L on the result shows no /nix/store/ dependency.
  • cargo build → brainpod 0.0.0-dev+d4ac6679d0b4, matching HEAD, refreshing on the next commit. BRAINPOD_VERSION=0.0.6 cargo build → brainpod 0.0.6.
  • brainpod describe --json reports the same version in cliVersion.
  • rustfmt --check and nixfmt-rfc-style --check are clean on the changed files.

Retroactively correcting the already-published v0.0.1–v0.0.5 artifacts is out of scope.

brainpod --version printed 0.1.0 for every published release because
Cargo.toml hardcoded that version and releases are tagged only in git,
so the tag never reached the crate.

The release workflow now derives the version from the tag, writes it to
VERSION, and flake.nix passes it to the build as BRAINPOD_VERSION, which
build.rs re-exports for clap. VERSION sits outside the crane source
filter, so stamping a release does not invalidate cargoArtifacts and only
the crate itself rebuilds.

A malformed or empty tag fails the release, and both workflows run the
binary they just built and fail if its reported version disagrees.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant