Skip to content

feat(cli): report build provenance in --version - #95

Merged
robpc merged 1 commit into
mainfrom
feat/version-build-info
Sep 4, 2026
Merged

robpc merged 1 commit into
mainfrom
feat/version-build-info

Conversation

@robpc

@robpc robpc commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Fixes #93.

desk --version reported 0.3.0 across changes that altered what commands do — #88 added -c/--calendar to the write commands, #89 changed the wall-clock time a naive --start resolves to. The same string named a build where cal create --start 2026-11-11T17:30:00 produces 4:30pm and one where it produces 5:30pm.

$ desk --version
desk, version 0.3.0 (a837d65, 2026-09-03)

$ desk --version --json
{ "version": "0.3.0", "commit": "a837d65",
  "commit_date": "2026-09-03", "source": "build" }

How the commit gets there

A hatchling build hook resolves it at build time and injects desk/_build_info.py into the wheel and sdist via force_include — deliberately not by writing into src/. Writing there would dirty the working copy on every local build, and gitignoring it would risk hatchling's VCS-aware file selection dropping it from the sdist. force_include sidesteps both.

When git is absent but a generated _build_info.py already exists — the sdist → wheel path — its values carry through rather than being overwritten with unknowns.

Runtime resolution is: baked-in build info → a git read of the package's own directory → nothing. The git step exists only for editable installs, where there was no build to bake anything into. The issue asked for build-time capture "rather than shelling out to git at runtime," which is about not depending on git in an installed wheel; step 1 satisfies that, and step 2 fires only where step 1 structurally cannot. Every git call is wrapped so no failure can break --version, and source in the JSON marks which path was taken so a caller knows how much to trust commit.

Also

  • --capabilities reports the commit too — an agent checking whether a fix is present reads that, not --version.
  • This replaces click.version_option, which cannot vary its output by another flag.
  • The version number keeps its scheme and its source in pyproject.toml, so ADR-011's SemVer decision is untouched. hatch-vcs was considered and rejected for exactly that reason — it solves provenance by changing something ADR-011 settled for unrelated reasons.

Verification

893 tests pass (14 new), ruff clean. The whole build chain was exercised end to end:

built from git present result
checkout → wheel yes provenance baked in
checkout → sdist yes provenance baked in
sdist → wheel no carried through, not blanked
installed wheel, run from /tmp no source: "build"
editable checkout yes source: "git"

Tests cover the git-failure paths too (missing binary, timeout, OSError), since a broken git must never take --version down.

Reasoning in ADR-036.

🤖 Generated with Claude Code

`desk --version` reported `0.3.0` across changes that altered what commands
do — #88 added -c/--calendar to the write commands, #89 changed the
wall-clock time a naive --start resolves to — so the same string named a
build where `cal create --start 2026-11-11T17:30:00` produces 4:30pm and one
where it produces 5:30pm.

Bumping the version per release helps but does not close this: desk is
commonly installed with `uv tool install git+https://...`, which tracks a
branch rather than a release, so between tags no version string can
distinguish two builds.

    $ desk --version
    desk, version 0.3.0 (a837d65, 2026-09-03)

A hatchling build hook resolves the commit at build time and injects
desk/_build_info.py into the wheel and sdist via force_include — never into
the source tree, which would either dirty the working copy on every local
build or, if gitignored, risk hatchling's VCS-aware file selection dropping
it from the sdist. When git is absent but a generated _build_info.py already
exists (the sdist -> wheel path), its values carry through instead of being
overwritten with unknowns.

Runtime resolution is: baked-in build info, else a git read of the package's
own directory, else nothing. The git step exists only for editable installs,
where there was no build to bake anything in; it is wrapped so no git
failure can break --version, and `source` in the JSON marks which path was
taken so a caller knows how much to trust `commit`.

--version --json returns the fields separately, for an operator recording
what a deployment runs. This replaces click.version_option, which cannot
vary its output by another flag. --capabilities gains the commit too, since
an agent checking whether a fix is present reads that rather than --version.

The version number keeps its scheme and its source in pyproject.toml, so
ADR-011's SemVer decision is untouched.

Fixes #93

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@robpc
robpc force-pushed the feat/version-build-info branch from a539594 to d2c1689 Compare September 4, 2026 03:23
@robpc
robpc merged commit f10f91b into main Sep 4, 2026
7 checks passed
@robpc
robpc deleted the feat/version-build-info branch September 4, 2026 03:25
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.

--version reports the same string across behaviour changes; include the commit

1 participant