commit 441521acb327ce5a87605bd3c6ba5ec60b743f4a
parent 41f3b2c6175001fb9e8a0656ec2dec69dbdb5a38
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:13:30 -0700
driver: add bounded suggestions targets and version discovery
Diffstat:
6 files changed, 221 insertions(+), 13 deletions(-)
diff --git a/driver/cmd/run.c b/driver/cmd/run.c
@@ -574,8 +574,7 @@ static int run_parse(int argc, char** argv, RunOptions* o) {
return 1;
}
if (driver_target_from_triple(argv[i], &o->target) != 0) {
- driver_errf(RUN_TOOL, "unrecognized target triple: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(argv[i])));
+ driver_err_unknown_target(RUN_TOOL, argv[i]);
return 1;
}
continue;
diff --git a/driver/driver.h b/driver/driver.h
@@ -66,6 +66,7 @@ int driver_lz4c(int argc, char** argv);
int driver_disas(int argc, char** argv);
int driver_mc(int argc, char** argv);
int driver_gram(int argc, char** argv);
+int driver_targets(int argc, char** argv);
/* Per-tool help printers. Write a multi-section help text to stdout and
* return. The tool entry-points call these when invoked with no args, -h,
@@ -112,6 +113,7 @@ void driver_help_lz4c(void);
void driver_help_disas(void);
void driver_help_mc(void);
void driver_help_gram(void);
+void driver_help_targets(void);
/* Multi-call top-level help (`kit`, `kit -h`, `kit --help`,
* `kit help`). Lists each tool with a one-line summary and explains
@@ -166,6 +168,10 @@ typedef struct KitReleaseKey {
/* Borrow the embedded release public-key set; *count receives its length. */
const KitReleaseKey* driver_release_keys(unsigned* count);
+/* Borrow the compiled stable channel-index URL. An empty string means this
+ * development build intentionally has no default channel. */
+const char* driver_release_index_url(void);
+
/* 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
@@ -225,6 +231,11 @@ int driver_target_to_triple(KitTargetSpec target, char* buf, size_t cap);
int driver_arch_from_name(const char* name, KitArchKind* arch_out,
uint8_t* ptr_size_out);
+/* Diagnose an invalid target against the public target-profile registry. A
+ * close authoritative triple/selector is suggested; otherwise the diagnostic
+ * points to `kit targets`. This never changes the caller's status or input. */
+void driver_err_unknown_target(const char* tool, const char* value);
+
/* Whether `path` is a compilable source file: a path some registered frontend
* claims (via the canonical extension registry, case-insensitively), excluding
* C headers. The C frontend registers ".h" so language-for-path can identify
@@ -249,6 +260,20 @@ uint64_t driver_epoch_from_env(void);
* Returns 0 on success (value in *out), nonzero on a malformed string. */
int driver_parse_u64(const char* s, uint64_t* out);
+typedef struct DriverSuggestion {
+ const char* value;
+ uint16_t distance;
+} DriverSuggestion;
+
+/* Return up to out_cap close authoritative values, ordered by edit distance
+ * with registry order as the stable tie-break. Work and storage are bounded:
+ * inputs/candidates longer than 64 bytes are ignored and no allocation occurs.
+ * Exact values are not returned; callers remain responsible for validation and
+ * must never treat a suggestion as an autocorrection. */
+size_t driver_suggest_values(const char* input, const char* const* candidates,
+ size_t candidate_count, DriverSuggestion* out,
+ size_t out_cap);
+
/* Map one hex digit (0-9, a-f, A-F) to its 0..15 value, or -1 if `c` is not a
* hex digit. */
int driver_hex_nibble(char c);
diff --git a/driver/env/common.c b/driver/env/common.c
@@ -392,12 +392,65 @@ int driver_parse_u64(const char* s, uint64_t* out) {
for (; *s; ++s) {
int d = driver_hex_nibble(*s);
if (d < 0 || d >= base) return 1;
+ if (v > (UINT64_MAX - (uint64_t)d) / (uint64_t)base) return 1;
v = v * (uint64_t)base + (uint64_t)d;
}
*out = v;
return 0;
}
+static uint16_t driver_edit_distance(const char* a, size_t an, const char* b,
+ size_t bn) {
+ uint16_t prev[65];
+ uint16_t curr[65];
+ size_t i, j;
+ for (j = 0; j <= bn; ++j) prev[j] = (uint16_t)j;
+ for (i = 1; i <= an; ++i) {
+ curr[0] = (uint16_t)i;
+ for (j = 1; j <= bn; ++j) {
+ uint16_t del = (uint16_t)(prev[j] + 1u);
+ uint16_t ins = (uint16_t)(curr[j - 1u] + 1u);
+ uint16_t sub = (uint16_t)(prev[j - 1u] + (a[i - 1u] != b[j - 1u]));
+ uint16_t best = del < ins ? del : ins;
+ if (sub < best) best = sub;
+ curr[j] = best;
+ }
+ for (j = 0; j <= bn; ++j) prev[j] = curr[j];
+ }
+ return prev[bn];
+}
+
+size_t driver_suggest_values(const char* input, const char* const* candidates,
+ size_t candidate_count, DriverSuggestion* out,
+ size_t out_cap) {
+ size_t input_len, i, count = 0;
+ if (!input || !candidates || !out || out_cap == 0) return 0;
+ input_len = driver_strlen(input);
+ if (input_len == 0 || input_len > 64) return 0;
+ for (i = 0; i < candidate_count; ++i) {
+ const char* candidate = candidates[i];
+ size_t candidate_len, max_len, pos;
+ uint16_t distance, threshold;
+ if (!candidate) continue;
+ candidate_len = driver_strlen(candidate);
+ if (candidate_len == 0 || candidate_len > 64) continue;
+ distance = driver_edit_distance(input, input_len, candidate, candidate_len);
+ if (distance == 0) continue;
+ max_len = input_len > candidate_len ? input_len : candidate_len;
+ threshold = (uint16_t)(max_len <= 2 ? 1 : (max_len <= 8 ? 2 : 3));
+ if (distance > threshold) continue;
+
+ pos = 0;
+ while (pos < count && out[pos].distance <= distance) ++pos;
+ if (pos >= out_cap) continue;
+ if (count < out_cap) ++count;
+ for (size_t move = count - 1u; move > pos; --move) out[move] = out[move - 1u];
+ out[pos].value = candidate;
+ out[pos].distance = distance;
+ }
+ return count;
+}
+
char* driver_path_join(DriverEnv* env, const char* a, const char* b,
size_t* out_size) {
size_t al = a ? driver_strlen(a) : 0u;
diff --git a/driver/main.c b/driver/main.c
@@ -25,6 +25,9 @@ typedef struct DriverToolDesc {
unsigned groups;
} DriverToolDesc;
+static int driver_version_command(int argc, char** argv);
+static void driver_help_version_command(void);
+
static const DriverToolDesc driver_tools[] = {
#if KIT_TOOL_CC_ENABLED
{"cc", driver_cc, NULL, driver_help_cc,
@@ -199,6 +202,12 @@ static const DriverToolDesc driver_tools[] = {
{"gram", driver_gram, NULL, driver_help_gram,
"Generate a C parser/lexer from an EBNF grammar", DRIVER_GROUP_OTHER},
#endif
+#if KIT_TOOL_TARGETS_ENABLED
+ {"targets", driver_targets, NULL, driver_help_targets,
+ "List and inspect compiled target profiles", DRIVER_GROUP_OTHER},
+#endif
+ {"version", driver_version_command, NULL, driver_help_version_command,
+ "Print canonical release/build/host identity", 0},
#if KIT_TOOL_UPDATE_ENABLED
{"update", driver_update, NULL, driver_help_update,
"Verify and install a newer kit; manage installed versions",
@@ -276,6 +285,24 @@ static int print_tool_help(const char* name) {
return -1;
}
+static void report_unknown_tool(const char* name) {
+ const char* candidates[64];
+ DriverSuggestion suggestions[3];
+ size_t candidate_count = 0;
+ size_t nsuggestions;
+ unsigned i;
+ for (i = 0; i < driver_tool_count() && candidate_count < 64; ++i)
+ candidates[candidate_count++] = driver_tools[i].name;
+ nsuggestions = driver_suggest_values(name, candidates, candidate_count,
+ suggestions, 3);
+ if (nsuggestions)
+ driver_errf("kit", "no such tool: %s; did you mean '%s'?", name,
+ suggestions[0].value);
+ else
+ driver_errf("kit", "no such tool: %s; run `kit --help` to list tools",
+ name);
+}
+
int driver_is_help_flag(const char* arg) {
if (!arg) return 0;
return driver_streq(arg, "--help") || driver_streq(arg, "-help");
@@ -312,6 +339,37 @@ void driver_print_version(const char* tool) {
kit_host_triple());
}
+static void driver_help_version_command(void) {
+ driver_printf(
+ "kit version — print release/build/host identity\n"
+ "\n"
+ "USAGE\n"
+ " kit version\n"
+ " kit --version\n"
+ "\n"
+ "Both forms print byte-identical canonical version output. `version` is\n"
+ "an internal discovery command and is not installed as a multicall alias.\n"
+ "\n"
+ "OPTIONS\n"
+ " -h, --help show this help and exit\n"
+ "\n"
+ "EXIT CODES\n"
+ " 0 success 2 bad usage\n");
+}
+
+static int driver_version_command(int argc, char** argv) {
+ if (driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_version_command();
+ return 0;
+ }
+ if (argc != 1) {
+ driver_errf("version", "unexpected argument: %s", argv[1]);
+ return 2;
+ }
+ driver_print_version(NULL);
+ return 0;
+}
+
void driver_help_top(void) {
unsigned i;
driver_printf(
@@ -358,11 +416,28 @@ void driver_help_top(void) {
int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) {
const char* name;
+ char normalized_name[64];
int rc;
if (argc < 1) return 2;
name = driver_basename(argv[0]);
+ /* Windows aliases use the executable suffix required by CreateProcess and
+ * PATHEXT (cc.exe, ld.exe, ...), while the centralized tool table stores
+ * suffix-free command names. Normalize only the final argv[0] component;
+ * ordinary command arguments such as `kit install cc.exe` stay strict. */
+ {
+ size_t n = driver_strlen(name);
+ if (n > 4u && n - 4u < sizeof(normalized_name) && name[n - 4u] == '.' &&
+ (name[n - 3u] == 'e' || name[n - 3u] == 'E') &&
+ (name[n - 2u] == 'x' || name[n - 2u] == 'X') &&
+ (name[n - 1u] == 'e' || name[n - 1u] == 'E')) {
+ driver_memcpy(normalized_name, name, n - 4u);
+ normalized_name[n - 4u] = '\0';
+ name = normalized_name;
+ }
+ }
+
/* Multi-call form first: argv[0] is the tool name (e.g. installed as
* a `cc` symlink). Help inside the tool is gated by the tool itself. */
rc = dispatch(name, argc, argv, ext);
@@ -380,7 +455,7 @@ 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")) {
+ if (driver_streq(argv[1], "--version")) {
driver_print_version(NULL);
return 0;
}
@@ -391,8 +466,7 @@ int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) {
return 0;
}
if (print_tool_help(argv[2]) == 0) return 0;
- driver_errf("kit", "no such tool: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(argv[2])));
+ report_unknown_tool(argv[2]);
return 2;
}
@@ -404,9 +478,7 @@ int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) {
if (rc != -1) return rc;
}
- driver_errf("kit", "no such tool: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(argv[1])));
- driver_help_top();
+ report_unknown_tool(argv[1]);
return 2;
}
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -61,6 +61,7 @@ DRIVER_TOOL_SRCS = \
$(call tool-cmd,DISAS,disas) \
$(call tool-cmd,MC,mc) \
$(call tool-cmd,GRAM,gram) \
+ $(call tool-cmd,TARGETS,targets) \
$(call tool-cmd,UPDATE,update)
DRIVER_SRCS += $(sort $(DRIVER_TOOL_SRCS))
diff --git a/test/audit/release/modules/standalone.sh b/test/audit/release/modules/standalone.sh
@@ -15,6 +15,10 @@ audit_standalone_stage() {
AUDIT_STANDALONE_SOURCE_DIR="$CASE_DIR/work/read only source"
AUDIT_STANDALONE_SENTINELS=$CASE_DIR/work/sentinel-bin
AUDIT_STANDALONE_PATH=$AUDIT_STANDALONE_RELOC/bin:$AUDIT_STANDALONE_SENTINELS:/usr/bin:/bin
+ case "$(uname -s 2>/dev/null || printf unknown)" in
+ MINGW*|MSYS*|CYGWIN*) AUDIT_STANDALONE_CC_ALIAS=$CASE_DIR/install/bin/cc.exe ;;
+ *) AUDIT_STANDALONE_CC_ALIAS=$CASE_DIR/install/bin/cc ;;
+ esac
{
printf 'cp -R %s %s\n' "$HARNESS_DIST" "$AUDIT_STANDALONE_RELOC"
@@ -116,7 +120,7 @@ audit_standalone_path_space() {
--all "$CASE_DIR/install/bin"
audit_expect_exit 0
audit_expect_stderr_empty
- audit_expect_file_exists "$CASE_DIR/install/bin/cc"
+ audit_expect_file_exists "$AUDIT_STANDALONE_CC_ALIAS"
audit_case_step_exec installed-alias /usr/bin/env \
"PATH=$CASE_DIR/install/bin:$AUDIT_STANDALONE_SENTINELS:/usr/bin:/bin" cc \
@@ -182,7 +186,7 @@ audit_standalone_post_install_move() {
--all "$CASE_DIR/install/bin"
audit_expect_exit 0
audit_expect_stderr_empty
- audit_expect_file_exists "$CASE_DIR/install/bin/cc"
+ audit_expect_file_exists "$AUDIT_STANDALONE_CC_ALIAS"
AUDIT_STANDALONE_MOVED="$CASE_DIR/work/Moved Kit Distribution"
printf 'mv %s %s\n' "$AUDIT_STANDALONE_RELOC" "$AUDIT_STANDALONE_MOVED" \
@@ -193,17 +197,71 @@ audit_standalone_post_install_move() {
return
fi
- audit_case_step_exec installed-after-move /usr/bin/env \
+ # Invoke the managed path itself here. A dangling POSIX symlink is skipped
+ # during PATH lookup, which could accidentally run an unrelated host `cc`
+ # and obscure the relocation result this case is meant to measure.
+ audit_case_step_exec installed-before-repair "$AUDIT_STANDALONE_CC_ALIAS" --help
+ case "$(uname -s 2>/dev/null || printf unknown)" in
+ MINGW*|MSYS*|CYGWIN*)
+ # Windows installs hard links. The executable entry survives the
+ # move; --force still refreshes the managed set below and is the
+ # documented repair operation.
+ audit_expect_exit 0
+ audit_expect_stdout_regex '^kit cc'
+ audit_expect_stderr_empty
+ ;;
+ *)
+ # POSIX installs absolute symlinks, which dangle until repaired.
+ audit_expect_exit_one_of 126,127
+ audit_expect_stderr_nonempty
+ ;;
+ esac
+ audit_expect_file_empty "$CASE_DIR/artifacts/sentinel.log"
+
+ audit_case_step_exec repair "$AUDIT_STANDALONE_MOVED/bin/kit" install \
+ --all --force "$CASE_DIR/install/bin"
+ audit_expect_exit 0
+ audit_expect_stderr_empty
+
+ audit_case_step_exec installed-after-repair /usr/bin/env \
"PATH=$CASE_DIR/install/bin:$AUDIT_STANDALONE_SENTINELS:/usr/bin:/bin" cc --help
audit_expect_exit 0
audit_expect_stdout_regex '^kit cc'
audit_expect_stderr_empty
audit_expect_file_empty "$CASE_DIR/artifacts/sentinel.log"
- audit_case_step_exec direct-after-move "$AUDIT_STANDALONE_MOVED/bin/kit" --help
+ # The repaired alias must rediscover both the moved support tree and the
+ # native SDK. Keep the distribution read-only so this also catches a
+ # regression that tries to publish runtime/cache artifacts beside `kit`.
+ chmod -R a-w "$AUDIT_STANDALONE_MOVED" \
+ >> "$CASE_DIR/setup.stdout" 2>> "$CASE_DIR/setup.stderr"
+ audit_case_step_exec installed-compile-after-repair /usr/bin/env \
+ "PATH=$CASE_DIR/install/bin:$AUDIT_STANDALONE_SENTINELS:/usr/bin:/bin" \
+ cc "$AUDIT_STANDALONE_SOURCE_DIR/native sdk malloc.c" \
+ -o "$CASE_DIR/artifacts/repaired installed program"
+ audit_expect_exit 0
+ audit_expect_stderr_empty
+ audit_expect_file_nonempty "$CASE_DIR/artifacts/repaired installed program"
+ if [ -f "$CASE_DIR/artifacts/repaired installed program" ]; then
+ audit_case_step_exec installed-run-after-repair \
+ "$CASE_DIR/artifacts/repaired installed program"
+ audit_expect_exit 0
+ audit_expect_stderr_empty
+ fi
+ audit_expect_file_empty "$CASE_DIR/artifacts/sentinel.log"
+
+ audit_case_step_exec direct-after-move "$AUDIT_STANDALONE_MOVED/bin/kit" cc \
+ "$AUDIT_STANDALONE_SOURCE_DIR/native sdk malloc.c" \
+ -o "$CASE_DIR/artifacts/direct moved program"
audit_expect_exit 0
- audit_expect_stdout_nonempty
audit_expect_stderr_empty
+ audit_expect_file_nonempty "$CASE_DIR/artifacts/direct moved program"
+ if [ -f "$CASE_DIR/artifacts/direct moved program" ]; then
+ audit_case_step_exec direct-run-after-move \
+ "$CASE_DIR/artifacts/direct moved program"
+ audit_expect_exit 0
+ audit_expect_stderr_empty
+ fi
audit_case_finish
}