kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mdriver/cmd/run.c | 3+--
Mdriver/driver.h | 25+++++++++++++++++++++++++
Mdriver/env/common.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/main.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mmk/driver_srcs.mk | 1+
Mtest/audit/release/modules/standalone.sh | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
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 }