# shellcheck shell=bash # brintium — the one place configuration is resolved. # # WHAT THIS REPLACES. The drivers used to be reached through a set of ~20-line # shims that lived in the CONSUMER repository, one per driver. Each shim # exported a few variables and exec'd the real driver; between them they set # exactly four (the sysroot, the LLVM bin directory, the source root for the # glibc arms, and which C driver the C++ driver delegates to). Because there # were six copies of that decision, they drifted: one exported the pass-plugin # paths, its sibling exported none, a third exported two of three — so WHICH # staged plugin a link resolved, and whether it was provenance-checked at all, # depended on which driver you came through. The plugin paths were eventually # taken out of the shims and derived from one pointer instead, which is the # same fix as this file, applied once. This file finishes it: configuration is # decided HERE, and every front door in bin/ reads it. # # THE CONTRACT. # * Explicit environment always wins. Every variable below is an override, # and the legacy spelling of each is honored where one exists. # * A path that does not exist is a LOUD refusal naming the variable, the # path it resolved to, and what produces it. There is no second candidate # and no fallback. A silently-resolved stale sysroot is the exact failure # this replaces: it compiles against one libc's headers and links another # libc's bitcode, and the image runs until it doesn't. # * Nothing is checked speculatively. A front door calls brintium_require for # the inputs it actually uses, as its first act, so `brintar` does not # refuse because a sysroot it never reads is missing. # # Sourced by every script in bin/. NOT by the implementations in libexec/ — # those inherit the resolved environment across the exec, which is why the # front doors exec rather than call: this toolchain compiles whole # distributions and a second process per translation unit is not free. brintium_fatal() { echo "brintium: FATAL: $*" >&2 exit 1 } # ── BRINTIUM — the repo root ──────────────────────────────────────────────── # Derived from this file's own location, never hardcoded: the checkout is moved # and cloned under many names. An explicit BRINTIUM still wins, and is checked, # because a wrong root resolves every path below to a wrong path. BRINTIUM="${BRINTIUM:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" [ -f "$BRINTIUM/libexec/config.sh" ] || brintium_fatal \ "BRINTIUM='$BRINTIUM' is not a brintium checkout (no libexec/config.sh under it). Unset BRINTIUM to derive it from the driver's own location, or point it at the checkout root." export BRINTIUM # ── BRINTIUM_LLVM — the pinned LLVM bin directory ─────────────────────────── # The pass plugins are out-of-tree LLVM plugins and clang has no stable plugin # ABI, so they must load into the exact LLVM they were built against. The # default is the fork this repo pins as a submodule. WASMLD_HWJS_LLVM_BIN is # the spelling the drivers and every existing consumer already read, so it is # honored as an input here and re-exported as the single answer. BRINTIUM_LLVM="${BRINTIUM_LLVM:-${WASMLD_HWJS_LLVM_BIN:-$BRINTIUM/llvm-project/build/bin}}" export BRINTIUM_LLVM export WASMLD_HWJS_LLVM_BIN="$BRINTIUM_LLVM" # ── BRINTIUM_SYSROOT — the libc/startfile staging tree ────────────────────── # What the drivers consume out of it: libc headers, the coro startfiles, the # libc bitcode pool and the builtins archive. One pointer for all of them, # because when they were separate variables a link could take its headers from # one tree and its bitcode from another and run until it didn't. # # NOT the pass plugins. Those are a derived artifact of THIS repository, built # by toolchain/build.sh into pass/coroutinize/build, which is the one home # libexec/brintld resolves. The sysroot is produced by the libc build, which # has no way to produce plugins, so a plugin path derived from this pointer # would name a directory nothing fills — the "one derived artifact, two ages" # defect with one of the ages being "never built". BRINTIUM_SYSROOT="${BRINTIUM_SYSROOT:-${HWJS_TOOLS_DIR:-$BRINTIUM/sysroot}}" export BRINTIUM_SYSROOT export HWJS_TOOLS_DIR="$BRINTIUM_SYSROOT" # A few driver paths were derived from a source root that used to be the # enclosing super-repo; standalone it would default to two levels above this # checkout, which is whatever directory happens to be there. Pin it to the # checkout so the value is at least ours. export HWJS_SUPER="${HWJS_SUPER:-$BRINTIUM}" # ── BRINTIUM_MUSL_SRC / BRINTIUM_GLIBC_SRC — the libc checkouts ───────────── # Both are submodules here, and both are read by the drivers rather than only # by the libc build: each crt startfile carries a sidecar attesting the sha256 # of the source it was compiled from, and the driver re-checks that sidecar at # the link that injects the crt. Those two checks were spelled as package # subpaths under the old super-repo, which name nothing in this layout — and # because an absent source reads as "cannot check", both went quiet rather # than loud. Resolved here so the drivers and the sysroot build read one # answer, and so an override moves both together. export BRINTIUM_MUSL_SRC="${BRINTIUM_MUSL_SRC:-$BRINTIUM/musl}" export BRINTIUM_GLIBC_SRC="${BRINTIUM_GLIBC_SRC:-$BRINTIUM/glibc}" # ── BRINTIUM_KERNEL_HEADERS / BRINTIUM_RT / BRINTIUM_KERNEL_SRC ──────────── # The three inputs that come from the kernel. None is produced by this # toolchain: the UAPI headers are installed by the kernel build, hwjs_user_rt.bc # is the kernel-owned runtime TU with its own attestation sidecar, and the third # is the kernel checkout both of those came out of. Guessing a default for any # of them means guessing WHICH kernel, so there is none — a caller that needs # one calls `brintium_require kernel-headers` / `rt` / `kernel-src` and refuses # by name. BRINTIUM_KERNEL_HEADERS="${BRINTIUM_KERNEL_HEADERS:-}" BRINTIUM_RT="${BRINTIUM_RT:-}" # The kernel SOURCE checkout, and the only one of the three that is a source # tree rather than a build product. Nothing here compiles against it and no # header is read out of it: it is read to ANSWER A QUESTION ABOUT THE RUNTIME. # The runtime arrives carrying an attestation of the kernel sources it was built # from, and the drivers re-key that attestation at every link that injects it — # by running the kernel's own scripts/hwjs-rt-srckey.sh over the kernel's own # sources, which is the one definition of that key there is. Without a kernel # checkout the question cannot be asked at all. # # It was the one input of the three that was never declared, because for as long # as this toolchain was a submodule of the repository that holds the kernel, a # relative walk up out of the sysroot happened to land on it. Standalone that # walk names nothing, and the refusal it produced pointed at a path nobody had # chosen. Declared here so the contract is written where a reader looks for it, # and so the drivers take a pointer instead of computing one. BRINTIUM_KERNEL_SRC="${BRINTIUM_KERNEL_SRC:-}" export BRINTIUM_KERNEL_HEADERS BRINTIUM_RT BRINTIUM_KERNEL_SRC if [ -n "$BRINTIUM_KERNEL_HEADERS" ]; then # Both driver arms take the UAPI headers under their own names; set both so # the pointer is one variable at this layer no matter which arm runs. export GLIBC_UAPI_INC="${GLIBC_UAPI_INC:-$BRINTIUM_KERNEL_HEADERS}" export WACKY_UAPI_INCLUDE="${WACKY_UAPI_INCLUDE:-$BRINTIUM_KERNEL_HEADERS}" fi if [ -n "$BRINTIUM_RT" ]; then # An explicit runtime pointer is the drivers' documented work-in-progress # escape: they take it verbatim and say, on every link, that its provenance # is unverified. Leaving BRINTIUM_RT unset keeps the attested default path, # where the RT-PROVENANCE sidecar is checked. Do not set it to "the same # file the default would find" — that trades a verified artifact for an # unverified one and changes nothing else. export WACKY_HWJS_RT="${WACKY_HWJS_RT:-$BRINTIUM_RT}" fi # ── WASMLD_HWJS — which substrate a build gets ────────────────────────────── # This defaulted to 0 for its whole life, and 0 selects a retired arm: a real # build has to pass 1 or it gets an image the coroutinize pass never ran over. # A knob whose default produces the wrong output is not a knob, so the default # is 1 here. 0 is still reachable, and announces itself, because a gate arm # that deliberately builds the retired shape must be visible in the log rather # than indistinguishable from a build that forgot. case "${WASMLD_HWJS:-}" in '') export WASMLD_HWJS=1 ;; 1) export WASMLD_HWJS=1 ;; 0) export WASMLD_HWJS=0 echo "brintium: NOTE: WASMLD_HWJS=0 — the RETIRED pre-coroutinizer arm. The whole-program merge and the pass do NOT run; the resulting module cannot fork, cannot be preempted, and the linker refuses most of this arm outright. Unset the variable for the supported build." >&2 ;; *) brintium_fatal "WASMLD_HWJS='$WASMLD_HWJS' is neither 0 nor 1. It selects the compilation substrate, so a value nobody can read is not something to guess at; unset it for the default (1)." ;; esac # ── brintium_require — refuse by name, at the vantage that needs it ───────── # Usage: brintium_require llvm sysroot (front doors, first thing) # brintium_require kernel-headers (arms that compile against UAPI) # brintium_require rt (arms that inject the runtime) # brintium_require kernel-src (arms that re-key its attestation) # # WHERE THE RUNTIME'S OWN CHECK LIVES. A link's decision to inject the runtime # is made inside the driver, past the exec — where the functions in this file # are out of scope — so the driver spells that refusal itself, in the same # terms. The case below is for any caller that resolves through this file and # knows up front that it needs the kernel checkout. brintium_require() { local what for what in "$@"; do case "$what" in llvm) [ -d "$BRINTIUM_LLVM" ] || brintium_fatal \ "BRINTIUM_LLVM resolves to '$BRINTIUM_LLVM', which does not exist. That is the pinned LLVM's bin/ directory, produced by llvm-project/build-brintos.sh (see toolchain/llvm-from-source.md for the flags and the pin). Point BRINTIUM_LLVM at an existing build or build it. Not searching elsewhere on purpose: the pass plugins have no stable ABI, and loading them into a different LLVM is a crash or a miscompile, not a version error." [ -x "$BRINTIUM_LLVM/clang" ] || brintium_fatal \ "BRINTIUM_LLVM='$BRINTIUM_LLVM' exists but holds no executable clang. That is a half-built or wrong-directory LLVM (the value must be the build tree's bin/, the directory containing clang, wasm-ld, opt and the llvm-* binutils). Rebuild it with llvm-project/build-brintos.sh, which builds the binutils too — a plain 'ninja clang lld' does not." ;; sysroot) [ -d "$BRINTIUM_SYSROOT" ] || brintium_fatal \ "BRINTIUM_SYSROOT resolves to '$BRINTIUM_SYSROOT', which does not exist. That tree holds the libc headers, the coro startfiles, the libc bitcode pool and the builtins archive that a link consumes; the libc build produces it and this toolchain does not. (The pass plugins are NOT in it — they are this repository's own artifact, built by toolchain/build.sh.) Point BRINTIUM_SYSROOT at the staged tree. Refusing rather than looking somewhere else: a sysroot found by search is a sysroot nobody chose, and the images it produces mix one libc's headers with another's bitcode." ;; kernel-headers) [ -n "$BRINTIUM_KERNEL_HEADERS" ] || brintium_fatal \ "BRINTIUM_KERNEL_HEADERS is not set, and this arm compiles against the kernel UAPI headers (linux/*.h, asm/*.h). They are installed by the kernel build, not by this toolchain, so there is no default to fall back to — a guess would compile the userland against a different kernel's ABI than the one it will run on. Set BRINTIUM_KERNEL_HEADERS to the installed UAPI include directory." [ -d "$BRINTIUM_KERNEL_HEADERS" ] || brintium_fatal \ "BRINTIUM_KERNEL_HEADERS='$BRINTIUM_KERNEL_HEADERS' does not exist. It must be the kernel's installed UAPI include directory (the one containing linux/ and asm/), produced by the kernel build." ;; kernel-src) [ -n "$BRINTIUM_KERNEL_SRC" ] || brintium_fatal \ "BRINTIUM_KERNEL_SRC is not set, and this arm re-keys the hwjs user runtime's attestation against the kernel it claims to have come from. That means running the kernel's own scripts/hwjs-rt-srckey.sh over the kernel's own sources; this toolchain has no kernel and cannot answer the question without one. There is no default, for the reason there is none for the other two and one more: a kernel checkout that is not the one the runtime was built from answers WRONG rather than not at all. Set BRINTIUM_KERNEL_SRC to the kernel checkout that produced the runtime." [ -d "$BRINTIUM_KERNEL_SRC" ] || brintium_fatal \ "BRINTIUM_KERNEL_SRC='$BRINTIUM_KERNEL_SRC' does not exist. It must be the kernel SOURCE checkout — the tree the runtime was built and attested in — not the installed UAPI headers and not a build directory." [ -x "$BRINTIUM_KERNEL_SRC/scripts/hwjs-rt-srckey.sh" ] || brintium_fatal \ "BRINTIUM_KERNEL_SRC='$BRINTIUM_KERNEL_SRC' exists but ships no executable scripts/hwjs-rt-srckey.sh. That script is the one definition of the key the runtime's attestation is written against, so a checkout without it cannot verify one — either this is not a kernel checkout, or it predates the script." ;; rt) [ -n "$BRINTIUM_RT" ] || brintium_fatal \ "BRINTIUM_RT is not set, and this arm injects the hwjs user runtime. hwjs_user_rt.bc is produced and attested by the KERNEL build; this toolchain cannot produce or default it. Set BRINTIUM_RT to the built runtime bitcode, or leave it unset and let the driver take the attested copy staged in \$BRINTIUM_SYSROOT/build." [ -f "$BRINTIUM_RT" ] || brintium_fatal \ "BRINTIUM_RT='$BRINTIUM_RT' does not exist. It must be the hwjs_user_rt bitcode the kernel build produces." ;; *) brintium_fatal "brintium_require: '$what' is not a known input (llvm, sysroot, kernel-headers, rt, kernel-src). This is a bug in the front door that called it, not in the caller's environment." ;; esac done } # ── brintium_exec_llvm_tool — the binutils front doors, one implementation ── # Six near-identical wrappers are how the shims drifted in the first place, so # the check and the exec live here and each front door names its tool. brintium_exec_llvm_tool() { local tool="$1"; shift local path="$BRINTIUM_LLVM/$tool" [ -x "$path" ] || brintium_fatal \ "'$tool' is not executable at '$path'. The pinned LLVM resolved (BRINTIUM_LLVM='$BRINTIUM_LLVM') but does not carry this tool — that is a partial build: 'ninja clang lld' produces neither the llvm-* binutils nor the wasm32 tools a link and a kernel build need. Rebuild with llvm-project/build-brintos.sh." exec "$path" "$@" } # ── brintium_select_libc — which libc a C/C++ line is for ─────────────────── # Sets BRINTIUM_LIBC to musl or glibc and BRINTIUM_ARGV to the caller's argv # with the -mlibc= selector removed (clang has no such flag and refuses it). # # Precedence, highest first: an explicit -mlibc=, the target triple, the name # the front door was invoked as (autotools calls wasm32-hwjs-linux-gnu-gcc, not # brintcc), then musl. An explicit -mlibc= that CONTRADICTS the triple is # refused rather than silently won: the two spellings disagreeing means the # caller believes something about this line that is not true, and picking # either one hides that. brintium_select_libc() { local self from_flag='' from_triple='' from_name='' a t want_target=0 self="$(basename -- "$0")" case "$self" in *-gnu-gcc|*-gnu-g++|*-gnu-cc|*-gnu-c++|*-gnu-clang|*-gnu-clang++) from_name=glibc ;; *-musl-gcc|*-musl-g++|*-musl-cc|*-musl-c++|*-musl-clang|*-musl-clang++) from_name=musl ;; esac BRINTIUM_ARGV=() for a in "$@"; do t='' if [ "$want_target" = 1 ]; then want_target=0; t="$a" else case "$a" in -mlibc=*) case "${a#-mlibc=}" in musl|glibc) ;; *) brintium_fatal "$self: '$a' — the libc selector takes musl or glibc, nothing else. Refusing rather than falling back to the default: a misspelled selector that silently built the other libc is a link failure a hundred translation units later." ;; esac if [ -n "$from_flag" ] && [ "$from_flag" != "${a#-mlibc=}" ]; then brintium_fatal "$self: the command line carries both -mlibc=$from_flag and $a. Pick one." fi from_flag="${a#-mlibc=}" continue ;; # stripped: clang must never see it --target=*|-target=*) t="${a#*=}" ;; -target|--target) want_target=1 ;; esac fi case "$t" in '') ;; *-gnu*) from_triple=glibc ;; *-musl*) from_triple=musl ;; # Anything else (the wasm32-hwjs-unknown link triple, for one) carries no # libc signal and must not be read as one. esac BRINTIUM_ARGV+=( "$a" ) done if [ -n "$from_flag" ] && [ -n "$from_triple" ] && [ "$from_flag" != "$from_triple" ]; then brintium_fatal "$self: -mlibc=$from_flag contradicts the target triple on the same line, which names $from_triple. One of the two is wrong; refusing rather than choosing for you." fi BRINTIUM_LIBC="${from_flag:-${from_triple:-${from_name:-musl}}}" export BRINTIUM_LIBC } # ── brintium_export_build_tools — what a foreign build system reads ───────── # One definition, because bin/brintconfigure and bin/brintmake have to agree. # A package configured against one compiler and built with another is the # classic way to get a tree that probes clean and links wrong, and the two # helpers are separate programs precisely so they can be run minutes apart. _brintium_set_tool() { local var="$1" val="$2" old="${!1:-}" if [ -n "$old" ] && [ "$old" != "$val" ]; then # Not a refusal: a stale CC in the environment is usually the shell's # history, not an instruction. But it is never silent — a cross build that # inherits the host's CC configures against the host. echo "brintium: NOTE: $var was '$old' in the environment; this run uses '$val'." >&2 fi export "$var=$val" } brintium_export_build_tools() { local libc="${1:-}" cc_extra='' # Only the compilers take the libc selector; the binutils have no such # notion and would refuse the flag. [ -n "$libc" ] && cc_extra=" -mlibc=$libc" _brintium_set_tool CC "$BRINTIUM/bin/brintcc$cc_extra" _brintium_set_tool CXX "$BRINTIUM/bin/brintc++$cc_extra" _brintium_set_tool AR "$BRINTIUM/bin/brintar" _brintium_set_tool RANLIB "$BRINTIUM/bin/brintranlib" _brintium_set_tool NM "$BRINTIUM/bin/brintnm" _brintium_set_tool STRIP "$BRINTIUM/bin/brintstrip" } # The libc selector both helpers accept, validated in one place. brintium_parse_libc_opt() { case "${1#--libc=}" in musl|glibc) printf '%s\n' "${1#--libc=}" ;; *) brintium_fatal "$(basename -- "$0"): '$1' — the libc selector takes musl or glibc, nothing else." ;; esac }