Add cross-build for FreeBSD/Linux amd64+arm64 from macOS arm64
Jaime Fournier <jaimef@linbsd.org>
f4754cd3f9986d354b20bfdde1336289c4500c65
--- a/.gitignore +++ b/.gitignore @@ -5,13 +5,26 @@ # CDB data files (generated from .data via bench/gen-zone.sh) *.cdb -# Static build artifacts -static/*.boot -static/*.wp.so -static/*.h -static/jdns -static/jdns-data -static/jdns-debug +# Cross-build artifacts (see static/build-jdns-*.ss) +jdns-macos-arm64 +jdns-data-macos-arm64 +jdns-linux-amd64 +jdns-data-linux-amd64 +jdns-linux-arm64 +jdns-data-linux-arm64 +jdns-freebsd-amd64 +jdns-data-freebsd-amd64 +jdns*.wp.so +jdns*-main.c +petite_boot.h +scheme_boot.h +program_boot.h + +# Per-machine compiled-library caches Chez emits during cross-builds +lib/**/*.tarm64osx +lib/**/*.ta6le +lib/**/*.ta6fb +lib/**/*.tarm64le # Bench logs and server-stderr dumps (large, regenerable) bench/logs/ --- a/Makefile +++ b/Makefile @@ -11,7 +11,9 @@ WASM_SOURCES = $(WASM_DIR)/Cargo.toml \ $(WASM_DIR)/dns/Cargo.toml $(WASM_DIR)/dns/src/lib.rs \ $(WASM_DIR)/cdb/Cargo.toml $(WASM_DIR)/cdb/src/lib.rs -.PHONY: all build test clean jdns jdns-data wasm wasm-clean +.PHONY: all build test clean jdns jdns-data wasm wasm-clean \ + static static-macos static-linux static-linux-arm64 static-freebsd \ + static-clean all: build @@ -49,6 +51,38 @@ test: build done @echo "All tests passed." +# Cross-builds. JERBOA_HOME must contain the cross-built Chez prefixes +# (.chez-cross-ta6le for linux-amd64, .chez-cross-ta6fb for freebsd-amd64, +# .chez-cross-tarm64le for linux-arm64) plus the matching xpatch under +# build/chez/xc-*/s/xpatch. See ~/mine/jerboa for how those are built. +# +# Each target produces both jdns-<plat> and jdns-data-<plat> binaries. +static: static-macos + +static-macos: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) --libdirs "$(LIBDIRS)" \ + --script static/build-jdns-macos.ss + +static-linux: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) --libdirs "$(LIBDIRS)" \ + --script static/build-jdns-linux.ss + +static-linux-arm64: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) --libdirs "$(LIBDIRS)" \ + --script static/build-jdns-linux-arm64.ss + +static-freebsd: + JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) --libdirs "$(LIBDIRS)" \ + --script static/build-jdns-freebsd.ss + +static-clean: + rm -f jdns-macos-arm64 jdns-data-macos-arm64 + rm -f jdns-linux-amd64 jdns-data-linux-amd64 + rm -f jdns-linux-arm64 jdns-data-linux-arm64 + rm -f jdns-freebsd-amd64 jdns-data-freebsd-amd64 + rm -f jdns*.wp.so jdns*-main.c + rm -f petite_boot.h scheme_boot.h program_boot.h + clean: find lib -name '*.so' -delete 2>/dev/null || true find lib -name '*.wpo' -delete 2>/dev/null || true deleted file mode 100644 --- a/static/build-boot.ss +++ /dev/null @@ -1,44 +0,0 @@ -#!chezscheme -;;; Build compiled program for static embedding. -;;; -;;; Usage: scheme --libdirs "lib:$JERBOA" --script static/build-boot.ss <entry.ss> <output.so> -;;; -;;; This compiles the entry point with whole-program optimization, -;;; producing a single .so file that can be loaded via Sscheme_program. - -(import (chezscheme)) - -(unless (>= (length (command-line)) 3) - (display "Usage: build-boot.ss <entry.ss> <output.so>\n" (current-error-port)) - (exit 1)) - -(define (string-suffix? str suffix) - (let ([slen (string-length str)] - [xlen (string-length suffix)]) - (and (>= slen xlen) - (string=? (substring str (- slen xlen) slen) suffix)))) - -(let ([entry-file (list-ref (command-line) 1)] - [output-so (list-ref (command-line) 2)]) - - ;; Step 1: Compile with WPO enabled - (compile-imported-libraries #t) - (generate-wpo-files #t) - - ;; Chez strips the .ss extension for output: bin/jdns.ss -> bin/jdns.wpo - (let* ([base (if (or (string-suffix? entry-file ".ss") - (string-suffix? entry-file ".scm")) - (substring entry-file 0 - (- (string-length entry-file) - (if (string-suffix? entry-file ".ss") 3 4))) - entry-file)] - [wpo-file (string-append base ".wpo")]) - - (display (format "Compiling ~a...\n" entry-file) (current-error-port)) - (compile-program entry-file) - - ;; Step 2: Whole-program optimization — bundle all libraries into one .so - (display (format "Whole-program optimization -> ~a\n" output-so) (current-error-port)) - (compile-whole-program wpo-file output-so) - - (display "Done.\n" (current-error-port)))) new file mode 100644 --- /dev/null +++ b/static/build-common.ss @@ -0,0 +1,234 @@ +;;; build-common.ss — Shared helpers for static/build-jdns-*.ss. +;;; +;;; Loaded by each per-target build script via (load "static/build-common.ss"). +;;; Defines: embed-as-c-array, find-cross-csv-dir, jdns-posix-symbols, +;;; jdns-posix-wrapped-symbols, jdns-wasm-weak-symbols, emit-jdns-main-c. +;;; +;;; Loaded BEFORE xpatch in cross builds so the helpers themselves don't +;;; get compiled for the target machine type — they only run on the host. + +;; Embed a binary file as a pair of C symbols: +;; static const unsigned char <stem>_data[]; +;; static const unsigned int <stem>_size; +(define (embed-as-c-array in-path stem out-path) + (let* ([bv (call-with-port (open-file-input-port in-path) get-bytevector-all)] + [n (bytevector-length bv)] + [arr-name (string-append stem "_data")] + [sz-name (string-append stem "_size")]) + (call-with-port (open-file-output-port out-path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + (lambda (out) + (fprintf out "static const unsigned char ~a[] = {\n" arr-name) + (let loop ([i 0]) + (when (< i n) + (fprintf out "0x~2,'0x," (bytevector-u8-ref bv i)) + (when (= (mod (+ i 1) 16) 0) (newline out)) + (loop (+ i 1)))) + (when (positive? n) (newline out)) + (display "};\n" out) + (fprintf out "static const unsigned int ~a = sizeof(~a);\n" + sz-name arr-name))) + (printf " embed ~a (~a bytes) -> ~a~n" in-path n out-path))) + +;; Resolve <cross-prefix>/lib/csv*/<machine> — Chez's per-machine static lib dir. +(define (find-cross-csv-dir cross-prefix machine) + (let ([lib (format "~a/lib" cross-prefix)]) + (unless (file-directory? lib) + (error 'find-cross-csv-dir "missing lib dir" 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 'find-cross-csv-dir "no csv* under" lib)) + (format "~a/~a/~a" lib (car csvs) machine)))) + +;; POSIX symbols jdns calls via foreign-procedure. Resolved by the libc +;; statically/dynamically linked into the final binary. +(define jdns-posix-symbols + '("socket" "bind" "close" "recvfrom" "sendto" "setsockopt" + "htons" "ntohs" "inet_pton" "inet_ntop" + "setuid" "setgid" "chdir" "chroot")) + +;; POSIX symbols that are macros or have varargs in libc — must go through +;; a thin C wrapper so foreign-procedure's int signature matches. +;; (htons/ntohs are macros on Linux and some BSDs; provide explicit wrappers.) +(define jdns-posix-wrapped-symbols '("htons" "ntohs")) + +;; Wasm-sandbox symbols pulled from (std wasm sandbox). The Scheme side +;; guards every call with wasm-sandbox-available?, so weak stubs that +;; return 0 are enough — jdns falls back to in-Scheme parsing. +(define jdns-wasm-weak-symbols + '("jerboa_wasm_module_new" "jerboa_wasm_module_free" + "jerboa_wasm_instance_new" "jerboa_wasm_instance_new_hosted" + "jerboa_wasm_instance_free" "jerboa_wasm_call" + "jerboa_wasm_memory_read" "jerboa_wasm_memory_write" "jerboa_wasm_memory_size" + "jerboa_wasm_add_fuel" "jerboa_wasm_fuel_remaining" "jerboa_wasm_get_log" + "jerboa_sm_module_new" "jerboa_sm_module_free" + "jerboa_sm_instance_new" "jerboa_sm_instance_new_hosted" + "jerboa_sm_instance_free" "jerboa_sm_call" + "jerboa_sm_add_fuel" "jerboa_sm_get_log" + "jerboa_last_error")) + +;; Emit main.c. Parameters: +;; out-port — file output port to write to +;; binary-name — used in messages and tempfile path +;; platform-tag — keyword: 'macos | 'linux-static | 'linux-arm64-static | 'freebsd +;; extra-posix-symbols — list of additional POSIX symbols (e.g. cap_rights_limit) +;; +;; Generates: includes, dlopen stubs (static targets only), wrappers, +;; weak stubs, register_ffi_symbols(), main() that loads boot files and +;; runs the embedded program via Sscheme_program. +(define (emit-jdns-main-c out binary-name platform-tag extra-posix-symbols) + (define static? (memq platform-tag '(linux-static linux-arm64-static))) + (define freebsd? (eq? platform-tag 'freebsd)) + (define macos? (eq? platform-tag 'macos)) + + (fprintf out "/* Generated by build-jdns-*.ss for ~a — do not edit. */\n" + binary-name) + (when static? (display "#define _GNU_SOURCE\n" out)) + (for-each + (lambda (h) (fprintf out "#include ~a\n" h)) + '("<stdlib.h>" "<string.h>" "<stdio.h>" "<unistd.h>" + "<sys/types.h>" "<sys/socket.h>" "<sys/stat.h>" + "<netinet/in.h>" "<arpa/inet.h>" "<fcntl.h>" "<errno.h>")) + (when freebsd? (display "#include <sys/sysctl.h>\n" out)) + (when (or static? freebsd?) (display "#include <sys/mman.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 \"program_boot.h\"\n\n" out) + + ;; dlopen stubs for fully-static binaries (musl's dlopen always fails in + ;; a -static link, but jerboa-dns calls (load-shared-object "libc.so") at + ;; init; the stub returns a non-NULL handle so the call succeeds and + ;; foreign-procedure routes through Sforeign_symbol). + (when static? + (display "/* dlopen stubs for static linking */\n" out) + (display "void *dlopen(const char *f, int flags) { (void)flags; return f ? NULL : (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 of named libraries is stubbed\";\n" out) + (display "char *dlerror(void) { return dlerror_msg; }\n\n" out)) + + ;; FreeBSD: libc uses __error, glibc uses __errno_location. Provide a + ;; wrapper and register it under both names so Scheme code that uses + ;; either spelling resolves. + (when freebsd? + (display "/* FreeBSD errno compat */\n" out) + (display "static int *freebsd_errno_location(void) { return &errno; }\n\n" out)) + + ;; Wrappers for POSIX macros / FreeBSD-specific symbols. + (display "/* POSIX wrappers (htons/ntohs are macros on some libcs) */\n" out) + (display "static unsigned short wrap_htons(unsigned short x) { return htons(x); }\n" out) + (display "static unsigned short wrap_ntohs(unsigned short x) { return ntohs(x); }\n" out) + (when freebsd? + (display "\n/* FreeBSD Capsicum cap_rights_limit — declared in <sys/capsicum.h> on FreeBSD;\n" out) + (display " * we forward-declare here to avoid pulling that header into the cross build. */\n" out) + (display "extern int cap_rights_limit(int, const void *);\n" out)) + (newline out) + + ;; Weak stubs for wasm symbols not linked into this build. __attribute__((weak)) + ;; lets the link succeed; at runtime Scheme guards wasm-sandbox-available? so + ;; these stubs are never actually called. + (display "/* Weak stubs for wasm sandbox symbols (libjerboa_native not linked) */\n" out) + (for-each + (lambda (n) + (fprintf out + "__attribute__((weak)) long ~a() { fprintf(stderr, \"[~a-weak] ~a\\n\"); fflush(stderr); return 0; }\n" + n binary-name n)) + jdns-wasm-weak-symbols) + (newline out) + + ;; Sbuild_heap callback — register every FFI symbol Scheme can dlsym at runtime. + (display "static void register_ffi_symbols(void) {\n" out) + (for-each + (lambda (n) + (cond + [(member n jdns-posix-wrapped-symbols) + (fprintf out " Sforeign_symbol(\"~a\", (void*)wrap_~a);\n" n n)] + [else + (fprintf out " Sforeign_symbol(\"~a\", (void*)~a);\n" n n)])) + jdns-posix-symbols) + (for-each + (lambda (n) + (fprintf out " Sforeign_symbol(\"~a\", (void*)~a);\n" n n)) + extra-posix-symbols) + (when freebsd? + (display " Sforeign_symbol(\"__error\", (void*)freebsd_errno_location);\n" out) + (display " Sforeign_symbol(\"__errno_location\", (void*)freebsd_errno_location);\n" out)) + (for-each + (lambda (n) + (fprintf out " Sforeign_symbol(\"~a\", (void*)~a);\n" n n)) + jdns-wasm-weak-symbols) + (display "}\n\n" out) + + ;; main() — write embedded program to tempfile, then Sscheme_program. + (display "static const char *write_program_tempfile(void) {\n" out) + (fprintf out " static char path[] = \"/tmp/~a-prog-XXXXXX\";\n" binary-name) + (display " int fd = mkstemp(path);\n" out) + (display " if (fd < 0) { perror(\"mkstemp\"); exit(1); }\n" out) + (display " if (write(fd, program_boot_data, program_boot_size) != (ssize_t)program_boot_size) {\n" out) + (display " perror(\"write\"); close(fd); unlink(path); exit(1);\n" out) + (display " }\n" out) + (display " close(fd);\n" out) + (display " return path;\n" out) + (display "}\n\n" out) + + (display "int main(int argc, const char *argv[]) {\n" out) + (display " Sscheme_init(NULL);\n" out) + (display " Sregister_boot_file_bytes(\"petite\", (void *)petite_boot_data, petite_boot_size);\n" out) + (display " Sregister_boot_file_bytes(\"scheme\", (void *)scheme_boot_data, scheme_boot_size);\n" out) + (display " Sbuild_heap(NULL, register_ffi_symbols);\n" out) + (display " const char *prog_path = write_program_tempfile();\n" out) + (display " int status = Sscheme_program(prog_path, argc, argv);\n" out) + (display " unlink(prog_path);\n" out) + (display " Sscheme_deinit();\n" out) + (display " return status;\n" out) + (display "}\n" out)) + +;; Generic per-binary pipeline: compile-program → compile-whole-program → +;; embed boot + program → generate main.c → invoke cross-cc. +;; Each target script calls (build-one-binary ...) twice — once for jdns, +;; once for jdns-data. +(define (build-one-binary entry-script + binary-name + csv-dir + platform-tag + extra-posix-symbols + link-cmd-fn) + (let* ([wpo-output (string-append binary-name ".wp.so")] + ;; entry "bin/jdns.ss" → wpo at "bin/jdns.wpo" + [base (let ([n (string-length entry-script)]) + (if (and (>= n 3) + (string=? (substring entry-script (- n 3) n) ".ss")) + (substring entry-script 0 (- n 3)) + entry-script))] + [wpo-input (string-append base ".wpo")] + [main-c (string-append binary-name "-main.c")]) + (printf "==> [~a] compile-program ~a~n" binary-name entry-script) + (compile-program entry-script) + (printf "==> [~a] compile-whole-program ~a -> ~a~n" + binary-name wpo-input wpo-output) + (compile-whole-program wpo-input wpo-output #t) + (printf "==> [~a] embed boot files + program~n" binary-name) + (embed-as-c-array (format "~a/petite.boot" csv-dir) "petite_boot" "petite_boot.h") + (embed-as-c-array (format "~a/scheme.boot" csv-dir) "scheme_boot" "scheme_boot.h") + (embed-as-c-array wpo-output "program_boot" "program_boot.h") + (printf "==> [~a] generate ~a~n" binary-name main-c) + (call-with-port (open-file-output-port main-c + (file-options no-fail) (buffer-mode block) (native-transcoder)) + (lambda (out) (emit-jdns-main-c out binary-name platform-tag extra-posix-symbols))) + (printf "==> [~a] link~n" binary-name) + (let ([cmd (link-cmd-fn binary-name main-c csv-dir)]) + (printf " ~a~n" cmd) + (let ([rc (system cmd)]) + (unless (zero? rc) + (error 'build-one-binary "link failed" binary-name rc)))) + (printf "=== ~a built ===~n" binary-name) + (system (format "ls -lh ~a" binary-name)) + (system (format "file ~a" binary-name)) + (newline))) new file mode 100644 --- /dev/null +++ b/static/build-jdns-freebsd.ss @@ -0,0 +1,73 @@ +#!chezscheme +;;; build-jdns-freebsd.ss — Cross-compile jdns + jdns-data from macOS arm64 +;;; to FreeBSD 14 amd64 (dynamic). +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa \ +;;; scheme --libdirs "lib:$JERBOA_HOME/lib" \ +;;; --script static/build-jdns-freebsd.ss +;;; +;;; Uses: +;;; $JERBOA_HOME/.chez-cross-ta6fb/ — cross-built Chez install +;;; $JERBOA_HOME/build/chez/xc-ta6fb/s/xpatch — host compiler → ta6fb mode +;;; x86_64-unknown-freebsd14-clang — wrapper around macOS clang+lld +;;; +;;; Dynamic-linked: FreeBSD libc uses symbol versioning that prevents +;;; useful -static against libc.a; the canonical model is dynamic against +;;; libc.so.7 / libm / libthr / libutil from the target system. +;;; +;;; Produces (in repo root): +;;; jdns-freebsd-amd64 +;;; jdns-data-freebsd-amd64 + +(import (chezscheme)) + +(load "static/build-common.ss") + +(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 csv-dir (find-cross-csv-dir cross-prefix "ta6fb")) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jdns-freebsd "missing file" p))) + +(require-file xpatch) +(require-file (format "~a/libkernel.a" csv-dir)) +(require-file (format "~a/scheme.h" csv-dir)) +(require-file (format "~a/petite.boot" csv-dir)) +(require-file (format "~a/scheme.boot" csv-dir)) +(require-file "bin/jdns.ss") +(require-file "bin/jdns-data.ss") + +(printf "==> build-jdns-freebsd (cross to ta6fb, dynamic)~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " csv-dir: ~a~n" csv-dir) +(printf " xpatch: ~a~n" xpatch) +(printf " cross-cc: ~a~n" cross-cc) +(printf "~n") + +(define orig-libdirs (library-directories)) +(printf "==> [1] loading xpatch~n") +(load xpatch) +(library-directories orig-libdirs) + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +;; FreeBSD link: dynamic, no -ldl (dlopen is in libc), -lthr for pthread, +;; -lutil for libutil — matches jerboa-shell's build-jsh-freebsd-cross. +;; -Wl,--export-dynamic so RTLD_DEFAULT sees Sforeign_symbol registrations. +(define (link-cmd binary main-c csv) + (format + "~a -O2 -Wl,--export-dynamic -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -lpthread -lutil" + cross-cc csv binary main-c csv csv csv)) + +;; cap_rights_limit is a FreeBSD-only Capsicum entry point. +(build-one-binary "bin/jdns.ss" "jdns-freebsd-amd64" csv-dir 'freebsd '("cap_rights_limit") link-cmd) +(build-one-binary "bin/jdns-data.ss" "jdns-data-freebsd-amd64" csv-dir 'freebsd '("cap_rights_limit") link-cmd) + +(printf "=== Build complete ===~n") +(system "ls -lh jdns-freebsd-amd64 jdns-data-freebsd-amd64") new file mode 100644 --- /dev/null +++ b/static/build-jdns-linux-arm64.ss @@ -0,0 +1,65 @@ +#!chezscheme +;;; build-jdns-linux-arm64.ss — Cross-compile jdns + jdns-data from macOS arm64 +;;; to Linux aarch64 musl (fully static). +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa \ +;;; scheme --libdirs "lib:$JERBOA_HOME/lib" \ +;;; --script static/build-jdns-linux-arm64.ss +;;; +;;; Uses: +;;; $JERBOA_HOME/.chez-cross-tarm64le/ — cross-built Chez install +;;; $JERBOA_HOME/build/chez/xc-tarm64le/s/xpatch — host compiler → tarm64le mode +;;; aarch64-linux-musl-gcc — C compile + static link +;;; +;;; Produces (in repo root): +;;; jdns-linux-arm64 +;;; jdns-data-linux-arm64 + +(import (chezscheme)) + +(load "static/build-common.ss") + +(define jerboa-home (or (getenv "JERBOA_HOME") "/Users/user/mine/jerboa")) +(define cross-prefix (format "~a/.chez-cross-tarm64le" jerboa-home)) +(define xpatch (format "~a/build/chez/xc-tarm64le/s/xpatch" jerboa-home)) +(define cross-cc (or (getenv "CROSS_CC") "aarch64-linux-musl-gcc")) +(define csv-dir (find-cross-csv-dir cross-prefix "tarm64le")) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jdns-linux-arm64 "missing file" p))) + +(require-file xpatch) +(require-file (format "~a/libkernel.a" csv-dir)) +(require-file (format "~a/scheme.h" csv-dir)) +(require-file (format "~a/petite.boot" csv-dir)) +(require-file (format "~a/scheme.boot" csv-dir)) +(require-file "bin/jdns.ss") +(require-file "bin/jdns-data.ss") + +(printf "==> build-jdns-linux-arm64 (cross to tarm64le, static musl)~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " csv-dir: ~a~n" csv-dir) +(printf " xpatch: ~a~n" xpatch) +(printf " cross-cc: ~a~n" cross-cc) +(printf "~n") + +(define orig-libdirs (library-directories)) +(printf "==> [1] loading xpatch~n") +(load xpatch) +(library-directories orig-libdirs) + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +(define (link-cmd binary main-c csv) + (format + "~a -O2 -static -Wl,--export-dynamic -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -ldl -lpthread" + cross-cc csv binary main-c csv csv csv)) + +(build-one-binary "bin/jdns.ss" "jdns-linux-arm64" csv-dir 'linux-arm64-static '() link-cmd) +(build-one-binary "bin/jdns-data.ss" "jdns-data-linux-arm64" csv-dir 'linux-arm64-static '() link-cmd) + +(printf "=== Build complete ===~n") +(system "ls -lh jdns-linux-arm64 jdns-data-linux-arm64") new file mode 100644 --- /dev/null +++ b/static/build-jdns-linux.ss @@ -0,0 +1,69 @@ +#!chezscheme +;;; build-jdns-linux.ss — Cross-compile jdns + jdns-data from macOS arm64 +;;; to Linux x86_64 musl (fully static). +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa \ +;;; scheme --libdirs "lib:$JERBOA_HOME/lib" \ +;;; --script static/build-jdns-linux.ss +;;; +;;; Uses: +;;; $JERBOA_HOME/.chez-cross-ta6le/ — cross-built Chez install +;;; $JERBOA_HOME/build/chez/xc-ta6le/s/xpatch — host compiler → ta6le mode +;;; x86_64-linux-musl-gcc — C compile + static link +;;; +;;; Produces (in repo root): +;;; jdns-linux-amd64 +;;; jdns-data-linux-amd64 + +(import (chezscheme)) + +(load "static/build-common.ss") + +(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 csv-dir (find-cross-csv-dir cross-prefix "ta6le")) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jdns-linux "missing file" p))) + +(require-file xpatch) +(require-file (format "~a/libkernel.a" csv-dir)) +(require-file (format "~a/scheme.h" csv-dir)) +(require-file (format "~a/petite.boot" csv-dir)) +(require-file (format "~a/scheme.boot" csv-dir)) +(require-file "bin/jdns.ss") +(require-file "bin/jdns-data.ss") + +(printf "==> build-jdns-linux (cross to ta6le, static musl)~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " csv-dir: ~a~n" csv-dir) +(printf " xpatch: ~a~n" xpatch) +(printf " cross-cc: ~a~n" cross-cc) +(printf "~n") + +;; Stage 1: load xpatch — switches the host compiler into ta6le emit mode. +;; xpatch's load form clobbers library-directories; restore it after. +(define orig-libdirs (library-directories)) +(printf "==> [1] loading xpatch~n") +(load xpatch) +(library-directories orig-libdirs) + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +;; Static link with --export-dynamic so Sforeign_symbol-registered functions +;; remain visible to Chez's dlsym(RTLD_DEFAULT, ...) lookups. +(define (link-cmd binary main-c csv) + (format + "~a -O2 -static -Wl,--export-dynamic -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -ldl -lpthread" + cross-cc csv binary main-c csv csv csv)) + +(build-one-binary "bin/jdns.ss" "jdns-linux-amd64" csv-dir 'linux-static '() link-cmd) +(build-one-binary "bin/jdns-data.ss" "jdns-data-linux-amd64" csv-dir 'linux-static '() link-cmd) + +(printf "=== Build complete ===~n") +(system "ls -lh jdns-linux-amd64 jdns-data-linux-amd64") new file mode 100644 --- /dev/null +++ b/static/build-jdns-macos.ss @@ -0,0 +1,57 @@ +#!chezscheme +;;; build-jdns-macos.ss — Native macOS arm64 build of jdns + jdns-data. +;;; +;;; Usage: +;;; JERBOA_HOME=/Users/user/mine/jerboa \ +;;; scheme --libdirs "lib:$JERBOA_HOME/lib" \ +;;; --script static/build-jdns-macos.ss +;;; +;;; Native build — no xpatch. The current host's $JERBOA_HOME/.chez is a +;;; tarm64osx install built by jerboa's Makefile; we link against its +;;; static libkernel.a + lib{z,lz4}.a. +;;; +;;; Produces (in repo root): +;;; jdns-macos-arm64 +;;; jdns-data-macos-arm64 + +(import (chezscheme)) + +(load "static/build-common.ss") + +(define jerboa-home (or (getenv "JERBOA_HOME") "/Users/user/mine/jerboa")) +(define chez-prefix (format "~a/.chez" jerboa-home)) +(define csv-dir (find-cross-csv-dir chez-prefix "tarm64osx")) +(define cc (or (getenv "CC") "cc")) + +(define (require-file p) + (unless (file-exists? p) + (error 'build-jdns-macos "missing file" p))) + +(require-file (format "~a/libkernel.a" csv-dir)) +(require-file (format "~a/scheme.h" csv-dir)) +(require-file (format "~a/petite.boot" csv-dir)) +(require-file (format "~a/scheme.boot" csv-dir)) +(require-file "bin/jdns.ss") +(require-file "bin/jdns-data.ss") + +(printf "==> build-jdns-macos~n") +(printf " JERBOA_HOME: ~a~n" jerboa-home) +(printf " csv-dir: ~a~n" csv-dir) +(printf " cc: ~a~n" cc) +(printf "~n") + +(compile-imported-libraries #t) +(generate-wpo-files #t) + +;; Native macOS link: dynamic-link libSystem (libc/libm/pthread bundled). +;; -Wl,-export_dynamic so dlsym(RTLD_DEFAULT, ...) sees the weak stubs. +(define (link-cmd binary main-c csv) + (format + "~a -O2 -I~a -Wl,-export_dynamic -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -lpthread -liconv -lncurses" + cc csv binary main-c csv csv csv)) + +(build-one-binary "bin/jdns.ss" "jdns-macos-arm64" csv-dir 'macos '() link-cmd) +(build-one-binary "bin/jdns-data.ss" "jdns-data-macos-arm64" csv-dir 'macos '() link-cmd) + +(printf "=== Build complete ===~n") +(system "ls -lh jdns-macos-arm64 jdns-data-macos-arm64") deleted file mode 100755 --- a/static/build-static.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/sh -# -# Build static jdns binaries. -# -# Produces: -# static/jdns — DNS server (single static binary) -# static/jdns-data — Zone compiler (single static binary) -# -# Each binary embeds the Chez Scheme runtime + boot files + compiled program. -# No external .boot files or shared libraries needed. - -set -e - -SCHEME="${SCHEME:-scheme}" -JERBOA="${JERBOA:-$HOME/jerboa/lib}" -LIBDIRS="lib:${JERBOA}" -# Find Chez Scheme installation directory -CSVDIR="" -for d in /usr/local/lib/csv*/"$($SCHEME -q <<'EOF' -(display (machine-type)) (exit) -EOF -)" /usr/lib/csv*/"$($SCHEME -q <<'EOF' -(display (machine-type)) (exit) -EOF -)"; do - if [ -f "$d/libkernel.a" ] 2>/dev/null; then - CSVDIR="$d" - break - fi -done - -if [ ! -f "$CSVDIR/libkernel.a" ]; then - echo "Error: Cannot find Chez Scheme installation (libkernel.a)" >&2 - echo " Searched: $CSVDIR" >&2 - exit 1 -fi - -echo "Chez Scheme: $CSVDIR" -echo "Jerboa: $JERBOA" - -STATIC_DIR="$(dirname "$0")" -cd "$(dirname "$0")/.." - -# Step 1: Build libraries (ensure .wpo files exist) -echo "==> Compiling libraries..." -$SCHEME --libdirs "$LIBDIRS" --compile-imported-libraries --script build.ss - -# Step 2: Build each entry point -build_binary() { - local name="$1" - local entry="$2" - local prog_so="static/${name}.wp.so" - - echo "==> Building $name..." - - # Compile with WPO into a single .so - $SCHEME --libdirs "$LIBDIRS" --script static/build-boot.ss "$entry" "$prog_so" - - # Convert boot files and program to C headers - echo " Converting to C headers..." - convert_to_header "$CSVDIR/petite.boot" "static/petite_boot" - convert_to_header "$CSVDIR/scheme.boot" "static/scheme_boot" - convert_to_header "$prog_so" "static/program_boot" - - # Compile and link static binary - echo " Linking static binary..." - cc -DSCHEME_STATIC \ - -I"$CSVDIR" \ - -o "static/${name}" \ - static/main.c \ - "$CSVDIR/libkernel.a" \ - "$CSVDIR/liblz4.a" \ - "$CSVDIR/libz.a" \ - -L/usr/local/lib \ - -lm -lthr -liconv -lncursesw \ - -static - - strip "static/${name}" - - echo " static/${name}: $(ls -lh "static/${name}" | awk '{print $5}')" -} - -convert_to_header() { - local input="$1" - local stem="$2" - local varname="$(basename "$stem")_data" - local sizename="$(basename "$stem")_size" - local header="${stem}.h" - - # Convert binary file to C unsigned char array using od - printf "static const unsigned char %s[] = {\n" "$varname" > "$header" - od -An -tx1 -v "$input" | sed 's/^ *//;s/ *$//;s/ */ /g;s/ /,0x/g;s/^/0x/;s/$/,/' >> "$header" - printf "};\n" >> "$header" - printf "static const unsigned int %s = sizeof(%s);\n" "$sizename" "$varname" >> "$header" -} - -build_binary "jdns" "bin/jdns.ss" -build_binary "jdns-data" "bin/jdns-data.ss" - -echo "" -echo "Static binaries:" -ls -lh static/jdns static/jdns-data -echo "" -echo "Verify (should show 'statically linked'):" -file static/jdns static/jdns-data deleted file mode 100644 --- a/static/main.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * jdns static binary entry point - * - * Embeds Chez Scheme boot files (petite.boot, scheme.boot) and - * the compiled program (.so) as binary blobs. - * Statically linked — no shared library dependencies. - * - * The program .so is written to a temp file and loaded via - * Sscheme_program, which sets command-line before executing. - */ - -#include "scheme.h" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <dlfcn.h> - -/* Embedded boot file data — generated by build-static.sh */ -#include "petite_boot.h" -#include "scheme_boot.h" -#include "program_boot.h" - -/* - * Override dlopen/dlsym for static binary. - * Chez's load-shared-object calls dlopen, which may fail in a fully - * static binary. We return a dummy handle so it doesn't error out. - * All actual symbol resolution happens via Sforeign_symbol. - */ -static int _static_dummy; - -void *dlopen(const char *path, int mode) { - (void)path; - (void)mode; - return &_static_dummy; -} - -void *dlsym(void *handle, const char *name) { - (void)handle; - (void)name; - return NULL; -} - -int dlclose(void *handle) { - (void)handle; - return 0; -} - -char *dlerror(void) { - return NULL; -} - -/* Wrapper functions for htons/ntohs which may be macros */ -static unsigned short wrap_htons(unsigned short x) { return htons(x); } -static unsigned short wrap_ntohs(unsigned short x) { return ntohs(x); } - -/* - * Called by Sbuild_heap after the Scheme heap is initialized - * but before boot files execute. Register FFI symbols here. - */ -static void custom_init(void) { - Sforeign_symbol("socket", (void *)socket); - Sforeign_symbol("bind", (void *)bind); - Sforeign_symbol("close", (void *)close); - Sforeign_symbol("recvfrom", (void *)recvfrom); - Sforeign_symbol("sendto", (void *)sendto); - Sforeign_symbol("setsockopt", (void *)setsockopt); - Sforeign_symbol("htons", (void *)wrap_htons); - Sforeign_symbol("ntohs", (void *)wrap_ntohs); - Sforeign_symbol("inet_pton", (void *)inet_pton); - Sforeign_symbol("inet_ntop", (void *)inet_ntop); - Sforeign_symbol("setuid", (void *)setuid); - Sforeign_symbol("setgid", (void *)setgid); - Sforeign_symbol("chdir", (void *)chdir); - Sforeign_symbol("chroot", (void *)chroot); -#ifdef __FreeBSD__ - { - extern int cap_rights_limit(int, const void *); - Sforeign_symbol("cap_rights_limit", (void *)cap_rights_limit); - } -#endif -} - -/* - * Write embedded program data to a temp file so Sscheme_program - * can load it. This lets Chez set command-line before execution. - */ -static const char *write_program_tempfile(void) { - static char path[] = "/tmp/jdns-prog-XXXXXX"; - int fd; - ssize_t written; - - fd = mkstemp(path); - if (fd < 0) { - perror("mkstemp"); - exit(1); - } - - written = write(fd, program_boot_data, program_boot_size); - if (written != (ssize_t)program_boot_size) { - perror("write"); - close(fd); - unlink(path); - exit(1); - } - - close(fd); - return path; -} - -int main(int argc, const char *argv[]) { - const char *prog_path; - - Sscheme_init(NULL); - - /* Register boot files (petite + scheme only — not the program) */ - Sregister_boot_file_bytes("petite", (void *)petite_boot_data, petite_boot_size); - Sregister_boot_file_bytes("scheme", (void *)scheme_boot_data, scheme_boot_size); - - /* Build heap with FFI symbols registered in custom_init */ - Sbuild_heap(NULL, custom_init); - - /* Write compiled program to temp file, load via Sscheme_program - * which sets (command-line) before executing */ - prog_path = write_program_tempfile(); - Sscheme_program(prog_path, argc, argv); - unlink(prog_path); - - Sscheme_deinit(); - return 0; -}