bootstrap-host.sh (8218B)
1 #!/bin/sh 2 # mk/bootstrap-host.sh - build the host bootstrap tools into build/host/. 3 # 4 # ./mk/bootstrap-host.sh # build what is missing 5 # ./mk/bootstrap-host.sh --force # rebuild everything 6 # ./mk/bootstrap-host.sh --check # verify only, build nothing (CI gate) 7 # 8 # Produces: 9 # build/host/bin/muon pinned muon, bootstrapped from C (no Python) 10 # build/host/bin/apk dynamic apk v3, used by repo-index.sh 11 12 # 13 # This file is duplicated in the unos repo, which needs the same pinned apk to 14 # assemble a rootfs. The alternatives -- a submodule, or fetching a bootstrap 15 # script over the network in order to bootstrap -- are both worse than two 16 # copies of a file that only changes when a pin changes. 17 # 18 # WHY THIS EXISTS 19 # --------------- 20 # These binaries are the input to every other build: mk/build.sh puts 21 # build/host/bin on PATH and repo-index.sh generates APKINDEX with 22 # `apk index`. Until now they were built by hand 23 # following prose in mk/README.md, which meant a fresh checkout or a CI 24 # container could not reproduce the toolchain at all. This script is that 25 # prose, executable. 26 # 27 # Python is banned from our tooling, so apk-tools is configured with muon 28 # rather than meson; upstream supports this. muon itself bootstraps from a 29 # single amalgamated C file, so the chain terminates at a C compiler. 30 set -eu 31 32 HERE=$(cd "$(dirname "$0")" && pwd) 33 ROOT=$(cd "${HERE}/.." && pwd) 34 HOSTDIR="${ROOT}/build/host" 35 SRCDEST="${ROOT}/build/work/sources" 36 WORK="${ROOT}/build/work/hostbootstrap" 37 38 # --- pins (keep in sync with mk/README.md "Host apk-tools v3") ------------- 39 MUON_VERSION=0.6.0 40 MUON_URL="https://github.com/muon-build/muon/archive/refs/tags/${MUON_VERSION}.tar.gz" 41 MUON_TARBALL="muon-${MUON_VERSION}.tar.gz" 42 MUON_SHA256=5300e58c4b4d43e3026856004c79d746075aaa9d9e66d76ba9f32ce249495b81 43 44 APK_VERSION=3.0.8 45 APK_URL="https://gitlab.alpinelinux.org/alpine/apk-tools/-/archive/v${APK_VERSION}/apk-tools-v${APK_VERSION}.tar.gz" 46 APK_TARBALL="apk-tools-v${APK_VERSION}.tar.gz" 47 APK_SHA256=e81c64a6e7c3806d45d4622c253e757aee970be84048554fa0eed79310e9453e 48 49 FORCE=0 50 CHECK=0 51 for a in "$@"; do 52 case "$a" in 53 --force) FORCE=1 ;; 54 --check) CHECK=1 ;; 55 -h|--help) sed -n '2,12p' "$0"; exit 0 ;; 56 *) echo "bootstrap-host.sh: unknown argument: $a" >&2; exit 1 ;; 57 esac 58 done 59 60 msg() { printf '==> %s\n' "$*"; } 61 die() { printf 'bootstrap-host.sh: error: %s\n' "$*" >&2; exit 1; } 62 63 # Run a build step quietly, but dump the whole log if it fails. Build noise is 64 # useless until something breaks, at which point you want all of it. 65 LOGFILE="${WORK}/bootstrap.log" 66 run() { 67 # run <description> <cmd...> 68 what="$1"; shift 69 if ! "$@" >>"${LOGFILE}" 2>&1; then 70 echo "bootstrap-host.sh: ${what} failed; last 40 lines:" >&2 71 tail -40 "${LOGFILE}" >&2 72 exit 1 73 fi 74 } 75 76 fetch() { 77 # fetch <url> <tarball> <sha256> 78 f="${SRCDEST}/$2" 79 mkdir -p "${SRCDEST}" 80 if [ ! -f "${f}" ]; then 81 msg "fetching $2" 82 curl -fL -o "${f}" "$1" || wget -O "${f}" "$1" || die "could not fetch $1" 83 fi 84 echo "$3 ${f}" | sha256sum -c - >/dev/null || die "checksum mismatch: $2" 85 } 86 87 # --- verification ---------------------------------------------------------- 88 # Version-checked, not just existence-checked: a stale binary from an older pin 89 # is worse than a missing one, because everything downstream still "works". 90 check_tools() { 91 rc=0 92 if [ -x "${HOSTDIR}/bin/muon" ]; then 93 v=$("${HOSTDIR}/bin/muon" version 2>/dev/null | head -1 || true) 94 case "$v" in 95 *"${MUON_VERSION}"*) msg "muon ok (${v})" ;; 96 *) echo " muon present but not ${MUON_VERSION}: ${v:-unknown}" >&2; rc=1 ;; 97 esac 98 else 99 echo " muon missing" >&2; rc=1 100 fi 101 102 if [ -x "${HOSTDIR}/bin/apk" ]; then 103 v=$("${HOSTDIR}/bin/apk" --version 2>/dev/null | head -1 || true) 104 case "$v" in 105 *"${APK_VERSION}"*) msg "apk ok (${v})" ;; 106 *) echo " apk present but not ${APK_VERSION}: ${v:-unknown}" >&2; rc=1 ;; 107 esac 108 else 109 echo " apk missing" >&2; rc=1 110 fi 111 return $rc 112 } 113 114 if [ "${CHECK}" = "1" ]; then 115 if check_tools; then 116 msg "host tools present and pinned" 117 exit 0 118 fi 119 die "host tools missing or stale; run ./mk/bootstrap-host.sh" 120 fi 121 122 if [ "${FORCE}" != "1" ] && check_tools 2>/dev/null; then 123 msg "host tools already present (use --force to rebuild)" 124 exit 0 125 fi 126 127 command -v cc >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 \ 128 || die "no C compiler on PATH" 129 130 mkdir -p "${HOSTDIR}/bin" "${WORK}" 131 rm -f "${LOGFILE}" 132 133 # --- muon ------------------------------------------------------------------ 134 # bootstrap.sh compiles src/amalgam.c with a plain c99 compiler, giving a 135 # muon capable of building the real muon. Pure C, no Python anywhere. 136 fetch "${MUON_URL}" "${MUON_TARBALL}" "${MUON_SHA256}" 137 138 msg "building muon ${MUON_VERSION}" 139 rm -rf "${WORK}/muon" 140 mkdir -p "${WORK}/muon" 141 tar -xzf "${SRCDEST}/${MUON_TARBALL}" -C "${WORK}/muon" 142 MUONSRC="${WORK}/muon/muon-${MUON_VERSION}" 143 [ -d "${MUONSRC}" ] || die "unexpected muon tarball layout" 144 145 # meson-docs and meson-tests are wrap subprojects that muon git-clones from 146 # GitHub during setup. They are documentation and upstream's own test corpus; 147 # we need neither, and an unpinned network fetch has no business in a bootstrap 148 # that is otherwise driven entirely by pinned, checksummed tarballs. tracy is a 149 # profiler. Disabling all three makes this step hermetic. 150 MUON_OPTS="-Dmeson-docs=disabled -Dmeson-tests=disabled -Dtracy=disabled -Dui=disabled" 151 152 cd "${MUONSRC}" 153 run "muon bootstrap" sh bootstrap.sh build-boot 154 [ -x build-boot/muon-bootstrap ] || die "muon bootstrap produced no binary" 155 156 # Build the real muon with the bootstrap one. Samurai is vendored in muon, so 157 # no ninja on the host is required either. 158 # shellcheck disable=SC2086 159 run "muon setup" ./build-boot/muon-bootstrap setup ${MUON_OPTS} build 160 run "muon build" ./build-boot/muon-bootstrap -C build samu 161 [ -x build/muon ] || die "muon self-build produced no binary" 162 install -m 0755 build/muon "${HOSTDIR}/bin/muon" 163 cd "${ROOT}" 164 msg "muon -> ${HOSTDIR}/bin/muon" 165 166 PATH="${HOSTDIR}/bin:${PATH}" 167 export PATH 168 169 # --- apk-tools ------------------------------------------------------------- 170 fetch "${APK_URL}" "${APK_TARBALL}" "${APK_SHA256}" 171 172 rm -rf "${WORK}/apk" 173 mkdir -p "${WORK}/apk" 174 tar -xzf "${SRCDEST}/${APK_TARBALL}" -C "${WORK}/apk" 175 APKSRC="${WORK}/apk/apk-tools-v${APK_VERSION}" 176 [ -d "${APKSRC}" ] || APKSRC="${WORK}/apk/$(ls -A "${WORK}/apk" | head -1)" 177 [ -d "${APKSRC}" ] || die "unexpected apk-tools tarball layout" 178 179 # lua/python/docs off: lua is a binding we do not use, python is banned, and 180 # docs need scdoc. help needs lua, so it goes too. 181 APK_OPTS="-Dlua=disabled -Dpython=disabled -Dhelp=disabled -Ddocs=disabled" 182 183 msg "building apk ${APK_VERSION} (dynamic)" 184 cd "${APKSRC}" 185 rm -rf build-dyn 186 # RUNPATH $ORIGIN/../lib so the binary finds any libs we later place in 187 # build/host/lib without needing LD_LIBRARY_PATH at every call site. 188 # shellcheck disable=SC2086 189 run "apk setup (dynamic)" muon setup \ 190 -Dprefix="${HOSTDIR}" \ 191 ${APK_OPTS} \ 192 -Dc_link_args="-Wl,-rpath,\$ORIGIN/../lib" \ 193 build-dyn 194 run "apk build (dynamic)" muon -C build-dyn samu 195 run "apk install (dynamic)" muon -C build-dyn install 196 [ -x "${HOSTDIR}/bin/apk" ] || die "dynamic apk not installed" 197 msg "apk -> ${HOSTDIR}/bin/apk" 198 199 # NOTE: we deliberately do NOT build an apk.static. 200 # 201 # There used to be one, justified in mk/README.md as "the future target binary 202 # - no libcrypto needed on the switch". That justification was already false: 203 # UNOS packages openssl, so libcrypto.so.3/libssl.so.3 are on the switch and 204 # the target apk links them dynamically like everything else. Its only real 205 # consumer was rootfs assembly, which works identically with the dynamic binary 206 # (verified: same 24 packages, same rootfs). 207 # 208 # Building it cost 10.6MB versus 2.0MB and, worse, required libcrypto.a, 209 # libssl.a, libz.a and libzstd.a on the build host -- static variants that many 210 # distributions and most CI containers do not ship at all. That is a hard 211 # bootstrap failure in exchange for nothing. 212 # 213 # If a statically linked apk is ever wanted for disaster recovery on a switch 214 # with a broken loader, it belongs in packages/ as a target artifact, not here. 215 216 check_tools || die "post-build verification failed" 217 msg "host bootstrap complete"