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:
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);