feat: cross-compile Rust native lib + FreeBSD target
ober
0740adab907fa030dddb37c02fde904db28f2cc7
--- a/.gitignore +++ b/.gitignore @@ -25,6 +25,12 @@ /jerboa-bin-*-main.c /jerboa-bin-*.wp.so +# `make native-cross` outputs (per-target Rust dylibs) +/lib-cross/ + +# FreeBSD sysroot for cross builds (downloaded base.txz extract) +/.freebsd-sysroot/ + # Repo-local Chez build/install artifacts /.chez/ /.chez-cross-*/ --- 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 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 +.PHONY: help chez chez-cross build binary binary-cross native-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>" @@ -36,6 +36,8 @@ help: @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-cross Cross-build Rust native lib for a target" + @echo " Args: CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cc> [CROSS_NATIVE_FEATURES=tls,...]" @echo " native Build Rust native library" @echo " clean Remove compiled .so and .wpo artifacts" @echo " clean-native Remove Rust build artifacts" @@ -196,6 +198,79 @@ chez-cross: $(SCHEME) $(MAKE) -C $(CHEZ_CROSS_BUILD_DIR) install @echo "==> [chez-cross] done. xpatch=$(CHEZ_XPATCH) prefix=$(CHEZ_CROSS_PREFIX)" +# ── Cross-compile the Rust native lib ──────────────────────────────────────── +# Maps a Chez machine type to a Rust target triple. Override RUST_TARGET to +# point cargo at something else. +# +# Output: lib-cross/<machine>/libjerboa_native.{so,dylib} +# Deploy: copy/symlink to lib/ on the target system before running the binary. +RUST_TARGET_ta6le = x86_64-unknown-linux-musl +RUST_TARGET_tarm64le = aarch64-unknown-linux-musl +RUST_TARGET_ta6osx = x86_64-apple-darwin +RUST_TARGET_tarm64osx = aarch64-apple-darwin +RUST_TARGET_ta6fb = x86_64-unknown-freebsd +RUST_TARGET ?= $(RUST_TARGET_$(CHEZ_TARGET_MACHINE)) + +# Feature set for cross builds. Many `full` features (duckdb, postgres, +# mozjs) don't cross-compile cleanly; default to a minimal build. +# Override e.g. CROSS_NATIVE_FEATURES="tls,crypto" to enable specific ones. +CROSS_NATIVE_FEATURES ?= + +# Per-target Rust flags. musl targets enforce `+crt-static` by default which +# disables cdylib — override so we can build .so. Other targets default empty. +CROSS_RUSTFLAGS_x86_64-unknown-linux-musl = -C target-feature=-crt-static +CROSS_RUSTFLAGS_aarch64-unknown-linux-musl = -C target-feature=-crt-static +CROSS_RUSTFLAGS ?= $(CROSS_RUSTFLAGS_$(RUST_TARGET)) + +# Extension differs by target OS. +CROSS_NATIVE_EXT_ta6le = so +CROSS_NATIVE_EXT_tarm64le = so +CROSS_NATIVE_EXT_ta6osx = dylib +CROSS_NATIVE_EXT_tarm64osx = dylib +CROSS_NATIVE_EXT_ta6fb = so + +# Translate "x86_64-unknown-linux-musl" → "X86_64_UNKNOWN_LINUX_MUSL" for +# cargo's CARGO_TARGET_<TRIPLE>_LINKER and AR_<triple> / CC_<triple> env vars. +empty := +space := $(empty) $(empty) +cargo_env_target = $(subst -,_,$(subst $(space),_,$(shell echo $(RUST_TARGET) | tr a-z A-Z))) +cc_env_target = $(subst -,_,$(RUST_TARGET)) + +# `rustup which` returns the absolute path inside the active rustup toolchain, +# bypassing PATH. Needed because a side-by-side Homebrew `rust` install may +# shadow rustup's cargo/rustc on $PATH — and Homebrew rust's rustlib only +# contains the host target, so cross builds fail with E0463 ("can't find crate +# for core") for any target other than aarch64-apple-darwin. +RUSTUP_CARGO = $(shell rustup which cargo 2>/dev/null) +RUSTUP_RUSTC = $(shell rustup which rustc 2>/dev/null) + +.PHONY: native-cross +native-cross: + @test -n "$(CHEZ_TARGET_MACHINE)" \ + || { echo "ERROR: set CHEZ_TARGET_MACHINE=<machine-type>" >&2; exit 1; } + @test -n "$(RUST_TARGET)" \ + || { echo "ERROR: no RUST_TARGET mapping for $(CHEZ_TARGET_MACHINE); set RUST_TARGET=<triple>" >&2; exit 1; } + @test -n "$(CROSS_CC)" \ + || { echo "ERROR: set CROSS_CC=<cross-cc>" >&2; exit 1; } + @test -n "$(RUSTUP_CARGO)" \ + || { echo "ERROR: rustup not found (or no active toolchain); install via https://rustup.rs" >&2; exit 1; } + @echo "==> [native-cross] $(CHEZ_TARGET_MACHINE) (rust target: $(RUST_TARGET))" + @rustup target list --installed 2>/dev/null | grep -q "^$(RUST_TARGET)$$" \ + || rustup target add $(RUST_TARGET) + cd $(RUST_NATIVE_DIR) && \ + CARGO_TARGET_$(cargo_env_target)_LINKER="$(CROSS_CC)" \ + CC_$(cc_env_target)="$(CROSS_CC)" \ + AR_$(cc_env_target)="$(CROSS_AR)" \ + RUSTC="$(RUSTUP_RUSTC)" \ + $(if $(CROSS_RUSTFLAGS),RUSTFLAGS="$(CROSS_RUSTFLAGS)") \ + "$(RUSTUP_CARGO)" build --release --target=$(RUST_TARGET) \ + --no-default-features \ + $(if $(CROSS_NATIVE_FEATURES),--features $(CROSS_NATIVE_FEATURES)) + @mkdir -p lib-cross/$(CHEZ_TARGET_MACHINE) + cp $(RUST_NATIVE_DIR)/target/$(RUST_TARGET)/release/libjerboa_native.$(CROSS_NATIVE_EXT_$(CHEZ_TARGET_MACHINE)) \ + lib-cross/$(CHEZ_TARGET_MACHINE)/ + @echo "==> [native-cross] -> lib-cross/$(CHEZ_TARGET_MACHINE)/libjerboa_native.$(CROSS_NATIVE_EXT_$(CHEZ_TARGET_MACHINE))" + # Cross-compiled binary. Drives support/build-binary.sh with TARGET_* env. .PHONY: binary-cross binary-cross: chez build chez-cross --- a/docs/cross-compile.md +++ b/docs/cross-compile.md @@ -42,6 +42,23 @@ brew install musl-cross --with-aarch64 # /opt/homebrew/bin/aarch64-linux-musl-gcc ``` +For FreeBSD targets, Apple's clang plus a FreeBSD sysroot suffices. The +repo ships `support/cross-cc-freebsd-{amd64,arm64}` — wrappers that point +Apple's `/usr/bin/clang` at `.freebsd-sysroot/<arch>/` with the correct +`--target=` triple. One-time setup: + +```sh +brew install lld # Apple's clang doesn't bundle ld.lld +mkdir -p .freebsd-sysroot/amd64 +curl -L -o .freebsd-sysroot/base.txz \ + https://download.freebsd.org/releases/amd64/14.4-RELEASE/base.txz +tar -xf .freebsd-sysroot/base.txz -C .freebsd-sysroot/amd64 \ + ./usr/include ./usr/lib ./lib +``` + +(Repeat with `arch=arm64` for `tarm64fb`. The sysroot dir is ~450 MB and is +gitignored — it lives outside the repo's tracked tree.) + --- ## End-to-end example @@ -63,6 +80,48 @@ jerboa-bin-ta6le: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped ``` +FreeBSD x86_64 from macOS arm64 (after sysroot setup, see above): + +```sh +make binary-cross \ + CHEZ_TARGET_MACHINE=ta6fb \ + CROSS_CC=$PWD/support/cross-cc-freebsd-amd64 \ + CROSS_AR=/usr/bin/ar \ + CROSS_RANLIB=/usr/bin/ranlib +``` + +Output: `./jerboa-bin-ta6fb` — a dynamically-linked FreeBSD ELF +(interpreter `/libexec/ld-elf.so.1`). + +``` +$ file jerboa-bin-ta6fb +jerboa-bin-ta6fb: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), + dynamically linked, for FreeBSD 14.4, FreeBSD-style +``` + +### Rust native lib + +The Rust FFI lib (`jerboa-native-rs`) cross-compiles via `rustup` targets: + +```sh +make native-cross CHEZ_TARGET_MACHINE=ta6le CROSS_CC=x86_64-linux-musl-gcc CROSS_AR=x86_64-linux-musl-ar +make native-cross CHEZ_TARGET_MACHINE=tarm64le CROSS_CC=aarch64-linux-musl-gcc CROSS_AR=aarch64-linux-musl-ar +``` + +Output: `lib-cross/<machine>/libjerboa_native.{so,dylib}`. Deploy alongside +the binary on the target (copy to `lib/` next to `jerboa-bin-<target>` or +set `LD_LIBRARY_PATH`). + +> ⚠️ Musl targets enforce `+crt-static` by default, which disables +> `cdylib`. The Makefile passes `-C target-feature=-crt-static` via +> `RUSTFLAGS` for musl targets so the `.so` is produced. + +> ⚠️ If a side-by-side Homebrew `rust` is installed, it shadows rustup's +> `cargo`/`rustc` on `$PATH` and only knows about the host target — +> producing `E0463 "can't find crate for core"`. The Makefile bypasses +> `$PATH` by invoking `rustup which cargo`/`rustup which rustc` absolute +> paths. + --- ## How it works --- a/jerboa-native-rs/src/landlock.rs +++ b/jerboa-native-rs/src/landlock.rs @@ -3,12 +3,12 @@ use crate::panic::{ffi_wrap, set_last_error}; // Landlock LSM syscall wrappers via libc // ABI v1-v5 support -// Syscall numbers for x86_64 -#[cfg(target_arch = "x86_64")] +// Syscall numbers for x86_64 and aarch64 (same on both per Linux kernel ABI). +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] const SYS_LANDLOCK_CREATE_RULESET: libc::c_long = 444; -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] const SYS_LANDLOCK_ADD_RULE: libc::c_long = 445; -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] const SYS_LANDLOCK_RESTRICT_SELF: libc::c_long = 446; // ABI versions and access rights new file mode 100755 --- /dev/null +++ b/support/cross-cc-freebsd @@ -0,0 +1,47 @@ +#!/bin/sh +# cross-cc-freebsd — Apple clang wrapper that emits FreeBSD ELF. +# +# Usage: cross-cc-freebsd <arch> [clang args...] +# +# <arch> is the leading argv[0] sniff value: "x86_64" or "aarch64". +# The wrapper picks a target triple, points clang at the FreeBSD sysroot in +# .freebsd-sysroot/<arch>/, and prepends the matching crt object dir to the +# link line so the FreeBSD libc resolves correctly. +# +# Intended to be invoked through one of two argv[0] symlinks: +# support/cross-cc-freebsd-amd64 -> cross-cc-freebsd +# support/cross-cc-freebsd-arm64 -> cross-cc-freebsd +# +# Override CLANG=/path/to/clang if Apple's /usr/bin/clang is missing. + +set -eu + +CLANG="${CLANG:-/usr/bin/clang}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +self="$(basename "$0")" +case "$self" in + cross-cc-freebsd-amd64) arch=amd64; triple=x86_64-unknown-freebsd14.0 ;; + cross-cc-freebsd-arm64) arch=arm64; triple=aarch64-unknown-freebsd14.0 ;; + *) + echo "ERROR: cross-cc-freebsd must be invoked via cross-cc-freebsd-{amd64,arm64} symlink" >&2 + exit 1 + ;; +esac + +SYSROOT="${FREEBSD_SYSROOT:-$REPO_DIR/.freebsd-sysroot/$arch}" +[ -d "$SYSROOT/usr/include" ] || { + echo "ERROR: FreeBSD sysroot missing at $SYSROOT (expected usr/include + usr/lib)" >&2 + echo " Download with: curl -L -o .freebsd-sysroot/base.txz \\" >&2 + echo " https://download.freebsd.org/releases/$arch/14.4-RELEASE/base.txz" >&2 + echo " Then: tar -xf .freebsd-sysroot/base.txz -C .freebsd-sysroot/$arch ./usr/include ./usr/lib ./lib" >&2 + exit 1 +} + +exec "$CLANG" \ + --target="$triple" \ + --sysroot="$SYSROOT" \ + -B"$SYSROOT/usr/lib" \ + -fuse-ld=lld \ + "$@" new file mode 120000 --- /dev/null +++ b/support/cross-cc-freebsd-amd64 @@ -0,0 +1 @@ +cross-cc-freebsd \ No newline at end of file new file mode 120000 --- /dev/null +++ b/support/cross-cc-freebsd-arm64 @@ -0,0 +1 @@ +cross-cc-freebsd \ No newline at end of file