build: track static-main.c + generate FFI symbols per-platform

ober

566d83228d8a6d00def3ab0f9bb64f496ce88571

diff --git a/.gitignore b/.gitignore
index 370c20f..aa70df1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # Build artifacts (jerbuild build)
 /jerboa-edge
-*-main.c
+/*-main.c
+support/ffi-symbols.gen
 *.so
 *.wpo
 *.boot
diff --git a/.jerbuild b/.jerbuild
index 05bb39f..6ac6ff8 100644
--- a/.jerbuild
+++ b/.jerbuild
@@ -13,7 +13,7 @@
 ;; the list below (the linked-in archive members would otherwise be
 ;; dead-stripped / unreachable via dlsym).
 (main-c "support/static-main.c")
-(ffi-symbols "support/ffi-symbols.list")
+(ffi-symbols "support/ffi-symbols.gen")
 (rust-crates
   ("@bundle/jerboa-native-rs/Cargo.toml"
    features: "tls,crypto,sqlite"
diff --git a/Makefile b/Makefile
index ba36d4a..6c0b3f2 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,8 @@ all: binary
 # platform — e.g. epoll on Linux) and relink. A static main.c sets
 # JERBOA_STATIC=1 so the std modules use the registered symbols, not dlopen.
 binary:
-	@test -f "$(NATIVE_A)" || $(JERBUILD) build
+	@touch support/ffi-symbols.gen
+	$(JERBUILD) build
 	sh support/gen-ffi-symbols.sh
 	$(JERBUILD) build
 
diff --git a/support/ffi-symbols.list b/support/ffi-symbols.list
deleted file mode 100644
index f9df1b3..0000000
--- a/support/ffi-symbols.list
+++ /dev/null
@@ -1,115 +0,0 @@
-# jerboa-native FFI symbols registered via Sforeign_symbol() (see .jerbuild).
-# AUTOGENERATED by support/gen-ffi-symbols.sh from libjerboa_native.a.
-
-jerboa_aead_open
-jerboa_aead_seal
-jerboa_antidebug_check_all
-jerboa_antidebug_check_breakpoint
-jerboa_antidebug_check_ld_preload
-jerboa_antidebug_check_tracer
-jerboa_antidebug_ptrace
-jerboa_antidebug_timing_check
-jerboa_aproc_close
-jerboa_aproc_dup
-jerboa_aproc_killpg
-jerboa_aproc_set_nonblock
-jerboa_aproc_spawn
-jerboa_aproc_spawn_pty
-jerboa_aproc_wait4
-jerboa_argon2id_hash
-jerboa_argon2id_verify
-jerboa_chacha20_open
-jerboa_chacha20_seal
-jerboa_deflate
-jerboa_freebsd_is_traced
-jerboa_freebsd_process_count
-jerboa_gunzip
-jerboa_gzip
-jerboa_hkdf_sha256
-jerboa_hmac_sha256
-jerboa_hmac_sha256_verify
-jerboa_inflate
-jerboa_integrity_hash_file
-jerboa_integrity_hash_region
-jerboa_integrity_hash_self
-jerboa_integrity_sign_verify
-jerboa_integrity_verify_hash
-jerboa_kill_probe
-jerboa_last_error
-jerboa_md5
-jerboa_mlockall
-jerboa_pbkdf2_derive
-jerboa_pbkdf2_verify
-jerboa_prctl_set_name
-jerboa_proc_self_exe
-jerboa_random_bytes
-jerboa_regex_captures
-jerboa_regex_compile
-jerboa_regex_compile_ex
-jerboa_regex_find
-jerboa_regex_find_at
-jerboa_regex_free
-jerboa_regex_group_count
-jerboa_regex_is_match
-jerboa_regex_replace_all
-jerboa_scrypt
-jerboa_secure_alloc
-jerboa_secure_free
-jerboa_secure_random_fill
-jerboa_secure_wipe
-jerboa_setproctitle
-jerboa_sha1
-jerboa_sha256
-jerboa_sha384
-jerboa_sha512
-jerboa_socks5_server_port
-jerboa_socks5_server_start
-jerboa_socks5_server_stats
-jerboa_socks5_server_stop
-jerboa_sqlite_bind_blob
-jerboa_sqlite_bind_double
-jerboa_sqlite_bind_int
-jerboa_sqlite_bind_null
-jerboa_sqlite_bind_text
-jerboa_sqlite_changes
-jerboa_sqlite_close
-jerboa_sqlite_column_blob
-jerboa_sqlite_column_count
-jerboa_sqlite_column_double
-jerboa_sqlite_column_int
-jerboa_sqlite_column_name
-jerboa_sqlite_column_text
-jerboa_sqlite_column_type
-jerboa_sqlite_errmsg
-jerboa_sqlite_exec
-jerboa_sqlite_finalize
-jerboa_sqlite_last_insert_rowid
-jerboa_sqlite_open
-jerboa_sqlite_prepare
-jerboa_sqlite_reset
-jerboa_sqlite_step
-jerboa_timing_safe_equal
-jerboa_tls_accept
-jerboa_tls_close
-jerboa_tls_connect
-jerboa_tls_connect_mtls
-jerboa_tls_connect_mtls_mem
-jerboa_tls_connect_mtls_pem_ca
-jerboa_tls_connect_pinned
-jerboa_tls_flush
-jerboa_tls_get_fd
-jerboa_tls_read
-jerboa_tls_server_free
-jerboa_tls_server_new
-jerboa_tls_server_new_mtls
-jerboa_tls_server_new_mtls_pem
-jerboa_tls_server_new_pem
-jerboa_tls_set_nonblock
-jerboa_tls_write
-jerboa_x25519_diffie_hellman
-jerboa_x25519_generate_keypair
-jerboa_x25519_public_from_private
-jerboa_x509_cert_fingerprint
-jerboa_x509_generate_self_signed
-jerboa_x509_generate_self_signed_mem
-jerboa_x509_generate_signed_by_ca_mem
diff --git a/support/gen-ffi-symbols.sh b/support/gen-ffi-symbols.sh
index 0902ab6..0ad50bf 100755
--- a/support/gen-ffi-symbols.sh
+++ b/support/gen-ffi-symbols.sh
@@ -1,19 +1,21 @@
 #!/bin/sh
-# Regenerate support/ffi-symbols.list from the jerboa-native archive that
-# jerbuild cargo-builds into its bundle cache. Run before the final
-# `jerbuild build` so the static binary registers exactly the jerboa_* symbols
-# present for THIS platform (e.g. epoll on Linux, kqueue on macOS).
+# Regenerate support/ffi-symbols.gen from the jerboa-native archive that
+# jerbuild cargo-builds into its bundle cache. Run between the two `jerbuild
+# build` passes so the static binary registers exactly the jerboa_* symbols
+# present for THIS platform + feature set (e.g. epoll on Linux, kqueue on
+# macOS). The output is .gitignored — it is a build artifact, not source.
 set -e
 JH="$(jerbuild --jerboa-home 2>/dev/null)"
 A="$JH/jerboa-native-rs/target/release/libjerboa_native.a"
-OUT="$(dirname "$0")/ffi-symbols.list"
+OUT="$(dirname "$0")/ffi-symbols.gen"
 if [ ! -f "$A" ]; then
   echo "gen-ffi-symbols: $A not found (run 'jerbuild build' once first)" >&2
+  : > "$OUT"
   exit 0
 fi
 {
-  echo "# jerboa-native FFI symbols registered via Sforeign_symbol() (see .jerbuild)."
   echo "# AUTOGENERATED by support/gen-ffi-symbols.sh from libjerboa_native.a."
+  echo "# jerboa-native FFI symbols registered via Sforeign_symbol() (see .jerbuild)."
   echo ""
   nm -gjU "$A" 2>/dev/null | sed 's/^_//' | grep -E '^jerboa_[A-Za-z0-9_]+$' | sort -u
 } > "$OUT"
diff --git a/support/static-main.c b/support/static-main.c
new file mode 100644
index 0000000..4fca78c
--- /dev/null
+++ b/support/static-main.c
@@ -0,0 +1,55 @@
+/* Custom main.c for a standalone static jerbuild binary (referenced by
+ * .jerbuild as main-c). Identical to jerbuild's stock template with ONE
+ * addition: it sets JERBOA_STATIC=1 before building the heap.
+ *
+ * The bundled std modules — (std db sqlite-native), (std crypto native-rust),
+ * etc. — read JERBOA_STATIC at library-load time and, when set, trust the
+ * jerboa-native symbols that are linked into this binary (and registered via
+ * register_ffi_symbols() below from the .jerbuild ffi-symbols list) instead of
+ * trying to dlopen a libjerboa_native.{so,dylib} that does not exist in a
+ * static build.
+ */
+#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"
+#include "ffi_symbols.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[]) {
+    /* Trust the linked-in jerboa-native symbols (see file header). */
+    setenv("JERBOA_STATIC", "1", 1);
+
+    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);
+    register_ffi_symbols();
+
+    const char *prog_path = write_program_tmpfile();
+    int status = Sscheme_program(prog_path, argc, argv);
+    unlink(prog_path);
+
+    Sscheme_deinit();
+    return status;
+}