kit

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

commit 6ee2d00783363da6a124fe1c01b3899257cbd1da
parent dc1854de22635fd2f86aabe8947012f22b496d58
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  2 Jun 2026 16:23:08 -0700

driver: auto-resolve hosted sysroot when targeting the host

Hosted libc (cc -lc / run --script) no longer requires an explicit
--sysroot for native builds. The resolver is generalized from a single
pre-flattened sysroot to separate include-dir and library-dir search
lists, fed by one of two producers funnelled through the single
driver_hosted_resolve chokepoint:

  - explicit --sysroot / KIT_SYSROOT, expanded per target-OS layout; or
  - a per-OS host probe (driver_default_hosted_dirs) that reads the live
    system: the macOS CLT/Xcode SDK, the Linux multiarch dirs
    (/usr/lib/<triple>, /lib/<triple>, ...), or the FreeBSD base dirs.

The probe is gated on the target matching the host OS *and* arch, so it
never fires for a cross-compile; cross builds still ask for --sysroot.
Resolvers search the libdir list per crt/libc file (a single sysroot
libdir is bit-identical to the old behavior) and promote existing
incdirs into the plan as owned copies.

Also:
  - add a FreeBSD hosted profile (interp /libexec/ld-elf.so.1, libc.so.7
    bound directly, __FreeBSD__=14); UNTESTED on the dev host, pending a
    base.txz harness.
  - standardize the env override to KIT_SYSROOT on every OS; drop SDKROOT
    and KIT_MINGW_SYSROOT.
  - make `cc -print-resource-dir`, `-print-search-dirs`, and the new
    `-print-sysroot` report the actually-resolved dirs (shared via the
    extracted driver_hosted_dirs_resolve); -print-search-dirs gains a
    kit `includes:` line.

Tests: test-driver-cc 58/0 (new cc-hosted-* and cc-print-* cases);
Windows + glibc/musl explicit-sysroot paths verified behavior-preserving.

Diffstat:
Mdriver/cmd/cc.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mdriver/cmd/run.c | 3++-
Mdriver/env.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/common.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/freebsd.c | 14++++++++++++++
Mdriver/env/linux.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/macos.c | 36++++++++++++++++++++++++++++++++++++
Mdriver/env/windows.c | 11+++++++++++
Mdriver/lib/hosted.c | 597+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdriver/lib/hosted.h | 11+++++++++++
Mtest/coff/README.md | 2+-
Mtest/coff/windows-system-dlls-smoke.sh | 4++--
Mtest/coff/windows-ucrt-hosted-smoke.sh | 4++--
Mtest/driver/run.sh | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 849 insertions(+), 232 deletions(-)

diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -62,6 +62,7 @@ typedef enum CcProbeKind { CC_PROBE_PRINT_LIBGCC_FILE_NAME, CC_PROBE_PRINT_MULTI_OS_DIRECTORY, CC_PROBE_PRINT_RESOURCE_DIR, + CC_PROBE_PRINT_SYSROOT, CC_PROBE_DUMPMACHINE, CC_PROBE_DUMPVERSION, CC_PROBE_DUMPSPECS, @@ -179,7 +180,7 @@ typedef struct CcOptions { const char** lib_search_paths; uint32_t nlib_search_paths; /* Owned `<sysroot>/lib` slot appended for Windows targets when a - * sysroot is in effect (cmdline or KIT_MINGW_SYSROOT). */ + * sysroot is in effect (cmdline or KIT_SYSROOT). */ char* owned_sysroot_lib_dir; size_t owned_sysroot_lib_dir_size; /* Pending -l names (resolved at end-of-parse). */ @@ -251,6 +252,10 @@ void driver_help_cc(void) { "SDK/sysroot\n" " --support-dir DIR kit support " "root\n" + " -print-search-dirs / -print-sysroot show the dirs " + "the\n" + " -print-resource-dir compiler will " + "search\n" " -S [options] input.c emit assembly " "(.s)\n" " --emit=c [options] input.c emit portable C " @@ -776,6 +781,15 @@ static int cc_set_probe(CcOptions* o, int kind, const char* arg) { return 0; } +/* Emit one element of a colon-separated search path. *first tracks whether a + * leading separator is needed; NULL/empty entries are skipped. */ +static void cc_print_path_elem(const char* dir, int* first) { + if (!dir || !dir[0]) return; + driver_printf("%s%.*s", *first ? "" : ":", + KIT_SLICE_ARG(kit_slice_cstr(dir))); + *first = 0; +} + static int cc_run_probe(CcOptions* o) { char triple[64]; if (driver_target_to_triple(o->target, triple, sizeof(triple)) != 0) { @@ -784,16 +798,60 @@ static int cc_run_probe(CcOptions* o) { } switch (o->probe_kind) { - case CC_PROBE_PRINT_SEARCH_DIRS: - driver_printf( - "install: %.*s\n", - KIT_SLICE_ARG(kit_slice_cstr(o->support_dir ? o->support_dir : ""))); + case CC_PROBE_PRINT_SEARCH_DIRS: { + DriverRuntimeSupport rt = {0}; + int have_rt = (driver_runtime_resolve(o->env, o->support_dir, + o->driver_path, &rt) == 0); + DriverHostedDirs dirs; + int have_dirs = 0; + uint32_t i; + int first; + /* Resolve the hosted include/library dirs directly: a probe run skips the + * parse-time hosted profile (no compile happens), so they are not yet in + * cf. Gated on hosted libc being engaged (-lc / sysroot). */ + if (o->wants_hosted_libc) { + DriverHostedRequest req = {0}; + req.env = o->env; + req.tool = CC_TOOL; + req.target = o->target; + req.sysroot = o->sysroot; + req.static_link = o->static_link; + req.link_inputs = 1; + have_dirs = (driver_hosted_dirs_resolve(&req, &dirs) == 0); + } + driver_printf("install: %.*s\n", + KIT_SLICE_ARG(kit_slice_cstr( + have_rt ? rt.support_root + : (o->support_dir ? o->support_dir : "")))); driver_printf("programs: =\n"); + /* libraries: hosted crt/libc search dirs, then user -L dirs, then the kit + * runtime dir holding libkit_rt.a. */ driver_printf("libraries: ="); - if (o->sysroot) - driver_printf("%.*s/lib", KIT_SLICE_ARG(kit_slice_cstr(o->sysroot))); + first = 1; + if (have_dirs) + for (i = 0; i < dirs.nlibdirs; ++i) + cc_print_path_elem(dirs.libdirs[i], &first); + for (i = 0; i < o->nlib_search_paths; ++i) + cc_print_path_elem(o->lib_search_paths[i], &first); + if (have_rt) cc_print_path_elem(rt.rt_root, &first); + driver_printf("\n"); + /* includes: user -I/-isystem, then the hosted system headers, then the + * freestanding runtime headers. (kit extension to GCC's output.) */ + driver_printf("includes: ="); + first = 1; + for (i = 0; i < o->cf.ninclude_dirs; ++i) + cc_print_path_elem(o->cf.include_dirs[i], &first); + for (i = 0; i < o->cf.nsystem_include_dirs; ++i) + cc_print_path_elem(o->cf.system_include_dirs[i], &first); + if (have_dirs) + for (i = 0; i < dirs.nincdirs; ++i) + cc_print_path_elem(dirs.incdirs[i], &first); + if (have_rt) cc_print_path_elem(rt.include_dir, &first); driver_printf("\n"); + if (have_dirs) driver_hosted_dirs_fini(&dirs); + if (have_rt) driver_runtime_support_fini(o->env, &rt); return 0; + } case CC_PROBE_PRINT_FILE_NAME: driver_printf( "%.*s\n", @@ -810,11 +868,43 @@ static int cc_run_probe(CcOptions* o) { case CC_PROBE_PRINT_MULTI_OS_DIRECTORY: driver_printf(".\n"); return 0; - case CC_PROBE_PRINT_RESOURCE_DIR: - driver_printf( - "%.*s\n", - KIT_SLICE_ARG(kit_slice_cstr(o->support_dir ? o->support_dir : ""))); + case CC_PROBE_PRINT_RESOURCE_DIR: { + /* clang convention: the resource-dir root, with builtin/freestanding + * headers under <resource-dir>/include. */ + DriverRuntimeSupport rt = {0}; + if (driver_runtime_resolve(o->env, o->support_dir, o->driver_path, &rt) == + 0) { + driver_printf("%.*s\n", KIT_SLICE_ARG(kit_slice_cstr(rt.rt_root))); + driver_runtime_support_fini(o->env, &rt); + } else { + driver_printf("%.*s\n", KIT_SLICE_ARG(kit_slice_cstr( + o->support_dir ? o->support_dir : ""))); + } + return 0; + } + case CC_PROBE_PRINT_SYSROOT: { + /* The effective sysroot root: command-line --sysroot or KIT_SYSROOT, else + * the native host probe when it resolves to a single tree (the macOS + * SDK). Empty for a multi-dir probe (Linux/FreeBSD) or a cross target + * with no sysroot, which have no single root. */ + DriverHostedRequest req = {0}; + DriverHostedDirs dirs; + req.env = o->env; + req.tool = CC_TOOL; + req.target = o->target; + req.sysroot = o->sysroot; + req.static_link = o->static_link; + req.link_inputs = 1; + if (driver_hosted_dirs_resolve(&req, &dirs) == 0) { + driver_printf( + "%.*s\n", + KIT_SLICE_ARG(kit_slice_cstr(dirs.root ? dirs.root : ""))); + driver_hosted_dirs_fini(&dirs); + } else { + driver_printf("\n"); + } return 0; + } case CC_PROBE_DUMPMACHINE: driver_printf("%.*s\n", KIT_SLICE_ARG(kit_slice_cstr(triple))); return 0; @@ -1007,7 +1097,7 @@ static int cc_apply_env(CcOptions* o) { * literal msvcrt.dll. Sysroot resolution order: * 1. -isysroot / --sysroot on the command line (already in * o->sysroot at this point); - * 2. KIT_MINGW_SYSROOT env var (e.g. .../x86_64-w64-mingw32). + * 2. KIT_SYSROOT env var (e.g. .../x86_64-w64-mingw32). * * No-op for non-Windows targets and for Windows when neither source * provides a sysroot — keeps existing tests untouched. The appended @@ -1023,7 +1113,7 @@ static int cc_append_windows_lib_dirs(CcOptions* o) { size_t off = 0; if (o->target.os != KIT_OS_WINDOWS) return 0; if (!sysroot || !sysroot[0]) { - sysroot = driver_getenv("KIT_MINGW_SYSROOT"); + sysroot = driver_getenv("KIT_SYSROOT"); if (!sysroot || !sysroot[0]) return 0; o->sysroot = sysroot; } @@ -1303,6 +1393,10 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { if (cc_set_probe(o, CC_PROBE_PRINT_RESOURCE_DIR, NULL) != 0) return 1; continue; } + if (driver_streq(a, "-print-sysroot")) { + if (cc_set_probe(o, CC_PROBE_PRINT_SYSROOT, NULL) != 0) return 1; + continue; + } if (driver_streq(a, "-dumpmachine")) { if (cc_set_probe(o, CC_PROBE_DUMPMACHINE, NULL) != 0) return 1; continue; diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -308,7 +308,8 @@ void driver_help_run(void) { " later token to the program as argv (an " "implicit\n" " `--` after FILE). Implies -lc (hosted libc),\n" - " since scripts are usually hosted; on macOS that\n" + " since scripts are usually hosted; on macOS " + "that\n" " still needs --sysroot for header resolution.\n" " Intended for `#!` script use:\n" " #!/usr/bin/env -S kit run --script\n" diff --git a/driver/env.h b/driver/env.h @@ -63,6 +63,65 @@ void driver_compiler_free(KitCompiler*); KitTarget driver_host_target(void); /* ---------------------------------------------------------------------- + * Hosted-libc search directories + * + * The hosted-libc resolver needs two sets of directories: include roots to add + * as system header search paths, and library roots to search for the C runtime + * objects and libc. They come from one of two producers -- expanding an + * explicit --sysroot/KIT_SYSROOT (portable, in driver/lib/hosted.c) or probing + * the live host (driver_default_hosted_dirs, below). The resolver copies what + * it needs into its plan, then releases the whole set with + * driver_hosted_dirs_fini; the strings stored here are transient scratch. + * + * DRIVER_HOSTED_MAX_INCDIRS must stay >= the plan's DRIVER_HOSTED_MAX_INCLUDES + * (driver/lib/hosted.h): every emitted incdir becomes one owned plan include. + * ---------------------------------------------------------------------- */ +#define DRIVER_HOSTED_MAX_INCDIRS 4 +#define DRIVER_HOSTED_MAX_LIBDIRS 8 + +typedef struct DriverHostedDirs { + DriverEnv* env; + /* The single sysroot root these dirs came from (an explicit --sysroot/ + * KIT_SYSROOT, or a host probe that resolves to one tree, e.g. the macOS + * SDK). NULL when the dirs come from a multi-dir source with no single root + * (the Linux/FreeBSD live-system probe). Borrowed; not freed by fini. */ + const char* root; + char* incdirs[DRIVER_HOSTED_MAX_INCDIRS]; + size_t incdir_sizes[DRIVER_HOSTED_MAX_INCDIRS]; + uint32_t nincdirs; + char* libdirs[DRIVER_HOSTED_MAX_LIBDIRS]; + size_t libdir_sizes[DRIVER_HOSTED_MAX_LIBDIRS]; + uint32_t nlibdirs; +} DriverHostedDirs; + +/* Append an include/library directory, heap-duplicated from `dir`. The _join + * forms join `base` + `sub` with a single '/' separator first. A NULL/empty dir + * is a successful no-op; returns nonzero on allocation failure or when the list + * is full (loud overflow -- never a silent drop). dirs->env must be set. */ +int driver_hosted_dirs_add_inc(DriverHostedDirs* dirs, const char* dir); +int driver_hosted_dirs_add_lib(DriverHostedDirs* dirs, const char* dir); +int driver_hosted_dirs_add_inc_join(DriverHostedDirs* dirs, const char* base, + const char* sub); +int driver_hosted_dirs_add_lib_join(DriverHostedDirs* dirs, const char* base, + const char* sub); +/* Free all stored dir strings and zero the lists. Idempotent. */ +void driver_hosted_dirs_fini(DriverHostedDirs* dirs); + +/* Probe the live host for hosted include/library dirs for `target`, used when + * no --sysroot/KIT_SYSROOT was given. Only the host's own platform is probed + * (the caller additionally gates on target-OS == host-OS) -- never for cross- + * compiles. Fills `out`, which the caller zero-inits with out->env set. Returns + * 0 when it produced at least one library dir, nonzero otherwise (out left + * empty). macOS resolves the SDK (the canonical Command Line Tools / Xcode + * roots; no subprocess, no env var) into <sdk>/usr/{include,lib}; Linux + * enumerates the multiarch dirs; FreeBSD the base dirs; Windows produces + * nothing (its MinGW sysroot comes from --sysroot/KIT_SYSROOT in the cc + * driver). The single env-var override, KIT_SYSROOT, is consulted by the + * caller, not here. */ +int driver_default_hosted_dirs(DriverEnv* env, KitTarget target, + DriverHostedDirs* out); + +/* ---------------------------------------------------------------------- * Host-shim helpers * * driver/env.c is the only TU in the driver allowed to depend on libc diff --git a/driver/env/common.c b/driver/env/common.c @@ -127,6 +127,70 @@ void driver_memcpy(void* dst, const void* src, size_t n) { memcpy(dst, src, n); } +/* ---------------- hosted dir lists ---------------- */ + +/* Mechanical only: join `a` (+ optional `/sub`), dup into the list. No per-OS + * policy lives here -- producers (driver/lib/hosted.c expansion and the + * per-host driver_default_hosted_dirs probes) supply the paths. */ +static int hd_store(DriverEnv* env, char** arr, size_t* sizes, uint32_t* n, + uint32_t cap, const char* a, const char* b) { + size_t alen, blen, slash, bytes, off; + char* out; + if (!a || !a[0]) return 0; /* empty base -> successful no-op */ + if (*n >= cap) return 1; /* loud overflow -- never a silent drop */ + alen = driver_strlen(a); + blen = b ? driver_strlen(b) : 0; + slash = (blen && a[alen - 1] != '/') ? 1u : 0u; + bytes = alen + slash + blen + 1u; + out = driver_alloc(env, bytes); + if (!out) return 1; + driver_memcpy(out, a, alen); + off = alen; + if (slash) out[off++] = '/'; + if (blen) { + driver_memcpy(out + off, b, blen); + off += blen; + } + out[off] = '\0'; + arr[*n] = out; + sizes[*n] = bytes; + (*n)++; + return 0; +} + +int driver_hosted_dirs_add_inc(DriverHostedDirs* d, const char* dir) { + return hd_store(d->env, d->incdirs, d->incdir_sizes, &d->nincdirs, + DRIVER_HOSTED_MAX_INCDIRS, dir, NULL); +} + +int driver_hosted_dirs_add_lib(DriverHostedDirs* d, const char* dir) { + return hd_store(d->env, d->libdirs, d->libdir_sizes, &d->nlibdirs, + DRIVER_HOSTED_MAX_LIBDIRS, dir, NULL); +} + +int driver_hosted_dirs_add_inc_join(DriverHostedDirs* d, const char* base, + const char* sub) { + return hd_store(d->env, d->incdirs, d->incdir_sizes, &d->nincdirs, + DRIVER_HOSTED_MAX_INCDIRS, base, sub); +} + +int driver_hosted_dirs_add_lib_join(DriverHostedDirs* d, const char* base, + const char* sub) { + return hd_store(d->env, d->libdirs, d->libdir_sizes, &d->nlibdirs, + DRIVER_HOSTED_MAX_LIBDIRS, base, sub); +} + +void driver_hosted_dirs_fini(DriverHostedDirs* d) { + uint32_t i; + if (!d || !d->env) return; + for (i = 0; i < d->nincdirs; ++i) + driver_free(d->env, d->incdirs[i], d->incdir_sizes[i]); + for (i = 0; i < d->nlibdirs; ++i) + driver_free(d->env, d->libdirs[i], d->libdir_sizes[i]); + d->nincdirs = 0; + d->nlibdirs = 0; +} + /* ---------------- string predicates ---------------- */ /* The driver's only NUL-terminated-string handling: thin boundary shims diff --git a/driver/env/freebsd.c b/driver/env/freebsd.c @@ -135,3 +135,17 @@ void os_host_target_fill(KitTarget* t) { t->os = KIT_OS_FREEBSD; t->obj = KIT_OBJ_ELF; } + +/* ---------------- default hosted dirs probe ---------------- */ +/* FreeBSD base system is flat: headers in /usr/include, crt + libc in /usr/lib + * and /lib (libc.so.7 lives in /lib). Host target only. UNTESTED on the macOS + * dev host -- see hosted_resolve_freebsd in driver/lib/hosted.c. */ +int driver_default_hosted_dirs(DriverEnv* env, KitTarget target, + DriverHostedDirs* out) { + (void)env; + if (target.os != KIT_OS_FREEBSD) return 1; + if (driver_hosted_dirs_add_inc(out, "/usr/include") != 0) return 1; + if (driver_hosted_dirs_add_lib(out, "/usr/lib") != 0) return 1; + if (driver_hosted_dirs_add_lib(out, "/lib") != 0) return 1; + return out->nlibdirs ? 0 : 1; +} diff --git a/driver/env/linux.c b/driver/env/linux.c @@ -140,3 +140,49 @@ void os_host_target_fill(KitTarget* t) { t->os = KIT_OS_LINUX; t->obj = KIT_OBJ_ELF; } + +/* ---------------- default hosted dirs probe ---------------- */ +/* Linux multiarch triple for the supported 64-bit arches; NULL otherwise. */ +static const char* linux_multiarch_triple(KitArchKind arch) { + switch (arch) { + case KIT_ARCH_ARM_64: + return "aarch64-linux-gnu"; + case KIT_ARCH_X86_64: + return "x86_64-linux-gnu"; + case KIT_ARCH_RV64: + return "riscv64-linux-gnu"; + default: + return NULL; + } +} + +/* A live Linux root is not sysroot-shaped: glibc keeps crt + libc_nonshared.a + * in /usr/lib/<triple> and libc.so.6 in /lib/<triple>; musl/Alpine is flat in + * /usr/lib + /lib. We hand the resolver an ordered library search list that + * covers both, plus the standard include roots, and let its per-file search + * bind each crt/libc wherever it actually lives. Host target only. */ +int driver_default_hosted_dirs(DriverEnv* env, KitTarget target, + DriverHostedDirs* out) { + const char* triple; + (void)env; + if (target.os != KIT_OS_LINUX) return 1; + triple = linux_multiarch_triple(target.arch); + /* Includes: top-level first, then the arch multiarch dir (glibc), then + * /usr/local/include. Emission order matters for header shadowing. */ + if (driver_hosted_dirs_add_inc(out, "/usr/include") != 0) return 1; + if (triple && + driver_hosted_dirs_add_inc_join(out, "/usr/include", triple) != 0) + return 1; + if (driver_hosted_dirs_add_inc(out, "/usr/local/include") != 0) return 1; + /* Libdirs: glibc multiarch first, then flat musl/base dirs, then lib64 + * (where the glibc x86_64 loader and some libs live). */ + if (triple) { + if (driver_hosted_dirs_add_lib_join(out, "/usr/lib", triple) != 0) return 1; + if (driver_hosted_dirs_add_lib_join(out, "/lib", triple) != 0) return 1; + } + if (driver_hosted_dirs_add_lib(out, "/usr/lib") != 0) return 1; + if (driver_hosted_dirs_add_lib(out, "/lib") != 0) return 1; + if (driver_hosted_dirs_add_lib(out, "/usr/lib64") != 0) return 1; + if (driver_hosted_dirs_add_lib(out, "/lib64") != 0) return 1; + return out->nlibdirs ? 0 : 1; +} diff --git a/driver/env/macos.c b/driver/env/macos.c @@ -127,3 +127,39 @@ void os_host_target_fill(KitTarget* t) { t->os = KIT_OS_MACOS; t->obj = KIT_OBJ_MACHO; } + +/* ---------------- default hosted dirs probe ---------------- */ +/* With no --sysroot or KIT_SYSROOT, locate the macOS SDK by stat'ing the + * canonical Command Line Tools and Xcode.app SDK roots -- the same locations + * `xcrun --show-sdk-path` reports, without a subprocess. Pure on-disk + * discovery: the env-var override is KIT_SYSROOT, handled uniformly in + * driver/lib/hosted.c. Only the macOS host target is probed; cross-targets get + * nothing. The <sdk>/usr/{include,lib} mapping mirrors + * hosted_dirs_from_sysroot's macOS case in driver/lib/hosted.c (layering + * forbids calling into it from the env TU). */ +int driver_default_hosted_dirs(DriverEnv* env, KitTarget target, + DriverHostedDirs* out) { + static const char* const candidates[] = { + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk", + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/" + "Developer/SDKs/MacOSX.sdk", + }; + const char* sdk = NULL; + size_t i; + (void)env; + if (target.os != KIT_OS_MACOS) return 1; + for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { + if (driver_path_exists(candidates[i])) { + sdk = candidates[i]; + break; + } + } + if (!sdk) return 1; + /* The SDK is a single sysroot-shaped tree, so record it as the root (a static + * literal -- stable for the process). -print-sysroot reports it. */ + out->root = sdk; + if (driver_hosted_dirs_add_inc_join(out, sdk, "usr/include") != 0 || + driver_hosted_dirs_add_lib_join(out, sdk, "usr/lib") != 0) + return 1; + return out->nlibdirs ? 0 : 1; +} diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -1677,6 +1677,17 @@ KitTarget driver_host_target(void) { return t; } +/* No host probe on Windows: the MinGW sysroot is supplied via --sysroot or the + * KIT_SYSROOT env var (handled in the cc driver), not auto-discovered from a + * fixed install path. */ +int driver_default_hosted_dirs(DriverEnv* env, KitTarget target, + DriverHostedDirs* out) { + (void)env; + (void)target; + (void)out; + return 1; +} + /* ============================================================ * env wiring * ============================================================ */ diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c @@ -40,20 +40,51 @@ static int hosted_add_input(DriverHostedInput* items, uint32_t* n, uint32_t cap, return 0; } -static int hosted_add_required(DriverHostedInput* items, uint32_t* n, - uint32_t cap, const DriverHostedRequest* req, - const char* base, const char* rel, - uint8_t kind) { +/* ---------------- libdir search ---------------- */ + +/* Return a freshly-owned `<libdir>/file` for the first libdir that has it, or + * NULL when none do (or on OOM -- the caller treats both as "not found"). */ +static char* hosted_find_in_libdirs(DriverEnv* env, + const DriverHostedDirs* dirs, + const char* file, size_t* out_size) { + uint32_t i; + for (i = 0; i < dirs->nlibdirs; ++i) { + size_t size = 0; + char* path = hosted_join2(env, dirs->libdirs[i], file, &size); + if (!path) return NULL; + if (driver_path_exists(path)) { + *out_size = size; + return path; + } + driver_free(env, path, size); + } + return NULL; +} + +static int hosted_libdir_has(DriverEnv* env, const DriverHostedDirs* dirs, + const char* file) { size_t size = 0; - char* path = hosted_join2(req->env, base, rel, &size); + char* path = hosted_find_in_libdirs(env, dirs, file, &size); + if (!path) return 0; + driver_free(env, path, size); + return 1; +} + +/* Search the libdir list for a required crt/libc file, binding the first hit + * as an owned input. The bound file path persists in the plan (freed by + * driver_hosted_plan_fini); the dir list itself is transient scratch. */ +static int hosted_add_required_search(DriverHostedInput* items, uint32_t* n, + uint32_t cap, + const DriverHostedRequest* req, + const DriverHostedDirs* dirs, + const char* file, uint8_t kind) { + size_t size = 0; + char* path = hosted_find_in_libdirs(req->env, dirs, file, &size); if (!path) { - driver_errf(req->tool, "out of memory"); - return 1; - } - if (!driver_path_exists(path)) { - driver_errf(req->tool, "hosted profile missing required file: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(path))); - driver_free(req->env, path, size); + driver_errf(req->tool, + "hosted profile missing required file: %.*s (searched %u " + "library dir(s))", + KIT_SLICE_ARG(kit_slice_cstr(file)), (unsigned)dirs->nlibdirs); return 1; } if (hosted_add_input(items, n, cap, kind, path, size) != 0) { @@ -64,24 +95,41 @@ static int hosted_add_required(DriverHostedInput* items, uint32_t* n, return 0; } +/* ---------------- include dirs ---------------- */ + +/* Promote one include dir into the plan as an owned copy, skipping it silently + * when it does not exist. The copy is required: plan->system_includes outlives + * the transient DriverHostedDirs (it feeds the preprocessor). */ static int hosted_add_existing_include(DriverHostedPlan* plan, DriverEnv* env, - const char* base, const char* rel) { - size_t size = 0; + const char* dir) { + size_t len, bytes; char* path; if (plan->nsystem_includes >= DRIVER_HOSTED_MAX_INCLUDES) return 1; - path = hosted_join2(env, base, rel, &size); + if (!driver_path_exists(dir)) return 0; + len = driver_strlen(dir); + bytes = len + 1u; + path = driver_alloc(env, bytes); if (!path) return 1; - if (!driver_path_exists(path)) { - driver_free(env, path, size); - return 0; - } + driver_memcpy(path, dir, len); + path[len] = '\0'; plan->system_includes[plan->nsystem_includes] = path; plan->owned_system_includes[plan->nsystem_includes] = path; - plan->owned_system_include_sizes[plan->nsystem_includes] = size; + plan->owned_system_include_sizes[plan->nsystem_includes] = bytes; plan->nsystem_includes++; return 0; } +static int hosted_add_incdirs(DriverHostedPlan* plan, DriverEnv* env, + const DriverHostedDirs* dirs) { + uint32_t i; + for (i = 0; i < dirs->nincdirs; ++i) { + if (hosted_add_existing_include(plan, env, dirs->incdirs[i]) != 0) return 1; + } + return 0; +} + +/* ---------------- defines ---------------- */ + static int hosted_add_define(DriverHostedPlan* plan, const char* name, const char* body) { if (plan->ndefines >= DRIVER_HOSTED_MAX_DEFINES) return 1; @@ -129,6 +177,22 @@ static int hosted_add_linux_defines(DriverHostedPlan* plan, int gnu) { return 0; } +static int hosted_add_freebsd_defines(DriverHostedPlan* plan) { + /* __FreeBSD__ must be present and version-encoded or the base headers fail to + * compile; the major can't be derived from the triple, so 14 is a documented + * conservative default. */ + if (hosted_add_clang_compat_defines(plan) != 0 || + hosted_add_define(plan, "__FreeBSD__", "14") != 0 || + hosted_add_define(plan, "__ELF__", "1") != 0 || + hosted_add_define(plan, "__unix__", "1") != 0 || + hosted_add_define(plan, "__unix", "1") != 0 || + hosted_add_define(plan, "unix", "1") != 0) + return 1; + return 0; +} + +/* ---------------- interpreters ---------------- */ + static const char* hosted_glibc_interp(KitArchKind arch) { switch (arch) { case KIT_ARCH_ARM_64: @@ -155,83 +219,81 @@ static const char* hosted_musl_interp(KitArchKind arch) { } } +/* ---------------- per-OS resolvers ---------------- + * Each consumes a DriverHostedDirs (include roots + library search dirs). An + * empty dir list means no sysroot was given and no host default was found, so + * the resolver emits its "requires --sysroot/KIT_SYSROOT" error. */ + static int hosted_resolve_darwin(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { - size_t size = 0; - char* libsystem = NULL; - if (!req->sysroot || !req->sysroot[0]) { + if (dirs->nlibdirs == 0) { driver_errf(req->tool, - "Darwin hosted profile requires --sysroot; try: --sysroot " - "\"$(xcrun --sdk macosx --show-sdk-path)\""); + "Darwin hosted profile requires a macOS SDK; pass --sysroot, " + "set KIT_SYSROOT, or install the Command Line Tools (try: " + "--sysroot \"$(xcrun --sdk macosx --show-sdk-path)\")"); return 1; } plan->profile_name = "macos-libSystem"; if (hosted_add_darwin_defines(plan) != 0 || - hosted_add_existing_include(plan, req->env, req->sysroot, - "usr/include") != 0) { + hosted_add_incdirs(plan, req->env, dirs) != 0) { driver_errf(req->tool, "out of memory"); return 1; } if (!req->link_inputs) return 0; - libsystem = - hosted_join2(req->env, req->sysroot, "usr/lib/libSystem.tbd", &size); - if (!libsystem) { - driver_errf(req->tool, "out of memory"); - return 1; - } - if (!driver_path_exists(libsystem)) { - driver_free(req->env, libsystem, size); - libsystem = - hosted_join2(req->env, req->sysroot, "usr/lib/libSystem.dylib", &size); + { + size_t size = 0; + char* libsystem = + hosted_find_in_libdirs(req->env, dirs, "libSystem.tbd", &size); + if (!libsystem) + libsystem = + hosted_find_in_libdirs(req->env, dirs, "libSystem.dylib", &size); if (!libsystem) { - driver_errf(req->tool, "out of memory"); + driver_errf(req->tool, + "hosted profile missing required file: " + "libSystem.tbd or libSystem.dylib"); + return 1; + } + if (hosted_add_input(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, + DRIVER_HOSTED_INPUT_DSO, libsystem, size) != 0) { + driver_free(req->env, libsystem, size); + driver_errf(req->tool, "too many hosted inputs"); return 1; } - } - if (!driver_path_exists(libsystem)) { - driver_errf(req->tool, "hosted profile missing required file: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(libsystem))); - driver_free(req->env, libsystem, size); - return 1; - } - if (hosted_add_input(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - DRIVER_HOSTED_INPUT_DSO, libsystem, size) != 0) { - driver_free(req->env, libsystem, size); - driver_errf(req->tool, "too many hosted inputs"); - return 1; } return 0; } static int hosted_resolve_linux_musl_static(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { plan->profile_name = "linux-musl-static"; if (hosted_add_linux_defines(plan, 0) != 0 || - hosted_add_existing_include(plan, req->env, req->sysroot, "include") != - 0) { + hosted_add_incdirs(plan, req->env, dirs) != 0) { driver_errf(req->tool, "out of memory"); return 1; } if (!req->link_inputs) return 0; - if (hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 || - hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crt1.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0 || + hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; - if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libc.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0) + if (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a", + DRIVER_HOSTED_INPUT_ARCHIVE) != 0) return 1; - if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL, - req, req->sysroot, "lib/crtn.o", - DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->final, &plan->nfinal, + DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; return 0; } static int hosted_resolve_linux_musl_dynamic(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { const char* interp = hosted_musl_interp(req->target.arch); if (!interp) { @@ -241,31 +303,31 @@ static int hosted_resolve_linux_musl_dynamic(const DriverHostedRequest* req, plan->profile_name = "linux-musl-dynamic"; plan->interp_path = interp; if (hosted_add_linux_defines(plan, 0) != 0 || - hosted_add_existing_include(plan, req->env, req->sysroot, "include") != - 0) { + hosted_add_incdirs(plan, req->env, dirs) != 0) { driver_errf(req->tool, "out of memory"); return 1; } if (!req->link_inputs) return 0; - if (hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/Scrt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 || - hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "Scrt1.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0 || + hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; - if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libc.so", - DRIVER_HOSTED_INPUT_DSO) != 0) + if (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.so", + DRIVER_HOSTED_INPUT_DSO) != 0) return 1; - if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL, - req, req->sysroot, "lib/crtn.o", - DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->final, &plan->nfinal, + DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; return 0; } static int hosted_resolve_linux_glibc_dynamic(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { const char* interp = hosted_glibc_interp(req->target.arch); if (!interp) { @@ -274,188 +336,297 @@ static int hosted_resolve_linux_glibc_dynamic(const DriverHostedRequest* req, } plan->profile_name = "linux-glibc-dynamic"; plan->interp_path = interp; + /* The arch multiarch include (include/<triple>) is already in the dir list, + * so glibc adds the same include set as the musl profiles. */ if (hosted_add_linux_defines(plan, 1) != 0 || - hosted_add_existing_include(plan, req->env, req->sysroot, "include") != - 0) { + hosted_add_incdirs(plan, req->env, dirs) != 0) { driver_errf(req->tool, "out of memory"); return 1; } - switch (req->target.arch) { - case KIT_ARCH_ARM_64: - if (hosted_add_existing_include(plan, req->env, req->sysroot, - "include/aarch64-linux-gnu") != 0) - return 1; - break; - case KIT_ARCH_X86_64: - if (hosted_add_existing_include(plan, req->env, req->sysroot, - "include/x86_64-linux-gnu") != 0) - return 1; - break; - case KIT_ARCH_RV64: - if (hosted_add_existing_include(plan, req->env, req->sysroot, - "include/riscv64-linux-gnu") != 0) - return 1; - break; - default: - break; - } if (!req->link_inputs) return 0; - if (hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/Scrt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 || - hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0) - return 1; - if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libc.so.6", - DRIVER_HOSTED_INPUT_DSO) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libc_nonshared.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0) - return 1; - if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL, - req, req->sysroot, "lib/crtn.o", - DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "Scrt1.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0 || + hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) + return 1; + if (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libc.so.6", DRIVER_HOSTED_INPUT_DSO) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libc_nonshared.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0) + return 1; + if (hosted_add_required_search(plan->final, &plan->nfinal, + DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; return 0; } static int hosted_resolve_linux(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { - char* libc_a; - char* libc_so; - char* libc_so6; - char* libc_nonshared; - size_t sa = 0; - size_t ss = 0; - size_t ss6 = 0; - size_t sns = 0; - int has_libc_a; - int has_libc_so; - int has_libc_so6; - int has_glibc_nonshared; - if (!req->sysroot || !req->sysroot[0]) { - driver_errf(req->tool, "Linux hosted profile requires --sysroot"); - return 1; - } - libc_a = hosted_join2(req->env, req->sysroot, "lib/libc.a", &sa); - libc_so = hosted_join2(req->env, req->sysroot, "lib/libc.so", &ss); - libc_so6 = hosted_join2(req->env, req->sysroot, "lib/libc.so.6", &ss6); - libc_nonshared = - hosted_join2(req->env, req->sysroot, "lib/libc_nonshared.a", &sns); - if (!libc_a || !libc_so || !libc_so6 || !libc_nonshared) { - if (libc_a) driver_free(req->env, libc_a, sa); - if (libc_so) driver_free(req->env, libc_so, ss); - if (libc_so6) driver_free(req->env, libc_so6, ss6); - if (libc_nonshared) driver_free(req->env, libc_nonshared, sns); - driver_errf(req->tool, "out of memory"); + int has_libc_a, has_libc_so, has_libc_so6, has_glibc_nonshared; + if (dirs->nlibdirs == 0) { + driver_errf(req->tool, + "Linux hosted profile requires --sysroot or KIT_SYSROOT"); return 1; } - has_libc_a = driver_path_exists(libc_a); - has_libc_so = driver_path_exists(libc_so); - has_libc_so6 = driver_path_exists(libc_so6); - has_glibc_nonshared = driver_path_exists(libc_nonshared); - driver_free(req->env, libc_a, sa); - driver_free(req->env, libc_so, ss); - driver_free(req->env, libc_so6, ss6); - driver_free(req->env, libc_nonshared, sns); + /* Booleans mean "exists in some libdir". With a single sysroot libdir this is + * bit-identical to the historical single-dir probe; with the multi-dir host + * probe, glibc's split (crt + libc_nonshared.a in /usr/lib/<triple>, + * libc.so.6 in /lib/<triple>) is handled by the per-file ordered search. */ + has_libc_a = hosted_libdir_has(req->env, dirs, "libc.a"); + has_libc_so = hosted_libdir_has(req->env, dirs, "libc.so"); + has_libc_so6 = hosted_libdir_has(req->env, dirs, "libc.so.6"); + has_glibc_nonshared = hosted_libdir_has(req->env, dirs, "libc_nonshared.a"); if (!req->static_link && has_libc_so6 && has_glibc_nonshared) - return hosted_resolve_linux_glibc_dynamic(req, plan); + return hosted_resolve_linux_glibc_dynamic(req, dirs, plan); if (has_libc_a && !(has_libc_so6 && has_glibc_nonshared)) - return hosted_resolve_linux_musl_static(req, plan); + return hosted_resolve_linux_musl_static(req, dirs, plan); if (!req->static_link && has_libc_so) - return hosted_resolve_linux_musl_dynamic(req, plan); + return hosted_resolve_linux_musl_dynamic(req, dirs, plan); driver_errf(req->tool, - "no supported Linux hosted libc found under sysroot: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(req->sysroot))); + "no supported Linux hosted libc found (searched %u library " + "dir(s))", + (unsigned)dirs->nlibdirs); return 1; } +/* FreeBSD base-system hosted profile. UNTESTED on the macOS dev host: the + * interp path, the libc.so.7-vs-linker-script binding, the crt set, and the + * __FreeBSD__ version are best-effort and want validation against a real + * FreeBSD root (a base.txz extraction harness paralleling + * test/libc/{glibc,musl} is the follow-up). libc binds libc.so.7 directly -- + * /usr/lib/libc.so is a GNU ld linker script kit's linker cannot parse. */ +static int hosted_resolve_freebsd(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, + DriverHostedPlan* plan) { + int static_link; + if (dirs->nlibdirs == 0) { + driver_errf(req->tool, + "FreeBSD hosted profile requires --sysroot or KIT_SYSROOT"); + return 1; + } + static_link = req->static_link && hosted_libdir_has(req->env, dirs, "libc.a"); + plan->profile_name = static_link ? "freebsd-static" : "freebsd-dynamic"; + if (!static_link) plan->interp_path = "/libexec/ld-elf.so.1"; + if (hosted_add_freebsd_defines(plan) != 0 || + hosted_add_incdirs(plan, req->env, dirs) != 0) { + driver_errf(req->tool, "out of memory"); + return 1; + } + if (!req->link_inputs) return 0; + if (hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, + static_link ? "crt1.o" : "Scrt1.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0 || + hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) + return 1; + if (static_link) { + if (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a", + DRIVER_HOSTED_INPUT_ARCHIVE) != 0) + return 1; + } else { + if (hosted_add_required_search(plan->after, &plan->nafter, + DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libc.so.7", DRIVER_HOSTED_INPUT_DSO) != 0) + return 1; + } + if (hosted_add_required_search(plan->final, &plan->nfinal, + DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) + return 1; + return 0; +} + static int hosted_resolve_windows_mingw(const DriverHostedRequest* req, + const DriverHostedDirs* dirs, DriverHostedPlan* plan) { - if (!req->sysroot || !req->sysroot[0]) { - driver_errf(req->tool, "Windows hosted profile requires --sysroot"); + if (dirs->nlibdirs == 0) { + driver_errf(req->tool, + "Windows hosted profile requires --sysroot or KIT_SYSROOT"); return 1; } plan->profile_name = "windows-mingw"; - if (hosted_add_existing_include(plan, req->env, req->sysroot, "include") != - 0) { + if (hosted_add_incdirs(plan, req->env, dirs) != 0) { driver_errf(req->tool, "out of memory"); return 1; } if (!req->link_inputs) return 0; - if (hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crt2.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 || - hosted_add_required(plan->before, &plan->nbefore, - DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot, - "lib/crtbegin.o", DRIVER_HOSTED_INPUT_OBJECT) != 0) - return 1; - if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmingw32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmoldname.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmingwex.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmsvcrt.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libadvapi32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libshell32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libuser32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libkernel32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmingw32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmoldname.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmingwex.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libmsvcrt.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || - hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, - req, req->sysroot, "lib/libkernel32.a", - DRIVER_HOSTED_INPUT_ARCHIVE) != 0) - return 1; - if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL, - req, req->sysroot, "lib/crtend.o", - DRIVER_HOSTED_INPUT_OBJECT) != 0) + if (hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crt2.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0 || + hosted_add_required_search(plan->before, &plan->nbefore, + DRIVER_HOSTED_MAX_BEFORE, req, dirs, + "crtbegin.o", DRIVER_HOSTED_INPUT_OBJECT) != 0) + return 1; + if (hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmingw32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmoldname.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmingwex.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmsvcrt.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libadvapi32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libshell32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libuser32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libkernel32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmingw32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmoldname.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmingwex.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libmsvcrt.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + hosted_add_required_search( + plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, + "libkernel32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0) + return 1; + if (hosted_add_required_search(plan->final, &plan->nfinal, + DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtend.o", + DRIVER_HOSTED_INPUT_OBJECT) != 0) return 1; return 0; } +/* ---------------- sysroot expansion (Producer A) ---------------- */ + +static const char* hosted_linux_inc_triple_sub(KitArchKind arch) { + switch (arch) { + case KIT_ARCH_ARM_64: + return "include/aarch64-linux-gnu"; + case KIT_ARCH_X86_64: + return "include/x86_64-linux-gnu"; + case KIT_ARCH_RV64: + return "include/riscv64-linux-gnu"; + default: + return NULL; + } +} + +/* Expand an explicit --sysroot/KIT_SYSROOT into the dir list, per target-OS + * layout. Mirrors the per-host probes' shape so both producers converge on the + * same resolver contract. Dirs are added unconditionally (existence is checked + * when the resolver consumes them); returns nonzero only on alloc/overflow. */ +static int hosted_dirs_from_sysroot(DriverHostedDirs* dirs, KitTarget target, + const char* sysroot) { + dirs->root = + sysroot; /* a single explicit sysroot; -print-sysroot reports it */ + switch (target.os) { + case KIT_OS_MACOS: + if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 || + driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0) + return 1; + return 0; + case KIT_OS_LINUX: { + const char* triple = hosted_linux_inc_triple_sub(target.arch); + if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0) + return 1; + if (triple && driver_hosted_dirs_add_inc_join(dirs, sysroot, triple) != 0) + return 1; + if (driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) return 1; + return 0; + } + case KIT_OS_FREEBSD: + if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 || + driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0 || + driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) + return 1; + return 0; + case KIT_OS_WINDOWS: + if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0 || + driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) + return 1; + return 0; + default: + return 0; /* unsupported target; the dispatcher emits the error */ + } +} + +/* ---------------- orchestration ---------------- */ + +int driver_hosted_dirs_resolve(const DriverHostedRequest* req, + DriverHostedDirs* out) { + const char* sysroot; + if (!req || !req->env || !out) return 1; + memset(out, 0, sizeof(*out)); + out->env = req->env; + /* KIT_SYSROOT: the single, portable, explicit env override. Works on every + * OS and applies to any target including cross-compiles, exactly like + * --sysroot. */ + sysroot = (req->sysroot && req->sysroot[0]) ? req->sysroot : NULL; + if (!sysroot) { + const char* env_sysroot = driver_getenv("KIT_SYSROOT"); + if (env_sysroot && env_sysroot[0]) sysroot = env_sysroot; + } + if (sysroot) { + if (hosted_dirs_from_sysroot(out, req->target, sysroot) != 0) { + driver_hosted_dirs_fini(out); + return 1; + } + } else { + /* Host probe: auto-discover the host's own includes/libs. Gated on the + * target matching the host OS *and* arch so it never fires for any + * cross-compile -- a host SDK is meaningless for a foreign platform. + * Best-effort: leaves dirs empty when nothing is found, and the resolver + * then emits its "requires --sysroot" error. */ + KitTarget host = driver_host_target(); + if (req->target.os == host.os && req->target.arch == host.arch) + (void)driver_default_hosted_dirs(req->env, req->target, out); + } + return 0; +} + int driver_hosted_resolve(const DriverHostedRequest* req, DriverHostedPlan* out) { DriverHostedPlan zero = {0}; + DriverHostedDirs dirs; int rc; if (!req || !out || !req->env || !req->tool) return 1; *out = zero; + if (driver_hosted_dirs_resolve(req, &dirs) != 0) { + driver_errf(req->tool, "out of memory"); + return 1; + } if (req->target.os == KIT_OS_MACOS && req->target.obj == KIT_OBJ_MACHO) { - rc = hosted_resolve_darwin(req, out); + rc = hosted_resolve_darwin(req, &dirs, out); } else if (req->target.os == KIT_OS_LINUX && req->target.obj == KIT_OBJ_ELF) { - rc = hosted_resolve_linux(req, out); + rc = hosted_resolve_linux(req, &dirs, out); + } else if (req->target.os == KIT_OS_FREEBSD && + req->target.obj == KIT_OBJ_ELF) { + rc = hosted_resolve_freebsd(req, &dirs, out); } else if (req->target.os == KIT_OS_WINDOWS && req->target.obj == KIT_OBJ_COFF) { - rc = hosted_resolve_windows_mingw(req, out); + rc = hosted_resolve_windows_mingw(req, &dirs, out); } else { driver_errf(req->tool, "no hosted libc profile for target"); rc = 1; } + driver_hosted_dirs_fini(&dirs); if (rc != 0) driver_hosted_plan_fini(req->env, out); return rc; } diff --git a/driver/lib/hosted.h b/driver/lib/hosted.h @@ -56,4 +56,15 @@ int driver_hosted_resolve(const DriverHostedRequest* req, DriverHostedPlan* out); void driver_hosted_plan_fini(DriverEnv* env, DriverHostedPlan* plan); +/* Resolve just the hosted include + library search directories for the + * request's target/sysroot -- KIT_SYSROOT or an explicit --sysroot (expanded + * per target-OS layout), else the native host probe (target OS+arch == host). + * No crt/libc files are bound. Fills `out` (zeroed internally; release with + * driver_hosted_dirs_fini). Returns 0 on success -- `out` may legitimately be + * empty when no sysroot applies and the target is not the native host; nonzero + * only on allocation failure. Shared by driver_hosted_resolve and by the cc + * driver's -print-search-dirs. */ +int driver_hosted_dirs_resolve(const DriverHostedRequest* req, + DriverHostedDirs* out); + #endif diff --git a/test/coff/README.md b/test/coff/README.md @@ -25,7 +25,7 @@ make test-coff This builds `build/test/kit-roundtrip-coff` and runs the embedded unit cases. It also runs `windows-ucrt-hosted-smoke.sh`, which self-skips unless an llvm-mingw UCRT sysroot is available via -`KIT_MINGW_SYSROOT` or under `/tmp/llvm-mingw*`. Wine is not needed. +`KIT_SYSROOT` or under `/tmp/llvm-mingw*`. Wine is not needed. ## Layers diff --git a/test/coff/windows-system-dlls-smoke.sh b/test/coff/windows-system-dlls-smoke.sh @@ -27,7 +27,7 @@ set -u ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)} KIT=${KIT:-"$ROOT/build/kit"} -SDK=${KIT_MINGW_SYSROOT:-} +SDK=${KIT_SYSROOT:-} KIT_KIT_DIR="$ROOT/test/lib" . "$ROOT/test/lib/kit_sh_kit.sh" @@ -432,7 +432,7 @@ for arch in x86_64 aarch64; do done if [ "$ran" -eq 0 ]; then - skip_test "$LABEL_SUITE" "set KIT_MINGW_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" + skip_test "$LABEL_SUITE" "set KIT_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" fi kit_summary "$LABEL_SUITE" diff --git a/test/coff/windows-ucrt-hosted-smoke.sh b/test/coff/windows-ucrt-hosted-smoke.sh @@ -16,7 +16,7 @@ set -u ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)} KIT=${KIT:-"$ROOT/build/kit"} -SDK=${KIT_MINGW_SYSROOT:-} +SDK=${KIT_SYSROOT:-} KIT_KIT_DIR="$ROOT/test/lib" . "$ROOT/test/lib/kit_sh_kit.sh" @@ -498,7 +498,7 @@ for arch in x86_64 aarch64; do done if [ "$ran" -eq 0 ]; then - skip_test "$LABEL_SUITE" "set KIT_MINGW_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" + skip_test "$LABEL_SUITE" "set KIT_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" fi kit_summary "$LABEL_SUITE" diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -189,6 +189,41 @@ else not_ok "cc-print-file-name-probe" "$work/cc-print-file-name.err" fi +# ---- cc -print-resource-dir is non-empty (the freestanding header root) ---- +if "$KIT" cc -print-resource-dir \ + > "$work/cc-resdir.out" 2> "$work/cc-resdir.err" && + [ -s "$work/cc-resdir.out" ]; then + ok "cc-print-resource-dir" +else + { echo "expected a non-empty resource dir"; cat "$work/cc-resdir.err"; } \ + > "$work/cc-resdir.diag" + not_ok "cc-print-resource-dir" "$work/cc-resdir.diag" +fi + +# ---- cc -print-search-dirs surfaces the hosted sysroot dirs ---- +# KIT_SYSROOT + a cross target makes the output deterministic on any host +# (independent of a native SDK). The Linux expansion must surface <sysroot>/lib, +# <sysroot>/include, and the arch multiarch include subdir. +if KIT_SYSROOT="$work/sr" "$KIT" cc -print-search-dirs -lc -target x86_64-linux \ + > "$work/cc-searchdirs.out" 2> "$work/cc-searchdirs.err" && + grep -q "$work/sr/lib" "$work/cc-searchdirs.out" && + grep -q "$work/sr/include" "$work/cc-searchdirs.out" && + grep -q "x86_64-linux-gnu" "$work/cc-searchdirs.out"; then + ok "cc-print-search-dirs-hosted" +else + cp "$work/cc-searchdirs.out" "$work/cc-searchdirs.diag" + not_ok "cc-print-search-dirs-hosted" "$work/cc-searchdirs.diag" +fi + +# ---- cc -print-sysroot echoes the effective explicit sysroot ---- +if [ "$("$KIT" cc -print-sysroot --sysroot /opt/sdkx 2>/dev/null)" = "/opt/sdkx" ] && + [ "$(KIT_SYSROOT=/opt/sdky "$KIT" cc -print-sysroot 2>/dev/null)" = "/opt/sdky" ]; then + ok "cc-print-sysroot" +else + "$KIT" cc -print-sysroot --sysroot /opt/sdkx > "$work/cc-psysroot.diag" 2>&1 + not_ok "cc-print-sysroot" "$work/cc-psysroot.diag" +fi + # ---- accepted-but-ignored compatibility flags log to stderr ---- if "$KIT" cc -Wall -Wextra -std=c11 -ffreestanding -c "$work/main.c" \ -o "$work/ignored.o" > "$work/cc-ignored.out" 2> "$work/cc-ignored.err"; then @@ -470,6 +505,81 @@ else skip_test "run-shebang-flaglike-arg" "no usable libc sysroot (set KIT_TEST_SYSROOT)" fi +# ---- hosted sysroot defaulting: --sysroot is not always required ---------- +# When hosted libc is engaged (-lc) but no --sysroot is given, the sysroot is +# resolved from KIT_SYSROOT (the single, portable env override — any target) or +# an on-disk host probe (auto-discovery; native target only, never cross). +cat > "$work/hosted-min.c" <<'SRC' +int main(void) { return 0; } +SRC +cat > "$work/hosted-hello.c" <<'SRC' +#include <stdio.h> +int main(void) { puts("hosted-autoprobe"); return 0; } +SRC + +# Tier 1 — KIT_SYSROOT supplies the sysroot. A Darwin cross-target with -c needs +# only the profile to resolve (defines/includes, no link inputs), so a bare +# existing directory suffices and this runs on every host. +mkdir -p "$work/fake-sdk/usr/include" +if KIT_SYSROOT="$work/fake-sdk" "$KIT" cc -c -target arm64-macos -lc \ + "$work/hosted-min.c" -o "$work/hosted-kitsysroot.o" \ + > "$work/hosted-kitsysroot.out" 2> "$work/hosted-kitsysroot.err"; then + ok "cc-hosted-kit-sysroot-env" +else + not_ok "cc-hosted-kit-sysroot-env" "$work/hosted-kitsysroot.err" +fi + +# The host probe must NOT fire for a cross-compile: a foreign-OS target with no +# --sysroot/KIT_SYSROOT must still error rather than borrow the host's libc. +# Pick a target whose OS differs from the host so the native-only gate always +# blocks the probe; scrub the env override so only an (incorrect) auto-probe +# could apply. +case "$(uname -s)" in + Linux) cross_target="arm64-macos" ;; # macOS is never the Linux host's OS + *) cross_target="x86_64-linux" ;; # Linux is never the macOS/other host's OS +esac +if env -u KIT_SYSROOT "$KIT" cc -c -target "$cross_target" -lc \ + "$work/hosted-min.c" -o "$work/hosted-cross.o" \ + > "$work/hosted-cross.out" 2> "$work/hosted-cross.err"; then + { echo "cross-compile unexpectedly resolved a sysroot via auto-probe" + sed 's/^/out: /' "$work/hosted-cross.out"; } > "$work/hosted-cross.diag" + not_ok "cc-hosted-cross-no-autoprobe" "$work/hosted-cross.diag" +else + ok "cc-hosted-cross-no-autoprobe" +fi + +# Tier 2 — host probe end-to-end: no --sysroot, no KIT_SYSROOT, native target. +# Compile + link + run a real hosted program so crt/libc binding is exercised, +# not just header/define resolution. Runs where a host libc is discoverable +# (a macOS host with the CLT/Xcode SDK, or a Linux host with libc headers); +# skip otherwise (e.g. FreeBSD here is untested). +autoprobe_ok=0 +case "$(uname -s)" in + Darwin) + { [ -d /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ] || + [ -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" ]; } && + autoprobe_ok=1 ;; + Linux) + [ -e /usr/include/stdio.h ] && autoprobe_ok=1 ;; +esac +if [ "$autoprobe_ok" -eq 1 ]; then + if env -u KIT_SYSROOT "$KIT" cc -lc "$work/hosted-hello.c" \ + -o "$work/hosted-autoprobe" \ + > "$work/hosted-autoprobe.out" 2> "$work/hosted-autoprobe.err" && + "$work/hosted-autoprobe" > "$work/hosted-autoprobe.run" 2>&1 && + grep -q "hosted-autoprobe" "$work/hosted-autoprobe.run"; then + ok "cc-hosted-sysroot-autoprobe" + else + { sed 's/^/err: /' "$work/hosted-autoprobe.err" + [ -f "$work/hosted-autoprobe.run" ] && + sed 's/^/run: /' "$work/hosted-autoprobe.run"; } \ + > "$work/hosted-autoprobe.diag" + not_ok "cc-hosted-sysroot-autoprobe" "$work/hosted-autoprobe.diag" + fi +else + skip_test "cc-hosted-sysroot-autoprobe" "no discoverable host libc to probe" +fi + # ---- archive link order is enforced (def after ref vs ref after def) ---- cat > "$work/order-main.c" <<'SRC' int foo(void);