commit 273bce54805eb3c219b67f86b86d704deb574330
parent fdf54b7c184c74cc10d483882d6110dd809dee69
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 12:46:59 -0700
selfdist: version backbone (VERSION + driver/version.c + --version everywhere)
Diffstat:
7 files changed, 141 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -159,6 +159,14 @@ $(BUILD_DIR)/driver/%.o: driver/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) $(DEPFLAGS) -c $< -o $@
+# version.o carries the injected VERSION / build-id / host-triple. It depends on
+# the dedicated version sentinel (not BUILD_CONFIG) so only this one TU rebuilds
+# when those change. The defines append to DRIVER_CFLAGS for this target only
+# (the pattern rule above uses $(DRIVER_CFLAGS) directly). See mk/flags.mk.
+$(BUILD_DIR)/driver/version.o $(BUILD_DIR)/custom-driver/version.o: $(VERSION_CONFIG)
+$(BUILD_DIR)/driver/version.o $(BUILD_DIR)/custom-driver/version.o: \
+ DRIVER_CFLAGS += $(KIT_VERSION_DEFS)
+
$(BUILD_DIR)/driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(DEPFLAGS) -c $< -o $@
diff --git a/VERSION b/VERSION
@@ -0,0 +1 @@
+2026.6.0
diff --git a/driver/driver.h b/driver/driver.h
@@ -109,6 +109,31 @@ void driver_help_gram(void);
* the multi-call dispatch. Writes to stdout. */
void driver_help_top(void);
+/* ----------------------------------------------------------------------
+ * Version / build provenance (driver/version.c)
+ *
+ * The single source of truth for the version string is the committed top-level
+ * VERSION file (CalVer YYYY.MINOR.PATCH); the build id (short git hash, with a
+ * "-dirty" suffix or "unknown" when unavailable) and host triple are injected
+ * at build time. These are driver-internal accessors, not libkit public API.
+ * See doc/plan/SELFDIST.md.
+ * ---------------------------------------------------------------------- */
+const char* kit_version_string(void); /* e.g. "2026.6.0" */
+const char* kit_build_id(void); /* e.g. "a3f91c2" / "unknown" */
+const char* kit_host_triple(void); /* e.g. "aarch64-macos" */
+
+/* Print the shared version banner to stdout. A NULL tool prints
+ * "kit <ver> (<build>, <triple>)"
+ * and a non-NULL tool prints
+ * "kit <tool> <ver> (<build>, <triple>)"
+ * so `kit --version` and `cc --version` / `ld --version` answer uniformly. */
+void driver_print_version(const char* tool);
+
+/* Scan argv[1..argc-1] for a "--version" request, returning 1 on a hit. Stops
+ * at "--" (like driver_argv_wants_help) so a flag forwarded to a JITed program
+ * or emulated guest is never hijacked. */
+int driver_argv_wants_version(int argc, char** argv);
+
/* Tool grouping, used by `install` to pick a default set without
* duplicating the tool list. The centralized table in main.c tags every
* row; the groups with a non-zero bit make up the default install set
diff --git a/driver/main.c b/driver/main.c
@@ -234,6 +234,15 @@ static int dispatch(const char* name, int argc, char** argv,
const KitDriverExtension* ext) {
const DriverToolDesc* tool = find_tool(name);
if (tool) {
+ /* `--version` is answered uniformly for every tool here, before the tool's
+ * own argument parser runs, so it works for both the symlink form
+ * (argv[0] == "cc ... --version") and the subcommand form
+ * ("kit cc ... --version", which also routes through dispatch). The scan
+ * stops at "--", so a flag forwarded to a JITed program / guest is safe. */
+ if (driver_argv_wants_version(argc, argv)) {
+ driver_print_version(tool->name);
+ return 0;
+ }
if (tool->main_ex) return tool->main_ex(argc, argv, ext);
return tool->main(argc, argv);
}
@@ -269,6 +278,24 @@ int driver_argv_wants_help(int argc, char** argv, int accept_short_h) {
return 0;
}
+int driver_argv_wants_version(int argc, char** argv) {
+ int i;
+ for (i = 1; i < argc; ++i) {
+ if (driver_streq(argv[i], "--")) break; /* see driver_argv_wants_help */
+ if (driver_streq(argv[i], "--version")) return 1;
+ }
+ return 0;
+}
+
+void driver_print_version(const char* tool) {
+ if (tool)
+ driver_printf("kit %s %s (%s, %s)\n", tool, kit_version_string(),
+ kit_build_id(), kit_host_triple());
+ else
+ driver_printf("kit %s (%s, %s)\n", kit_version_string(), kit_build_id(),
+ kit_host_triple());
+}
+
void driver_help_top(void) {
unsigned i;
driver_printf(
@@ -323,6 +350,11 @@ int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) {
return 0;
}
+ if (driver_streq(argv[1], "--version") || driver_streq(argv[1], "version")) {
+ driver_print_version(NULL);
+ return 0;
+ }
+
if (driver_streq(argv[1], "help")) {
if (argc < 3) {
driver_help_top();
diff --git a/driver/version.c b/driver/version.c
@@ -0,0 +1,27 @@
+/* kit version / build provenance.
+ *
+ * The three strings below are injected once per `make` invocation into this
+ * single translation unit (see mk/flags.mk + the version.o rule in the
+ * Makefile), so a version bump or a new commit recompiles only version.o and
+ * not the world. The compiled-in defaults keep a standalone compile working.
+ *
+ * These are driver-internal provenance accessors, not libkit public API; they
+ * live in driver.h, not include/kit/. The single source of truth for the
+ * version string is the committed top-level VERSION file (CalVer
+ * YYYY.MINOR.PATCH). See doc/plan/SELFDIST.md. */
+
+#include "driver.h"
+
+#ifndef KIT_VERSION_STR
+#define KIT_VERSION_STR "0.0.0"
+#endif
+#ifndef KIT_BUILD_ID
+#define KIT_BUILD_ID "unknown"
+#endif
+#ifndef KIT_HOST_TRIPLE
+#define KIT_HOST_TRIPLE "unknown"
+#endif
+
+const char* kit_version_string(void) { return KIT_VERSION_STR; }
+const char* kit_build_id(void) { return KIT_BUILD_ID; }
+const char* kit_host_triple(void) { return KIT_HOST_TRIPLE; }
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -20,7 +20,8 @@ DRIVER_ENV_SRCS_EFFECTIVE := $(filter-out driver/env/posix_dbg.c $(DRIVER_ENV_UC
endif
endif
-DRIVER_SRCS = driver/main.c driver/lib/target.c $(DRIVER_ENV_SRCS_EFFECTIVE)
+DRIVER_SRCS = driver/main.c driver/version.c driver/lib/target.c \
+ $(DRIVER_ENV_SRCS_EFFECTIVE)
# One entry per subcommand. cc.c serves both `cc` and `check`; the final
# $(sort) below dedupes it. The cas/pkg tools additionally pull in
diff --git a/mk/flags.mk b/mk/flags.mk
@@ -78,6 +78,52 @@ $(BUILD_CONFIG): FORCE
} > $@.tmp
@if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi
+# --- kit version / build-id (single source of truth: top-level VERSION) -----
+# Captured once per `make` invocation and injected only into driver/version.o
+# (via KIT_VERSION_DEFS + the version.o rule in the Makefile), so a version bump
+# or new commit recompiles exactly one TU rather than the world. The build id is
+# the short git hash with a "-dirty" suffix when the tree has uncommitted
+# tracked changes, or "unknown" without git. KIT_HOST_TRIPLE defaults to a
+# canonical name for the build host and is overridden by the cross/release
+# pipeline. No __DATE__/__TIME__ anywhere, and the values are stable for a given
+# tree, so the self-host byte-identity gate is preserved. See doc/plan/SELFDIST.md.
+KIT_VERSION := $(strip $(shell cat VERSION 2>/dev/null))
+ifeq ($(KIT_VERSION),)
+KIT_VERSION := 0.0.0
+endif
+KIT_GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>/dev/null)
+KIT_GIT_DIRTY := $(shell git diff --quiet HEAD 2>/dev/null || printf -- '-dirty')
+KIT_BUILD_ID := $(if $(KIT_GIT_HASH),$(KIT_GIT_HASH)$(KIT_GIT_DIRTY),unknown)
+# Canonical host triple (matches the scripts/hosted.sh selector naming used by
+# the release index + artifact names), derived in pure make from HOST_ARCH /
+# HOST_OS (mk/env.mk). Overridden outright by the cross/release pipeline.
+KIT_TRIPLE_ARCH := $(HOST_ARCH)
+ifeq ($(HOST_ARCH),rv64)
+KIT_TRIPLE_ARCH := riscv64
+endif
+KIT_TRIPLE_OS := $(HOST_OS)
+ifeq ($(HOST_OS),darwin)
+KIT_TRIPLE_OS := macos
+else ifeq ($(HOST_OS),linux)
+KIT_TRIPLE_OS := linux-gnu
+endif
+KIT_HOST_TRIPLE ?= $(KIT_TRIPLE_ARCH)-$(KIT_TRIPLE_OS)
+KIT_VERSION_DEFS = -DKIT_VERSION_STR='"$(KIT_VERSION)"' \
+ -DKIT_BUILD_ID='"$(KIT_BUILD_ID)"' \
+ -DKIT_HOST_TRIPLE='"$(KIT_HOST_TRIPLE)"'
+
+# A dedicated sentinel so only version.o (which lists it as a prerequisite)
+# rebuilds when the version, commit, or host triple changes.
+VERSION_CONFIG = $(BUILD_DIR)/.version-config
+$(VERSION_CONFIG): FORCE
+ @mkdir -p $(dir $@)
+ @{ \
+ printf '%s\n' 'KIT_VERSION=$(KIT_VERSION)'; \
+ printf '%s\n' 'KIT_BUILD_ID=$(KIT_BUILD_ID)'; \
+ printf '%s\n' 'KIT_HOST_TRIPLE=$(KIT_HOST_TRIPLE)'; \
+ } > $@.tmp
+ @if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi
+
# Freestanding objects must not see host SDK/libc headers. Homebrew clang can
# also inject a configured sysroot before command-line flags; use
# --no-default-config when the selected compiler supports it, but omit it for