build: portable `make binary` target for FreeBSD/Linux/macOS
ober
6d8b4d12cfa27e9ecab0c82ea7cc6fcacff1959f
--- a/.gitignore +++ b/.gitignore @@ -7,6 +7,11 @@ \#* .#* +# `make binary` outputs +/jerboa-bin +/jerboa-bin-main.c +/jerboa-bin.wp.so + # Rust build artifacts jerboa-native-rs/target/ --- a/Makefile +++ b/Makefile @@ -7,13 +7,14 @@ 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 build 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 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 help: @echo "Usage: make <target>" @echo "" @echo "Build:" @echo " build Compile all Jerboa libraries" + @echo " binary Build a self-contained jerboa-bin binary (FreeBSD/Linux/macOS)" @echo " native Build Rust native library" @echo " clean Remove compiled .so and .wpo artifacts" @echo " clean-native Remove Rust build artifacts" @@ -71,6 +72,14 @@ help: build: $(SCHEME) --libdirs $(LIBDIRS) --script support/build.ss +# Build a self-contained Jerboa binary that bundles petite.boot, scheme.boot, +# and a WPO-compiled entry program. Output: ./jerboa-bin +# Override entry script with BINARY_ENTRY=path/to/script.ss +BINARY_ENTRY ?= support/binary-entry.ss +BINARY_OUTPUT ?= jerboa-bin +binary: build + SCHEME=$(SCHEME) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT) + 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/support/binary-entry.ss @@ -0,0 +1,43 @@ +#!chezscheme +;;; binary-entry.ss — Default entry point compiled into `make binary` output. +;;; +;;; With no args: starts the Jerboa REPL. +;;; With one arg ending in .ss: loads that script. +;;; --version / -v: prints version. + +(import (except (chezscheme) make-hash-table hash-table? sort sort! printf fprintf + path-extension path-absolute? with-input-from-string + with-output-to-string iota 1+ 1- partition make-date make-time + meta atom?) + (jerboa prelude)) + +(let ([args (cdr (command-line))]) + (cond + [(null? args) + (displayln "Jerboa Scheme — type (exit) to quit") + (let loop () + (display "jerboa> ") + (flush-output-port (current-output-port)) + (let ([form (read)]) + (cond + [(eof-object? form) (newline)] + [else + (guard (exn [#t (display "Error: ") + (display-condition exn) + (newline)]) + (let ([result (eval form (interaction-environment))]) + (unless (eq? result (void)) + (write result) (newline)))) + (loop)])))] + [(or (string=? (car args) "--version") + (string=? (car args) "-v")) + (displayln "jerboa-bin 0.1.0")] + [(or (string=? (car args) "--help") + (string=? (car args) "-h")) + (displayln "Usage: jerboa-bin [<script.ss> | --version | --help]") + (displayln " no args start REPL") + (displayln " <script.ss> load and run a Scheme script") + (displayln " --version, -v print version") + (displayln " --help, -h print this help")] + [else + (load (car args))])) new file mode 100755 --- /dev/null +++ b/support/build-binary.sh @@ -0,0 +1,181 @@ +#!/bin/sh +# build-binary.sh — Build a self-contained Jerboa binary. +# +# Usage: +# support/build-binary.sh <entry.ss> <output-name> +# +# Portable across FreeBSD, Linux, and macOS — uses the system Chez install +# (libkernel.a + petite.boot + scheme.boot) discovered by globbing standard +# prefixes for csv*/<machine-type>/. +# +# Output: ./<output-name> (a real ELF/Mach-O binary that runs Jerboa) + +set -eu + +if [ $# -lt 2 ]; then + echo "Usage: $0 <entry.ss> <output-name>" >&2 + exit 1 +fi + +SCRIPT="$1" +OUTPUT="$2" + +JERBOA_HOME="${JERBOA_HOME:-$(cd "$(dirname "$0")/.." && pwd)}" +SCHEME="${SCHEME:-scheme}" +LIBDIRS="${JERBOA_HOME}/lib" + +# ── 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 +CC="${CC:-$CC_DEFAULT}" + +# ── Find the Chez install dir (libkernel.a + scheme.h + boot files) ────────── +MACHINE_TYPE=$("$SCHEME" -q <<'EOF' +(display (machine-type)) (exit) +EOF +) + +CSV_DIR="" +for prefix in /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" + break 2 + fi + done +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 " /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" +echo "" + +# ── Step 1: WPO-compile entry script -> program.so ─────────────────────────── +WPO_SO="${OUTPUT}.wp.so" +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" \ + --script "$JERBOA_HOME/support/build-boot.ss" "$SCRIPT" "$WPO_SO" "$OBJ_DIR" +echo "" + +# ── Step 2: Embed boot files + program as C byte arrays ────────────────────── +echo "==> [2/4] Embedding boot files as C arrays" + +embed() { + in="$1"; stem="$2" + var="${stem}_data"; sz="${stem}_size"; out="$stem.h" + printf 'static const unsigned char %s[] = {\n' "$var" > "$out" + # macOS `od` emits a trailing blank line; drop it before the substitution + # so we don't generate a stray "0x," at the end. + od -An -tx1 -v "$in" \ + | sed -e '/^[[:space:]]*$/d' \ + -e 's/^ *//;s/ *$//;s/ */ /g;s/ /,0x/g;s/^/0x/;s/$/,/' >> "$out" + printf '};\nstatic const unsigned int %s = sizeof(%s);\n' "$sz" "$var" >> "$out" +} + +embed "$CSV_DIR/petite.boot" petite_boot +embed "$CSV_DIR/scheme.boot" scheme_boot +embed "$WPO_SO" program_boot +echo "" + +# ── Step 3: Generate main.c ────────────────────────────────────────────────── +echo "==> [3/4] Generate ${OUTPUT}-main.c" + +cat > "${OUTPUT}-main.c" <<'CMAIN' +/* Jerboa binary entry point — generated by support/build-binary.sh */ +#include "scheme.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "petite_boot.h" +#include "scheme_boot.h" +#include "program_boot.h" + +static const char *write_program_tmpfile(void) { + static char path[] = "/tmp/jerboa-prog-XXXXXX"; + int fd = mkstemp(path); + if (fd < 0) { perror("mkstemp"); exit(1); } + ssize_t n = write(fd, program_boot_data, program_boot_size); + if (n != (ssize_t)program_boot_size) { + perror("write"); close(fd); unlink(path); exit(1); + } + close(fd); + return path; +} + +int main(int argc, const char *argv[]) { + Sscheme_init(NULL); + Sregister_boot_file_bytes("petite", + (void *)petite_boot_data, petite_boot_size); + Sregister_boot_file_bytes("scheme", + (void *)scheme_boot_data, scheme_boot_size); + Sbuild_heap(NULL, NULL); + + const char *prog_path = write_program_tmpfile(); + Sscheme_program(prog_path, argc, argv); + unlink(prog_path); + + Sscheme_deinit(); + return 0; +} +CMAIN +echo "" + +# ── Step 4: Compile + link ─────────────────────────────────────────────────── +echo "==> [4/4] Compile + link -> $OUTPUT" + +EXTRA_ARCHIVES="" +for a in liblz4.a libz.a; do + if [ -f "$CSV_DIR/$a" ]; then + EXTRA_ARCHIVES="$EXTRA_ARCHIVES $CSV_DIR/$a" + fi +done + +# shellcheck disable=SC2086 +$CC -I"$CSV_DIR" -O2 \ + -o "$OUTPUT" \ + "${OUTPUT}-main.c" \ + "$CSV_DIR/libkernel.a" \ + $EXTRA_ARCHIVES \ + $OS_LIBS + +echo "" +echo "=== Build complete ===" +ls -lh "$OUTPUT" +file "$OUTPUT" 2>/dev/null || true