Spec M25 -- sysroots and cross-compilation resolution
Owner's direction (2026-09-03, restated 2026-09-04): cross-compiling must not make the developer
hunt for files, and everything mc downloads must be checksummed, cached and reproducible. mc
redistributes nothing. mingw-w64 import libraries matter for USER programs that need more than
kernel32 (msvcrt, user32, ws2_32); the compiler itself never needs them.
Goal: one resolution chain in the driver -- explicit path, then the running system, then
~/.mc/sysroots/<target>, then a message with the exact command -- plus mc sysroot
list|path|fetch|stub, so that a fresh checkout on any of the three hosts can build for any of the
five targets without a hand-assembled directory, and so that a macOS box with no SDK can still
link. Depends on M14 (driver/TOML), M16/M20 (the Linux and Windows targets), M37 and M38 (the host
layer, on main at 2b83243).
What exists #
{sysroot}is onetoml_getand a path join.src/driver.mc:344-348:toml_get("sysroot.path"),toml_err_key("sysroot.path", "missing key")when absent,drv_path(p)to resolve it against the config's directory. There is no existence check anywhere. A wrong or missing directory is not diagnosed bymc:drv_ph(src/driver.mc:353-358) substitutes the string,drv_link(:365-393) spawns the linker, and the message the user sees is the linker's --ld.lld: error: cannot open build/sysroot/linux-aarch64/crt1.o: No such file or directory-- followed by_exit(1)(src/driver.mc:392). The[sysroot]error surface today is exactly one line, and it only fires when the key is missing while some argument uses the placeholder.{sdk}is the only commandmcruns on its own (docs/reference/cli.md§ 4):drv_sdk(src/driver.mc:167-193) spawnsxcrun --show-sdk-pathonce per build, captures stdout withposix_spawn_file_actions_addopenon fd 1 into<out>.sdk, reads it back, strips the newline andunlinks it. On a host wherehost_has_sdk()is 0 -- Linux (src/host_linux.mc:41) and Windows (src/host_windows.mc:52) -- it is a config error before any spawn:{sdk} needs xcrun: it exists only on macos(src/driver.mc:171-172).mcreads no environment variable.MC_SYSROOTis a script variable only (scripts/link-linux.sh:35,scripts/link-windows.sh:61,scripts/test-linux.sh:83,scripts/bootstrap-linux.sh:30); the compiler never looks at it.host_environ()exists (src/driver.mc:157) but is passed straight toposix_spawnpand is 0 on Windows (src/host_windows.mc:42, M38 Decision 5).- How
mc buildfindscrt1.otoday. It does not: the developer writes[sysroot] path = "build/sysroot/linux-aarch64"and the four files get there out of band --scripts/sysroot-linux.sh(apk add musl-devin a throwawayalpine:3container,docker run --platform linux/arm64|amd64, copycrt1.o crti.o crtn.o libc.a), or apt on a Linux runner (.github/workflows/ci.yml:277-291,cp /usr/lib/aarch64-linux-musl/*afterapt-get install musl-dev), orMC_SYSROOT=/usr/lib/aarch64-linux-musl(docs/guide/90-linux-host.md:20,.github/workflows/ci.yml:402). Windows never downloads anything:scripts/sysroot-windows.shwrites a 13-namekernel32.defand runsllvm-dlltool -m arm64|i386:x86-64 -d ... -l kernel32.lib. mcalready has SHA-256 in the language (src/sha256.mc:132,sha256(p, n, out), bundled asmc/sha256) and already spawns tools (drv_spawn,src/driver.mc:151-162). It has no network, noopendir, and nogetenv.- The extern -> library mapping already exists in the parser:
dylib_add(src/parse.mc:378),extern_lib_pattern_add(:411,[externs] "sqlite3_*" = "sqlite3"),extern_lib_find(name)(:435, "1 (libSystem) for every name neither one claims"), and the full signature tablenfuncs/fs_at/fs_namewithN_EXTERNnodes (src/gen_resolve.mc:106). Indrv_entrythe compile and the link happen in one process (src/driver.mc:410-415), so at the momentdrv_linkruns, the program's externs and their library ordinals are still in memory. toml_parsetakes a path and fills ONE global table (src/toml.mc:415,tm_nat:65). A secondtoml_parseduringmc buildwould destroy the project's config. This decides where the source list may live (Decision 3).- Subcommand dispatch is two lines:
src/main.mc:151and:153.
Design #
1. drv_sysroot becomes a chain #
One function, same signature, same single call site (drv_ph, src/driver.mc:356):
uptr drv_sysroot() {
1. [sysroot].path -> drv_path(p), and now CHECKED: if the directory has none
of the target's marker files, name it and stop.
2. host == target -> the running system's own files (probes below)
3. ~/.mc/sysroots/<os>-<arch>/ (or [sysroot].cache, or --sysroot-dir)
4. sysroot_missing(os, arch) -> the instructions, exit 2
}
The result is cached in a global like drv_sdk_cache (src/driver.mc:66); the chain runs at most
once per build and only when some [linker].args value mentions {sysroot} -- exactly the
laziness {sdk} already has.
"Present" is a marker-file test, not a directory walk. mc has no opendir; it has open
(src/arena.mc:5). One helper, path_exists(p) = open(p, O_RDONLY, 0) >= 0 then close, and a
per-target marker list from the target registry: crt1.o + libc.a for linux-*,
kernel32.lib for windows-*, usr/lib/libSystem.tbd for macos-*.
The per-OS "running system" probes, tried in order, first hit wins:
| host | probed, in order |
|---|---|
| linux | /usr/lib/<arch>-linux-musl (Debian/Ubuntu musl-dev, the CI layout), /usr/lib/musl/lib, /usr/lib (Alpine, where apk add musl-dev puts all four) |
| macos | drv_sdk() for {sdk}; for {sysroot} on a macOS target, the same SDK path. Nothing to probe when host_has_sdk() is 0 -- that is what § 4 is for |
| windows | the directory holding the compiler's own generated kernel32.lib: [sysroot].path, then build/sysroot/windows-<arch>. mc cannot regenerate it (that is llvm-dlltool), so the miss prints sh scripts/sysroot-windows.sh --arch <arch> or mc sysroot fetch windows-<arch> |
The probe is skipped entirely when host_os()/host_arch() differ from the target, so a cross build
never picks up the host's own libc.a.
~ and HOME. mc has no getenv. Two host functions join the interface, in the shape M38
set with host_exe_suffix() (src/host_macos.mc:78): host_home() returns the user's home
directory or 0. macOS/Linux scan host_environ() -- a NUL-terminated array of KEY=VALUE
pointers -- for HOME=. Windows cannot: host_environ() is 0 there
(src/host_windows.mc:42), so host_windows.mc declares
extern i64 GetEnvironmentVariableA(uptr name, uptr buf, i64 size) and asks for USERPROFILE;
the name joins scripts/sysroot-windows.sh's kernel32.def (a 14th line) and
lib/sys_windows_host.mc. [sysroot].cache in mc.toml and a --sysroot-dir DIR flag override,
so CI never depends on HOME.
2. Downloading: mc spawns a downloader, it does not speak HTTP #
The language reaches sockets by extern and examples/api/lib/http.mc is HTTP/1.1 in plaintext
over socket/bind/listen/accept -- a server, and no TLS anywhere. HTTPS is out of reach, and an
http:// fetch of a checksummed file would still be a downgrade nobody should ship. So
mc sysroot fetch spawns, through the existing drv_spawn:
| host | downloader | present because |
|---|---|---|
| macos | curl -fLsS -o FILE URL | /usr/bin/curl ships with the system |
| linux | curl, else wget -q -O FILE URL | one of the two is on every distribution; both on the CI runners |
| windows | curl.exe | shipped in System32 since Windows 10 1803; on PATH under Git Bash |
drv_spawn returns the exit code and posix_spawnp searches PATH, so "no downloader" is
cannot run curl today; M25 turns it into the offline message (§ 5). The checksum is computed
by mc itself with src/sha256.mc over the downloaded bytes -- no shasum/sha256sum/
certutil divergence across three OSes, and the verification is part of the compiler rather than
part of a script.
Extraction. Verified on this machine: /usr/bin/tar is bsdtar 3.5.3 - libarchive 3.7.4
zlib/1.2.12 liblzma/5.4.3, so macOS handles .tar.gz and .tar.xz out of the box. GNU tar on
Linux handles both (-J). Windows 10 1803+ ships tar.exe (libarchive), which reliably handles
.tar.gz and .zip; its .xz support is not something to rely on -- so any Windows-side
archive is chosen as .zip. An Alpine .apk is a gzip tar (three concatenated gzip members) and
tar -xzf reads it on all three, extracting usr/lib/... next to .PKGINFO.
3. Where the source list lives: a bundled .mc table, not a TOML file #
toml_parse fills one global table (src/toml.mc:65,415); parsing a second file during
mc build would overwrite the project's config, and mc has no toml_parse_str to feed a
bundled blob to. So the list is src/sysroots.mc -- rows of sysroot_src(target, kind, url,
sha256, size, strip, files) registered in a table the same way target() and backend() are
registered in src/main.mc:134-146. It costs no parser, it is deterministic to print, and
mc sysroot list reads it with no I/O. docs/reference/sysroot.md carries the same rows in a
table, and a scripts/check-sysroots.sh diffs the two the way check-bundle diffs the bundle --
which is how the URLs and hashes stay honest.
4. The three targets #
Linux (musl). Alpine's CDN, one file: https://dl-cdn.alpinelinux.org/alpine/v3.22/main/
<aarch64|x86_64>/musl-dev-<ver>.apk, pinned by version and sha256 -- not resolved through
APKINDEX.tar.gz, which changes under us and would make a build non-reproducible (a
mc sysroot fetch --index that reads APKINDEX to report a newer pin is a follow-up, not this
milestone). musl-dev alone carries all four files; the musl package holds only the dynamic
loader, which a static link does not use, so the plan row's "and musl-<ver>.apk" is dropped.
Extract usr/lib/{crt1.o,crti.o,crtn.o,libc.a} into ~/.mc/sysroots/linux-<arch>/. A developer
who needs more (libsqlite3.a for examples/api/mc.linux.toml, which today says "needs apk add
sqlite-static") drops it in the same directory, and {sysroot}/libsqlite3.a resolves.
Windows (import libraries) -- synthesized by default, fetched on request. An import library is
a list of names, which is why scripts/sysroot-windows.sh can already build kernel32.lib from a
13-line .def with llvm-dlltool and no download. mc knows the program's externs and which
[libs] entry each belongs to (extern_lib_find, src/parse.mc:435), so it can write
build/stubs/<lib>.def and spawn llvm-dlltool -- the exact Windows mirror of the Apple stub
idea, covering user32/ws2_32/msvcrt for a user program with no download at all. Data exports would
need a DATA keyword the synthesizer cannot infer; that is the documented gap, and the reason
mc sysroot fetch windows-<arch> still exists: llvm-mingw's GitHub release
(llvm-mingw-<date>-ucrt-<host>.tar.xz, ...-ucrt-x86_64.zip on Windows), from which only
<triple>/lib/lib*.a is extracted (aarch64-w64-mingw32, x86_64-w64-mingw32).
To be verified before this lands: lld-link -- the linker every Windows mc.toml names
(docs/build.md:867) -- accepts mingw-style lib*.a import archives. lld does have MinGW
import-library handling, but on the lld-link (link.exe-compatible) driver rather than
ld.lld, and the acceptance below makes it an explicit step rather than an assumption. If it does
not hold, the fetch extracts the .def files from the mingw-w64 source release (~10 MB, all of
mingw-w64-crt/lib-common/*.def and lib-arm64/*.def) and llvm-dlltool builds the .lib
locally -- lighter than a 400 MB toolchain and identical in shape to what already works.
macOS. Two roads, and the first one is "no road at all":
--exe(src/backend_exe.mc,macho-exe) needs no SDK, no stub and no linker -- it binds dylibs by ordinal and signs the file itself. On macOS that is the default whenmc.tomlhas no[linker](src/driver.mc:404-411). Anything below matters only for the.o+ldroad.-
Synthesized
.tbdstubs.scripts/link.sh:6-7shows what theldroad needs today:-syslibroot "$(xcrun --show-sdk-path)" -lSystem. Both halves come from the SDK. A TBD v4 file is text;mcwritesbuild/stubs/libSystem.tbdlisting exactly the symbols the program declaresexternfor that library, and the[linker]line becomes the stub's path instead of-syslibroot/-lSystem:--- !tapi-tbd tbd-version: 4 targets: [ arm64-macos ] install-name: '/usr/lib/libSystem.B.dylib' current-version: 1351.0 exports: - targets: [ arm64-macos ] symbols: [ _write, _read, _close, _mmap, dyld_stub_binder ]dyld_stub_binderis not one of the program's externs and must be added unconditionally: it is what-lSystemreally contributes to a lazily-bound image. Symbol names are not SDK content, so nothing is redistributed. Honest scoping:lditself ships with the Command Line Tools, and the CLT install brings an SDK with it -- so "macOS withldbut no SDK" is really "ld64.lldfrom Homebrew LLVM, or anxcrunthat fails". The acceptance below tests that shape (ld64.lld+ stub,PATHwithoutxcrun) rather than pretending a bare macOS has a linker. mc sysroot fetch macos-<arch>from a community.tbdmirror stays as the plan describes, for a program linking third-party objects whose symbolsmcnever sees. URL + sha256 insrc/sysroots.mc, extracting onlyusr/lib/*.tbd, printing themc.tomllines.mcembeds nothing from these mirrors.
5. mc sysroot, the cache, and being offline #
A third dispatch line beside src/main.mc:151-153, into a new src/sysroot.mc:
mc sysroot list every registered target, its resolution and where from
mc sysroot path <os>-<arch> the resolved directory on stdout, or exit 2
mc sysroot fetch <os>-<arch> [--yes] [--sysroot-dir DIR]
mc sysroot stub [DIR] [--config F] write the .tbd/.def stubs for a project without linking
list is a fixed walk of the target registry plus path_exists probes -- deterministic output,
no directory listing, safe in a golden test. fetch prints the plan first (URL, size, sha256,
destination), then needs --yes: mc has no isatty, so there is no TTY prompt, and requiring
the flag is the honest version of "asks for confirmation".
Cache layout, ~/.mc/sysroots/<os>-<arch>/<kind>/, with a sibling manifest.toml written by
mc (source URL, sha256, size, the sysroots.mc row id, the extracted file names -- no date,
docs/determinism.md) so list can say where a directory came from and check-sysroots.sh can
re-verify it.
Offline / missing, one message, exit 2 (a code docs/reference/cli.md § Exit codes does not
use yet -- 1 is diagnostics, 3 is the limits verdict):
mc.toml:14:8: no sysroot for linux-aarch64 [sysroot.path]
tried: build/sysroot/linux-aarch64 (no crt1.o), ~/.mc/sysroots/linux-aarch64 (absent)
run: mc sysroot fetch linux-aarch64 --yes
or: curl -fLO https://dl-cdn.alpinelinux.org/alpine/v3.22/main/aarch64/musl-dev-1.2.5-r10.apk
sha256 <hex>
tar -xzf musl-dev-1.2.5-r10.apk -C ~/.mc/sysroots/linux-aarch64 --strip-components=2 usr/lib
The same block comes out of a failed fetch (downloader missing, non-zero curl, checksum
mismatch), so there is one text to get right and one to document.
6. CI #
linux-arm64andlinux-x86_64(.github/workflows/ci.yml:235,300): replace the apt fallback (:277-291,:337-351) withmc sysroot fetch linux-<arch> --yes --sysroot-dir build/sysroot/linux-<arch>, keeping the existingactions/cachestep keyed onsrc/sysroots.mc. That is the proof the fetch path works, on the runner that most resembles a user's machine. Docker (scripts/sysroot-linux.sh) stays as the local path and as the fallback if the CDN is down.check(macOS,:31): acheck-stubsstep -- build a program withld64.lldagainst synthesized stubs,PATHstripped ofxcrun, and run it. This is the macOS acceptance and it needs no network.- Leave alone:
linux-arm64-host/linux-x86-64-host(:360,:436) keepMC_SYSROOT=/usr/lib/aarch64-linux-musl-- they are the "host == target, use the running system" case and should stay the probe's regression test; all four Windows jobs keepscripts/sysroot-windows.sh, which needs nothing.
Out of scope #
No TLS and no HTTP client in mc. No package manager: fetch knows a fixed pinned list, not a
resolver. No APKINDEX resolution (a --index reporter is a follow-up). No frameworks in the macOS
stub writer (libSystem and [libs] entries only). No .def DATA inference on Windows. No
proxy configuration beyond what curl/wget read from the environment themselves. No wasm target
(M27+). Nothing is fetched implicitly: mc build never downloads.
Files and estimated deltas #
| file | delta |
|---|---|
src/sysroot.mc (new): the chain, the probes, path_exists, mc sysroot | ~380 |
src/sysroots.mc (new): the pinned source rows + registry | ~120 |
src/stubs.mc (new): .tbd and .def writers over fs_at/extern_lib_find | ~200 |
src/driver.mc | ~+40: drv_sysroot becomes the chain; {stubs} placeholder in drv_ph |
src/host_macos.mc, src/host_linux.mc, src/host_windows.mc | +12 each: host_home(), host_downloader() |
lib/sys_windows_host.mc, scripts/sysroot-windows.sh | +15 / +1: GetEnvironmentVariableA |
src/main.mc | +2: the sysroot dispatch line, drv_usage gains its line |
src/core.mc, tools/bundle.list, src/bundle_data.mc | +3 entries, regenerated |
scripts/check-sysroots.sh (new), scripts/check-stubs.sh (new) | ~60 + ~80 |
.github/workflows/ci.yml | ~+40 net (two fetch steps replacing two apt blocks, one stub step) |
Makefile | +15 (check-sysroots, check-stubs, wired into check) |
tests/proj/stub.toml, tests/golden/sysroot-list.txt | new |
docs/reference/sysroot.md (new, ~250), docs/guide/50-cross-compile.md, docs/build.md § [sysroot]/[linker], docs/reference/{cli,toml,hooks,diagnostics}.md, docs/guide/{90-linux-host,95-windows-host}.md, docs/ci.md, docs/plan.md | updated |
Acceptance #
- macOS with no SDK: with
PATHstripped ofxcrun, a program usingwriteandsqlite3_openlinks withld64.lldagainstbuild/stubs/*.tbdwritten bymcand runs;mc sysroot listshowsmacos-aarch64 stubs (synthesized). - Fetch into a temp cache and build for Linux:
mc sysroot fetch linux-aarch64 --yes --sysroot-dir $TMPwrites four files whose sha256 match the manifest;mc buildfor linux/aarch64 then succeeds with no[sysroot].pathin the config at all, and the binary runs indocker --platform linux/arm64. - Offline: with a
PATHthat has nocurland nowget,mc sysroot fetchprints the manual block and exits 2;mc buildfor an unresolvable target prints the same block and exits 2 (not 1 --docs/reference/cli.mdgains the row). - Running-system probe: on the Linux host jobs,
mc buildfor the host target with no[sysroot]finds/usr/lib/<arch>-linux-musland links;mc sysroot path linux-<arch>prints it. - Windows import libs:
mc sysroot stubon a program declaringMessageBoxAwritesuser32.def,llvm-dlltoolturns it intouser32.lib, andlld-linklinks it on thewindows-11-armrunner. Separately,mc sysroot fetch windows-aarch64 --yesextracts llvm-mingw'saarch64-w64-mingw32/lib/lib*.aand a link againstlibws2_32.asucceeds -- the § 4 verification; if it fails, the.def-from-source variant lands instead and this spec's Decision 6 is amended in the same PR. mc sysroot listoutput is byte-stable againsttests/golden/sysroot-list.txton all three hosts (target names and resolution kinds only; absolute paths are printed bypath, not bylist).check-sysroots.shprovessrc/sysroots.mcanddocs/reference/sysroot.mdagree;make checkgreen on macOS, and the Linux and Windows subsets green.- No behaviour change for a config that already sets
[sysroot].pathat a populated directory: every existing golden object is byte-identical.
Risks #
- Pinned URLs rot. Alpine prunes old point releases from
dl-cdnwhen a branch ages out. The brake:check-sysroots.shruns a HEAD request in a scheduled CI job, never in PR CI, so a dead URL is a maintenance issue and not a red PR; and the manual block is printed with the URL so a user can substitute a mirror. lld-linkand mingw.a-- § 4. This is the one unverified fact in the spec and it has a designed fallback.GetEnvironmentVariableAwidens the Windows surface by one kernel32 name and one more place where a missing.defline becomes an unresolved external.scripts/sysroot-windows.sh's comment already says to keep the list and theexterns in step.- Exit code 2 is new and scripts may treat "non-zero" uniformly;
scripts/test-*.shneed a read-through. - Scope. Three new modules plus a stub writer is a large milestone. It splits cleanly into
three commits -- the chain and the probes (no network at all), then
mc sysroot/fetch, then the stub writers -- and the first alone is already worth having.
Decisions (architect, 2026-09-04 -- all twelve recommendations adopted) #
- The chain lives in
drv_sysroot's one call site, not in a new placeholder.{sysroot}keeps meaning what it means; only how it is found changes.[sysroot].pathstill wins, and is now checked rather than passed through to the linker's error message. - Spawn
curl/wget/curl.exe; verify withmc's ownsrc/sha256.mc. No HTTP inmc, and no dependence on three different checksum CLIs. - The pinned source list is
src/sysroots.mc, a bundled.mctable, not a TOML file.toml_parsehas one global table (src/toml.mc:65) and no string entry point; a second parse duringmc buildwould clobber the project config.docs/reference/sysroot.mdmirrors it and a check script enforces the agreement. - Cache at
~/.mc/sysroots/<os>-<arch>/, withmanifest.tomland no dates.HOMEvia a newhost_home();USERPROFILEviaGetEnvironmentVariableAon Windows, becausehost_environ()is 0 there.[sysroot].cacheand--sysroot-diroverride, and CI uses the override so no job depends onHOME. - Missing sysroot = exit 2, with one message shared by
buildandfetch. Reserve 2 for "the environment is not ready", distinct from 1 (a diagnostic) and 3 (a limits verdict). -
Windows: synthesize import libraries from the program's own externs by default (
llvm-dlltoolover a generated.def, the exact mirror of the Apple.tbdroad and of whatscripts/sysroot-windows.shalready does), and keepmc sysroot fetch windows-*from llvm-mingw for the cases synthesis cannot cover. Pending the § 4 verification, prefer the mingw-w64 source release (.deffiles, ~10 MB) over the 400 MB binary toolchain.AMENDED 2026-09-04, after the verification § 4 asked for.
lld-linkDOES accept mingw-stylelib*.aimport archives, on both architectures, so the.def-from-source fallback is not needed andmc sysroot fetch windows-*pins the llvm-mingw release archives directly. What was run, on this machine:$ cat probe.mc extern i64 htons(i64 v); i64 main() { return htons(1); } $ mc --backend=coff-obj-arm64 probe.mc -o probe.obj $ llvm-nm probe.obj U htons 00000000 T main $ tar -xJf llvm-mingw-20260826-ucrt-ubuntu-22.04-aarch64.tar.xz --strip-components=3 \ llvm-mingw-20260826-ucrt-ubuntu-22.04-aarch64/aarch64-w64-mingw32/lib/libws2_32.a $ lld-link -machine:arm64 -subsystem:console -entry:main -nodefaultlib \ -out:probe.exe probe.obj libws2_32.a $ echo $? 0 $ llvm-readobj --coff-imports probe.exe Format: COFF-ARM64 Import { Name: WS2_32.dll ImportLookupTableRVA: 0x2028 ImportAddressTableRVA: 0x2038 Symbol: htons (0) }llvm-objdump -d probe.exeshows lld synthesized the ARM64 import thunk itself (adrp x16/ldr x16, [x16, #0x38]/br x16). The same probe with--backend=coff-obj-x86_64,-machine:x64andx86_64-w64-mingw32/lib/libws2_32.aalso links with exit 0 and the sameWS2_32.dll/htonsimport. Recorded indocs/reference/sysroot.md§ 8 as well; the marker for a Windows sysroot therefore acceptskernel32.lib(whatscripts/sysroot-windows.shgenerates) orlibkernel32.a(what the fetch unpacks). - macOS:
--exefirst, stubs second, mirrors third. Say plainly in the guide that the built-in Mach-O writer needs no SDK at all, so the stubs matter only for the.o+ldroad; and scope the stub acceptance told64.lld, since a machine with Apple'sldhas an SDK.dyld_stub_bindergoes into every synthesizedlibSystem.tbd. - Linux:
musl-devonly, pinned version + sha256, no APKINDEX resolution. Reproducibility beats freshness; an--indexreporter can come later. - CI: convert the two
linux-*suite legs' sysroot step tomc sysroot fetch; leave the twolinux-*-hostlegs onMC_SYSROOT=/usr/lib/<arch>-linux-musl(they are the running-system probe's regression) and leave all four Windows legs onscripts/sysroot-windows.sh. Add one macOScheck-stubsstep. Docker stays the local path. mc sysroot fetchrequires--yes; there is no TTY prompt, becausemchas noisattyand inventing one for this is not worth an extern.- New reference page
docs/reference/sysroot.md(the resolution chain, the cache layout, the pinned table, every message), withdocs/guide/50-cross-compile.mdgaining a short "you do not have to assemble this by hand" section that links to it.docs/README.mdgains the row. - Ship in three commits -- chain+probes,
mc sysroot+fetch, stub writers -- so the no-network half can land and be used even if the fetch half slips.
Architect's additions: (a) mc build never downloads, ever -- only mc sysroot fetch --yes does;
(b) exit code 2 is documented in docs/reference/cli.md before any script relies on it; (c) the
three commits of Decision 12 are each gated by make check; (d) the lld-link-vs-mingw .a
verification (acceptance 5) is the first thing the Windows half does, and the outcome is written
into this file's Decision 6 in the same PR.
Deviations from this spec, as built #
-
The "no sysroot" message carries no
file:line:col. § 5 illustrates it asmc.toml:14:8: no sysroot for linux-aarch64 [sysroot.path]; what shipped is a baremc: no sysroot for linux-aarch64followed by the sametried:/run:/or:block (src/sysroot.mc,sysroot_missing();docs/reference/sysroot.md§ 5 anddocs/reference/diagnostics.mddocument the shipped form).Why: the chain is not run from a key. It is run lazily, the first time some
[linker].argsvalue asks for{sysroot}(the same laziness{sdk}has), and there is no single position to blame —[sysroot].pathmay be absent,[target].osis what selected the target, and the argument that triggered it may be one of several. Worse, the same text is whatmc sysroot path <target>prints, and that command reads no config at all: a position would have to be invented for half the callers. Every line of the message names an absolute directory, so nothing about which sysroot was wanted is lost — only which line asked, and that is one target per build.Threading the position of the
{sysroot}-bearing[linker].argsentry throughdrv_phstays open as a follow-up; it is atoml_err_key-shaped change to the driver, not to this file.