Dev-loop Go build path (no meson involved) now computes commit id (git, -dirty suffixed, "unknown" on git failure) and build time (UTC date) via Make := (computed once per invocation), passing the identical -ldflags shape go_build (task 4.1) already establishes. Live-verified: make build-cli embeds real, git-matching values (go version -m). make build-server hits a pre-existing, unrelated CGO failure (bug-1597, missing CGO_CFLAGS/CGO_LDFLAGS) -- confirmed via stash A/B that this reproduces identically on the unmodified Makefile, not a regression from this change. Reproduced apiserver's build invocation manually with bug-1597's own documented workaround to prove the ldflags mechanism itself works correctly. Task 4.3/13 of build-time-versioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 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/STRMK_VERSION/STRcannot express the empty-string-to-unknownsubstitution at the preprocessor level, so implemented as new runtime functionsxce_version_field()/xce_compose_version()(utils.h/utils.c) instead;MK_VERSION/STRmarked SUPERSEDED with an explicit doc comment and handoff notes for 3.2 (round 1 REJECTED for silently orphaningMK_VERSION+ undocumentedargp_program_versionfile-scope-const-initializer trap; round 2 APPROVED after adding the SUPERSEDED comment + NOTE FOR CALLERS guidance)- Both fields render as
unknownwhen their underlying define is empty (verified via"", not justNULL), never a blank/garbled string - Observable: a build with both meson options unset produces a
--versionstring containingunknownfor both fields, not empty gaps -- 5 new unit tests intest_utils.cconfirm this - Requirements: 1.4, 2.2
- Boundary: utils.h
-
3.2 (P) Controller and scheduler: version in --version and startup log
argp_program_versionmigrated toxce_compose_version()via astatic char version_buf[128]computed and assigned beforeargp_parse()in all 3 files (controller/main.c, scheduler/main.c, test.c); zero remainingMK_VERSIONcallers repo-wide outside its own definition- Startup log lines read the identical
version_buf, never recomputed -- extended to BOTH of scheduler's mutually-exclusive first-INFO branches (--peersand no---peers), not just the one literally quoted in this task's text, since Requirement 3.1's "when the daemon starts" is unconditional - Observable confirmed live (independently re-verified by review, not just self-reported):
xce_controller --version,xce_test --version, andxce_scheduler --versionall print commit id + build time and exit; all startup log branches for both daemons are byte-identical to their own--versionoutput - Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.3
- Boundary: controller/main.c, scheduler/main.c, controller/testsuite/test.c
- Depends: 3.1
-
3.3 (P) Worker: add --version and startup log line
- Added
OPT_VERSION=257(long-only, beyond ASCII range, no collision with"n:l:vh"/OPT_PEERS=256); handling case computesxce_compose_version()into astatic char version_buf[128]and returns before any startup work (check_user()/logdir/xcecd_init/xcecd_start) - Both startup INFO log branches (
--peersand no---peers) extended to log the same buffer - Observable confirmed live (independently re-verified by review):
xcecd --versionprints and exits; both startup log branches (captured via a realsudodaemon run) are byte-identical to--versionoutput - Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.3
- Boundary: worker/main.c
- Depends: 3.1
- Added
-
4. Core: Go binary version display
-
4.1 go_build script injects ldflags from environment
src/tools/go_buildreadsXCE_GIT_COMMIT/XCE_BUILD_TIMEvia[ -z ... ](correctly empty-safe, not just unset-safe) and appends-ldflags "-X main.GitCommit=... -X main.BuildTime=..."to itsgo buildinvocation, falling back tounknown- Confirmed (research finding, independently reproduced by review):
-Xon amainpackage var that doesn't exist yet is silently ignored by the Go linker, not a build error -- this task has no real ordering dependency on 4.4/4.5 - 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
- Both meson.build files pass
XCE_GIT_COMMIT/XCE_BUILD_TIME(fromget_option('commit_id')/get_option('build_time')) intogo_build's env, UNQUOTED (go_build'sfix_input_envs()only quote-stripsCURRENT_SOURCE_DIR/BUILD_ROOT, not these two -- quoting them would embed literal"chars and corrupt the-ldflagsargument) - Verified live (independently, by review): real commit id + build time reach
go build's actual invocation and are embedded in both binaries' Go build-info metadata (go version -m) - Observable partially deferred by design: no
main.GitCommit/main.BuildTimesymbols exist in either binary yet (go tool nmconfirms) since apiserver/xcecli'smainpackages don't declare those vars until tasks 4.4/4.5 -- this task's own scope (_Depends: 4.1_only) is the env-var plumbing itself, not the full round trip; task 6.2 owns re-verifying that once 4.4 lands - Requirements: 1.1, 1.2, 4.1, 4.2
- Boundary: apiserver/meson.build, xcecli/meson.build
- Depends: 4.1
- Both meson.build files pass
-
4.3 (P) Makefile dev-loop path computes and passes the same values
GIT_COMMIT_HASH/GIT_STATUS/GIT_COMMIT/BUILD_TIME/VERSION_LDFLAGS(:=, computed once per invocation) mirrorxce-build.sh's dirty-check semantics andgo_build's exact-ldflagsshape;build-cli/build-serverrecipes reference$(VERSION_LDFLAGS)unknownfallback verified via empty-output detection (robust forgit rev-parse --short HEADspecifically: never succeeds with empty stdout)--versionCLI output not yet observable (tasks 4.4/4.5 not landed) -- verified instead viago version -m's embedded build-info metadata, matching livegit rev-parse --short HEAD+ dirty suffixmake build-server's pre-existing CGO failure (bug-1597) independently confirmed unrelated via a stash A/B (identical failure on the unmodified Makefile) -- not a regression- 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.