build: add linux-amd64 + freebsd-amd64 cross-compile from macOS
ober
608216ee00aa5d9cfea0d8cc29e8a5b4dc061d76
--- a/.gitignore +++ b/.gitignore @@ -2,5 +2,24 @@ *.wpo *.boot *.o +*.tarm64osx !jerboa-aws-main.c /jerboa-aws +/jerboa_aws_program.h +/jerboa_aws_petite_boot.h +/jerboa_aws_scheme_boot.h +/jerboa_aws_app_boot.h + +# Cross-compile artifacts +/jerboa-aws-linux-amd64 +/jerboa-aws-linux-amd64-main.c +/jerboa-aws-linux-amd64.wp.so +/jerboa-aws-freebsd-amd64 +/jerboa-aws-freebsd-amd64-main.c +/jerboa-aws-freebsd-amd64.wp.so +/petite_boot.h +/scheme_boot.h +/jaws_program.h + +.claude/ +.DS_Store --- a/Makefile +++ b/Makefile @@ -7,7 +7,14 @@ CHEZSSL ?= $(HOME)/mine/chez-ssl/src LIBDIRS = lib:$(JERBOA):$(GHERKIN):$(CHEZHTTPS):$(CHEZSSL) COMPILE = $(SCHEME) -q --libdirs $(LIBDIRS) --compile-imported-libraries -.PHONY: all compile binary build run run-pssm clean help test pssm +# Cross-compile uses libdirs minus gherkin (unused by jerboa-aws code). +XC_LIBDIRS = lib:$(JERBOA):$(CHEZHTTPS):$(CHEZSSL) +MUSL_OPENSSL_DIR ?= $(HOME)/musl-openssl +FREEBSD_SYSROOT ?= $(HOME)/freebsd-sysroot + +.PHONY: all compile binary build run run-pssm clean help test pssm \ + linux linux-amd64 freebsd freebsd-amd64 \ + jerboa-aws-linux-amd64 jerboa-aws-freebsd-amd64 all: compile @@ -45,13 +52,63 @@ clean: rm -f jerboa_aws_scheme_boot.h jerboa_aws_app_boot.h rm -f jerboa-aws-all.so aws.so aws.wpo jerboa-aws.boot rm -f pssm + rm -f jerboa-aws-linux-amd64 jerboa-aws-linux-amd64-main.c jerboa-aws-linux-amd64.wp.so + rm -f jerboa-aws-freebsd-amd64 jerboa-aws-freebsd-amd64-main.c jerboa-aws-freebsd-amd64.wp.so + rm -f petite_boot.h scheme_boot.h jaws_program.h + +# ── Cross-compile targets ────────────────────────────────────────────────── +# Linux x86_64 musl (static): bundles chez-ssl/chez-https + AWS code into a +# single ELF. Requires: +# - $(JERBOA_HOME)/.chez-cross-ta6le (cross-built Chez install) +# - x86_64-linux-musl-gcc on PATH +# - $(MUSL_OPENSSL_DIR)/usr/{lib,include} (Alpine static libssl.a + libcrypto.a) +# - $(CHEZSSL)/../chez_ssl_shim-linux-musl.o +# +linux: linux-amd64 +linux-amd64: jerboa-aws-linux-amd64 +jerboa-aws-linux-amd64: + @command -v x86_64-linux-musl-gcc >/dev/null 2>&1 || { \ + echo "Error: x86_64-linux-musl-gcc not on PATH. brew install FiloSottile/musl-cross/musl-cross"; exit 1; } + @test -d $(JERBOA_HOME)/.chez-cross-ta6le || { \ + echo "Error: $(JERBOA_HOME)/.chez-cross-ta6le missing. cd $(JERBOA_HOME) && make binary"; exit 1; } + @test -f $(MUSL_OPENSSL_DIR)/usr/lib/libssl.a || { \ + echo "Error: $(MUSL_OPENSSL_DIR)/usr/lib/libssl.a missing."; exit 1; } + @test -f $(CHEZSSL)/../chez_ssl_shim-linux-musl.o || { \ + echo "Error: chez_ssl_shim-linux-musl.o missing. cd $(CHEZSSL)/.. && make chez_ssl_shim-linux-musl.o"; exit 1; } + JERBOA_HOME=$(JERBOA_HOME) MUSL_OPENSSL_DIR=$(MUSL_OPENSSL_DIR) \ + $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jaws-cross.ss + +# FreeBSD 14 amd64 (dynamic): libssl/libcrypto resolved from FreeBSD base at +# runtime. Requires: +# - $(JERBOA_HOME)/.chez-cross-ta6fb (cross-built Chez install) +# - x86_64-unknown-freebsd14-clang wrapper on PATH +# - $(FREEBSD_SYSROOT) (extracted base.txz) +# - $(CHEZSSL)/../chez_ssl_shim-freebsd-amd64.o +# +freebsd: freebsd-amd64 +freebsd-amd64: jerboa-aws-freebsd-amd64 +jerboa-aws-freebsd-amd64: + @command -v x86_64-unknown-freebsd14-clang >/dev/null 2>&1 || { \ + echo "Error: x86_64-unknown-freebsd14-clang wrapper not on PATH. See ~/.local/bin"; exit 1; } + @test -d $(JERBOA_HOME)/.chez-cross-ta6fb || { \ + echo "Error: $(JERBOA_HOME)/.chez-cross-ta6fb missing. cd $(JERBOA_HOME) && make binary"; exit 1; } + @test -d $(FREEBSD_SYSROOT) || { \ + echo "Error: $(FREEBSD_SYSROOT) missing. Extract base.txz to ~/freebsd-sysroot"; exit 1; } + @test -f $(CHEZSSL)/../chez_ssl_shim-freebsd-amd64.o || { \ + echo "Error: chez_ssl_shim-freebsd-amd64.o missing. cd $(CHEZSSL)/.. && make chez_ssl_shim-freebsd-amd64.o"; exit 1; } + JERBOA_HOME=$(JERBOA_HOME) FREEBSD_SYSROOT=$(FREEBSD_SYSROOT) \ + $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jaws-freebsd-cross.ss help: @echo "Targets:" - @echo " all - Compile all modules" - @echo " build - Build standalone binary (./jerboa-aws)" - @echo " pssm - Build pssm wrapper script (./pssm)" - @echo " run - Run interpreted (ARGS='ec2 describe-instances')" - @echo " run-pssm - Run pssm interpreted (ARGS=\"'web-*' uptime\")" - @echo " test - Run tests" - @echo " clean - Remove build artifacts" + @echo " all - Compile all modules" + @echo " build - Build standalone binary (./jerboa-aws)" + @echo " pssm - Build pssm wrapper script (./pssm)" + @echo " run - Run interpreted (ARGS='ec2 describe-instances')" + @echo " run-pssm - Run pssm interpreted (ARGS=\"'web-*' uptime\")" + @echo " test - Run tests" + @echo " clean - Remove build artifacts" + @echo "" + @echo "Cross-compile from macOS:" + @echo " linux-amd64 - Build jerboa-aws-linux-amd64 (static musl)" + @echo " freebsd-amd64 - Build jerboa-aws-freebsd-amd64 (dynamic)" --- a/build-binary.ss +++ b/build-binary.ss @@ -28,22 +28,24 @@ (printf " ~a: ~a bytes~n" output-path size))) ;; --- Locate Chez install directory --- +(define (find-csv-dir lib-dir mt) + (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))) + (let ((d (format "~a/~a/~a" lib-dir (car dirs) mt))) + (and (file-exists? (format "~a/main.o" d)) d))) + (else (lp (cdr dirs)))))) + (define chez-dir (or (getenv "CHEZ_DIR") - (let* ((mt (symbol->string (machine-type))) + (let* ((mt (symbol->string (machine-type))) (home (getenv "HOME")) - (lib-dir (format "~a/.local/lib" home)) - (csv-dir - (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-dir - (file-exists? (format "~a/main.o" csv-dir)) - csv-dir)))) + (jerboa-home (or (getenv "JERBOA_HOME") + (and home (format "~a/mine/jerboa" home))))) + (or (and jerboa-home (find-csv-dir (format "~a/.chez/lib" jerboa-home) mt)) + (and home (find-csv-dir (format "~a/.local/lib" home) mt)))))) (unless chez-dir (display "Error: Cannot find Chez install dir. Set CHEZ_DIR.\n") new file mode 100644 --- /dev/null +++ b/build-jaws-cross.ss @@ -0,0 +1,268 @@ +#!chezscheme +;;; build-jaws-cross.ss — Cross-compile jerboa-aws from macOS arm64 to Linux x86_64 musl. +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa scheme -q --libdirs <libs> \ +;;; --script build-jaws-cross.ss +;;; +;;; Uses: +;;; - $JERBOA_HOME/.chez-cross-ta6le/ — cross-built Chez install (ta6le) +;;; - $JERBOA_HOME/build/chez/xc-ta6le/s/xpatch — host -> ta6le emit-mode loader +;;; - x86_64-linux-musl-gcc — C compile + final static link +;;; - ~/mine/chez-ssl/chez_ssl_shim-linux-musl.o (built by chez-ssl Makefile) +;;; - ~/musl-openssl/usr/{lib,include} — Alpine static libssl.a + libcrypto.a +;;; +;;; Produces: jerboa-aws-linux-amd64 (static Linux x86_64 ELF). +;;; +;;; Architecture: WPO-as-program (single .wp.so loaded via Sscheme_program). The +;;; chez-ssl C surface (chez_ssl_*/chez_tcp_*) is statically linked from +;;; chez_ssl_shim.o and registered via Sforeign_symbol so a -static binary +;;; resolves it without dlsym. + +(import (chezscheme)) + +;; ── Params ────────────────────────────────────────────────────────────────── +(define jerboa-home + (or (getenv "JERBOA_HOME") "/Users/user/mine/jerboa")) + +(define cross-prefix (format "~a/.chez-cross-ta6le" jerboa-home)) +(define xpatch (format "~a/build/chez/xc-ta6le/s/xpatch" jerboa-home)) +(define cross-cc (or (getenv "CROSS_CC") "x86_64-linux-musl-gcc")) + +(define output "jerboa-aws-linux-amd64") +(define entry-script "aws.ss") + +(define aws-repo (current-directory)) +(define chez-ssl-repo + (or (getenv "CHEZSSL_REPO") (format "~a/mine/chez-ssl" (getenv "HOME")))) +(define chez-https-repo + (or (getenv "CHEZHTTPS_REPO") (format "~a/mine/chez-https" (getenv "HOME")))) +(define chez-ssl-shim-o + (or (getenv "CHEZ_SSL_SHIM_LINUX_O") + (format "~a/chez_ssl_shim-linux-musl.o" chez-ssl-repo))) +(define musl-openssl-dir + (or (getenv "MUSL_OPENSSL_DIR") (format "~a/musl-openssl" (getenv "HOME")))) + +(define cross-csv-dir + (let ([lib (format "~a/lib" cross-prefix)]) + (unless (file-directory? lib) + (error 'build-jaws-cross "cross prefix lib dir missing" lib)) + (let* ([entries (directory-list lib)] + [csvs (filter (lambda (e) + (and (>= (string-length e) 3) + (string=? (substring e 0 3) "csv"))) + entries)]) + (when (null? csvs) + (error 'build-jaws-cross "no csv* in cross lib" lib)) + (format "~a/~a/ta6le" lib (car csvs))))) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jaws-cross "missing file" p))) + +(require-file xpatch) +(require-file (format "~a/libkernel.a" cross-csv-dir)) +(require-file (format "~a/scheme.h" cross-csv-dir)) +(require-file (format "~a/petite.boot" cross-csv-dir)) +(require-file (format "~a/scheme.boot" cross-csv-dir)) +(require-file entry-script) +(require-file chez-ssl-shim-o) +(require-file (format "~a/usr/lib/libssl.a" musl-openssl-dir)) +(require-file (format "~a/usr/lib/libcrypto.a" musl-openssl-dir)) + +(printf "==> build-jaws-cross~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " cross csv-dir: ~a~n" cross-csv-dir) +(printf " xpatch: ~a~n" xpatch) +(printf " cross-cc: ~a~n" cross-cc) +(printf " chez-ssl shim: ~a~n" chez-ssl-shim-o) +(printf " musl openssl: ~a~n" musl-openssl-dir) +(printf " output: ~a~n" output) +(printf "~n") + +;; ── Stage 1: load xpatch (target=ta6le emit mode) ────────────────────────── +;; xpatch reset library-directories; restore + extend with our source libdirs. +(define orig-libdirs (library-directories)) +(printf "==> [1/6] loading xpatch (compiler -> ta6le emit mode)~n") +(load xpatch) +(library-directories + (append + (list (cons (format "~a/lib" aws-repo) (format "~a/lib" aws-repo)) + (cons (format "~a/lib" jerboa-home) (format "~a/lib" jerboa-home)) + (cons (format "~a/src" chez-https-repo) (format "~a/src" chez-https-repo)) + (cons (format "~a/src" chez-ssl-repo) (format "~a/src" chez-ssl-repo))) + orig-libdirs)) + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +;; ── Stage 2: compile-program aws.ss ──────────────────────────────────────── +(printf "==> [2/6] compile-program ~a~n" entry-script) +(compile-program entry-script) + +(define entry-wpo + (let ([n (string-length entry-script)]) + (string-append (substring entry-script 0 (- n 3)) ".wpo"))) + +;; ── Stage 3: compile-whole-program → wpo .so ─────────────────────────────── +(define wpo-output (string-append output ".wp.so")) +(printf "==> [3/6] compile-whole-program ~a -> ~a~n" entry-wpo wpo-output) +(compile-whole-program entry-wpo wpo-output #t) + +;; ── Stage 4: embed boot files + program as C arrays ──────────────────────── +(define (embed-as-c-array in-path var-name out-path) + (let* ([bv (call-with-port (open-file-input-port in-path) get-bytevector-all)] + [n (bytevector-length bv)]) + (call-with-port (open-file-output-port out-path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + (lambda (out) + (display (format "static const unsigned char ~a[] = {\n" var-name) out) + (let loop ([i 0]) + (when (< i n) + (display (format "0x~2,'0x," (bytevector-u8-ref bv i)) out) + (when (= (mod (+ i 1) 16) 0) (newline out)) + (loop (+ i 1)))) + (when (positive? n) (newline out)) + (display "};\n" out) + (display (format "static const unsigned int ~a_size = sizeof(~a);\n" + var-name var-name) + out))) + (printf " embed ~a (~a bytes) -> ~a~n" in-path n out-path))) + +(printf "==> [4/6] embed boot files + program as C arrays~n") +(embed-as-c-array (format "~a/petite.boot" cross-csv-dir) "petite_boot" "petite_boot.h") +(embed-as-c-array (format "~a/scheme.boot" cross-csv-dir) "scheme_boot" "scheme_boot.h") +(embed-as-c-array wpo-output "jaws_program" "jaws_program.h") + +;; ── Stage 5: generate main.c ─────────────────────────────────────────────── +(define main-c-path (string-append output "-main.c")) + +;; chez-ssl/chez_ssl_shim.c symbol surface — must match the foreign-procedure +;; declarations in chez-ssl.sls. +(define chez-ssl-symbols + '("chez_ssl_init" "chez_ssl_cleanup" + "chez_ssl_connect" "chez_ssl_write" "chez_ssl_read" + "chez_ssl_read_all" "chez_ssl_free_buf" "chez_ssl_close" + "chez_ssl_memcpy" + "chez_ssl_server_ctx" "chez_ssl_server_accept" "chez_ssl_server_ctx_free" + "chez_tcp_listen" "chez_tcp_accept" + "chez_tcp_connect" "chez_tcp_close" + "chez_tcp_read" "chez_tcp_write" "chez_tcp_read_all" + "chez_tcp_set_timeout" + "chez_tcp_conn_wrap" + "chez_conn_write" "chez_conn_read")) + +(printf "==> [5/6] generate ~a (~a chez-ssl symbols)~n" + main-c-path (length chez-ssl-symbols)) + +(define (write-main-c) + (call-with-port + (open-file-output-port main-c-path + (file-options no-fail) (buffer-mode block) (native-transcoder)) + (lambda (out) + (display "/* Auto-generated by build-jaws-cross.ss. Do not edit. */\n" out) + (display "#define _GNU_SOURCE\n" out) + (display "#include <stdio.h>\n" out) + (display "#include <stdlib.h>\n" out) + (display "#include <string.h>\n" out) + (display "#include <unistd.h>\n" out) + (display "#include <sys/mman.h>\n" out) + (display "#include <fcntl.h>\n" out) + (display "#include \"scheme.h\"\n" out) + (display "#include \"petite_boot.h\"\n" out) + (display "#include \"scheme_boot.h\"\n" out) + (display "#include \"jaws_program.h\"\n\n" out) + + ;; dlopen stubs (musl-static has no dlopen) + (display "/* dlopen stubs for static linking. Returning non-NULL for any\n" out) + (display " * named load makes (load-shared-object \"...\") succeed silently;\n" out) + (display " * foreign-procedure lookup uses Sforeign_symbol-registered entries. */\n" out) + (display "void *dlopen(const char *f, int flags) { (void)f; (void)flags; return (void*)1; }\n" out) + (display "void *dlsym(void *h, const char *s) { (void)h; (void)s; return NULL; }\n" out) + (display "int dlclose(void *h) { (void)h; return 0; }\n" out) + (display "static char dlerror_msg[] = \"static binary: dlopen stubbed\";\n" out) + (display "char *dlerror(void) { return dlerror_msg; }\n\n" out) + + ;; extern decls for chez-ssl symbols + (display "/* chez_ssl_shim.o exports — registered with Sforeign_symbol below. */\n" out) + (for-each + (lambda (sym) (display (format "extern void ~a(void);\n" sym) out)) + chez-ssl-symbols) + (newline out) + + (display "int main(int argc, char *argv[]) {\n" out) + (display " /* Argv passthrough via env vars (matches native jerboa-aws-main.c). */\n" out) + (display " char countbuf[32];\n" out) + (display " snprintf(countbuf, sizeof(countbuf), \"%d\", argc - 1);\n" out) + (display " setenv(\"AWS_ARGC\", countbuf, 1);\n" out) + (display " for (int i = 1; i < argc; i++) {\n" out) + (display " char name[32];\n" out) + (display " snprintf(name, sizeof(name), \"AWS_ARG%d\", i - 1);\n" out) + (display " setenv(name, argv[i], 1);\n" out) + (display " }\n\n" out) + + ;; Memfd-backed program path + (display " int fd = memfd_create(\"jerboa-aws-program\", MFD_CLOEXEC);\n" out) + (display " if (fd < 0) { perror(\"memfd_create\"); return 1; }\n" out) + (display " if (write(fd, jaws_program, jaws_program_size) != (ssize_t)jaws_program_size) {\n" out) + (display " perror(\"write memfd\"); close(fd); return 1;\n" out) + (display " }\n" out) + (display " char prog_path[64];\n" out) + (display " snprintf(prog_path, sizeof(prog_path), \"/proc/self/fd/%d\", fd);\n\n" out) + + (display " Sscheme_init(NULL);\n" out) + (display " Sregister_boot_file_bytes(\"petite\", (void*)petite_boot, petite_boot_size);\n" out) + (display " Sregister_boot_file_bytes(\"scheme\", (void*)scheme_boot, scheme_boot_size);\n\n" out) + + (display " /* Register chez_ssl_shim symbols so a -static binary resolves\n" out) + (display " * (foreign-procedure \"chez_ssl_*\" ...) without dlsym. */\n" out) + (for-each + (lambda (sym) + (display (format " Sforeign_symbol(\"~a\", (void*)~a);\n" sym sym) out)) + chez-ssl-symbols) + (newline out) + + (display " Sbuild_heap(NULL, NULL);\n" out) + (display " const char *script_args[] = { argv[0] };\n" out) + (display " int status = Sscheme_program(prog_path, 1, script_args);\n\n" out) + (display " close(fd);\n" out) + (display " Sscheme_deinit();\n" out) + (display " return status;\n" out) + (display "}\n" out)))) +(write-main-c) + +;; ── Stage 6: compile + link with cross-cc ────────────────────────────────── +(printf "==> [6/6] compile + link with ~a~n" cross-cc) + +(define link-cmd + (format + (string-append + "~a -static -O2 " + "-I~a -I~a/usr/include " + "~a ~a " + "-Wl,--start-group " + "~a/libkernel.a ~a/libz.a ~a/liblz4.a " + "~a/usr/lib/libssl.a ~a/usr/lib/libcrypto.a " + "-lpthread -lm -ldl " + "-Wl,--end-group " + "-o ~a") + cross-cc + cross-csv-dir musl-openssl-dir + main-c-path chez-ssl-shim-o + cross-csv-dir cross-csv-dir cross-csv-dir + musl-openssl-dir musl-openssl-dir + output)) + +(printf " ~a~n" link-cmd) +(unless (zero? (system link-cmd)) + (error 'build-jaws-cross "link failed")) + +(printf "~n==> done: ~a~n" output) +(printf " file size: ~a bytes~n" + (call-with-port (open-file-input-port output) + (lambda (p) + (let loop ([n 0]) + (let ([b (get-u8 p)]) + (if (eof-object? b) n (loop (+ n 1)))))))) new file mode 100644 --- /dev/null +++ b/build-jaws-freebsd-cross.ss @@ -0,0 +1,250 @@ +#!chezscheme +;;; build-jaws-freebsd-cross.ss — Cross-compile jerboa-aws from macOS to FreeBSD 14 amd64. +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa scheme -q --libdirs <libs> \ +;;; --script build-jaws-freebsd-cross.ss +;;; +;;; Uses: +;;; - $JERBOA_HOME/.chez-cross-ta6fb/ — cross-built Chez install (ta6fb) +;;; - $JERBOA_HOME/build/chez/xc-ta6fb/s/xpatch — host -> ta6fb emit-mode loader +;;; - x86_64-unknown-freebsd14-clang — wrapper with sysroot + lld + libpath +;;; - ~/mine/chez-ssl/chez_ssl_shim-freebsd-amd64.o +;;; - ~/freebsd-sysroot/usr/lib/{libssl,libcrypto,...}.so (dynamic link) +;;; +;;; Produces: jerboa-aws-freebsd-amd64 (dynamic FreeBSD x86_64 ELF). Dynamic +;;; because FreeBSD libc.so uses symbol versioning that libc.a/libc_nonshared.a +;;; can't satisfy. libssl/libcrypto resolved from FreeBSD base at run time. + +(import (chezscheme)) + +;; ── Params ────────────────────────────────────────────────────────────────── +(define jerboa-home + (or (getenv "JERBOA_HOME") "/Users/user/mine/jerboa")) + +(define cross-prefix (format "~a/.chez-cross-ta6fb" jerboa-home)) +(define xpatch (format "~a/build/chez/xc-ta6fb/s/xpatch" jerboa-home)) +(define cross-cc (or (getenv "CROSS_CC") "x86_64-unknown-freebsd14-clang")) + +(define output "jerboa-aws-freebsd-amd64") +(define entry-script "aws.ss") + +(define aws-repo (current-directory)) +(define chez-ssl-repo + (or (getenv "CHEZSSL_REPO") (format "~a/mine/chez-ssl" (getenv "HOME")))) +(define chez-https-repo + (or (getenv "CHEZHTTPS_REPO") (format "~a/mine/chez-https" (getenv "HOME")))) +(define chez-ssl-shim-o + (or (getenv "CHEZ_SSL_SHIM_FREEBSD_O") + (format "~a/chez_ssl_shim-freebsd-amd64.o" chez-ssl-repo))) +(define freebsd-sysroot + (or (getenv "FREEBSD_SYSROOT") (format "~a/freebsd-sysroot" (getenv "HOME")))) + +(define cross-csv-dir + (let ([lib (format "~a/lib" cross-prefix)]) + (unless (file-directory? lib) + (error 'build-jaws-freebsd-cross "cross prefix lib dir missing" lib)) + (let* ([entries (directory-list lib)] + [csvs (filter (lambda (e) + (and (>= (string-length e) 3) + (string=? (substring e 0 3) "csv"))) + entries)]) + (when (null? csvs) + (error 'build-jaws-freebsd-cross "no csv* in cross lib" lib)) + (format "~a/~a/ta6fb" lib (car csvs))))) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jaws-freebsd-cross "missing file" p))) + +(require-file xpatch) +(require-file (format "~a/libkernel.a" cross-csv-dir)) +(require-file (format "~a/scheme.h" cross-csv-dir)) +(require-file (format "~a/petite.boot" cross-csv-dir)) +(require-file (format "~a/scheme.boot" cross-csv-dir)) +(require-file entry-script) +(require-file chez-ssl-shim-o) +(require-file (format "~a/usr/lib/libssl.so" freebsd-sysroot)) + +(printf "==> build-jaws-freebsd-cross~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " cross csv-dir: ~a~n" cross-csv-dir) +(printf " xpatch: ~a~n" xpatch) +(printf " cross-cc: ~a~n" cross-cc) +(printf " chez-ssl shim: ~a~n" chez-ssl-shim-o) +(printf " freebsd sysroot: ~a~n" freebsd-sysroot) +(printf " output: ~a~n" output) +(printf "~n") + +;; ── Stage 1: load xpatch (target=ta6fb emit mode) ────────────────────────── +(define orig-libdirs (library-directories)) +(printf "==> [1/6] loading xpatch (compiler -> ta6fb emit mode)~n") +(load xpatch) +(library-directories + (append + (list (cons (format "~a/lib" aws-repo) (format "~a/lib" aws-repo)) + (cons (format "~a/lib" jerboa-home) (format "~a/lib" jerboa-home)) + (cons (format "~a/src" chez-https-repo) (format "~a/src" chez-https-repo)) + (cons (format "~a/src" chez-ssl-repo) (format "~a/src" chez-ssl-repo))) + orig-libdirs)) + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +;; ── Stage 2: compile-program aws.ss ──────────────────────────────────────── +(printf "==> [2/6] compile-program ~a~n" entry-script) +(compile-program entry-script) + +(define entry-wpo + (let ([n (string-length entry-script)]) + (string-append (substring entry-script 0 (- n 3)) ".wpo"))) + +;; ── Stage 3: compile-whole-program → wpo .so ─────────────────────────────── +(define wpo-output (string-append output ".wp.so")) +(printf "==> [3/6] compile-whole-program ~a -> ~a~n" entry-wpo wpo-output) +(compile-whole-program entry-wpo wpo-output #t) + +;; ── Stage 4: embed boot files + program as C arrays ──────────────────────── +(define (embed-as-c-array in-path var-name out-path) + (let* ([bv (call-with-port (open-file-input-port in-path) get-bytevector-all)] + [n (bytevector-length bv)]) + (call-with-port (open-file-output-port out-path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + (lambda (out) + (display (format "static const unsigned char ~a[] = {\n" var-name) out) + (let loop ([i 0]) + (when (< i n) + (display (format "0x~2,'0x," (bytevector-u8-ref bv i)) out) + (when (= (mod (+ i 1) 16) 0) (newline out)) + (loop (+ i 1)))) + (when (positive? n) (newline out)) + (display "};\n" out) + (display (format "static const unsigned int ~a_size = sizeof(~a);\n" + var-name var-name) + out))) + (printf " embed ~a (~a bytes) -> ~a~n" in-path n out-path))) + +(printf "==> [4/6] embed boot files + program as C arrays~n") +(embed-as-c-array (format "~a/petite.boot" cross-csv-dir) "petite_boot" "petite_boot.h") +(embed-as-c-array (format "~a/scheme.boot" cross-csv-dir) "scheme_boot" "scheme_boot.h") +(embed-as-c-array wpo-output "jaws_program" "jaws_program.h") + +;; ── Stage 5: generate main.c ─────────────────────────────────────────────── +(define main-c-path (string-append output "-main.c")) + +(define chez-ssl-symbols + '("chez_ssl_init" "chez_ssl_cleanup" + "chez_ssl_connect" "chez_ssl_write" "chez_ssl_read" + "chez_ssl_read_all" "chez_ssl_free_buf" "chez_ssl_close" + "chez_ssl_memcpy" + "chez_ssl_server_ctx" "chez_ssl_server_accept" "chez_ssl_server_ctx_free" + "chez_tcp_listen" "chez_tcp_accept" + "chez_tcp_connect" "chez_tcp_close" + "chez_tcp_read" "chez_tcp_write" "chez_tcp_read_all" + "chez_tcp_set_timeout" + "chez_tcp_conn_wrap" + "chez_conn_write" "chez_conn_read")) + +(printf "==> [5/6] generate ~a (~a chez-ssl symbols)~n" + main-c-path (length chez-ssl-symbols)) + +(define (write-main-c) + (call-with-port + (open-file-output-port main-c-path + (file-options no-fail) (buffer-mode block) (native-transcoder)) + (lambda (out) + (display "/* Auto-generated by build-jaws-freebsd-cross.ss. Do not edit. */\n" out) + (display "#include <stdio.h>\n" out) + (display "#include <stdlib.h>\n" out) + (display "#include <string.h>\n" out) + (display "#include <unistd.h>\n" out) + (display "#include <fcntl.h>\n" out) + (display "#include <sys/types.h>\n" out) + (display "#include <sys/sysctl.h>\n" out) + (display "#include \"scheme.h\"\n" out) + (display "#include \"petite_boot.h\"\n" out) + (display "#include \"scheme_boot.h\"\n" out) + (display "#include \"jaws_program.h\"\n\n" out) + + ;; FreeBSD: __errno_location alias (glibc/musl name) -> __error (FreeBSD). + (display "/* glibc/musl name for errno location -> FreeBSD __error. */\n" out) + (display "extern int *__error(void);\n" out) + (display "int *__errno_location(void) { return __error(); }\n\n" out) + + ;; extern decls for chez-ssl symbols + (display "/* chez_ssl_shim.o exports — registered with Sforeign_symbol below. */\n" out) + (for-each + (lambda (sym) (display (format "extern void ~a(void);\n" sym) out)) + chez-ssl-symbols) + (newline out) + + (display "int main(int argc, char *argv[]) {\n" out) + (display " /* Argv passthrough via env vars (matches native jerboa-aws-main.c). */\n" out) + (display " char countbuf[32];\n" out) + (display " snprintf(countbuf, sizeof(countbuf), \"%d\", argc - 1);\n" out) + (display " setenv(\"AWS_ARGC\", countbuf, 1);\n" out) + (display " for (int i = 1; i < argc; i++) {\n" out) + (display " char name[32];\n" out) + (display " snprintf(name, sizeof(name), \"AWS_ARG%d\", i - 1);\n" out) + (display " setenv(name, argv[i], 1);\n" out) + (display " }\n\n" out) + + ;; FreeBSD: no /proc/self/fd/N, write program to a tmpfile instead. + (display " /* FreeBSD has no /proc/self/fd/N (without fdescfs mounted), so write the\n" out) + (display " * embedded program to a tmpfile that we unlink after Sscheme_program loads it. */\n" out) + (display " char tmpl[] = \"/tmp/jerboa-aws-XXXXXX\";\n" out) + (display " int fd = mkstemp(tmpl);\n" out) + (display " if (fd < 0) { perror(\"mkstemp\"); return 1; }\n" out) + (display " if (write(fd, jaws_program, jaws_program_size) != (ssize_t)jaws_program_size) {\n" out) + (display " perror(\"write tmpfile\"); close(fd); unlink(tmpl); return 1;\n" out) + (display " }\n" out) + (display " fsync(fd);\n\n" out) + + (display " Sscheme_init(NULL);\n" out) + (display " Sregister_boot_file_bytes(\"petite\", (void*)petite_boot, petite_boot_size);\n" out) + (display " Sregister_boot_file_bytes(\"scheme\", (void*)scheme_boot, scheme_boot_size);\n\n" out) + + (display " /* Register chez_ssl_shim symbols so the WPO resolves them at runtime. */\n" out) + (for-each + (lambda (sym) + (display (format " Sforeign_symbol(\"~a\", (void*)~a);\n" sym sym) out)) + chez-ssl-symbols) + (newline out) + + (display " Sbuild_heap(NULL, NULL);\n" out) + (display " const char *script_args[] = { argv[0] };\n" out) + (display " int status = Sscheme_program(tmpl, 1, script_args);\n\n" out) + (display " close(fd);\n" out) + (display " unlink(tmpl);\n" out) + (display " Sscheme_deinit();\n" out) + (display " return status;\n" out) + (display "}\n" out)))) +(write-main-c) + +;; ── Stage 6: compile + link with cross-cc ────────────────────────────────── +;; FreeBSD dynamic link: libssl/libcrypto from base, no -static (libc symbol +;; versioning), libutil for openpty (pulled in by libssl? safe to include). +(printf "==> [6/6] compile + link with ~a~n" cross-cc) + +(define link-cmd + (format + (string-append + "~a -O2 -Wl,--export-dynamic " + "-I~a " + "~a ~a " + "~a/libkernel.a ~a/libz.a ~a/liblz4.a " + "-lssl -lcrypto -lpthread -lm -lutil " + "-o ~a") + cross-cc + cross-csv-dir + main-c-path chez-ssl-shim-o + cross-csv-dir cross-csv-dir cross-csv-dir + output)) + +(printf " ~a~n" link-cmd) +(unless (zero? (system link-cmd)) + (error 'build-jaws-freebsd-cross "link failed")) + +(printf "~n==> done: ~a~n" output)