diff --git a/.kiro/specs/build-time-versioning/design.md b/.kiro/specs/build-time-versioning/design.md index 2f4194b0..dcdf086d 100644 --- a/.kiro/specs/build-time-versioning/design.md +++ b/.kiro/specs/build-time-versioning/design.md @@ -176,7 +176,7 @@ graph TB - `src/tools/go_build` (consumed by both `apiserver/meson.build` and `xcecli/meson.build`'s `custom_target()`) reads `XCE_GIT_COMMIT`/`XCE_BUILD_TIME` from its environment (empty-safe — falls back to `"unknown"` when unset, e.g. a bare `go build` invoked by hand) and appends `-ldflags "-X main.GitCommit=$v1 -X main.BuildTime=$v2"` to its existing `go build` invocation. - `src/frontend/Makefile`'s `build-server`/`build-cli` targets compute the same two values directly (git/date, dev-loop path, no meson involved) and pass the identical `-ldflags` shape — same output format as the meson path, different computation site (mirrors the C-side split). - `apiserver/cmd/main.go`: package-level `var (GitCommit = "unknown"; BuildTime = "unknown")`, a new `-version` bool flag (checked first, before `utils.InitLogger()` — prints and exits, no logging side effects), and the existing `logger.Info("********** Initialize Logger done!")` call site extended to also log both values (same convention, `"********** done!"` is untouched; the version fields ride as structured zap fields on that same line or an adjacent one). -- `xcecli/cmd/main.go`: same two vars; `rootCmd.Version = fmt.Sprintf(...)` set once at init (Cobra then handles `--version` natively — zero new flag-parsing code); its own existing `logger.Info("********** Initialize Logger on client side done!")` extended the same way. +- `xcecli/cmd/main.go`: same two vars; version string passed into `router.Execute(cm, version)`, which sets `rootCmd.Version` before `rootCmd.Execute()` runs (Cobra then handles `--version` natively — zero new flag-parsing code). Correction (task 4.5): xcecli's startup log is deliberately NOT extended — Requirement 3 scopes the startup-log obligation to "the controller, scheduler, worker, or apiserver daemon" only; xcecli is a one-shot CLI, not a daemon, and was never in Requirement 3's scope. **Contracts**: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [ ] diff --git a/.kiro/specs/build-time-versioning/tasks.md b/.kiro/specs/build-time-versioning/tasks.md index 4227b703..95a87081 100644 --- a/.kiro/specs/build-time-versioning/tasks.md +++ b/.kiro/specs/build-time-versioning/tasks.md @@ -6,7 +6,7 @@ - Observable: a plain `meson setup` with neither option set still configures successfully (both defines resolve to empty string, no build failure) - _Requirements: 1.1, 1.2, 1.4, 4.3_ -- [ ] 2. Foundation: compute real values at both build entry points +- [x] 2. Foundation: compute real values at both build entry points - [x] 2.1 (P) xce-build.sh computes and passes commit id and build time - Compute `commit_id` via `git rev-parse --short HEAD`, appended with `-dirty` when the tree has uncommitted changes (`git status --porcelain` or `git diff --quiet HEAD --`) - Compute `build_time` via `date -u +%Y-%m-%dT%H:%M:%SZ` @@ -24,7 +24,7 @@ - _Requirements: 1.1, 1.2, 1.4, 4.1, 4.2, 5.1, 5.2_ - _Boundary: Nix packaging_ -- [ ] 3. Core: C daemon version display +- [x] 3. Core: C daemon version display - [x] 3.1 Extend the shared version macro with build time and unknown fallback - ~~Extend `MK_VERSION`/`STR`~~ — `MK_VERSION`/`STR` cannot express the empty-string-to-`unknown` substitution at the preprocessor level, so implemented as new runtime functions `xce_version_field()`/`xce_compose_version()` (`utils.h`/`utils.c`) instead; `MK_VERSION`/`STR` marked SUPERSEDED with an explicit doc comment and handoff notes for 3.2 (round 1 REJECTED for silently orphaning `MK_VERSION` + undocumented `argp_program_version` file-scope-const-initializer trap; round 2 APPROVED after adding the SUPERSEDED comment + NOTE FOR CALLERS guidance) - Both fields render as `unknown` when their underlying define is empty (verified via `""`, not just `NULL`), never a blank/garbled string @@ -48,7 +48,7 @@ - _Boundary: worker/main.c_ - _Depends: 3.1_ -- [ ] 4. Core: Go binary version display +- [x] 4. Core: Go binary version display - [x] 4.1 go_build script injects ldflags from environment - `src/tools/go_build` reads `XCE_GIT_COMMIT`/`XCE_BUILD_TIME` via `[ -z ... ]` (correctly empty-safe, not just unset-safe) and appends `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` to its `go build` invocation, falling back to `unknown` - Confirmed (research finding, independently reproduced by review): `-X` on a `main` package 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 @@ -81,11 +81,13 @@ - _Boundary: apiserver/cmd/main.go_ - _Depends: 4.1_ -- [ ] 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-in `rootCmd.Version` field from them once at init - - Observable: `xcecli --version` (Cobra's native handling) prints both values and exits +- [x] 4.5 (P) xcecli: version field and vars + - `GitCommit`/`BuildTime` vars in `main.go`; `router.go`'s `Execute()` extended with a `version string` parameter, setting `rootCmd.Version` before `rootCmd.Execute()` runs (the sole call site, confirmed by both implementer and review) + - Cobra's native `--version`/`-v` auto-registers once `rootCmd.Version != ""` (confirmed via reading Cobra v1.9.1 source directly) -- zero additional flag-parsing code, as designed + - Observable confirmed live (independently re-verified by review, both build paths): `xcecli --version` prints commit id (matching `git rev-parse --short HEAD` + dirty suffix) and build time, exits 0 + - xcecli's startup log deliberately NOT extended -- Requirement 3 scopes that obligation to the four daemons only; xcecli is a one-shot CLI (design.md corrected to match) - _Requirements: 1.1, 1.2, 2.1_ - - _Boundary: xcecli/cmd/main.go_ + - _Boundary: xcecli/cmd/main.go, xcecli/router/router.go_ - _Depends: 4.1_ - [ ] 5. Core: xceadmin version display diff --git a/src/frontend/xcecli/cmd/main.go b/src/frontend/xcecli/cmd/main.go index b6ad0a5b..03016459 100755 --- a/src/frontend/xcecli/cmd/main.go +++ b/src/frontend/xcecli/cmd/main.go @@ -36,6 +36,19 @@ import ( "go.uber.org/zap" ) +// GitCommit and BuildTime are injected at link time via +// `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see +// src/tools/go_build and src/frontend/Makefile's build-cli target). The +// "unknown" defaults matter: a bare `go build`/`go run` that bypasses that +// ldflags injection must still report "unknown" rather than an empty +// string, matching this feature's unknown-fallback requirement (see +// xce_version_field() in src/lib/utils.c for the equivalent C-side +// convention). +var ( + GitCommit = "unknown" + BuildTime = "unknown" +) + func main() { // init logger @@ -59,7 +72,8 @@ func main() { } // execute the command - if err := cmd.Execute(configManager); err != nil { + version := fmt.Sprintf("%s built %s", GitCommit, BuildTime) + if err := cmd.Execute(configManager, version); err != nil { fmt.Println(strings.ToLower(err.Error())) os.Exit(1) } diff --git a/src/frontend/xcecli/router/router.go b/src/frontend/xcecli/router/router.go index 6feece31..a333555e 100755 --- a/src/frontend/xcecli/router/router.go +++ b/src/frontend/xcecli/router/router.go @@ -26,11 +26,12 @@ var ( ) // execute the root command -func Execute(cm *config.ConfigManager) error { +func Execute(cm *config.ConfigManager, version string) error { if cm == nil { return fmt.Errorf("the config manager is not initialized") } configManager = cm + rootCmd.Version = version rootCmd.CompletionOptions.DisableDefaultCmd = true rootCmd.SilenceErrors = true rootCmd.SilenceUsage = true