From 88649a046297723be4664196311a50fdb78cf4a7 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 20 Aug 2026 11:02:56 +0000 Subject: [PATCH] feat(build-time-versioning): controller/scheduler --version and startup log Migrates argp_program_version in controller/main.c, scheduler/main.c, and xce_test's test.c from the compile-time MK_VERSION/STR construction to xce_compose_version(), computed into a static buffer and assigned before argp_parse() runs (argp_program_version is a file-scope pointer, not a constant expression it can be initialized from at compile time). Each daemon's startup log now carries the identical version string (never recomputed) on its first INFO line -- scheduler's BOTH mutually-exclusive branches (--peers and no-peers), not just the one literally named in the task text, since "when the daemon starts" is unconditional. Live-verified: --version and startup-log output are byte-identical for both daemons across all branches. No remaining MK_VERSION callers repo-wide. Task 3.2/13 of build-time-versioning. Co-Authored-By: Claude Sonnet 5 --- .kiro/specs/build-time-versioning/tasks.md | 9 ++++--- src/controller/main.c | 23 +++++++++++++++--- src/controller/testsuite/test.c | 20 ++++++++++++++-- src/scheduler/src/main.c | 28 ++++++++++++++++++---- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/.kiro/specs/build-time-versioning/tasks.md b/.kiro/specs/build-time-versioning/tasks.md index 971435cb..9670ba1c 100644 --- a/.kiro/specs/build-time-versioning/tasks.md +++ b/.kiro/specs/build-time-versioning/tasks.md @@ -32,11 +32,10 @@ - _Requirements: 1.4, 2.2_ - _Boundary: utils.h_ -- [ ] 3.2 (P) Controller and scheduler: version in --version and startup log - - Update each daemon's `argp_program_version` construction to use `xce_compose_version()`/`xce_version_field()` from 3.1 (NOT the now-superseded `MK_VERSION`/`STR`) -- since `argp_program_version` is a file-scope `const char*` global (mutable pointer, not `const char* const`) currently initialized at compile time, this requires computing the composed string into a static buffer inside `main()` and assigning the pointer there, before `argp_parse()` runs, rather than a one-line macro swap - - 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 - - `src/controller/testsuite/test.c:662` is a THIRD `MK_VERSION`/`STR` call site (the `xce_test` REPL driver's own version output) discovered during 3.1's review, outside this sub-task's original scope -- migrate it too in this same task (same file family, same daemon, same minimal fix shape), so no caller of the superseded macro remains after this task lands - - Observable: `xce_controller --version` and `xce_scheduler --version` each print commit id + build time and exit without starting the daemon; each daemon's startup log's first lines include the identical two values; `xce_test --version` (or equivalent) also reflects the new composed string, not the old commit-id-only one +- [x] 3.2 (P) Controller and scheduler: version in --version and startup log + - `argp_program_version` migrated to `xce_compose_version()` via a `static char version_buf[128]` computed and assigned before `argp_parse()` in all 3 files (controller/main.c, scheduler/main.c, test.c); zero remaining `MK_VERSION` callers 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 (`--peers` and 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`, and `xce_scheduler --version` all print commit id + build time and exit; all startup log branches for both daemons are byte-identical to their own `--version` output - _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_ diff --git a/src/controller/main.c b/src/controller/main.c index bd891e34..b654cffd 100644 --- a/src/controller/main.c +++ b/src/controller/main.c @@ -19,8 +19,13 @@ int validate_cert_files(const char *cert_path, const char *key_path, static char doc[] = "xce_controller"; static char args_doc[] = ""; -const char* argp_program_version - = MK_VERSION("xce v0.0.1-", STR(VERSION_COMMIT_ID)); +/* build-time-versioning task 3.2: no longer a compile-time initializer -- + * argp reads this by pointer when --version/-V is processed, and + * xce_compose_version() (utils.h) is a runtime function, not a constant + * expression, so it cannot initialize a file-scope global. main() assigns + * this to a static buffer, computed via xce_compose_version(), before + * argp_parse() runs. See utils.h's NOTE FOR TASK 3.2 above MK_VERSION. */ +const char* argp_program_version = NULL; const char* argp_program_bug_address = BUG_EMAIL; /* Long-option key (above ASCII to avoid collisions with short opts) */ @@ -311,11 +316,23 @@ int main(int argc, char* argv[]) char key_file[XCE_MAXPATH_LEN + 1]; char cert_file[XCE_MAXPATH_LEN + 1]; char myname[XCE_MAXNAME_LEN + 1]; + /* build-time-versioning task 3.2: composed once, read by both + * argp_program_version (--version/-V) and the startup INFO log below -- + * never recomputed, so the two can never disagree (Req 2.2). static so + * it outlives this point in main() (contract documented on + * xce_compose_version() in utils.c). */ + static char version_buf[128]; /* Capture the NULL-terminated argv for self-restart BEFORE argp_parse so a * later demotion/rejoin convergence can execv() the same image (Req 1.3). */ g_saved_argv = argv; + /* Must run before argp_parse(): argp reads argp_program_version by + * pointer when --version/-V is processed. */ + xce_compose_version(version_buf, sizeof(version_buf), "xce v0.0.1-", + STR(VERSION_COMMIT_ID), STR(VERSION_BUILD_TIME)); + argp_program_version = version_buf; + memset(&arguments, 0x0, sizeof(arguments)); arguments.conf_keep_n = 2; arguments.conf_stale_timeout = 86400; @@ -362,7 +379,7 @@ int main(int argc, char* argv[]) arguments.myname = myname; } - INFO("My name is %s", arguments.myname); + INFO("My name is %s, version %s", arguments.myname, version_buf); const char *certdir = arguments.certdir; char certdir_buf[XCE_MAXPATH_LEN + 1]; diff --git a/src/controller/testsuite/test.c b/src/controller/testsuite/test.c index 43920b4e..4c55edfa 100644 --- a/src/controller/testsuite/test.c +++ b/src/controller/testsuite/test.c @@ -658,8 +658,13 @@ static int break_out = 0; static char doc[] = "xce_test"; static char args_doc[] = ""; -const char* argp_program_version - = MK_VERSION("xce v0.0.1-", STR(VERSION_COMMIT_ID)); +/* build-time-versioning task 3.2: no longer a compile-time initializer -- + * argp reads this by pointer when --version/-V is processed, and + * xce_compose_version() (utils.h) is a runtime function, not a constant + * expression, so it cannot initialize a file-scope global. main() assigns + * this to a static buffer, computed via xce_compose_version(), before + * argp_parse() runs. See utils.h's NOTE FOR TASK 3.2 above MK_VERSION. */ +const char* argp_program_version = NULL; const char* argp_program_bug_address = BUG_EMAIL; struct arguments { @@ -4382,8 +4387,19 @@ int main(int argc, char* argv[]) char* loglevel = "info"; int logsize = 64 * 1024 * 1024; int logcount = 10; + /* build-time-versioning task 3.2: xce_test's own --version/-V output + * (via argp_program_version) -- static so it outlives this point in + * main() (contract documented on xce_compose_version() in utils.c). */ + static char version_buf[128]; memset(&arguments, 0x0, sizeof(arguments)); + + /* Must run before argp_parse(): argp reads argp_program_version by + * pointer when --version/-V is processed. */ + xce_compose_version(version_buf, sizeof(version_buf), "xce v0.0.1-", + STR(VERSION_COMMIT_ID), STR(VERSION_BUILD_TIME)); + argp_program_version = version_buf; + argp_parse(&argp, argc, argv, 0, 0, &arguments); if (arguments.verbose != 0) { diff --git a/src/scheduler/src/main.c b/src/scheduler/src/main.c index 153f3de5..be2499ad 100644 --- a/src/scheduler/src/main.c +++ b/src/scheduler/src/main.c @@ -22,8 +22,13 @@ extern int scheduler_req_handler(xce_request_t*, void*); static char doc[] = "xce_scheduler"; static char args_doc[] = ""; -const char* argp_program_version - = MK_VERSION("xce v0.0.0.1-", STR(VERSION_COMMIT_ID)); +/* build-time-versioning task 3.2: no longer a compile-time initializer -- + * argp reads this by pointer when --version/-V is processed, and + * xce_compose_version() (utils.h) is a runtime function, not a constant + * expression, so it cannot initialize a file-scope global. main() assigns + * this to a static buffer, computed via xce_compose_version(), before + * argp_parse() runs. See utils.h's NOTE FOR TASK 3.2 above MK_VERSION. */ +const char* argp_program_version = NULL; const char* argp_program_bug_address = BUG_EMAIL; /* Long-option identifiers (beyond ASCII range to avoid collisions with the @@ -224,9 +229,21 @@ int main(int argc, char* argv[]) xce_client_config_t config; xce_cluster_config_t cluster_cfg = { 0 }; const xce_cluster_config_t* cluster_cfg_ptr = NULL; + /* build-time-versioning task 3.2: composed once, read by both + * argp_program_version (--version/-V) and the startup INFO log below -- + * never recomputed, so the two can never disagree (Req 2.2). static so + * it outlives this point in main() (contract documented on + * xce_compose_version() in utils.c). */ + static char version_buf[128]; memset(&arguments, 0x0, sizeof(arguments)); + /* Must run before argp_parse(): argp reads argp_program_version by + * pointer when --version/-V is processed. */ + xce_compose_version(version_buf, sizeof(version_buf), "xce v0.0.0.1-", + STR(VERSION_COMMIT_ID), STR(VERSION_BUILD_TIME)); + argp_program_version = version_buf; + argp_parse(&argp, argc, argv, 0, 0, &arguments); if (arguments.logfile != NULL) { @@ -374,11 +391,12 @@ int main(int argc, char* argv[]) "only <=%d validated for HA", cluster_cfg.candidates_count, N_MAX_MASTERS); } - INFO("HA cluster config: %d candidate(s), identity=%s", - cluster_cfg.candidates_count, cluster_cfg.identity); + INFO("HA cluster config: %d candidate(s), identity=%s, version %s", + cluster_cfg.candidates_count, cluster_cfg.identity, version_buf); cluster_cfg_ptr = &cluster_cfg; } else { - INFO("No --peers flag; using single-controller path"); + INFO("No --peers flag; using single-controller path, version %s", + version_buf); } init_config(&config);