selfhost.sh (7469B)
1 #!/usr/bin/env bash 2 # scripts/selfhost.sh — self-host orchestrator: the engine behind 3 # `make test-selfhost`. For each target it builds a kit that RUNS on that target, 4 # then uses that kit to compile + run a program there. The shape differs by OS 5 # (see doc/plan/PORT.md): 6 # 7 # macos native 3-stage `make bootstrap`, then a program through stage3 8 # linux native 3-stage in a podman container (emulated for a non-host arch) 9 # freebsd native 3-stage in the VM 10 # windows CROSS-build kit.exe on the host (the VM has no seed compiler); 11 # on-VM self-compile/run is a tracked follow-up (doc/plan/PORT.md) 12 # 13 # selfhost.sh <selector> [DEPTH] 14 # 15 # selector hosted.sh grammar (mode=selfhost; freestanding is cross-only and 16 # rejected). DEPTH: smoke (default) | full (toy corpus through the 17 # on-target kit). 18 # 19 # env: KIT, KIT_VM (default 1), DEPTH, KIT_SELFHOST_DRYRUN=1 (print the plan). 20 # Provision-or-error: missing podman / VM / sysroot is a hard error, reported up 21 # front (provision with `make provision TARGET=…`). 22 23 set -u 24 25 ROOT="$(cd "$(dirname "$0")/.." && pwd)" 26 KIT="${KIT:-$ROOT/build/kit}" 27 HOSTED="$ROOT/scripts/hosted.sh" 28 CASES="$ROOT/test/cross/cases" 29 BUILD_DIR="$ROOT/build/test/selfhost" 30 SELECTOR="${1:-${TARGET:-all}}" 31 DEPTH="${2:-${DEPTH:-smoke}}" 32 export KIT_VM="${KIT_VM:-1}" 33 DRYRUN="${KIT_SELFHOST_DRYRUN:-0}" 34 35 mkdir -p "$BUILD_DIR" 36 # shellcheck source=../test/lib/kit_sh_report.sh 37 . "$ROOT/test/lib/kit_sh_report.sh" 38 # exec_vm_supported (provisioning pre-flight) + VM teardown live in the exec seam. 39 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1 40 export have_podman 41 # shellcheck source=../test/lib/exec_target.sh 42 . "$ROOT/test/lib/exec_target.sh" 43 kit_report_init 44 trap exec_target_teardown_all EXIT 45 46 tok_os() { printf '%s' "${1%%-*}"; } 47 tok_arch() { printf '%s' "${1##*-}"; } 48 tok_libc() { case "$1" in linux-*) local r="${1#linux-}"; printf '%s' "${r%%-*}" ;; *) printf '' ;; esac; } 49 50 _host_arch_is() { # canonical arch token -> true if it matches this host 51 local want="$1" m; m="$(uname -m 2>/dev/null)" 52 case "$want" in 53 aa64) [ "$m" = arm64 ] || [ "$m" = aarch64 ] ;; 54 x64) [ "$m" = x86_64 ] || [ "$m" = amd64 ] ;; 55 rv64) [ "$m" = riscv64 ] ;; 56 *) return 1 ;; 57 esac 58 } 59 60 # ---- provisioning pre-flight ---------------------------------------------- 61 preflight_token() { 62 local t="$1" os arch sr 63 os="$(tok_os "$t")"; arch="$(tok_arch "$t")" 64 case "$os" in 65 macos) 66 { [ "$(uname -s)" = Darwin ] && _host_arch_is "$arch"; } || \ 67 echo "selfhost macos needs a Darwin host with matching arch" ;; 68 linux) 69 [ "$have_podman" = 1 ] || echo "podman required for linux self-host — install podman" ;; 70 freebsd) 71 case "$arch" in 72 aa64|x64) ;; 73 *) echo "freebsd self-host: only aa64/x64 have a native compiler in the base VM"; return ;; 74 esac 75 exec_vm_supported "$("$HOSTED" tag "$t")" || \ 76 echo "freebsd VM not provisioned/reachable — run 'make provision TARGET=$t KIT_VM=1'" ;; 77 windows) 78 sr="$("$HOSTED" path "$t" 2>/dev/null)" 79 { [ -n "$sr" ] && [ -d "$sr" ]; } || \ 80 echo "windows UCRT sysroot missing — run 'make provision TARGET=$t'" ;; 81 esac 82 } 83 84 # ---- step runner ----------------------------------------------------------- 85 run_step() { # label cmd... 86 local label="$1"; shift 87 if [ "$DRYRUN" = 1 ]; then printf 'selfhost: PLAN %-28s %s\n' "$label" "$*"; return 0; fi 88 printf '\nselfhost: step %s →\n' "$label" 89 if "$@"; then kit_pass "$label"; return 0; else kit_fail "$label" "step failed (see output above)"; return 1; fi 90 } 91 92 # macOS: after the native bootstrap, prove the self-built stage3 compiles + runs 93 # a program (the "compile and run on the target" half of self-host). 94 macos_stage3_hello() { 95 local s3="$ROOT/build/debug/bootstrap/stage3/kit" d="$BUILD_DIR/macos" 96 [ -x "$s3" ] || { echo "stage3 kit missing at $s3"; return 1; } 97 mkdir -p "$d" 98 # -lc engages kit's hosted profile (SDK/libc discovery), like the bootstrap. 99 "$s3" cc -lc "$CASES/hello.c" -o "$d/hello" 2>"$d/cc.err" || { sed 's/^/ | /' "$d/cc.err"; return 1; } 100 "$d/hello" >"$d/out" 2>"$d/err" || return 1 101 [ "$(cat "$d/out")" = "$(cat "$CASES/hello.stdout")" ] 102 } 103 104 # ---- dispatch -------------------------------------------------------------- 105 selfhost_token() { 106 local t="$1" os arch libc toy seed_env 107 os="$(tok_os "$t")"; arch="$(tok_arch "$t")"; libc="$(tok_libc "$t")" 108 toy=0; [ "$DEPTH" = full ] && toy=1 109 seed_env="" 110 case "$os" in 111 macos) 112 if [ "$DEPTH" = full ]; then 113 run_step "$t:bootstrap+toy" make -C "$ROOT" test-bootstrap-toy 114 else 115 run_step "$t:bootstrap" make -C "$ROOT" bootstrap-debug && \ 116 run_step "$t:run" macos_stage3_hello 117 fi ;; 118 linux) 119 # Always bootstrap from a cross-compiled seed built on the host (clang, 120 # independent of kit), never an in-container clang stage1: there is no 121 # point exercising clang inside the target environment, and for a non-host 122 # arch that clang would run emulated. kit_cross builds the stage1 seed 123 # here; linux_bootstrap then runs only kit's stages 2/3 in the container. 124 run_step "$t:seed" bash "$ROOT/scripts/kit_cross.sh" "linux-$libc-$arch" --cc=clang || return 1 125 seed_env="KIT_LINUX_BOOT_SEED=build/kit-cross/clang/linux-$libc-$arch/kit" 126 run_step "$t:bootstrap" env KIT_LINUX_BOOT_ARCH="$arch" KIT_LINUX_BOOT_TOY="$toy" $seed_env \ 127 bash "$ROOT/scripts/linux_bootstrap.sh" "$libc" both ;; 128 freebsd) 129 # Cross-build the stage1 seed on the host (clang), then bootstrap stages 130 # 2/3 in the VM from it — clang is never run in the VM. 131 run_step "$t:seed" bash "$ROOT/scripts/kit_cross.sh" "freebsd-$arch" --cc=clang || return 1 132 run_step "$t:bootstrap" env KIT_FREEBSD_BOOT_TOY="$toy" \ 133 KIT_FREEBSD_BOOT_SEED="build/kit-cross/clang/freebsd-$arch/kit" \ 134 bash "$ROOT/scripts/freebsd_bootstrap.sh" "$arch" both ;; 135 windows) 136 run_step "$t:cross-build" bash "$ROOT/scripts/windows_cross.sh" "$arch" 137 printf 'selfhost: NOTE %s — on-VM self-compile/run is a tracked follow-up (kit.exe cross-built here); see doc/plan/PORT.md\n' "$t" ;; 138 esac 139 } 140 141 # ---- drive ----------------------------------------------------------------- 142 # freestanding is cross-only. If the user EXPLICITLY named it, that's an error 143 # with a clear message; for a wildcard like `all` it is silently dropped by the 144 # mode filter below. 145 for it in $(printf '%s' "$SELECTOR" | tr , ' '); do 146 case "$it" in freestanding|freestanding-*) 147 echo "selfhost: '$it' has no hosted environment — freestanding is cross-only (use test-cross)" >&2 148 exit 2 ;; 149 esac 150 done 151 152 TOKENS="$("$HOSTED" expand "$SELECTOR" --mode=selfhost)" || exit 1 153 [ -n "$TOKENS" ] || { echo "selfhost: selector '$SELECTOR' expanded to nothing" >&2; exit 2; } 154 155 printf 'selfhost: DEPTH=%s KIT_VM=%s\nselfhost: targets=%s\n' \ 156 "$DEPTH" "$KIT_VM" "$(echo $TOKENS | tr '\n' ' ')" 157 158 if [ "$DRYRUN" != 1 ]; then 159 missing=0 160 for t in $TOKENS; do 161 reason="$(preflight_token "$t")" 162 if [ -n "$reason" ]; then printf 'selfhost: NOT PROVISIONED %-22s %s\n' "$t" "$reason"; missing=1; fi 163 done 164 if [ "$missing" = 1 ]; then 165 echo "selfhost: aborting — provision the targets above, or narrow TARGET / set KIT_VM=0." >&2 166 exit 1 167 fi 168 fi 169 170 for t in $TOKENS; do selfhost_token "$t"; done 171 172 KIT_SKIP_IS_FAILURE=0 173 kit_summary test-selfhost 174 kit_exit