Hermetic, pre-built sysroots for cross-compiling with Kairo (Clang-as-a-library, forked from LLVM 22.1.0), and the scripts that build them.
A sysroot here is a self-contained directory holding a target's C library headers and archives, a static libc++ (where the target has one), the compiler-rt builtins, and a SYSROOT.toml that tells Kairo how to compile and link against it. Kairo never uses the host's headers or libraries, so a build gives the same result on any machine.
- Release assets (tarballs plus
manifest.json) are on GitHub Releases under the tagv1-llvm22. - This repository holds only the curation pipeline (
curate/) and provenance (DOWNLOADS.md). Tarballs are never committed.
- Targets
- Using a sysroot
- Repository layout
- SYSROOT.toml reference
- manifest.json reference
- Building sysroots from source
- Local-only targets: MSVC and Darwin
- Validating a sysroot
- Cutting a release
- Licensing
- Known gaps
Sysroot (--target) |
Clang triple | libc | libc++ | Linking |
|---|---|---|---|---|
x86_64-linux-musl |
x86_64-unknown-linux-musl |
musl 1.2.6 | yes | fully static |
aarch64-linux-musl |
aarch64-unknown-linux-musl |
musl 1.2.6 | yes | fully static |
armv7-linux-musl |
armv7-unknown-linux-musl |
musl 1.2.6 | yes | fully static |
i686-linux-musl |
i686-unknown-linux-musl |
musl 1.2.6 | yes | fully static |
riscv64-linux-musl |
riscv64-unknown-linux-musl |
musl 1.2.6 | yes | fully static |
x86_64-linux-gnu-glibc2.31 |
x86_64-linux-gnu |
glibc 2.31 | yes | glibc dynamic, libc++ static |
aarch64-linux-gnu-glibc2.31 |
aarch64-linux-gnu |
glibc 2.31 | yes | glibc dynamic, libc++ static |
armv7-linux-gnueabihf-glibc2.31 |
armv7-linux-gnueabihf |
glibc 2.31 | yes | glibc dynamic, libc++ static |
i686-linux-gnu-glibc2.31 |
i686-linux-gnu |
glibc 2.31 | yes | glibc dynamic, libc++ static |
riscv64-linux-gnu-glibc2.31 |
riscv64-linux-gnu |
glibc 2.31 | yes | glibc dynamic, libc++ static |
x86_64-windows-gnu |
x86_64-pc-windows-gnu |
mingw-w64 (UCRT) | yes | PE/COFF |
aarch64-windows-gnu |
aarch64-pc-windows-gnu |
mingw-w64 (UCRT) | yes | PE/COFF |
armv7-windows-gnu |
armv7-pc-windows-gnu |
mingw-w64 (UCRT) | yes | PE/COFF |
i686-windows-gnu |
i686-pc-windows-gnu |
mingw-w64 (UCRT) | yes | PE/COFF |
x86_64-unknown-freebsd |
x86_64-unknown-freebsd14.4 |
FreeBSD 14.4 base | yes | fully static |
aarch64-unknown-freebsd |
aarch64-unknown-freebsd14.4 |
FreeBSD 14.4 base | yes | fully static |
x86_64-unknown-netbsd |
x86_64-unknown-netbsd10.1 |
NetBSD 10.1 base | yes | fully static |
wasm32-wasi |
wasm32-unknown-wasi |
wasi-libc (wasi-sdk 33) | yes | exception-enabled (-fwasm-exceptions) |
A binary built against the glibc 2.31 sysroots runs on any distribution with glibc 2.31 or newer (Ubuntu 20.04+, Debian 11+, RHEL 9+, Fedora 32+).
| Sysroot | Why it is local only | Script | Runs on |
|---|---|---|---|
x86_64-windows-msvc, aarch64-windows-msvc (ARM64), i686-windows-msvc (x86) |
Microsoft CRT and Windows SDK license forbids redistribution | curate/msvc.sh |
Linux, using an xwin splat |
arm64-apple-macosx, arm64e-apple-macosx, x86_64-apple-macosx |
Apple SDK license forbids redistribution | curate/darwin.sh |
macOS with Xcode |
See Local-only targets.
- Download the tarball for your target and
manifest.jsonfrom thev1-llvm22release. - Verify it against the manifest:
jq -r '.targets["x86_64-linux-musl"].sha256' manifest.json sha256sum x86_64-linux-musl-llvm22.tar.xz - Extract it anywhere. Every tarball's root directory is the sysroot name:
tar -xJf x86_64-linux-musl-llvm22.tar.xz -C ~/kairo-sysroots/ # -> ~/kairo-sysroots/x86_64-linux-musl/
- Point Kairo at it with
--sysroot.--resource-dirnames Kairo's clang resource directory, where clang's builtin headers such asstddef.hlive:kairo --sysroot=$HOME/kairo-sysroots/x86_64-linux-musl \ --resource-dir=<kairo>/build/llvm/lib/clang/22 \ --target x86_64-linux-musl main.k -o main
Kairo reads the sysroot's SYSROOT.toml to find include and library directories, startup objects, link libraries and compiler-rt. Nothing else needs configuring.
curate/ the pipeline: one script per family, plus packaging
fetch-*.sh download upstream packages into extract/
build-glibc-runtimes.sh build libc++/libunwind/compiler-rt for the glibc targets
build-builtins.sh build compiler-rt builtins for linux-musl and windows-gnu
build-bsd-runtimes.sh build libc++/libc++abi/libunwind + builtins for FreeBSD/NetBSD
build-wasi-builtins.sh build compiler-rt builtins for wasm32-wasi
<family>.sh stage extract/ -> staging/<sysroot>/ and write SYSROOT.toml
decompress-debug.sh decompress zlib debug sections (so any lld can link)
tar-dist.sh staging/<sysroot>/ -> dist/<sysroot>-llvm22.tar.xz
emit-manifest.sh dist/*.tar.xz -> dist/manifest.json
replay-validate.sh compile and link a test program from SYSROOT.toml alone
link-templates/ validated link recipe per family (the source of truth)
DOWNLOADS.md every upstream URL and version used
dist/manifest.json the release manifest (committed; tarballs are not)
extract/ (gitignored) unpacked upstream inputs
runtimes-build/ (gitignored) CMake build trees for the LLVM runtimes
runtimes-out/ (gitignored) installed runtimes, before staging
staging/ (gitignored) finished sysroots, one directory each
Not every script is executable in git, so this README runs them all as bash curate/<script>.
Every sysroot has a flat SYSROOT.toml at its root. All paths are relative to the sysroot directory.
| Key | Type | Meaning | Read by Kairo |
|---|---|---|---|
triple |
string | Sysroot name (matches the --target spelling) |
yes |
llvm_version |
string | LLVM version the C++ runtimes were built with | yes |
family |
string | linux-musl, linux-gnu, windows-gnu, windows-msvc, freebsd, netbsd, wasi, darwin |
yes |
sysroot |
string | Root for the paths below (always .) |
yes |
include_dirs |
string[] | Header search dirs. Dirs containing /c++/ go first, then clang's resource headers, then the rest |
yes |
lib_dirs |
string[] | Library search dirs | yes |
has_libcxx |
bool | Whether a static libc++ is present (needed for Kairo programs) | yes |
static |
bool | ELF: fully static link. windows-msvc: static CRT (/MT) |
yes |
crt_startup / crt_end |
string[] | Startup/teardown objects in link order | yes |
libc_link |
string[] | C library inputs, in the linker's own syntax (-lc for ELF, libcmt.lib for lld-link) |
yes |
libcxx_link |
string[] | C++ runtime inputs, same syntax | yes |
rtlib / unwindlib |
string | compiler-rt, libunwind |
yes |
linker |
string | lld or lld-link |
yes |
resource_dir |
string | Sysroot-relative dir holding compiler-rt (compiler-rt/lib/<triple>/ or compiler-rt/lib/windows/) |
yes |
cc_isolation |
string[] | Flags that cut off host headers (-nostdlibinc -nostdinc++) |
no (Kairo hard-codes them) |
glibc_version |
string | linux-gnu only: the glibc floor | no |
cc_defines |
string[] | Macros every C/C++ TU must define (windows-msvc: _CRT_STDIO_ISO_WIDE_SPECIFIERS) |
not yet |
local_only |
string[] | Dirs holding vendor files that must never ship. tar-dist.sh excludes them |
no |
redistributable |
bool | false: tar-dist.sh refuses to pack the sysroot (darwin) |
no |
msvc_crt |
string | windows-msvc: static |
no |
"Read by Kairo" is current as of kairo-lang/Linker/API/SysrootManifest.k, which ignores unknown keys. The exact link line each family needs is in curate/link-templates/<family>.recipe.md.
dist/manifest.json is what Kairo downloads to find and verify sysroots. curate/emit-manifest.sh generates it from the tarballs in dist/ and each sysroot's SYSROOT.toml. Never edit it by hand.
{
"release": "v1-llvm22",
"llvm_version": "22.1.0",
"targets": {
"x86_64-linux-gnu-glibc2.31": {
"asset": "x86_64-linux-gnu-glibc2.31-llvm22.tar.xz",
"url": "https://github.com/kairolang/kairo-sysroots/releases/download/v1-llvm22/...",
"sha256": "…",
"size": 5616196,
"family": "linux-gnu",
"has_libcxx": true,
"glibc_version": "2.31",
"resource_dir": "compiler-rt"
}
}
}Optional per-target keys: glibc_version, resource_dir, local_only. The download base URL is BASE in emit-manifest.sh.
-
Linux x86_64 host with
bash,cmake,ninja,tar/xz,curl,jq,llvm-objcopy,llvm-readobj. -
A kairo-lang checkout next to this repo (
../kairo-lang) with:- the LLVM fork source at
Lib/llvm-runtimes, - its built clang at
build/llvm/bin/clang(andclang++,clang-cl,lld-link). Build them withninja -C build/llvm clang lld, plusln -sf clang build/llvm/bin/clang++.
The fork's clang is required wherever runtimes are built from source. It carries the
__float128patches, without which compiler-rt's quad-float routines silently compile to nothing. - the LLVM fork source at
-
For MSVC:
xwin(cargo install xwin --locked).
Every staging script writes staging/<sysroot>/ and replaces anything already there.
Upstream URLs and exact versions for every input are in DOWNLOADS.md.
linux-gnu (glibc 2.31): fully scripted.
bash curate/fetch-glibc.sh # Ubuntu 20.04 debs -> extract/<name>/
bash curate/build-glibc-runtimes.sh # libc++, libc++abi, libunwind, compiler-rt from source
bash curate/glibc.sh # -> staging/<arch>-linux-gnu*-glibc2.31/linux-musl: download the Alpine musl-dev, linux-headers, libc++-static and llvm-libunwind-static apks listed in DOWNLOADS.md, and unpack each arch's set into extract/<arch>-linux-musl/. Then:
bash curate/fetch-musl-libcxx.sh # grafts libc++ headers into extract/
bash curate/fetch-musl-compiler-rt.sh # Alpine crtbegin/crtend -> staging/compiler-rt-musl/
bash curate/musl.sh # -> staging/<arch>-linux-musl/
bash curate/build-builtins.sh # compiler-rt builtins from the fork (f128, i128, bf16)windows-gnu: unpack llvm-mingw-20260224-ucrt-ubuntu-22.04-x86_64.tar.xz into extract/mingw/, then run bash curate/mingw.sh and bash curate/build-builtins.sh. The builtins are built from the fork rather than taken from llvm-mingw. llvm-mingw's were built by a stock clang and lack f128 and the 32-bit i128 routines.
FreeBSD / NetBSD: unpack FreeBSD base.txz into extract/<arch>-unknown-freebsd/, and NetBSD base.tar.xz plus comp.tar.xz into extract/x86_64-unknown-netbsd/. Then run bash curate/freebsd.sh or bash curate/netbsd.sh, then bash curate/build-bsd-runtimes.sh. The staging scripts keep usr/include, usr/lib and lib as they are, so the base system's relative symlinks still resolve. build-bsd-runtimes.sh adds LLVM 22 libc++/libc++abi/libunwind (libcxx/) and compiler-rt builtins (compiler-rt/lib/<os>/) built from the fork. FreeBSD's base libc++ is 19, and NetBSD's base has only libstdc++.
wasm32-wasi: unpack wasi-sysroot-33.0+m.tar.gz into extract/wasm32-wasi/, then bash curate/wasi.sh. Then run bash curate/build-wasi-builtins.sh to build the compiler-rt builtins (including bf16) from the fork's source into compiler-rt/lib/wasm32-unknown-wasi/.
windows-msvc / darwin: see the next section.
Microsoft's and Apple's licenses allow using their SDKs but not redistributing them. This repository therefore ships only instructions and our own compiled code, never vendor files. You accept the vendor license yourself, on your own machine.
- Fetch the CRT and SDK. This step accepts Microsoft's license terms; read them first:
That's about 1.6 GB. It writes
xwin --accept-license --arch x86_64,aarch64,x86 \ --cache-dir extract/xwin-cache splat --output extract/xwinextract/xwin/{crt,sdk}with lowercase symlinks, so#include <Windows.h>resolves on a case-sensitive filesystem. - Build the sysroots:
For each arch, this copies the splat into
bash curate/msvc.sh ../kairo-lang # all three bash curate/msvc.sh ../kairo-lang arm64 i386 # aliases accepted
staging/<arch>-windows-msvc/msvc/and builds libc++ (vcruntime ABI, static CRT/MT) and the compiler-rt builtins with the fork'sclang-cl. It writesSYSROOT.toml, then links a C++ test program twice: once directly and once replayed from the TOML.
Resulting layout:
<arch>-windows-msvc/
SYSROOT.toml
include/c++/v1/ ours
lib/libc++.lib ours
compiler-rt/lib/windows/clang_rt.builtins-<arch>.lib ours
msvc/ Microsoft, local_only
Binaries are fully static: an x86_64 test binary imports only KERNEL32.dll and needs no VC++ redistributable.
What a consumer must do (details in curate/link-templates/windows-msvc.recipe.md):
- Search the CRT/SDK headers after clang's resource headers.
- Define
_CRT_STDIO_ISO_WIDE_SPECIFIERS. libc++ is built with it, and the UCRT's/failifmismatchrejects a mismatch at link time. - Compile in MS language mode (
-fms-extensions -fms-compatibility).
tar-dist.sh packs these sysroots without msvc/. The result is about 1.4 MB of LLVM-licensed code, and the user supplies their own splat in msvc/.
Run on a Mac with Xcode installed. Apple's license only allows the SDK on Apple-branded hardware.
bash curate/darwin.sh <kairo-repo> arm64 x86_64 arm64eThis stages the local Apple SDK (xcrun --sdk macosx --show-sdk-path), with Apple's libc++ replaced by ours and compiler-rt quad-float builtins added. Its SYSROOT.toml says redistributable = false, and tar-dist.sh refuses to pack it.
replay-validate.sh reads only SYSROOT.toml, rebuilds the clang command from it, and compiles and links a C++ program that throws and catches (or a C program when has_libcxx = false). If the target matches the host, it also runs the result; wasi runs under node.
# ELF targets: point -resource-dir at the sysroot's compiler-rt
bash curate/replay-validate.sh staging/x86_64-linux-musl ../kairo-lang/build/llvm/bin/clang \
-resource-dir=staging/x86_64-linux-musl/compiler-rt
# windows-msvc: builtins are found through resource_dir automatically
bash curate/replay-validate.sh staging/x86_64-windows-msvc ../kairo-lang/build/llvm/bin/clang \
-B../kairo-lang/build/llvm/binThe strongest test is building a real Kairo program against the sysroot. From the kairo-lang checkout:
build/x86_64-linux-gnu/debug/bin/kairo --sysroot=<sysroot-dir> \
--resource-dir=$PWD/build/llvm/lib/clang/22 \
--builtins-dir=$PWD/Lib/builtin --std-dir=$PWD/Lib/std \
test.k --cache-dir=/tmp/kais -o test --target <sysroot-name>bash curate/tar-dist.sh # every sysroot in staging/ (darwin skipped, msvc/ excluded)
bash curate/tar-dist.sh x86_64-linux-musl # or specific ones
bash curate/emit-manifest.sh # regenerate dist/manifest.json; checks it is valid JSON
gh release upload v1-llvm22 dist/* --clobbertar-dist.shrunsdecompress-debug.shfirst, so no tarball contains zlib-compressed debug sections, which an lld built without zlib refuses to link. It then packs with sorted names and zero owner, keeping symlinks as symlinks.- License guard:
tar-dist.shrefuses any sysroot whose TOML saysredistributable = false, excludes everylocal_onlydir, and deletes the tarball if alocal_onlypath still slipped in. - Commit the regenerated
dist/manifest.json. Kairo pins sysroots by its sha256 values.
| Part | License |
|---|---|
| Scripts in this repository | as stated in the repository |
| libc++, libc++abi, libunwind, compiler-rt (built here or by upstream) | Apache-2.0 WITH LLVM-exception |
| musl | MIT |
| glibc | LGPL-2.1-or-later (dynamically linked; the target system supplies the real .so) |
| mingw-w64 / llvm-mingw | ZPL-2.1 and public domain (see llvm-mingw) |
| FreeBSD / NetBSD base | BSD |
| wasi-libc / wasi-sdk | Apache-2.0 WITH LLVM-exception, MIT, CC0 |
| Microsoft CRT + Windows SDK | Microsoft license. Never redistributed: you fetch it with xwin and accept the terms yourself |
| Apple macOS SDK | Apple Xcode/SDK license. Never redistributed: use your local Xcode on Apple hardware |
- compiler-rt builtins need the fork's clang. Every builtins archive is built from Kairo's LLVM. The fork carries
__float128on every target, bf16 lowering for WebAssembly, and__trunctfbf2beyond x86_64. A stock LLVM 22 builds archives without f128 on most targets, without bf16 on wasm, and without the 32-bit i128 routines unless-fforce-enable-int128reaches the compiler. The build scripts check for these symbols and stop if any is missing. - COFF has no symbol aliases. compiler-rt defines
__eqtf2,__netf2,__lttf2,__gttf2and__cmptf2as aliases, and they vanish on windows-msvc.msvc.shadds them as forwarding functions. windows-gnu is unaffected: MinGW's COFF supports aliases through weak externals. - Kairo on windows-msvc. The sysroots link correctly through the clang driver, but Kairo can't build for them until kairo-lang:
- passes the MS language-mode flags on its cc1 line (
ClangBackend.k,_driver_target_args), - stops importing
unistd.hunconditionally (Lib/std/io.k), - reads
cc_definesfromSYSROOT.toml.
- passes the MS language-mode flags on its cc1 line (
- FreeBSD/NetBSD and base C++ libraries. Kairo binaries link LLVM 22 libc++ statically. A system C++ library built against the base runtime (libc++ 19/libcxxrt on FreeBSD, libstdc++ on NetBSD) can still be linked, but C++ objects can't cross between the two runtimes. Plain C libraries are unaffected.