Replace launcher with a self-contained native Jerboa binary
ober
35d39937ad74dd99cb1dfa995efc2557d77370ea
--- a/.build.yml +++ b/.build.yml @@ -20,6 +20,9 @@ tasks: bash packaging/linux/build-tarball.sh - verify: | cd jerboa-browser + # This CI image has no Chez/Jerboa, so build-tarball.sh ships sources only + # (no native binary). Verify the FFI .so + the tarball; the self-contained + # jerboa-browser binary is built/checked on a host with the toolchain. so=qt-webengine/build/libjerboa_browser.so tb=qt-webengine/build/jerboa-browser-0.0.1-linux-x86_64.tar.gz test -f "$so" || { echo "MISSING $so"; exit 1; } @@ -29,3 +32,5 @@ tasks: ldd "$so" | grep -i 'qt6\|webengine' || true echo "--- tarball ---" ls -lh "$tb" + tar tzf "$tb" | grep -E 'lib/libjerboa_browser.so$' \ + || { echo "lib missing from tarball"; exit 1; } --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,14 @@ build/ **/build/ +# Native binary + Jerboa build artifacts (see build-binary.ss / Makefile) +/jerboa-browser +.bcache/ +*.so +*.wpo +*.boot +jb-main.c +jb_*.h + # macOS .DS_Store new file mode 100644 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +JERBOA_HOME ?= $(realpath $(CURDIR)/../jerboa) +SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme +LIBDIRS := $(CURDIR)/scheme:$(JERBOA_HOME)/lib + +.PHONY: binary test repl clean help +.DEFAULT_GOAL := help + +# Build the self-contained native ./jerboa-browser (Chez + boot + (browser)). +binary: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) -q --libdirs $(LIBDIRS) --script build-binary.ss + @echo "" && ls -lh jerboa-browser && file jerboa-browser + +# Run the (browser) test suite in the interpreter (no binary needed). +test: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) -q --libdirs $(LIBDIRS) --script scheme/browser-test.ss + +# REPL with (browser) preloaded, in the interpreter. +repl: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) -q --libdirs $(LIBDIRS) --program scheme/browser-repl.ss + +clean: + rm -f jerboa-browser jb-main.c jb-main.o jb_*.h jerboa-browser.boot + rm -rf .bcache + find scheme -name '*.so' -delete 2>/dev/null || true + find scheme -name '*.wpo' -delete 2>/dev/null || true + +help: + @echo "jerboa-browser — programmable Qt WebEngine pane from the Jerboa REPL" + @echo "" + @echo " make binary build the self-contained native ./jerboa-browser" + @echo " make test run the (browser) test suite (interpreter)" + @echo " make repl REPL with (browser) preloaded (interpreter)" + @echo " make clean remove build artifacts" + @echo "" + @echo " env: JERBOA_HOME (default ../jerboa), SCHEME, JERBOA_BROWSER_LIB" deleted file mode 100755 --- a/bin/jerboa-browser +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# -# jerboa-browser — run a Jerboa REPL or script with the (browser) library and -# its native dylib on the path. -# -# Resolves the repo-local Chez + Jerboa stdlib, puts scheme/ on --libdirs so -# (import (browser)) works, and points JERBOA_BROWSER_LIB at the built dylib. -# -# Usage: -# bin/jerboa-browser # REPL with (browser) preloaded -# bin/jerboa-browser repl -# bin/jerboa-browser test # run scheme/browser-test.ss -# bin/jerboa-browser run <file> # run a Jerboa script -# -# Override JERBOA_HOME / SCHEME / JERBOA_BROWSER_LIB via the environment. - -set -euo pipefail - -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -JERBOA_HOME="${JERBOA_HOME:-$HOME/mine/jerboa}" -SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}" -LIBDIRS="$JERBOA_HOME/lib:$REPO/scheme" - -export JERBOA_BROWSER_LIB="${JERBOA_BROWSER_LIB:-$REPO/qt-webengine/build/libjerboa_browser.dylib}" - -if [ ! -x "$SCHEME" ]; then - echo "Error: Chez Scheme not found at $SCHEME (set SCHEME or JERBOA_HOME)" >&2 - exit 1 -fi -if [ ! -f "$JERBOA_BROWSER_LIB" ]; then - echo "Error: backend dylib not found at $JERBOA_BROWSER_LIB" >&2 - echo "Build it: cmake -S qt-webengine -B qt-webengine/build -DCMAKE_PREFIX_PATH=/opt/homebrew && cmake --build qt-webengine/build" >&2 - exit 1 -fi - -case "${1:-repl}" in - repl) - exec "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<'REPL' -(import (jerboa prelude) (browser) (std repl)) -(display ";; (browser) loaded — call (browser-init) then (browser-open-context ...)\n") -(jerboa-repl) -REPL - ) - ;; - test) - exec "$SCHEME" --libdirs "$LIBDIRS" --script "$REPO/scheme/browser-test.ss" - ;; - run) - shift - [ $# -ge 1 ] || { echo "usage: jerboa-browser run <file>" >&2; exit 1; } - exec "$SCHEME" --libdirs "$LIBDIRS" --script "$1" - ;; - *) - echo "usage: jerboa-browser [repl|test|run <file>]" >&2 - exit 1 - ;; -esac new file mode 100644 --- /dev/null +++ b/build-binary.ss @@ -0,0 +1,174 @@ +#!chezscheme +;;; build-binary.ss — compile scheme/browser-main.ss into a self-contained +;;; native `jerboa-browser` executable: the Chez kernel, the boot files, the +;;; (browser) library AND the full library closure it (and the REPL) need are +;;; all embedded, so `(import (browser))` works at the prompt with no library +;;; path. No hand-written C/C++: this script generates a tiny C bootstrap and +;;; links it (the gitsafe recipe). The native Qt backend (libjerboa_browser.*) +;;; stays a runtime dlopen; the Chromium sandbox is left on. +;;; +;;; Usage: make binary +;;; (scheme -q --libdirs scheme:$JERBOA_HOME/lib --script build-binary.ss) +;;; +;;; macOS → statically links Chez kernel/lz4/zlib/ncurses (only libSystem + +;;; libiconv dynamic). Linux → dynamic link against the system Chez libs. +;;; Compiled library objects go to ./.bcache so the read-only Jerboa tree is +;;; never written to. + +(import (chezscheme)) + +(define repo (current-directory)) +(define bcache (format "~a/.bcache" repo)) + +(define machine (symbol->string (machine-type))) +(define macos? + (or (string=? machine "ta6osx") (string=? machine "a6osx") + (string=? machine "tarm64osx") (string=? machine "arm64osx"))) + +;; --- locate Jerboa + Chez ------------------------------------------------ +(define jerboa-dir + (or (getenv "JERBOA_HOME") + (let ((p (format "~a/mine/jerboa" (getenv "HOME")))) + (and (file-exists? p) p)) + (begin (display "Error: cannot find Jerboa; set JERBOA_HOME\n") (exit 1)))) + +(define (find-csv-dir lib-dir mt) + (let ((csv (let lp ((dirs (guard (e (#t '())) (directory-list lib-dir)))) + (cond + ((null? dirs) #f) + ((and (> (string-length (car dirs)) 3) + (string=? "csv" (substring (car dirs) 0 3))) + (format "~a/~a/~a" lib-dir (car dirs) mt)) + (else (lp (cdr dirs))))))) + (and csv (file-exists? (format "~a/main.o" csv)) csv))) + +(define chez-dir + (or (getenv "CHEZ_DIR") + (find-csv-dir (format "~a/.chez/lib" jerboa-dir) machine) + (find-csv-dir (format "~a/.local/lib" (getenv "HOME")) machine) + (find-csv-dir "/opt/homebrew/lib" machine) + (find-csv-dir "/usr/local/lib" machine) + (find-csv-dir "/usr/lib" machine) + (begin (display "Error: cannot find Chez install dir; set CHEZ_DIR\n") (exit 1)))) + +(printf "Jerboa dir: ~a\nChez dir: ~a\nMachine: ~a\n" jerboa-dir chez-dir machine) + +;; --- library search path ------------------------------------------------- +;; (source . object) pairs: read .ss/.sls from the repo + Jerboa stdlib, write +;; the compiled .so under ./.bcache (NOT into the read-only Jerboa tree). +(system (format "rm -rf ~a" bcache)) +(mkdir bcache) +(library-directories + (list (cons (format "~a/scheme" repo) (format "~a/scheme" bcache)) + (cons (format "~a/lib" jerboa-dir) (format "~a/jlib" bcache)))) + +;; --- helpers ------------------------------------------------------------- +(define (file->c-header in out array size-name) + (let* ((p (open-file-input-port in)) + (data (get-bytevector-all p)) + (n (bytevector-length data))) + (close-port p) + (call-with-output-file out + (lambda (o) + (fprintf o "/* Auto-generated — do not edit */\n") + (fprintf o "static const unsigned char ~a[] = {\n" array) + (let lp ((i 0)) + (when (< i n) + (when (= 0 (modulo i 16)) (fprintf o " ")) + (fprintf o "0x~2,'0x" (bytevector-u8-ref data i)) + (when (< (+ i 1) n) (fprintf o ",")) + (when (= 15 (modulo i 16)) (fprintf o "\n")) + (lp (+ i 1)))) + (fprintf o "\n};\n") + (fprintf o "static const unsigned int ~a = ~a;\n" size-name n)) + 'replace) + (printf " ~a: ~a bytes\n" out n))) + +;; All compiled .so under dir (recursively) — the library closure to embed. +(define (find-so dir) + (let loop ((entries (guard (e (#t '())) (directory-list dir))) (acc '())) + (if (null? entries) acc + (let* ((name (car entries)) (path (string-append dir "/" name))) + (cond + ((file-directory? path) (loop (cdr entries) (append (find-so path) acc))) + ((and (> (string-length name) 3) + (string=? ".so" (substring name (- (string-length name) 3) (string-length name)))) + (loop (cdr entries) (cons path acc))) + (else (loop (cdr entries) acc))))))) + +;; --- [1/4] compile the program + its full imported-library closure ------- +(printf "\n[1/4] Compiling (optimize-level 3)...\n") +(parameterize ((compile-imported-libraries #t) + (optimize-level 3) + (cp0-effort-limit 500) + (cp0-score-limit 50) + (cp0-outer-unroll-limit 1) + (commonization-level 4) + (enable-arithmetic-left-associative #t) + (debug-level 0) + (generate-inspector-information #f)) + (compile-program "scheme/browser-main.ss")) + +;; --- [2/4] boot file (program closure) + C headers ----------------------- +(printf "[2/4] Boot file + C headers...\n") +(define closure (find-so bcache)) +(printf " embedding ~a libraries\n" (length closure)) +(apply make-boot-file "jerboa-browser.boot" '("scheme" "petite") closure) +(file->c-header "scheme/browser-main.so" "jb_program.h" "jb_program_data" "jb_program_size") +(file->c-header (format "~a/petite.boot" chez-dir) "jb_petite_boot.h" "petite_boot_data" "petite_boot_size") +(file->c-header (format "~a/scheme.boot" chez-dir) "jb_scheme_boot.h" "scheme_boot_data" "scheme_boot_size") +(file->c-header "jerboa-browser.boot" "jb_boot.h" "jb_boot_data" "jb_boot_size") + +;; --- [3/4] generate C bootstrap, compile + link -------------------------- +(printf "[3/4] Compiling + linking...\n") +(call-with-output-file "jb-main.c" + (lambda (o) + (fprintf o "/* Auto-generated — do not edit */\n") + (fprintf o "#define _GNU_SOURCE\n#include <stdlib.h>\n#include <stdio.h>\n") + (fprintf o "#include <string.h>\n#include <unistd.h>\n#include \"scheme.h\"\n") + (fprintf o "#include \"jb_petite_boot.h\"\n#include \"jb_scheme_boot.h\"\n") + (fprintf o "#include \"jb_boot.h\"\n#include \"jb_program.h\"\n\n") + (fprintf o "int main(int argc, char *argv[]) {\n") + (fprintf o " char prog_path[256];\n") + (fprintf o " const char *tmpdir = getenv(\"TMPDIR\"); if (!tmpdir) tmpdir = \"/tmp\";\n") + (display " snprintf(prog_path, sizeof(prog_path), \"%s/jerboa-browser-XXXXXX\", tmpdir);\n" o) + (fprintf o " int fd = mkstemp(prog_path);\n") + (fprintf o " if (fd < 0) { perror(\"mkstemp\"); return 1; }\n") + (fprintf o " if (write(fd, jb_program_data, jb_program_size) != (ssize_t)jb_program_size) {\n") + (fprintf o " perror(\"write\"); close(fd); unlink(prog_path); return 1; }\n") + (fprintf o " close(fd);\n") + (fprintf o " Sscheme_init(NULL);\n") + (fprintf o " Sregister_boot_file_bytes(\"petite\", (void*)petite_boot_data, petite_boot_size);\n") + (fprintf o " Sregister_boot_file_bytes(\"scheme\", (void*)scheme_boot_data, scheme_boot_size);\n") + (fprintf o " Sregister_boot_file_bytes(\"jerboa-browser\", (void*)jb_boot_data, jb_boot_size);\n") + (fprintf o " Sbuild_heap(NULL, NULL);\n") + (fprintf o " int status = Sscheme_script(prog_path, argc, (const char **)argv);\n") + (fprintf o " unlink(prog_path); Sscheme_deinit(); return status;\n}\n")) + 'replace) + +(define cc (or (getenv "CC") "cc")) +(define (sh cmd) (printf " ~a\n" cmd) (let ((rc (system cmd))) (unless (= rc 0) (printf "Error (rc=~a)\n" rc) (exit 1)))) + +(sh (format "~a -c -O2 -I~a -o jb-main.o jb-main.c" cc chez-dir)) +(if macos? + (let* ((ncurses (or (let ((p (getenv "NCURSES_STATIC_PATH"))) (and p (> (string-length p) 0) (file-exists? p) p)) + (let ((p "/opt/homebrew/opt/ncurses/lib/libncurses.a")) (and (file-exists? p) p)) + (let ((p "/usr/local/opt/ncurses/lib/libncurses.a")) (and (file-exists? p) p)))) + (static (format "~a/libkernel.a ~a/liblz4.a ~a/libz.a~a" + chez-dir chez-dir chez-dir (if ncurses (string-append " " ncurses) ""))) + (dynamic (string-append (if ncurses "" " -lncurses") " -liconv -lpthread -lm"))) + (sh (format "~a -o jerboa-browser jb-main.o ~a~a" cc static dynamic)) + (sh "strip -x jerboa-browser")) + (begin + (sh (format "~a -o jerboa-browser jb-main.o -L~a -lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses" + cc chez-dir)) + (sh "strip jerboa-browser"))) + +;; --- [4/4] cleanup ------------------------------------------------------- +(printf "[4/4] Cleanup...\n") +(for-each (lambda (f) (when (file-exists? f) (delete-file f))) + '("jb-main.c" "jb-main.o" "jb_program.h" "jb_petite_boot.h" "jb_scheme_boot.h" + "jb_boot.h" "jerboa-browser.boot" "scheme/browser-main.so")) +(system (format "rm -rf ~a" bcache)) + +(printf "\nDone! Binary: ./jerboa-browser\n") --- a/jerboa-browser.md +++ b/jerboa-browser.md @@ -647,8 +647,12 @@ Result (macOS, the verified target): `icudtl.dat` resources, platform/imageformat/tls/etc. plugins) and documents the fixups and why macdeployqt alone SIGABRTs on first page load. - Linux: `packaging/linux/build-tarball.sh` stages backend `.so` + `scheme/` + - launcher relying on system Qt6 WebEngine; marked **not verified on the macOS - dev host**, with linuxdeployqt/AppImage noted as the no-prerequisite path. + `build-binary.ss`/`Makefile`, and — when the Jerboa toolchain is present — + compiles the self-contained native `jerboa-browser` binary (a Jerboa program + embedding Chez + boot + `(browser)`, built by `build-binary.ss`, not a C/C++ + launcher; `make binary` otherwise). Relies on system Qt6 WebEngine; the + `.so`+tarball build is green on builds.sr.ht (amd64, no Chez there, so it ships + sources), with linuxdeployqt/AppImage noted as the no-prerequisite path. Goal: make deployment honest and reproducible. --- a/packaging/README.md +++ b/packaging/README.md @@ -101,8 +101,13 @@ packaging/linux/build-tarball.sh # → qt-webengine/build/jerboa-browser-<v > **Status: not verified on this host.** Development is on macOS, so the Linux > script is provided and reviewed but has not been executed end-to-end here. The > tarball bundles the backend `libjerboa_browser.so`, the Jerboa `scheme/` -> sources, and the `bin/jerboa-browser` launcher; it relies on a **system Qt 6 -> WebEngine** install (documented prerequisite) rather than vendoring Qt. +> sources, and `build-binary.ss` + `Makefile`. When the Jerboa toolchain (Chez + +> Jerboa stdlib) is present on the build host it also compiles and bundles the +> self-contained native `bin/jerboa-browser` — a Jerboa program with the Chez +> kernel, boot image, and the `(browser)` library all embedded (built by +> `build-binary.ss`, **not** a C/C++ launcher); otherwise unpack and run `make +> binary` to produce it. Either way it relies on a **system Qt 6 WebEngine** +> install (documented prerequisite) rather than vendoring Qt. For a fully self-contained Linux artifact, run the bundle through [`linuxdeployqt`](https://github.com/probonopd/linuxdeployqt) to produce an --- a/packaging/linux/build-tarball.sh +++ b/packaging/linux/build-tarball.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash # -# build-tarball.sh — Ticket 3.1: package the Jerboa Browser for Linux. +# build-tarball.sh — package the Jerboa Browser for Linux. # -# Produces a relocatable tarball containing the browser backend shared object, -# the Jerboa `(browser)` scheme sources, and a launcher. It relies on a SYSTEM -# Qt 6 WebEngine install (documented prerequisite) rather than vendoring Qt; for -# a fully self-contained artifact run the staged tree through linuxdeployqt to -# get an AppImage (see packaging/README.md). +# Builds the Qt WebEngine FFI backend (libjerboa_browser.so) via CMake, then — +# when the Jerboa toolchain (Chez + Jerboa stdlib) is available — compiles the +# self-contained native `jerboa-browser` binary with build-binary.ss. The +# tarball always ships the sources + build files so the binary can be built +# later with `make binary`. It relies on a SYSTEM Qt 6 WebEngine install rather +# than vendoring Qt; for a fully self-contained artifact run the staged tree +# through linuxdeployqt to get an AppImage (see packaging/README.md). # -# STATUS: not verified on the macOS dev host — review before relying on it. -# -# Env overrides: BUILD, QT_PREFIX, VERSION. +# Env overrides: BUILD, QT_PREFIX, VERSION, JERBOA_HOME, SCHEME. set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" @@ -19,8 +19,10 @@ VERSION="${VERSION:-0.0.1}" ARCH="$(uname -m)" STAGE="$BUILD/jerboa-browser-$VERSION-linux-$ARCH" TARBALL="$STAGE.tar.gz" +JERBOA_HOME="${JERBOA_HOME:-$HOME/mine/jerboa}" +SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}" -echo "==> configuring + building the backend (.so)" +echo "==> configuring + building the FFI backend (.so)" cmake_args=(-S "$REPO/qt-webengine" -B "$BUILD") [ -n "${QT_PREFIX:-}" ] && cmake_args+=(-DCMAKE_PREFIX_PATH="$QT_PREFIX") cmake "${cmake_args[@]}" >/dev/null @@ -34,20 +36,24 @@ rm -rf "$STAGE" mkdir -p "$STAGE/lib" "$STAGE/bin" "$STAGE/scheme" cp -f "$LIB" "$STAGE/lib/" cp -f "$REPO"/scheme/*.ss "$STAGE/scheme/" 2>/dev/null || true +cp -f "$REPO/build-binary.ss" "$REPO/Makefile" "$STAGE/" -# Relocatable launcher: resolves its own dir, points the Jerboa toolchain at the -# bundled backend, and forwards to the repo's bin/jerboa-browser logic. -cat > "$STAGE/bin/jerboa-browser" <<'LAUNCH' -#!/usr/bin/env bash -set -euo pipefail -HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" -export JERBOA_BROWSER_LIB="$HERE/lib/libjerboa_browser.so" -export LD_LIBRARY_PATH="$HERE/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -# Jerboa toolchain + scheme path are expected from the environment / system -# install; $HERE/scheme holds the (browser) module sources. -exec "${JERBOA:-jerboa}" "$@" -LAUNCH -chmod +x "$STAGE/bin/jerboa-browser" +# The native binary embeds Chez + the (browser) library; build it when the +# Jerboa toolchain is present (e.g. a developer host). CI images without Chez +# skip this — the tarball still carries the sources + build-binary.ss. +HAVE_BIN=0 +if [ -x "$SCHEME" ]; then + echo "==> building the native jerboa-browser binary ($SCHEME)" + ( cd "$REPO" && JERBOA_HOME="$JERBOA_HOME" SCHEME="$SCHEME" make binary ) + if [ -x "$REPO/jerboa-browser" ]; then + cp -f "$REPO/jerboa-browser" "$STAGE/bin/jerboa-browser" + chmod +x "$STAGE/bin/jerboa-browser" + HAVE_BIN=1 + fi +else + echo "==> Chez/Jerboa not found at $SCHEME — skipping the native binary" + echo " (unpack, then run 'make binary' with the Jerboa toolchain installed)" +fi cat > "$STAGE/README.txt" <<EOF Jerboa Browser $VERSION (linux-$ARCH) @@ -57,14 +63,18 @@ Prerequisite: Qt 6 WebEngine runtime installed system-wide equivalent). The Chromium sandbox is left enabled. Layout: - lib/libjerboa_browser.so browser backend (FFI target) - scheme/ the (browser) Jerboa module - bin/jerboa-browser launcher (sets JERBOA_BROWSER_LIB + LD_LIBRARY_PATH) + lib/libjerboa_browser.so browser backend (the native binary dlopens this) + scheme/ the (browser) Jerboa module + entry points + build-binary.ss, Makefile build the native binary: \`make binary\` +$([ "$HAVE_BIN" = 1 ] && echo " bin/jerboa-browser self-contained native binary (repl | test | run <file>)" || echo " bin/ (empty — build with 'make binary'; needs Chez + Jerboa)") + +The native jerboa-browser binary embeds Chez Scheme, the boot image, and the +(browser) library; only libjerboa_browser.so + system Qt 6 are external. For a no-prerequisite AppImage, run this tree through linuxdeployqt. EOF echo "==> creating $TARBALL" tar -C "$BUILD" -czf "$TARBALL" "$(basename "$STAGE")" -echo " $TARBALL" +echo " $TARBALL (native binary: $([ "$HAVE_BIN" = 1 ] && echo yes || echo no))" du -sh "$TARBALL" new file mode 100644 --- /dev/null +++ b/scheme/browser-main.ss @@ -0,0 +1,74 @@ +#!chezscheme +;;; browser-main.ss — entry point for the compiled `jerboa-browser` binary. +;;; +;;; This is the program that build-binary.ss compiles + whole-program-optimizes +;;; into a self-contained native executable (Chez kernel + boot files + the +;;; (browser) library all embedded). At runtime only the native Qt backend +;;; (libjerboa_browser.{dylib,so}) and a system Qt 6 WebEngine are needed; the +;;; Chromium sandbox is left on. +;;; +;;; jerboa-browser REPL with (browser) preloaded +;;; jerboa-browser repl +;;; jerboa-browser test run the (browser) test suite +;;; jerboa-browser run FILE run a Jerboa script with (browser) available + +(import (chezscheme) (browser) (std repl)) + +;; Directory of argv[0], or "." when it has no slash / is unavailable. +(define (exe-dir) + (let ((cl (command-line))) + (if (pair? cl) + (let* ((p (car cl)) + (i (let loop ((i (- (string-length p) 1))) + (cond ((< i 0) #f) + ((char=? (string-ref p i) #\/) i) + (else (loop (- i 1))))))) + (if i (substring p 0 i) ".")) + "."))) + +;; First existing candidate path for a bundled file `name`, else #f. Covers the +;; in-tree run (cwd = repo root) and the relocated tarball (bin/ + scheme/). +(define (locate name) + (let* ((d (exe-dir)) + (cands (list (string-append d "/../scheme/" name) + (string-append d "/scheme/" name) + (string-append d "/" name) + (string-append "scheme/" name)))) + (let loop ((cs cands)) + (cond ((null? cs) #f) + ((file-exists? (car cs)) (car cs)) + (else (loop (cdr cs))))))) + +(define (run-repl) + ;; jerboa-repl evaluates in (interaction-environment); import (browser) into it + ;; so browser-* is callable at the prompt with no further imports. + (eval '(import (browser)) (interaction-environment)) + (display ";; (browser) loaded — call (browser-init) then (browser-open-context ...)\n") + (jerboa-repl)) + +(define (usage port code) + (display "usage: jerboa-browser [repl|test|run <file>]\n" port) + (exit code)) + +(define (main) + (let ((args (cdr (command-line)))) + (cond + ((or (null? args) (string=? (car args) "repl")) + (run-repl)) + ((string=? (car args) "test") + (let ((f (locate "browser-test.ss"))) + (if f + (load f) ; the suite calls (exit) with its own code + (begin (display "jerboa-browser: test suite (browser-test.ss) not found\n" + (current-error-port)) + (exit 1))))) + ((string=? (car args) "run") + (if (pair? (cdr args)) + (load (cadr args)) + (usage (current-error-port) 1))) + ((member (car args) '("-h" "--help" "help")) + (usage (current-output-port) 0)) + (else + (usage (current-error-port) 1))))) + +(main) new file mode 100644 --- /dev/null +++ b/scheme/browser-repl.ss @@ -0,0 +1,9 @@ +;;; browser-repl.ss — REPL bootstrap for the `jerboa-browser` CLI. +;;; +;;; Run as an R6RS top-level program (scheme --program): preload the Jerboa +;;; prelude and the (browser) wrapper, print a one-line hint, then hand control +;;; to the Jerboa REPL. The `jerboa-browser` binary sets --libdirs and +;;; JERBOA_BROWSER_LIB before exec'ing Chez on this file. +(import (jerboa prelude) (browser) (std repl)) +(displayln ";; (browser) loaded — call (browser-init) then (browser-open-context ...)") +(jerboa-repl) --- a/scheme/browser.ss +++ b/scheme/browser.ss @@ -71,9 +71,43 @@ ;; This MUST precede the define-c-lambda binds: foreign-procedure resolution ;; is eager in Chez, so the symbols have to be loaded first. Binding the load ;; to a def keeps it a definition (library bodies evaluate defs in order). + ;; Native backend extension: .dylib on macOS, .so elsewhere. + (def (jwb-lib-ext) + (let ((mt (symbol->string (machine-type)))) + (if (or (string=? mt "ta6osx") (string=? mt "a6osx") + (string=? mt "tarm64osx") (string=? mt "arm64osx")) + "dylib" "so"))) + + ;; Directory of argv[0] ("." when it has no slash / is unavailable). + (def (jwb-exe-dir) + (let ((cl (command-line))) + (if (pair? cl) + (let* ((p (car cl)) + (i (let loop ((i (- (string-length p) 1))) + (cond ((< i 0) #f) + ((char=? (string-ref p i) #\/) i) + (else (loop (- i 1))))))) + (if i (substring p 0 i) ".")) + "."))) + + ;; Find libjerboa_browser next to the binary (relocatable tarball: bin/ + lib/), + ;; in the in-tree build dir, else fall back to the first candidate for a clear + ;; error. JERBOA_BROWSER_LIB overrides all of this. + (def (jwb-find-lib) + (let* ((name (string-append "libjerboa_browser." (jwb-lib-ext))) + (d (jwb-exe-dir)) + (cands (list (string-append d "/../lib/" name) + (string-append d "/" name) + (string-append d "/../qt-webengine/build/" name) + (string-append "qt-webengine/build/" name)))) + (let loop ((cs cands)) + (cond ((null? cs) (car cands)) + ((file-exists? (car cs)) (car cs)) + (else (loop (cdr cs))))))) + (def browser-lib-path (or (getenv "JERBOA_BROWSER_LIB") - "/Users/user/mine/jerboa-browser/qt-webengine/build/libjerboa_browser.dylib")) + (jwb-find-lib))) (def _loaded (begin