First attempt used builtins.currentTime at flake-evaluation time -- this broke the plain, documented `nix build .#xcecore_debug` command entirely (pure-eval strips builtins.currentTime, requiring --impure) and produced a raw epoch-seconds string instead of UTC ISO-8601. Caught by task-local review (REJECTED, round 1) before merge. Fixed by moving the computation into each derivation's own build phase: a preConfigure hook in xce.nix/xcecli.nix runs `date -u` (stdenv's bundled coreutils, unaffected by pure-eval) and appends -Dbuild_time= to mesonFlagsArray, with a fail-loud guard against empty output. commit_id's existing self.shortRev/self.dirtyShortRev eval-time mechanism is untouched. Verified via nix eval (no --impure) and nix build reaching configure with real values on both xcecore_debug and xcecli (round 2, APPROVED). Task 2.2/13 of build-time-versioning. Also corrects design.md/research.md, which described the now-superseded builtins.currentTime approach. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 KiB
Implementation Plan
-
1. Foundation: meson build-time option and C project-wide define
- Add a
build_timemeson option (string, default'') alongside the existingcommit_idoption insrc/meson.options - Extend
src/meson.build's existingadd_project_arguments()call to also defineVERSION_BUILD_TIMEfor every C component, mirroring howVERSION_COMMIT_IDis already threaded through - Observable: a plain
meson setupwith neither option set still configures successfully (both defines resolve to empty string, no build failure) - Requirements: 1.1, 1.2, 1.4, 4.3
- Add a
-
2. Foundation: compute real values at both build entry points
-
2.1 (P) xce-build.sh computes and passes commit id and build time
- Compute
commit_idviagit rev-parse --short HEAD, appended with-dirtywhen the tree has uncommitted changes (git status --porcelainorgit diff --quiet HEAD --) - Compute
build_timeviadate -u +%Y-%m-%dT%H:%M:%SZ - Fall back to the literal string
unknownforcommit_idwhengititself fails (e.g. no.gitpresent) - Append
-Dcommit_id="$commit_id" -Dbuild_time="$build_time"toMESON_OPTSfor every build profile, before the existingmeson setup/meson setup --reconfigurecalls - After computing both values, verify each is non-empty (
build_timealways is;commit_idfalls back tounknownonly on a genuinegitfailure) and print a loud warning to stderr if either resolves empty anyway -- the build tooling must not silently proceed with a value it cannot confirm - Observable: running
/xce-build scheduler debugfollowed by the resulting binary's--versionshows a commit id matching the currentgit rev-parse --short HEADat build time, not empty; deliberately breaking the computation (e.g. temporarily renaming.git) prints the warning instead of silently succeeding - Requirements: 1.1, 1.2, 1.3, 1.4, 4.1, 4.2, 5.1, 5.2
- Boundary: xce-build.sh
- Compute
-
2.2 (P) Nix packages compute and pass build time
Add a— corrected during review (round 1 REJECTED):builtins.currentTime-derived build-time value insrc/flake.nixbuiltins.currentTimebreaks pure-evalnix build(requires--impure) and yields non-UTC epoch seconds. Implemented instead as apreConfigurebuild-phasedate -u +%Y-%m-%dT%H:%M:%SZhook inxce.nix/xcecli.nixappending tomesonFlagsArray, alongside the unchanged eval-time-Dcommit_id=wiring- Fail loudly (non-empty guard) if
date -uever produces empty output, before the value reachesmesonFlagsArray - Observable:
nix build .#xcecore_debugproduces a binary whose--versionshows a non-empty, ISO-8601 UTC build time, without anygitinvocation inside the sandboxed derivation, and without requiring--impure - Requirements: 1.1, 1.2, 1.4, 4.1, 4.2, 5.1, 5.2
- Boundary: Nix packaging
-
3. Core: C daemon version display
-
3.1 Extend the shared version macro with build time and unknown fallback
- Extend
MK_VERSION/STRinsrc/include/utils.hso the composed string carries both commit id and build time - Both fields render as
unknownwhen their underlying define is empty, never a blank/garbled string - Observable: a build with both meson options unset produces a
--versionstring containingunknownfor both fields, not empty gaps - Requirements: 1.4, 2.2
- Boundary: utils.h
- Extend
-
3.2 (P) Controller and scheduler: version in --version and startup log
- Update each daemon's
argp_program_versionconstruction to use the extended macro from 3.1 - Add the version string to each daemon's existing first startup INFO log line (controller's
"My name is %s"line, scheduler's"HA cluster config..."line) rather than a second, independently-formatted line - Observable:
xce_controller --versionandxce_scheduler --versioneach print commit id + build time and exit without starting the daemon; each daemon's startup log's first lines include the identical two values - Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.3
- Boundary: controller/main.c, scheduler/main.c
- Depends: 3.1
- Update each daemon's
-
3.3 (P) Worker: add --version and startup log line
- Add a long-only
--versionoption to worker'sgetopt_long()option table and a handling case that prints the same macro output and exits before any other startup work - Add the version string to worker's existing first startup INFO log line (its own
"HA cluster config..."line) - Observable:
xcecd --versionprints commit id + build time and exits without starting the daemon; the startup log's first lines include the identical two values - Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.3
- Boundary: worker/main.c
- Depends: 3.1
- Add a long-only
-
4. Core: Go binary version display
-
4.1 go_build script injects ldflags from environment
src/tools/go_buildreadsXCE_GIT_COMMIT/XCE_BUILD_TIMEfrom its environment (both optional, empty-safe) and appends-ldflags "-X main.GitCommit=... -X main.BuildTime=..."to itsgo buildinvocation, falling back tounknownfor any unset value- Observable: invoking
go_buildwith neither env var set still produces a working binary whose injected vars readunknown - Requirements: 1.1, 1.2, 1.4, 4.1, 4.2
- Boundary: go_build script
-
4.2 apiserver and xcecli meson builds pass resolved values through
src/frontend/apiserver/meson.buildandsrc/frontend/xcecli/meson.buildread the project'scommit_id/build_timemeson options and pass them asXCE_GIT_COMMIT/XCE_BUILD_TIMEenv vars into their existingcustom_target()calls- Observable: a meson-driven
/xce-build apiserver(with 2.1's values set) produces anxce_apiserverbinary whose linked-in vars are non-unknown - Requirements: 1.1, 1.2, 4.1, 4.2
- Boundary: apiserver/meson.build, xcecli/meson.build
- Depends: 4.1
-
4.3 (P) Makefile dev-loop path computes and passes the same values
src/frontend/Makefile'sbuild-server/build-clitargets compute commit id (git, dirty-suffixed) and build time (date) directly and pass the identical-ldflagsshape 4.1 establishes, independent of meson- Fall back to
unknownfor commit id whengititself fails (e.g. no.gitpresent), matching 2.1's/4.1's fallback discipline - Observable:
make build-serverandmake build-clieach produce a binary whose--version/-versionoutput matches the same commit idgit rev-parse --short HEADreports at build time - Requirements: 1.1, 1.2, 1.3, 1.4, 4.1, 4.2, 5.1, 5.2
- Boundary: Makefile
- Depends: 4.1
-
4.4 (P) apiserver: version flag, vars, and startup log
- Declare
GitCommit/BuildTimepackage vars (defaultunknown) insrc/frontend/apiserver/cmd/main.go - Add a
-versionflag, checked beforeutils.InitLogger(), that prints both values and exits with no logging side effects - Extend the existing
logger.Info("********** Initialize Logger done!")call site to also log both values - Observable:
xce_apiserver -versionprints both values and exits without starting the server; the startup log's first lines include the identical two values - Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2
- Boundary: apiserver/cmd/main.go
- Depends: 4.1
- Declare
-
4.5 (P) xcecli: version field and vars
- Declare the same two package vars in
src/frontend/xcecli/cmd/main.go; set Cobra's built-inrootCmd.Versionfield from them once at init - Observable:
xcecli --version(Cobra's native handling) prints both values and exits - Requirements: 1.1, 1.2, 2.1
- Boundary: xcecli/cmd/main.go
- Depends: 4.1
- Declare the same two package vars in
-
5. Core: xceadmin version display
-
5.1 (P) meson-build.sh generates a build-info module before packaging
src/xceadmin/meson.build'scustom_target()passes the resolved commit id/build time tomeson-build.sh(new positional args or env vars)meson-build.shwrites a small_build_info.py(containingGIT_COMMIT/BUILD_TIMEstring constants,unknownfallback) into the mirrored source tree's xceadmin package directory, before the existingpip installstep, matching the schema-sync step's own before-install ordering- After writing the module, verify it actually landed in the mirrored tree before proceeding to
pip install(e.g. a file-exists check) and fail the build loudly if it did not, rather than silently packaging the fallback-shaped-but-wrong wheel - Observable: a meson-driven
/xce-build xceadminproduces a wheel whose installed_build_info.pycontains non-unknownvalues when 2.1's values are set - Requirements: 1.1, 1.2, 1.4, 4.1, 4.2
- Boundary: xceadmin/meson.build, xceadmin/meson-build.sh
- Depends: 1
-
5.2 (P) Committed fallback stub and CLI version wiring
- Commit a fallback
src/xceadmin/src/xceadmin/_build_info.pywith both fields set tounknown(used whenever 5.1's generated version isn't present, e.g.pip install -e .) - Wire
click.version_option()oncli.py's top-level group, importing_build_infodefensively and composing its version string frompyproject.toml's static version plus the two build-info fields - Observable: both
pip install -e .(no meson) and the meson-built wheel produce a workingxceadmin --versionthat prints and exits; the editable-install case showsunknownfor both build fields, never an import error - Requirements: 1.1, 1.2, 2.1
- Boundary: xceadmin/src/xceadmin
- Commit a fallback
-
6. Integration: cross-toolchain consistency and regression verification
-
6.1 Motivating-incident regression test
- Build a C daemon via
/xce-build(any profile) and confirm its--versioncommit id is non-unknownand matchesgit rev-parse --short HEADat build time -- the exact scenario that was previously silent/empty - Repeat against at least the
asananddebugprofiles specifically (the motivating incident was adebug-profile binary), confirming both carry accurate, matching values - Observable: an automated check fails loudly if
xce-build.sh's computed commit id ever regresses to empty/missing, in either profile - Requirements: 4.1, 4.2, 4.3, 5.1, 5.2
- Depends: 2.1, 3.2, 3.3
- Build a C daemon via
-
6.2 Version/startup-log consistency test
- For one C daemon (controller or scheduler) and one Go binary (apiserver), capture the startup log's version line and separately run
--version/-version, asserting the two report byte-identical commit id and build time - Observable: a passing test proves
--versionand the startup log can never disagree for the same artifact - Requirements: 2.2
- Depends: 3.2, 4.4
- For one C daemon (controller or scheduler) and one Go binary (apiserver), capture the startup log's version line and separately run
-
6.3 xceadmin end-to-end build/install/version cycle
- Run xceadmin's full meson-driven build (schema sync, venv install, pytest, wheel build), install the resulting wheel, and confirm
xceadmin --versionreports the generated (non-unknown) build-info fields - Observable: the installed wheel's
--versionoutput differs from an editable (pip install -e .) install'sunknown-only output, proving the generated module actually replaces the fallback - Requirements: 1.1, 1.2, 2.1, 4.1, 4.2
- Depends: 5.1, 5.2
- Run xceadmin's full meson-driven build (schema sync, venv install, pytest, wheel build), install the resulting wheel, and confirm
Implementation Notes
- Task 2.2:
builtins.currentTimecannot be used for anything feeding anix buildderivation without--impure— Nix's flake pure-eval mode strips it entirely, breaking the plain, documentednix build .#xcecore/.#xcecore_debugcommands. It also yields a raw epoch-seconds integer, not UTC ISO-8601. Any nix-side build metadata that must vary per-build (timestamps, etc.) belongs in a derivation'spreConfigure(or similar) build-phase shell hook using ordinary coreutils (date -u), appended tomesonFlagsArray— never in eval-time Nix expressions.self.shortRev/self.dirtyShortRevremain the correct, eval-time-safe mechanism specifically for the commit id (Nix computes those specially even under pure-eval), so this distinction is per-value, not a blanket "avoid nix expressions" rule.