feat: cross-compile Jerboa binaries for foreign OS/arch
ober
d3258f20ac86baf3a17309e2444ff274987ce793
--- a/.gitignore +++ b/.gitignore @@ -20,8 +20,14 @@ /jerboa-bin-main.c /jerboa-bin.wp.so +# `make binary-cross` outputs (per-target ELF/Mach-O + intermediates) +/jerboa-bin-* +/jerboa-bin-*-main.c +/jerboa-bin-*.wp.so + # Repo-local Chez build/install artifacts /.chez/ +/.chez-cross-*/ /build/ # Rust build artifacts --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ CHEZ_EXT_LIBDIRS = $(CHEZ_EXT_DIR)/chez-https/src:$(CHEZ_EXT_DIR)/chez-ssl/src:$ # Shared object paths for legacy FFI-based chez-* libraries CHEZ_EXT_LDPATH = $(CHEZ_EXT_DIR)/chez-ssl:$(CHEZ_EXT_DIR)/chez-zlib:$(CHEZ_EXT_DIR)/chez-pcre2:$(CHEZ_EXT_DIR)/chez-leveldb:$(CHEZ_EXT_DIR)/chez-epoll:$(CHEZ_EXT_DIR)/chez-inotify:$(CHEZ_EXT_DIR)/chez-crypto:$(CHEZ_EXT_DIR)/chez-sqlite:$(CHEZ_EXT_DIR)/chez-postgresql -.PHONY: help chez build binary test test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-native test-gaps native clean-native audit-native clean fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-websocket-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict docker-build docker-push +.PHONY: help chez chez-cross build binary binary-cross test test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-native test-gaps native clean-native audit-native clean fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-websocket-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict docker-build docker-push help: @echo "Usage: make <target>" @@ -33,6 +33,9 @@ help: @echo " chez Build and install vendored Chez Scheme locally" @echo " build Compile all Jerboa libraries" @echo " binary Build a self-contained jerboa-bin binary (FreeBSD/Linux/macOS)" + @echo " chez-cross Build cross Chez (target libkernel.a + boot files + xpatch)" + @echo " Args: CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cross-cc>" + @echo " binary-cross Build a jerboa-bin for a foreign target (same args as chez-cross)" @echo " native Build Rust native library" @echo " clean Remove compiled .so and .wpo artifacts" @echo " clean-native Remove Rust build artifacts" @@ -117,6 +120,95 @@ BINARY_OUTPUT ?= jerboa-bin binary: chez build SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT) +# ── Cross-compilation ──────────────────────────────────────────────────────── +# Override these to cross-compile: +# CHEZ_TARGET_MACHINE — Chez machine type (e.g. ta6osx, ta6le, tarm64le, ta6fb) +# CROSS_CC — cross C compiler (e.g. "clang -arch x86_64", +# x86_64-linux-musl-gcc) +# CROSS_CC_FOR_BUILD — host-runnable cc used to build zuo (defaults to cc) +# CROSS_AR / CROSS_RANLIB — target archiver/ranlib. Default: derived from CROSS_CC +# prefix when CROSS_CC matches "<triple>-gcc" (e.g. x86_64-linux-musl-gcc +# implies x86_64-linux-musl-ar / x86_64-linux-musl-ranlib). +# CROSS_STRIP — target strip (same derivation as AR; "true" to skip). +CHEZ_TARGET_MACHINE ?= +CROSS_CC ?= +CROSS_CC_FOR_BUILD ?= cc + +# Derive AR/RANLIB/STRIP from CROSS_CC when it has a "<triple>-gcc" shape. +# Caller can still override explicitly. +CROSS_TOOL_PREFIX := $(patsubst %-gcc,%-,$(firstword $(CROSS_CC))) +CROSS_AR ?= $(if $(filter %-,$(CROSS_TOOL_PREFIX)),$(CROSS_TOOL_PREFIX)ar,) +CROSS_RANLIB ?= $(if $(filter %-,$(CROSS_TOOL_PREFIX)),$(CROSS_TOOL_PREFIX)ranlib,) +CROSS_STRIP ?= $(if $(filter %-,$(CROSS_TOOL_PREFIX)),$(CROSS_TOOL_PREFIX)strip,true) + +CHEZ_CROSS_PREFIX = $(JERBOA_HOME)/.chez-cross-$(CHEZ_TARGET_MACHINE) +CHEZ_CROSS_BUILD_DIR = $(JERBOA_HOME)/build/chez-cross-$(CHEZ_TARGET_MACHINE) +CHEZ_XPATCH = $(CHEZ_BUILD_DIR)/xc-$(CHEZ_TARGET_MACHINE)/s/xpatch + +# Extra flags for the cross Chez configure. Callers can add e.g. `--static` +# for fully-static musl builds. We default to disabling curses/x11/iconv +# because (a) self-contained binaries don't need a REPL or X11, and (b) the +# musl-cross sysroot on macOS typically lacks ncurses/x11 headers. +CROSS_CHEZ_CONFIGURE_FLAGS ?= --threads --disable-x11 --disable-curses --disable-iconv + +CHEZ_CROSS_INSTALL_FLAGS = \ + --installprefix=$(CHEZ_CROSS_PREFIX) \ + --installbin=$(CHEZ_CROSS_PREFIX)/bin \ + --installlib=$(CHEZ_CROSS_PREFIX)/lib \ + --installman=$(CHEZ_CROSS_PREFIX)/share/man \ + --installdoc=$(CHEZ_CROSS_PREFIX)/share/doc \ + --as-is + +# Build target boot files + xpatch (in host build dir) + target libkernel.a +# (in a separate cross build dir). Requires the host Chez to already be built. +.PHONY: chez-cross +chez-cross: $(SCHEME) + @test -n "$(CHEZ_TARGET_MACHINE)" \ + || { echo "ERROR: set CHEZ_TARGET_MACHINE=<machine-type> (e.g. ta6osx, ta6le)" >&2; exit 1; } + @test -n "$(CROSS_CC)" \ + || { echo "ERROR: set CROSS_CC=<cross-cc> (e.g. \"clang -arch x86_64\")" >&2; exit 1; } + @echo "==> [chez-cross] bootquick XM=$(CHEZ_TARGET_MACHINE) (produces target boot files + xpatch)" + $(MAKE) -C $(CHEZ_BUILD_DIR) bootquick XM=$(CHEZ_TARGET_MACHINE) + @test -f $(CHEZ_XPATCH) || { echo "ERROR: xpatch not produced at $(CHEZ_XPATCH)" >&2; exit 1; } + @echo "==> [chez-cross] configure --cross --force -m=$(CHEZ_TARGET_MACHINE)" + @mkdir -p $(CHEZ_CROSS_BUILD_DIR) + cd $(CHEZ_CROSS_BUILD_DIR) && $(JERBOA_HOME)/vendor/ChezScheme/configure \ + --cross --force -m=$(CHEZ_TARGET_MACHINE) \ + $(CROSS_CHEZ_CONFIGURE_FLAGS) \ + CC="$(CROSS_CC)" CC_FOR_BUILD="$(CROSS_CC_FOR_BUILD)" \ + $(if $(CROSS_AR),AR="$(CROSS_AR)") \ + $(if $(CROSS_RANLIB),RANLIB="$(CROSS_RANLIB)") \ + $(CHEZ_CROSS_INSTALL_FLAGS) + @echo "==> [chez-cross] stage target boot files + generated headers into cross dir" + @mkdir -p $(CHEZ_CROSS_BUILD_DIR)/boot/$(CHEZ_TARGET_MACHINE) + @mkdir -p $(CHEZ_CROSS_BUILD_DIR)/$(CHEZ_TARGET_MACHINE)/boot/$(CHEZ_TARGET_MACHINE) + cp $(CHEZ_BUILD_DIR)/boot/$(CHEZ_TARGET_MACHINE)/* \ + $(CHEZ_CROSS_BUILD_DIR)/boot/$(CHEZ_TARGET_MACHINE)/ + cp $(CHEZ_BUILD_DIR)/boot/$(CHEZ_TARGET_MACHINE)/* \ + $(CHEZ_CROSS_BUILD_DIR)/$(CHEZ_TARGET_MACHINE)/boot/$(CHEZ_TARGET_MACHINE)/ + @echo "==> [chez-cross] build target libkernel.a" + @# CHOST tells the bundled zlib's configure to use the cross-prefix toolchain + @# instead of running host-OS detection (which on macOS picks libtool, not ar). + CHOST="$(CROSS_TOOL_PREFIX:%-=%)" \ + AR="$(CROSS_AR)" \ + RANLIB="$(CROSS_RANLIB)" \ + $(MAKE) -C $(CHEZ_CROSS_BUILD_DIR) + $(MAKE) -C $(CHEZ_CROSS_BUILD_DIR) install + @echo "==> [chez-cross] done. xpatch=$(CHEZ_XPATCH) prefix=$(CHEZ_CROSS_PREFIX)" + +# Cross-compiled binary. Drives support/build-binary.sh with TARGET_* env. +.PHONY: binary-cross +binary-cross: chez build chez-cross + @test -n "$(CHEZ_TARGET_MACHINE)" \ + || { echo "ERROR: set CHEZ_TARGET_MACHINE=<machine-type>" >&2; exit 1; } + SCHEME=$(SCHEME) \ + JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) \ + JERBOA_CROSS_PREFIX=$(CHEZ_CROSS_PREFIX) \ + JERBOA_XPATCH=$(CHEZ_XPATCH) \ + TARGET_MACHINE=$(CHEZ_TARGET_MACHINE) \ + CC="$(CROSS_CC)" \ + support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT)-$(CHEZ_TARGET_MACHINE) + test: test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-regex-all check-docs: new file mode 100644 --- /dev/null +++ b/docs/cross-compile.md @@ -0,0 +1,230 @@ +# Cross-Compiling Jerboa Binaries + +Jerboa's `make binary` target produces a single self-contained ELF/Mach-O +that bundles Chez Scheme's runtime, boot files, and a WPO-compiled program +(see [single-binary.md](single-binary.md) for the bundling details). +Cross-compilation extends that pipeline so the **host** Chez emits boot +files, FASL, and a linked binary for a **target** machine type. + +The two new Makefile entry points are: + +```sh +make chez-cross CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cross-cc> +make binary-cross CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cross-cc> +``` + +`binary-cross` depends on `chez-cross`, so most users invoke only +`binary-cross`. + +--- + +## Supported targets + +| `CHEZ_TARGET_MACHINE` | OS | Arch | Typical `CROSS_CC` | +|-----------------------|------------|----------|----------------------------------| +| `ta6le` | Linux | x86_64 | `x86_64-linux-musl-gcc` | +| `tarm64le` | Linux | arm64 | `aarch64-linux-musl-gcc` | +| `ta6osx` | macOS | x86_64 | `clang -arch x86_64` | +| `tarm64osx` | macOS | arm64 | `clang -arch arm64` | +| `ta6fb` | FreeBSD | x86_64 | `x86_64-freebsd-gcc` (or sysroot)| +| `tarm64fb` | FreeBSD | arm64 | `aarch64-freebsd-gcc` | + +Any machine type Chez itself supports (`vendor/ChezScheme/configure +--help` lists them) will work, provided you supply a matching C +cross-compiler and the target's libc / sysroot. + +On macOS arm64, the easiest Linux toolchain is `musl-cross` from Homebrew: + +```sh +brew install musl-cross --with-aarch64 +# provides: +# /opt/homebrew/bin/x86_64-linux-musl-gcc +# /opt/homebrew/bin/aarch64-linux-musl-gcc +``` + +--- + +## End-to-end example + +Cross-compile a Linux x86_64 static binary from macOS arm64: + +```sh +make binary-cross \ + CHEZ_TARGET_MACHINE=ta6le \ + CROSS_CC=x86_64-linux-musl-gcc +``` + +Output: `./jerboa-bin-ta6le` — a statically-linked ELF that runs on any +Linux x86_64 kernel with no shared-library dependencies. + +``` +$ file jerboa-bin-ta6le +jerboa-bin-ta6le: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), + statically linked, with debug_info, not stripped +``` + +--- + +## How it works + +Chez 10 supports two compilation modes for cross targets: + +1. `make bootquick XM=<target>` in a host Chez build dir produces a + `xc-<target>/s/xpatch` file. +2. Loading that xpatch into a running host Chez patches its compiler so + that `compile-file` / `compile-program` / `compile-whole-program` emit + FASL for `<target>` instead of for the host. + +`support/build-boot.ss` (the script that drives WPO) honors a +`JERBOA_XPATCH` env var. When set, it loads the xpatch file before +compiling, switching the host Chez into cross-emit mode. This is faster +and simpler than building a second Chez binary. + +The pipeline therefore has three phases: + +### Phase 1 — `chez-cross` + +1. **Build target boot files** in the host Chez build directory: + + ```sh + make -C build/chez bootquick XM=<target> + ``` + + Produces `build/chez/boot/<target>/{petite.boot,scheme.boot, + libkernel.a,scheme.h,...}` and the all-important + `build/chez/xc-<target>/s/xpatch`. + +2. **Configure a cross build dir** with `--cross --force` so Chez's + Makefile-cross only builds the C kernel for the target (no Scheme + bootstrap — the boot files came from step 1): + + ```sh + cd build/chez-cross-<target> + ../../vendor/ChezScheme/configure \ + --cross --force -m=<target> \ + --threads --disable-x11 --disable-curses --disable-iconv \ + CC=<cross-cc> CC_FOR_BUILD=cc \ + AR=<cross-ar> RANLIB=<cross-ranlib> + ``` + + The `--disable-*` flags matter: the musl-cross sysroots on macOS lack + `curses.h` / `iconv.h`. Override with + `CROSS_CHEZ_CONFIGURE_FLAGS=...` if you have a sysroot that includes + them. + +3. **Stage the target boot files** into the cross build dir at *both* + `boot/<target>/` and `<target>/boot/<target>/` (zuo expects them in + the top-level input dir AND inside the workarea). + +4. **`make && make install`** — this builds the target `libkernel.a`, + `libz.a`, and `liblz4.a` using the cross toolchain. The zlib subbuild + honors `CHOST` to pick the cross toolchain instead of running its + own host detection (which on macOS hardcodes Apple's `libtool` as + `AR`). + +### Phase 2 — Cross WPO + +`support/build-binary.sh` exports `JERBOA_XPATCH=<path/to/xpatch>` and +invokes the host scheme on `build-boot.ss`. `build-boot.ss`: + +```scheme +(let ([orig-libdirs (library-directories)] + [xpatch (getenv "JERBOA_XPATCH")]) + (when (and xpatch (not (string=? xpatch ""))) + (load xpatch) ; switch compiler to target mode + (library-directories orig-libdirs))) ; xpatch resets this; restore +``` + +> ⚠️ `load` of the xpatch resets `library-directories` to +> `(("." . "."))`. Capture and restore around the load or imports break +> with "library (jerboa prelude) not found". + +After the xpatch is loaded, `compile-program` + `compile-whole-program` +emit a single `.wp.so` containing target FASL. + +### Phase 3 — Cross link + +`build-binary.sh` then embeds `petite.boot`, `scheme.boot`, and the +`.wp.so` as C byte arrays in `<output>-main.c`, and links with the cross +toolchain: + +```sh +<cross-cc> -I<csv-dir> -O2 \ + -o jerboa-bin-<target> \ + jerboa-bin-<target>-main.c \ + <csv-dir>/libkernel.a \ + <csv-dir>/liblz4.a <csv-dir>/libz.a \ + -lm -ldl -lpthread -static # Linux musl +``` + +For cross builds, `build-binary.sh` automatically omits curses/iconv +from the link line (matching the `--disable-*` flags it configures +Chez with). Override the link libs with `OS_LIBS_OVERRIDE=...` if your +target needs different system libraries. + +--- + +## Tunables + +All Makefile variables are overridable on the command line: + +| Variable | Default | Purpose | +|----------|---------|---------| +| `CHEZ_TARGET_MACHINE` | *(required)* | Chez machine type, e.g. `ta6le` | +| `CROSS_CC` | *(required)* | C compiler for the target | +| `CROSS_CC_FOR_BUILD` | `cc` | Host-runnable cc, used to build zuo | +| `CROSS_AR` | derived from `CROSS_CC` prefix | Target archiver | +| `CROSS_RANLIB` | derived | Target ranlib | +| `CROSS_STRIP` | derived | Target strip | +| `CROSS_CHEZ_CONFIGURE_FLAGS` | `--threads --disable-x11 --disable-curses --disable-iconv` | Extra flags for the cross Chez configure | +| `BINARY_ENTRY` | `support/binary-entry.ss` | Entry script to bundle | +| `BINARY_OUTPUT` | `jerboa-bin` | Output prefix; final name is `<prefix>-<target>` | + +For a fully-static musl Linux build, the default already passes +`-static` via `build-binary.sh`. Override with +`OS_LIBS_OVERRIDE="-lm -ldl -lpthread"` to get a dynamically-linked +binary. + +--- + +## Validation + +Cross-built binaries can be smoke-tested with Docker (or any Linux +runtime): + +```sh +docker run --rm --platform linux/amd64 -v "$PWD":/w alpine:3 \ + /w/jerboa-bin-ta6le -e '(display (machine-type)) (newline)' +# → ta6le +``` + +For macOS x86_64 targets cross-built from arm64, run via Rosetta: + +```sh +arch -x86_64 ./jerboa-bin-ta6osx -e '(machine-type)' +# → ta6osx +``` + +--- + +## Limitations + +1. **Rust native lib** (`jerboa-native-rs`) is not auto-cross-compiled + yet. For features that require the Rust lib, build it separately: + + ```sh + cd jerboa-native-rs + cargo build --release --target=x86_64-unknown-linux-musl --no-default-features + ``` + +2. **Curses-dependent features disabled in cross builds** — the REPL's + line editor (expeditor) is omitted. Cross-built binaries run scripts, + not interactive REPLs, unless you supply a sysroot with `curses.h` + and override `CROSS_CHEZ_CONFIGURE_FLAGS`. + +3. **No xc-pb shortcut** — we always go through a full `bootquick` for + the target. This takes ~10–15 seconds per fresh target. + +4. **Windows targets untested.** The pipeline should work for `ta6nt` + etc. but `build-binary.sh` assumes POSIX `od`, `sed`, and `mkstemp` + in main.c. --- a/support/build-binary.sh +++ b/support/build-binary.sh @@ -24,41 +24,101 @@ SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}" JERBOA_CHEZ_PREFIX="${JERBOA_CHEZ_PREFIX:-$JERBOA_HOME/.chez}" LIBDIRS="${JERBOA_HOME}/lib" +# ── Cross-compilation parameters (all optional; unset = native build) ──────── +# TARGET_MACHINE Chez machine type to emit (e.g. ta6osx, ta6le). +# JERBOA_CROSS_PREFIX Install prefix of the cross-built Chez (target libkernel.a + boot files). +# JERBOA_XPATCH Path to xc-<target>/s/xpatch (makes host Scheme emit target FASL). +TARGET_MACHINE="${TARGET_MACHINE:-}" +JERBOA_CROSS_PREFIX="${JERBOA_CROSS_PREFIX:-}" +JERBOA_XPATCH="${JERBOA_XPATCH:-}" + +if [ -n "$TARGET_MACHINE" ]; then + CROSS_BUILD=yes + [ -n "$JERBOA_CROSS_PREFIX" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not" >&2; exit 1; } + [ -n "$JERBOA_XPATCH" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_XPATCH is not" >&2; exit 1; } + [ -f "$JERBOA_XPATCH" ] || { echo "ERROR: xpatch not found at $JERBOA_XPATCH" >&2; exit 1; } +else + CROSS_BUILD=no +fi + +# ── Derive target OS from machine type (for picking OS_LIBS) ───────────────── +# Machine type suffix encodes the OS: le=Linux, osx=macOS, fb=FreeBSD, nt=Windows. +machine_type_os() { + case "$1" in + *osx) echo Darwin ;; + *nt) echo Windows ;; + *fb) echo FreeBSD ;; + *ob) echo OpenBSD ;; + *nb) echo NetBSD ;; + *s2) echo Solaris ;; + *le|*) echo Linux ;; + esac +} + # ── Detect OS and pick compiler + base link flags ──────────────────────────── -OS=$(uname -s) -case "$OS" in - FreeBSD) - CC_DEFAULT=cc - OS_LIBS="-lm -lpthread -lncurses -L/usr/local/lib -liconv" - ;; - Darwin) - CC_DEFAULT=cc - OS_LIBS="-lm -lpthread -lncurses -liconv" - ;; - Linux) - CC_DEFAULT=gcc - # Try both -lncurses and -ltinfo; -ltinfo is split out on some distros. - if [ -f /usr/lib/x86_64-linux-gnu/libtinfo.so ] || [ -f /usr/lib64/libtinfo.so ]; then - OS_LIBS="-lm -ldl -lpthread -ltinfo" - else - OS_LIBS="-lm -ldl -lpthread -lncurses" - fi - ;; - *) - CC_DEFAULT=cc - OS_LIBS="-lm -lpthread -lncurses" - ;; -esac +HOST_OS=$(uname -s) +if [ "$CROSS_BUILD" = yes ]; then + TARGET_OS=$(machine_type_os "$TARGET_MACHINE") +else + TARGET_OS="$HOST_OS" +fi + +# Cross builds use `make chez-cross` which configures Chez with +# --disable-curses --disable-x11 --disable-iconv by default, so libkernel.a +# won't reference those libs. Native builds still link them because the +# host Chez was built --as-is (typically with curses/iconv enabled). +if [ "$CROSS_BUILD" = yes ]; then + case "$TARGET_OS" in + FreeBSD) CC_DEFAULT=cc; OS_LIBS="-lm -lpthread" ;; + Darwin) CC_DEFAULT=cc; OS_LIBS="-lm -lpthread" ;; + Linux) CC_DEFAULT=gcc; OS_LIBS="-lm -ldl -lpthread -static" ;; + *) CC_DEFAULT=cc; OS_LIBS="-lm -lpthread" ;; + esac +else + case "$TARGET_OS" in + FreeBSD) + CC_DEFAULT=cc + OS_LIBS="-lm -lpthread -lncurses -L/usr/local/lib -liconv" + ;; + Darwin) + CC_DEFAULT=cc + OS_LIBS="-lm -lpthread -lncurses -liconv" + ;; + Linux) + CC_DEFAULT=gcc + # Try both -lncurses and -ltinfo; -ltinfo is split out on some distros. + if [ -f /usr/lib/x86_64-linux-gnu/libtinfo.so ] || [ -f /usr/lib64/libtinfo.so ]; then + OS_LIBS="-lm -ldl -lpthread -ltinfo" + else + OS_LIBS="-lm -ldl -lpthread -lncurses" + fi + ;; + *) + CC_DEFAULT=cc + OS_LIBS="-lm -lpthread -lncurses" + ;; + esac +fi CC="${CC:-$CC_DEFAULT}" -# ── Find the Chez install dir (libkernel.a + scheme.h + boot files) ────────── -MACHINE_TYPE=$("$SCHEME" -q <<'EOF' +# Allow caller to override link libs entirely (musl-static, etc.). +OS_LIBS="${OS_LIBS_OVERRIDE:-$OS_LIBS}" + +# ── Determine target machine type ─────────────────────────────────────────── +if [ "$CROSS_BUILD" = yes ]; then + MACHINE_TYPE="$TARGET_MACHINE" + CSV_SEARCH_PREFIX="$JERBOA_CROSS_PREFIX/lib" +else + MACHINE_TYPE=$("$SCHEME" -q <<'EOF' (display (machine-type)) (exit) EOF ) + CSV_SEARCH_PREFIX="$JERBOA_CHEZ_PREFIX/lib" +fi +# ── Find the Chez install dir (libkernel.a + scheme.h + boot files) ────────── CSV_DIR="" -for prefix in "$JERBOA_CHEZ_PREFIX/lib" /usr/local/lib /usr/lib /usr/lib64 /opt/homebrew/lib /opt/local/lib; do +for prefix in "$CSV_SEARCH_PREFIX" /usr/local/lib /usr/lib /usr/lib64 /opt/homebrew/lib /opt/local/lib; do for d in "$prefix"/csv*/"$MACHINE_TYPE"; do if [ -f "$d/libkernel.a" ] && [ -f "$d/scheme.h" ] && [ -f "$d/petite.boot" ]; then CSV_DIR="$d" @@ -70,14 +130,19 @@ done if [ -z "$CSV_DIR" ]; then echo "ERROR: cannot find Chez install for machine-type '$MACHINE_TYPE'." >&2 echo " Searched csv*/$MACHINE_TYPE under:" >&2 - echo " $JERBOA_CHEZ_PREFIX/lib /usr/local/lib /usr/lib /usr/lib64 /opt/homebrew/lib /opt/local/lib" >&2 + echo " $CSV_SEARCH_PREFIX /usr/local/lib /usr/lib /usr/lib64 /opt/homebrew/lib /opt/local/lib" >&2 exit 1 fi echo "=== Jerboa binary build: $SCRIPT -> $OUTPUT ===" -echo " OS: $OS" -echo " CC: $CC" -echo " Chez: $CSV_DIR" +if [ "$CROSS_BUILD" = yes ]; then + echo " Mode: CROSS ($HOST_OS -> $TARGET_OS, $MACHINE_TYPE)" + echo " xpatch: $JERBOA_XPATCH" +else + echo " Mode: native ($HOST_OS, $MACHINE_TYPE)" +fi +echo " CC: $CC" +echo " Chez: $CSV_DIR" echo "" # ── Step 1: WPO-compile entry script -> program.so ─────────────────────────── @@ -86,7 +151,7 @@ OBJ_DIR=$(mktemp -d "/tmp/jerboa-bin-obj.XXXXXX") trap 'rm -rf "$OBJ_DIR" "$WPO_SO" petite_boot.h scheme_boot.h program_boot.h "${OUTPUT}-main.c"' EXIT echo "==> [1/4] WPO compile" -"$SCHEME" --libdirs "$LIBDIRS" \ +JERBOA_XPATCH="$JERBOA_XPATCH" "$SCHEME" --libdirs "$LIBDIRS" \ --script "$JERBOA_HOME/support/build-boot.ss" "$SCRIPT" "$WPO_SO" "$OBJ_DIR" echo "" --- a/support/build-boot.ss +++ b/support/build-boot.ss @@ -29,6 +29,26 @@ [output-so (list-ref args 1)] [obj-dir (and (>= (length args) 3) (list-ref args 2))]) + ;; Cross-compilation: if JERBOA_XPATCH is set, load the xpatch file so + ;; that compile-program / compile-whole-program emit FASL for the target + ;; machine instead of the host. See ChezScheme/BUILDING §"CROSS COMPILING + ;; SCHEME PROGRAMS". + ;; + ;; Loading xpatch swaps in a cross compiler and resets parameters like + ;; `library-directories` to defaults — so we must capture --libdirs before + ;; the load and re-apply after. + (let ([orig-libdirs (library-directories)] + [xpatch (getenv "JERBOA_XPATCH")]) + (when (and xpatch (not (string=? xpatch ""))) + (unless (file-exists? xpatch) + (display (format "ERROR: JERBOA_XPATCH=~a does not exist\n" xpatch) + (current-error-port)) + (exit 1)) + (display (format " loading xpatch ~a (cross-compile mode)\n" xpatch) + (current-error-port)) + (load xpatch) + (library-directories orig-libdirs))) + ;; When a separate object directory is requested, redirect compiled library ;; output there while keeping source lookup in the original lib directories. ;; This lets us compile against a read-only source tree (e.g. Docker :ro mount).