freebsd_sysroot.sh (5446B)
1 #!/usr/bin/env bash 2 # Build FreeBSD cross-compile sysroots from the official base.txz dist sets. 3 # 4 # Reproducible and VM-free: downloads (and caches) the base.txz for an arch, 5 # verifies it against the release MANIFEST, then extracts the curated subset 6 # kit -- and a clang/lld reference build -- needs. Both the downloaded tarball 7 # and the extracted sysroot live under the XDG cache so `make clean` does not 8 # wipe them. 9 # 10 # usage: 11 # scripts/freebsd_sysroot.sh <arch>|all extract (cached unless FORCE=1) 12 # scripts/freebsd_sysroot.sh path <arch> print the sysroot dir 13 # 14 # arches: amd64|x64 | aarch64|arm64 | riscv64|rv64 15 # 16 # env: 17 # KIT_FREEBSD_RELEASE release tag (default: 15.0-RELEASE) 18 # KIT_FREEBSD_SYSROOT_CACHE cache root (default: $XDG_CACHE_HOME/kit/freebsd-sysroot/<release>) 19 # KIT_FREEBSD_DIST_URL mirror base (default: https://download.freebsd.org/releases) 20 # FORCE=1 re-download / re-extract even if cached 21 22 set -eu 23 24 RELEASE="${KIT_FREEBSD_RELEASE:-15.0-RELEASE}" 25 XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" 26 CACHE="${KIT_FREEBSD_SYSROOT_CACHE:-$XDG_CACHE_HOME/kit/freebsd-sysroot/$RELEASE}" 27 MIRROR="${KIT_FREEBSD_DIST_URL:-https://download.freebsd.org/releases}" 28 29 die() { printf 'freebsd-sysroot: %s\n' "$*" >&2; exit 1; } 30 31 canon_arch() { 32 case "${1:-}" in 33 amd64|x64|x86_64) echo amd64 ;; 34 aarch64|arm64|aa64) echo aarch64 ;; 35 riscv64|rv64) echo riscv64 ;; 36 *) die "unknown arch '${1:-}'" ;; 37 esac 38 } 39 40 # FreeBSD dist layout is <machine>/<machine_arch>/<release>/base.txz. 41 dist_path() { 42 case "$(canon_arch "$1")" in 43 amd64) echo amd64/amd64 ;; 44 aarch64) echo arm64/aarch64 ;; 45 riscv64) echo riscv/riscv64 ;; 46 esac 47 } 48 49 sysroot_dir() { printf '%s/%s\n' "$CACHE" "$(canon_arch "$1")"; } 50 51 # Curated members pulled out of base.txz. Headers + crt objects + the static 52 # and shared libc, the FreeBSD-15 libsys split, and the compiler runtime 53 # (libcompiler_rt / libgcc) whose helpers libc references -- e.g. rv64's 54 # binary128 __multf3. The libgcc.a / libgcc_s.so symlinks and their targets 55 # (libcompiler_rt.a, lib/libgcc_s.so.1) are listed together so they resolve. 56 # Missing optional members (some arches lack a given crt variant) are 57 # tolerated; a missing libc.a is fatal. 58 SYSROOT_MEMBERS=( 59 ./usr/include 60 ./usr/lib/crt1.o ./usr/lib/crti.o ./usr/lib/crtn.o ./usr/lib/Scrt1.o 61 ./usr/lib/crtbegin.o ./usr/lib/crtbeginT.o ./usr/lib/crtbeginS.o 62 ./usr/lib/crtend.o ./usr/lib/crtendS.o 63 ./usr/lib/libc.a ./usr/lib/libsys.a ./usr/lib/libm.a 64 ./usr/lib/libssp_nonshared.a ./usr/lib/libc_nonshared.a 65 ./usr/lib/libcompiler_rt.a ./usr/lib/libgcc.a ./usr/lib/libgcc_eh.a 66 ./usr/lib/libgcc_s.so 67 ./usr/lib/libpthread.a ./usr/lib/libthr.a 68 ./lib/libc.so.7 ./lib/libsys.so.7 ./lib/libgcc_s.so.1 69 ./lib/libpthread.so.3 ./lib/libthr.so.3 70 ) 71 72 fetch_txz() { 73 local arch="$1" dp url txz want got 74 dp="$(dist_path "$arch")" 75 url="$MIRROR/$dp/$RELEASE/base.txz" 76 txz="$CACHE/dist/$arch-base.txz" 77 mkdir -p "$CACHE/dist" 78 if [ "${FORCE:-0}" = 1 ] || [ ! -f "$txz" ]; then 79 printf 'download %s\n' "$url" >&2 80 curl -fL --retry 3 -o "$txz.tmp" "$url" || die "download failed: $url" 81 mv "$txz.tmp" "$txz" 82 fi 83 # Verify against the release MANIFEST (sha256 in column 2). Best effort: if 84 # the MANIFEST is unreachable we proceed with the cached tarball. 85 want="$(curl -fsL "$MIRROR/$dp/$RELEASE/MANIFEST" 2>/dev/null \ 86 | awk '$1=="base.txz"{print $2}')" 87 if [ -n "$want" ]; then 88 got="$(shasum -a 256 "$txz" | awk '{print $1}')" 89 [ "$want" = "$got" ] || die "base.txz sha256 mismatch for $arch ($got != $want); rm $txz and retry" 90 printf 'verified base.txz sha256 %s\n' "$got" >&2 91 fi 92 printf '%s\n' "$txz" 93 } 94 95 build_sysroot() { 96 local arch dst txz 97 arch="$(canon_arch "$1")" 98 dst="$(sysroot_dir "$arch")" 99 if [ "${FORCE:-0}" != 1 ] && [ -f "$dst/usr/lib/libc.a" ]; then 100 printf 'sysroot cached: %s\n' "$dst" 101 return 0 102 fi 103 txz="$(fetch_txz "$arch")" 104 rm -rf "$dst" 105 mkdir -p "$dst" 106 printf 'extract sysroot %s -> %s\n' "$arch" "$dst" 107 # bsdtar/GNU tar both extract named members (and whole dirs); a member absent 108 # from the archive is a warning + nonzero exit, not a stop -- tolerate it and 109 # gate on libc.a instead. 110 tar -xf "$txz" -C "$dst" "${SYSROOT_MEMBERS[@]}" 2>/dev/null || true 111 [ -f "$dst/usr/lib/libc.a" ] || die "extraction incomplete for $arch (no usr/lib/libc.a)" 112 # FreeBSD uses libthr as the real thread library; libpthread is a symlink. 113 # Recreate the symlinks so -lpthread/-lthr resolve correctly. 114 if [ -f "$dst/lib/libthr.so.3" ]; then 115 [ -e "$dst/usr/lib/libthr.so" ] || ln -s ../../lib/libthr.so.3 "$dst/usr/lib/libthr.so" 116 [ -e "$dst/usr/lib/libpthread.so" ] || ln -s ../../lib/libthr.so.3 "$dst/usr/lib/libpthread.so" 117 fi 118 if [ -f "$dst/lib/libpthread.so.3" ]; then 119 [ -e "$dst/usr/lib/libpthread.so" ] || ln -s ../../lib/libpthread.so.3 "$dst/usr/lib/libpthread.so" 120 fi 121 if [ -f "$dst/usr/lib/libthr.a" ] && [ ! -e "$dst/usr/lib/libpthread.a" ]; then 122 ln -s libthr.a "$dst/usr/lib/libpthread.a" 123 fi 124 printf 'sysroot ready: %s\n' "$dst" 125 } 126 127 cmd="${1:-}" 128 case "$cmd" in 129 ""|-h|--help|help) 130 sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' 131 ;; 132 path) 133 [ $# -eq 2 ] || die "usage: $0 path <arch>" 134 sysroot_dir "$2" 135 ;; 136 all) 137 for a in amd64 aarch64 riscv64; do build_sysroot "$a"; done 138 ;; 139 *) 140 build_sysroot "$cmd" 141 ;; 142 esac