check_rv64_env.sh (12054B)
1 #!/usr/bin/env bash 2 # test/lib/check_rv64_env.sh — kit rv64 "doctor". 3 # 4 # Quick prerequisite check for the rv64 lane of the test harness. Each 5 # checked tool/feature is reported as a one-liner with status (OK / MISSING / 6 # UNUSABLE), what was looked for, and how to install/fix it. 7 # 8 # Usage: 9 # bash test/lib/check_rv64_env.sh # run all checks, exit 0 if at 10 # # least one runner is available 11 # # AND the cross-compile toolchain 12 # # is usable. Exit 1 otherwise. 13 # 14 # Or source it from a harness: 15 # source test/lib/check_rv64_env.sh 16 # check_rv64_env # populates the RV64_ENV_* globals 17 # # below and prints the summary. 18 # rv64_runner_summary # one-line "ready" / "blocked: ..." 19 # classify_podman_rv64_error <stderr_file> 20 # # echoes a one-line diagnostic 21 # # categorizing a podman failure. 22 # 23 # After check_rv64_env returns, these globals are set: 24 # RV64_HAVE_CLANG_TARGET 0/1 — clang accepts --target=riscv64-linux-gnu 25 # RV64_HAVE_LLD 0/1 — ld.lld on PATH 26 # RV64_HAVE_QEMU 0/1 — qemu-riscv64{,-static} on PATH 27 # RV64_QEMU_BIN path or empty 28 # RV64_HAVE_PODMAN 0/1 — podman on PATH (and not forced off) 29 # RV64_HAVE_NATIVE 0/1 — host is riscv64 Linux 30 # RV64_HAVE_ANY_RUNNER 0/1 — at least one of native/qemu/podman 31 # RV64_HAVE_CROSS 0/1 — clang rv64 + ld.lld both usable 32 # RV64_READY 0/1 — runner + cross both OK 33 # 34 # Honors these env knobs: 35 # KIT_FORCE_NO_PODMAN=1 pretend podman is missing (for diagnostic 36 # dry-runs). Reported in the summary. 37 # 38 # Install hints are deliberately tied to the detected host OS so the 39 # message a contributor sees is actionable on their box. 40 41 # ---- platform install hints ------------------------------------------------ 42 43 _rv64_os_tag() { 44 case "$(uname -s 2>/dev/null)" in 45 Darwin) echo darwin ;; 46 Linux) 47 if [ -r /etc/os-release ]; then 48 . /etc/os-release 49 case "${ID:-}:${ID_LIKE:-}" in 50 *alpine*) echo alpine ;; 51 *debian*|ubuntu:*|*:*debian*) echo debian ;; 52 *fedora*|*rhel*|*:*rhel*|*:*fedora*) echo fedora ;; 53 *arch*|*:*arch*) echo arch ;; 54 *) echo linux ;; 55 esac 56 else 57 echo linux 58 fi 59 ;; 60 *) echo other ;; 61 esac 62 } 63 64 _rv64_hint_qemu() { 65 case "$(_rv64_os_tag)" in 66 darwin) echo "use podman, or run qemu-user inside a Linux VM" ;; 67 debian) echo "apt install qemu-user-static" ;; 68 fedora) echo "dnf install qemu-user-static" ;; 69 alpine) echo "apk add qemu-riscv64" ;; 70 arch) echo "pacman -S qemu-user-static-binfmt" ;; 71 *) echo "install a qemu-user package that provides qemu-riscv64" ;; 72 esac 73 } 74 75 _rv64_hint_clang() { 76 case "$(_rv64_os_tag)" in 77 darwin) echo "brew install llvm (and add it to PATH)" ;; 78 debian) echo "apt install clang lld" ;; 79 fedora) echo "dnf install clang lld" ;; 80 alpine) echo "apk add clang lld" ;; 81 arch) echo "pacman -S clang lld" ;; 82 *) echo "install a clang build that includes RISC-V" ;; 83 esac 84 } 85 86 _rv64_hint_lld() { 87 case "$(_rv64_os_tag)" in 88 darwin) echo "brew install lld" ;; 89 debian) echo "apt install lld" ;; 90 fedora) echo "dnf install lld" ;; 91 alpine) echo "apk add lld" ;; 92 arch) echo "pacman -S lld" ;; 93 *) echo "install ld.lld (LLVM linker)" ;; 94 esac 95 } 96 97 _rv64_hint_podman_riscv64() { 98 case "$(_rv64_os_tag)" in 99 darwin) echo "ensure 'podman machine' is running and the VM has qemu-user binfmt for riscv64 (try 'podman machine start')" ;; 100 linux|debian|fedora|alpine|arch) echo "register binfmt for riscv64 (e.g. 'docker run --privileged --rm tonistiigi/binfmt --install riscv64')" ;; 101 *) echo "register binfmt riscv64 in podman's runtime environment" ;; 102 esac 103 } 104 105 # ---- colors (degrade gracefully) ------------------------------------------- 106 107 if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then 108 _rv64_grn() { printf '\033[32m%s\033[0m' "$1"; } 109 _rv64_red() { printf '\033[31m%s\033[0m' "$1"; } 110 _rv64_yel() { printf '\033[33m%s\033[0m' "$1"; } 111 else 112 _rv64_grn() { printf '%s' "$1"; } 113 _rv64_red() { printf '%s' "$1"; } 114 _rv64_yel() { printf '%s' "$1"; } 115 fi 116 117 _rv64_ok() { printf ' [%s] %s\n' "$(_rv64_grn ok)" "$1"; } 118 _rv64_miss() { printf ' [%s] %s — %s\n' "$(_rv64_red MISSING)" "$1" "$2"; } 119 _rv64_warn() { printf ' [%s] %s — %s\n' "$(_rv64_yel WARN)" "$1" "$2"; } 120 121 # ---- individual probes ----------------------------------------------------- 122 123 _rv64_probe_clang() { 124 RV64_HAVE_CLANG_TARGET=0 125 if ! command -v clang >/dev/null 2>&1; then 126 _rv64_miss "clang" "no 'clang' on PATH (install: $(_rv64_hint_clang))" 127 return 128 fi 129 # Use -march=rv64gc so we catch builds that have the triple parser but 130 # no RISC-V backend wired in. 131 local err 132 err="$(clang --target=riscv64-linux-gnu -march=rv64gc \ 133 -c -x c - -o /dev/null </dev/null 2>&1)" 134 if [ $? -eq 0 ]; then 135 RV64_HAVE_CLANG_TARGET=1 136 _rv64_ok "clang --target=riscv64-linux-gnu" 137 else 138 # Two distinct failure modes that we surface differently: 139 # - "error: unknown target triple" → clang built without RISC-V 140 # - everything else → something else broke 141 if printf '%s' "$err" | grep -q "unknown target"; then 142 _rv64_miss "clang RISC-V backend" \ 143 "clang accepts the triple but lacks RISC-V (install: $(_rv64_hint_clang))" 144 else 145 _rv64_miss "clang --target=riscv64-linux-gnu" \ 146 "clang rejects the target ($(printf '%s' "$err" | head -1)). Install: $(_rv64_hint_clang)" 147 fi 148 fi 149 } 150 151 _rv64_probe_lld() { 152 RV64_HAVE_LLD=0 153 if command -v ld.lld >/dev/null 2>&1; then 154 RV64_HAVE_LLD=1 155 _rv64_ok "ld.lld (ELF cross-link)" 156 else 157 _rv64_miss "ld.lld" "not on PATH — needed to link rv64 ELF (install: $(_rv64_hint_lld))" 158 fi 159 } 160 161 _rv64_probe_qemu() { 162 RV64_HAVE_QEMU=0 163 RV64_QEMU_BIN="" 164 local bin 165 bin="$(command -v qemu-riscv64-static 2>/dev/null \ 166 || command -v qemu-riscv64 2>/dev/null \ 167 || true)" 168 if [ -n "$bin" ]; then 169 RV64_HAVE_QEMU=1 170 RV64_QEMU_BIN="$bin" 171 _rv64_ok "qemu-riscv64 user-mode emulator ($bin)" 172 else 173 if [ "$(_rv64_os_tag)" = "darwin" ]; then 174 _rv64_miss "qemu-riscv64" \ 175 "not on PATH; macOS/Homebrew qemu provides system emulators only ($(_rv64_hint_qemu))" 176 else 177 _rv64_miss "qemu-riscv64" \ 178 "not on PATH (install: $(_rv64_hint_qemu))" 179 fi 180 fi 181 } 182 183 _rv64_probe_podman() { 184 RV64_HAVE_PODMAN=0 185 if [ "${KIT_FORCE_NO_PODMAN:-0}" = "1" ]; then 186 _rv64_warn "podman" "disabled via KIT_FORCE_NO_PODMAN=1" 187 return 188 fi 189 if command -v podman >/dev/null 2>&1; then 190 RV64_HAVE_PODMAN=1 191 _rv64_ok "podman ($(command -v podman))" 192 else 193 _rv64_miss "podman" \ 194 "not on PATH. Install your platform's podman package, then ensure binfmt riscv64 is registered ($(_rv64_hint_podman_riscv64))" 195 fi 196 } 197 198 _rv64_probe_native() { 199 RV64_HAVE_NATIVE=0 200 if [ "$(uname -s 2>/dev/null)" = "Linux" ] && \ 201 [ "$(uname -m 2>/dev/null)" = "riscv64" ]; then 202 RV64_HAVE_NATIVE=1 203 _rv64_ok "native riscv64 host (kernel can exec rv64 ELF directly)" 204 fi 205 # No "MISSING" line — native rv64 is one of several mutually 206 # acceptable runners, not a strict prereq. 207 } 208 209 # ---- public entry points --------------------------------------------------- 210 211 check_rv64_env() { 212 printf 'kit rv64 environment check (host: %s/%s)\n' \ 213 "$(uname -s 2>/dev/null)" "$(uname -m 2>/dev/null)" 214 _rv64_probe_clang 215 _rv64_probe_lld 216 _rv64_probe_native 217 _rv64_probe_qemu 218 _rv64_probe_podman 219 220 RV64_HAVE_ANY_RUNNER=0 221 if [ "${RV64_HAVE_NATIVE:-0}" -eq 1 ] || \ 222 [ "${RV64_HAVE_QEMU:-0}" -eq 1 ] || \ 223 [ "${RV64_HAVE_PODMAN:-0}" -eq 1 ]; then 224 RV64_HAVE_ANY_RUNNER=1 225 fi 226 RV64_HAVE_CROSS=0 227 if [ "${RV64_HAVE_CLANG_TARGET:-0}" -eq 1 ] && \ 228 [ "${RV64_HAVE_LLD:-0}" -eq 1 ]; then 229 RV64_HAVE_CROSS=1 230 fi 231 RV64_READY=0 232 if [ "$RV64_HAVE_ANY_RUNNER" -eq 1 ] && [ "$RV64_HAVE_CROSS" -eq 1 ]; then 233 RV64_READY=1 234 fi 235 printf '\nSummary: %s\n' "$(rv64_runner_summary)" 236 } 237 238 rv64_runner_summary() { 239 local runners="" blocked="" 240 [ "${RV64_HAVE_NATIVE:-0}" -eq 1 ] && runners="${runners}native " 241 [ "${RV64_HAVE_QEMU:-0}" -eq 1 ] && runners="${runners}qemu-riscv64 " 242 [ "${RV64_HAVE_PODMAN:-0}" -eq 1 ] && runners="${runners}podman " 243 [ "${RV64_HAVE_CLANG_TARGET:-0}" -eq 0 ] && blocked="${blocked}clang-rv64 " 244 [ "${RV64_HAVE_LLD:-0}" -eq 0 ] && blocked="${blocked}ld.lld " 245 [ "${RV64_HAVE_ANY_RUNNER:-0}" -eq 0 ] && blocked="${blocked}no-runner " 246 if [ "${RV64_READY:-0}" -eq 1 ]; then 247 printf 'READY (runners: %s)' "${runners% }" 248 elif [ -z "$runners" ] && [ -n "$blocked" ]; then 249 printf 'BLOCKED (missing: %s)' "${blocked% }" 250 else 251 printf 'BLOCKED (have: %s; missing: %s)' \ 252 "${runners:-none}" "${blocked:-none}" 253 fi 254 } 255 256 # Classify a podman stderr capture into a single-line diagnostic. 257 # Reads from the file path passed in $1. Always echoes one line and 258 # returns 0 — the caller picks how to render it. 259 classify_podman_rv64_error() { 260 local f="$1" 261 local body="" 262 [ -f "$f" ] && body="$(cat "$f" 2>/dev/null)" 263 # Lowercase for matching; some podman messages vary slightly. 264 local lc; lc="$(printf '%s' "$body" | tr '[:upper:]' '[:lower:]')" 265 266 # Most common first: binfmt / qemu not registered in the podman VM 267 # (or on Linux host). Manifests as "exec format error" or the 268 # qemu_riscv64-binfmt magic line missing. 269 if printf '%s' "$lc" | grep -qE "exec format error|no such file or directory.*qemu"; then 270 printf 'podman cannot exec riscv64 ELF: binfmt/qemu not registered in podman VM. Fix: %s\n' \ 271 "$(_rv64_hint_podman_riscv64)" 272 return 0 273 fi 274 # Wrong-arch cached image — podman happily ran an amd64/arm64 275 # busybox/alpine and the rv64 ELF then died with exec format. 276 if printf '%s' "$lc" | grep -qE "image platform .* does not match|no matching manifest"; then 277 printf 'podman image manifest has no riscv64 variant (or cached image is wrong arch). Fix: re-pull with --platform linux/riscv64 (e.g. podman pull --platform linux/riscv64 alpine:latest)\n' 278 return 0 279 fi 280 # Registry unreachable on first pull. 281 if printf '%s' "$lc" | grep -qE "no such host|connection refused|i/o timeout|tls handshake timeout|temporary failure in name resolution"; then 282 printf 'podman cannot reach the registry to pull a riscv64 image. Fix: check network / proxy, or pre-pull the image while online\n' 283 return 0 284 fi 285 # podman machine not running (Darwin). 286 if printf '%s' "$lc" | grep -qE "cannot connect to podman|connection refused.*podman.sock|machine .* is not running"; then 287 printf 'podman machine is not running. Fix: podman machine start\n' 288 return 0 289 fi 290 # Generic fallthrough. 291 local first; first="$(printf '%s' "$body" | head -1)" 292 printf 'podman riscv64 run failed: %s\n' "${first:-unknown error}" 293 } 294 295 # When invoked directly (not sourced), run the doctor and use its READY 296 # flag to set the exit code. 297 if [ "${BASH_SOURCE[0]:-$0}" = "$0" ]; then 298 check_rv64_env 299 [ "${RV64_READY:-0}" -eq 1 ] || exit 1 300 exit 0 301 fi