Add make macos target: native jemacs-qt binary for macOS

ober

0d88a51333fe6e63752012b278a07de595d979d1

diff --git a/Makefile b/Makefile
index 60d9908..8aa83e5 100644
--- a/Makefile
+++ b/Makefile
@@ -41,13 +41,16 @@ export CHEZ_PCRE2_LIB := $(HOME)/mine/chez-pcre2
 export JSH_FFI_LIB := $(HOME)/mine/jerboa-shell
 export CHEZ_QT_LIB := .
 export CHEZ_QT_SHIM_DIR := .
+ifeq ($(UNAME_S),Darwin)
+  export CHEZ_DIR := $(shell ls -d /opt/homebrew/lib/csv*/tarm64osx 2>/dev/null | head -1)
+endif
 
 .PHONY: all build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
         test-org-duration test-org-element test-org-fold test-org-footnote \
         test-org-lint test-org-num test-org-property test-org-src test-org-tempo \
         test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary binary-qt \
         test-pty test-emacs test-functional test-term-hang \
-        docker-deps static-qt clean-docker check-root build-jemacs-qt-static
+        docker-deps static-qt clean-docker check-root build-jemacs-qt-static macos
 
 all: build test
 
@@ -152,6 +155,12 @@ libqt_shim.$(SHLIB_EXT): vendor/qt_shim.cpp
 qt_chez_shim.$(SHLIB_EXT): vendor/qt_chez_shim.c vendor/qt_shim.h
 	gcc $(SHLIB_FLAGS) -O2 -o qt_chez_shim.$(SHLIB_EXT) vendor/qt_chez_shim.c -Ivendor -DQT_SCINTILLA_AVAILABLE -Wall
 
+# macOS native binary — compile all modules + link jemacs-qt binary
+macos: build libqt_shim.$(SHLIB_EXT) qt_chez_shim.$(SHLIB_EXT) repl_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT)
+	cd $(HOME)/mine/chez-pcre2 && make
+	JSH_DIR=$(JSH) $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script build-binary-qt.ss
+	./jemacs-qt --version
+
 run-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) qt_chez_shim.$(SHLIB_EXT)
 	$(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script qt-main.ss
 
diff --git a/build-binary-qt.ss b/build-binary-qt.ss
index 2f75bb0..029493d 100644
--- a/build-binary-qt.ss
+++ b/build-binary-qt.ss
@@ -92,6 +92,13 @@
   (let ((v (getenv "JEMACS_STATIC")))
     (and v (not (string=? v "0")) (not (string=? v "")))))
 
+;; Platform detection
+(define macos-build?
+  (let ((mt (symbol->string (machine-type))))
+    (and (>= (string-length mt) 3)
+         (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx"))))
+(define shlib-ext (if macos-build? "dylib" "so"))
+
 ;; Check critical dependencies
 ;; For static builds, skip checks on .o files that this script compiles in step 5
 ;; (jemacs-qt-chez-shim.o, pcre2_shim.o). Only check pre-existing external deps.
@@ -101,15 +108,18 @@
       (printf "Error: ~a not found at ~a~n" label path)
       (exit 1)))
   (if jemacs-static?
+    ;; Static: need pre-compiled jerboa core and libqt_shim.a
     (list (format "~a/jerboa/core.so" jerboa-dir)
           (format "~a/libqt_shim.a" qt-shim-dir))
-    (list (format "~a/jerboa/core.so" jerboa-dir)
-          "qt_chez_shim.so"
-          (format "~a/pcre2_shim.so" pcre2-dir)
-          (format "~a/libqt_shim.so" qt-shim-dir)))
+    ;; Dynamic: only check shim files — Scheme libs are compiled by step 1
+    (list (format "qt_chez_shim.~a" shlib-ext)
+          (format "~a/pcre2_shim.~a" pcre2-dir shlib-ext)
+          (format "libqt_shim.~a" shlib-ext)))
   (if jemacs-static?
     (list "jerboa core.so" "libqt_shim.a")
-    (list "jerboa core.so" "qt_chez_shim.so" "pcre2_shim.so" "libqt_shim.so")))
+    (list (format "qt_chez_shim.~a" shlib-ext)
+          (format "pcre2_shim.~a" shlib-ext)
+          (format "libqt_shim.~a" shlib-ext))))
 
 (printf "Chez dir:      ~a~n" chez-dir)
 (printf "Jerboa dir:    ~a~n" jerboa-dir)
@@ -377,12 +387,15 @@
     (unless (= 0 (system cmd))
       (display "Error: qt_chez_shim compilation failed\n")
       (exit 1)))
-  ;; Shared .so for dynamic builds
+  ;; Shared library for dynamic builds (platform-appropriate flags + extension)
   (unless jemacs-static?
-    (let ((cmd (format "gcc -shared -fPIC -O2 -DQT_SCINTILLA_AVAILABLE -o qt_chez_shim.so vendor/qt_chez_shim.c -I~a ~a -Wall 2>&1"
-                       qt-shim-dir qt-cflags)))
+    (let* ((shlib-flags (if macos-build?
+                            "-dynamiclib -Wl,-undefined,dynamic_lookup"
+                            "-shared -fPIC"))
+           (cmd (format "gcc ~a -O2 -DQT_SCINTILLA_AVAILABLE -o qt_chez_shim.~a vendor/qt_chez_shim.c -I~a ~a -Wall 2>&1"
+                        shlib-flags shlib-ext qt-shim-dir qt-cflags)))
       (unless (= 0 (system cmd))
-        (display "Error: qt_chez_shim.so compilation failed\n")
+        (display "Error: qt_chez_shim shlib compilation failed\n")
         (exit 1)))))
 
 ;; Static: generate + compile foreign symbol registration table
@@ -446,9 +459,9 @@ echo OK"
         (display "Error: qt_static_symbols.c compilation failed\n")
         (exit 1)))))
 
-;; jsh FFI shim (needed for static builds so ffi_* symbols are in the binary)
+;; jsh FFI shim (needed for static builds and macOS dynamic builds so ffi_* symbols are in the binary)
 ;; ffi-shim.c lives in the jsh root (parent of jsh-dir which is the src/ subdir)
-(when jemacs-static?
+(when (or jemacs-static? macos-build?)
   (let* ((jsh-root (path-parent jsh-dir))
          (cmd (format "gcc -c -O2 -o jemacs-qt-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
                       jsh-root)))
@@ -457,7 +470,7 @@ echo OK"
       (exit 1))))
 
 ;; jsh embed-crypto (pure C crypto for embed encryption — no external deps)
-(when jemacs-static?
+(when (or jemacs-static? macos-build?)
   (let* ((jsh-root (path-parent jsh-dir))
          (cmd (format "gcc -c -O2 -o jemacs-qt-embed-crypto.o ~a/embed-crypto.c -I~a -Wall 2>&1"
                       jsh-root jsh-root)))
@@ -466,7 +479,7 @@ echo OK"
       (exit 1))))
 
 ;; jsh ssh-agent stub (chez_ssh_agent_stop referenced in main.sls but not needed for jemacs)
-(when jemacs-static?
+(when (or jemacs-static? macos-build?)
   (let ((stub-file "jemacs-qt-ssh-agent-stub.c"))
     (call-with-output-file stub-file
       (lambda (out)
@@ -517,6 +530,88 @@ echo OK"
       (display "Error: landlock-shim.c compilation failed\n")
       (exit 1))))
 
+;; macOS: generate symbol registration table.
+;; Two-part: (1) custom symbols from .o files via nm (exact, no undefined refs)
+;;           (2) system/POSIX symbols via dlsym(RTLD_DEFAULT, name) at runtime
+;;               (covers open, close, fork, etc. that live in libSystem.dylib)
+(when (and macos-build? (not jemacs-static?))
+  (let* ((include-dir chez-dir)
+         ;; Part 1: extract custom symbol names from linked .o files
+         (nm-cmd
+           "nm -gU jemacs-qt-jsh-ffi.o jemacs-qt-embed-crypto.o jemacs-qt-ssh-agent-stub.o jemacs-qt-chez-shim.o 2>/dev/null | \
+awk '$2 == \"T\" || $2 == \"t\" { print $NF }' | sed 's/^_//' | sort -u | \
+grep -v '^$' | grep -v '^register_static_foreign_symbols$'")
+         (custom-syms
+           (let* ((tmpf "/tmp/macos_nm_syms.txt")
+                  (_ (system (format "~a > ~a 2>/dev/null" nm-cmd tmpf)))
+                  (p (if (file-exists? tmpf) (open-input-file tmpf) #f))
+                  (lines (if p
+                             (let loop ((acc '()))
+                               (let ((line (get-line p)))
+                                 (if (eof-object? line)
+                                     (begin (close-port p) (delete-file tmpf) (reverse acc))
+                                     (loop (if (> (string-length line) 0)
+                                               (cons line acc) acc)))))
+                             '())))
+             lines))
+         ;; Part 2: POSIX/system symbol names to register via RTLD_DEFAULT
+         (system-syms
+           '("_exit" "__error"
+             "accept" "access" "bind" "chdir" "chmod" "chown" "chroot"
+             "close" "connect" "dup" "dup2" "execve" "execvp"
+             "fcntl" "fdopen" "flock" "fork"
+             "freeaddrinfo" "fstat" "ftruncate"
+             "getaddrinfo" "getegid" "geteuid" "getgid" "getgrnam"
+             "getpagesize" "getpgid" "getpid" "getppid"
+             "getpwnam" "getrlimit" "getsockname" "getuid"
+             "htons" "inet_ntop" "inet_pton" "ioctl" "isatty"
+             "kevent" "kill" "kqueue" "listen" "localtime"
+             "lseek" "lstat" "madvise" "mkdir" "mkdtemp"
+             "mkfifo" "mkstemp" "mmap" "msync" "munmap"
+             "ntohs" "open" "pipe" "read" "readlink" "realpath"
+             "recvfrom" "rmdir" "sandbox_init" "sandbox_free_error"
+             "sendto" "setegid" "setenv" "seteuid" "setgid"
+             "setpgid" "setrlimit" "setsid" "setsockopt" "setuid"
+             "sigaddset" "sigdelset" "sigemptyset" "sigfillset"
+             "sigismember" "sigprocmask" "sigwait"
+             "socket" "stat" "strerror" "strftime"
+             "syscall" "sysconf" "tcgetattr" "tcgetpgrp"
+             "tcsetattr" "tcsetpgrp"
+             "umask" "unlink" "unsetenv" "waitpid" "write")))
+    ;; Write qt_static_symbols.c
+    (call-with-output-file "qt_static_symbols.c"
+      (lambda (out)
+        (fprintf out "/* Auto-generated (macOS) — do not edit */\n")
+        (fprintf out "#include \"scheme.h\"\n")
+        (fprintf out "#include <dlfcn.h>\n\n")
+        ;; Forward declare custom symbols
+        (for-each (lambda (sym)
+                    (fprintf out "extern void ~a(void);\n" sym))
+                  custom-syms)
+        (fprintf out "\nvoid register_static_foreign_symbols(void) {\n")
+        ;; Register custom symbols
+        (for-each (lambda (sym)
+                    (fprintf out "    Sforeign_symbol(\"~a\", (void*)~a);\n" sym sym))
+                  custom-syms)
+        ;; Register system symbols via RTLD_DEFAULT
+        (fprintf out "    /* System/POSIX symbols via RTLD_DEFAULT */\n")
+        (fprintf out "    { void *_s;\n")
+        (for-each (lambda (sym)
+                    (fprintf out "    if ((_s = dlsym(RTLD_DEFAULT, \"~a\"))) Sforeign_symbol(\"~a\", _s);\n"
+                             sym sym))
+                  system-syms)
+        (fprintf out "    }\n")
+        (fprintf out "}\n"))
+      'replace)
+    (let ((total (+ (length custom-syms) (length system-syms))))
+      (printf "  Generated qt_static_symbols.c with ~a symbol registrations (~a custom + ~a system)~n"
+              total (length custom-syms) (length system-syms)))
+    (let* ((cmd (format "gcc -c -O2 -o qt_static_symbols.o qt_static_symbols.c -I~a -Wall 2>&1"
+                        include-dir)))
+      (unless (= 0 (system cmd))
+        (display "Error: qt_static_symbols.c (macOS) compilation failed\n")
+        (exit 1)))))
+
 ;; jemacs-qt-main.c
 (let* ((static-flag (if jemacs-static? "-DJEMACS_STATIC_BUILD" ""))
        (cmd (format "gcc -c -O2 ~a -o jemacs-qt-main.o jemacs-qt-main.c -I~a -I. -Wall 2>&1"
@@ -570,12 +665,37 @@ qt_static_symbols.o \
       (display "Error: Static link failed\n")
       (exit 1)))
   ;; ─── Dynamic link (default local build) ────────────────────────────────
-  (let* ((pcre2-libs (shell-output "pkg-config --libs libpcre2-8" "-lpcre2-8"))
+  (let* ((macos? (string=? "Darwin" (shell-output "uname -s" "Linux")))
+         (pcre2-libs (shell-output "pkg-config --libs libpcre2-8" "-lpcre2-8"))
          (qt-libs    (shell-output "pkg-config --libs Qt6Widgets" "-lQt6Widgets -lQt6Gui -lQt6Core"))
-         ;; Link against ./libqt_shim.so (local copy with JEMACS_CHEZ_SMP)
-         ;; rather than qt-shim-dir (gerbil-qt vendor — no Sdeactivate)
-         (cmd (format "g++ -rdynamic -o jemacs-qt jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o ~a ~a -L~a -lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses -lstdc++ -L. -lqt_shim -lqscintilla2_qt6 -lvterm -Wl,-rpath,~a -Wl,-rpath,'$ORIGIN' 2>&1"
-                      pcre2-libs qt-libs chez-dir chez-dir)))
+         (qsci-dir   (shell-output "brew --prefix qscintilla2 2>/dev/null" ""))
+         (vterm-dir  (shell-output "brew --prefix libvterm 2>/dev/null" ""))
+         (ncurses-dir (shell-output "brew --prefix ncurses 2>/dev/null" ""))
+         ;; Link against ./libqt_shim.dylib/.so (local copy with JEMACS_CHEZ_SMP)
+         (cmd
+           (if macos?
+             ;; macOS: frameworks, no -rdynamic/-uuid, use @executable_path rpath
+             (format "g++ -o jemacs-qt \
+jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o jemacs-qt-jsh-ffi.o jemacs-qt-embed-crypto.o jemacs-qt-ssh-agent-stub.o qt_static_symbols.o \
+~a ~a \
+-L~a -lkernel -llz4 -lz -lm -ldl -lpthread -liconv -lstdc++ \
+-L. -lqt_shim \
+-L~a/lib -lqscintilla2_qt6 \
+-L~a/lib -lvterm \
+-L~a/lib -lncurses \
+-Wl,-rpath,~a -Wl,-rpath,@executable_path 2>&1"
+                     pcre2-libs qt-libs
+                     chez-dir
+                     qsci-dir vterm-dir ncurses-dir
+                     chez-dir)
+             ;; Linux: standard dynamic link
+             (format "g++ -rdynamic -o jemacs-qt \
+jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o \
+~a ~a \
+-L~a -lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses -lstdc++ \
+-L. -lqt_shim -lqscintilla2_qt6 -lvterm \
+-Wl,-rpath,~a -Wl,-rpath,'$ORIGIN' 2>&1"
+                     pcre2-libs qt-libs chez-dir chez-dir))))
     (printf "  ~a~n" cmd)
     (unless (= 0 (system cmd))
       (display "Error: Link failed\n")
@@ -611,15 +731,14 @@ qt_static_symbols.o \
     (printf "~nInstall:~n")
     (printf "  cp jemacs-qt /usr/local/bin/~n"))
   (begin
-    ;; Copy shim .so files alongside binary for dynamic builds
-    (system (format "cp ~a/libqt_shim.so . 2>/dev/null; true" qt-shim-dir))
-    ;; qt_chez_shim.so already built locally from vendor/qt_chez_shim.c
-    (system (format "cp ~a/pcre2_shim.so . 2>/dev/null; true" pcre2-dir))
+    ;; Copy shim files alongside binary for dynamic builds
+    ;; (libqt_shim is already built locally; pcre2_shim may need copying)
+    (system (format "cp ~a/pcre2_shim.~a . 2>/dev/null; true" pcre2-dir shlib-ext))
     (printf "~nBundle (keep these together):~n")
     (printf "  ./jemacs-qt~n")
-    (printf "  ./libqt_shim.so~n")
-    (printf "  ./qt_chez_shim.so~n")
-    (printf "  ./pcre2_shim.so~n")
+    (printf "  ./libqt_shim.~a~n" shlib-ext)
+    (printf "  ./qt_chez_shim.~a~n" shlib-ext)
+    (printf "  ./pcre2_shim.~a~n" shlib-ext)
     (printf "  ./vterm_shim.so~n")
     (printf "~nRun:~n")
     (printf "  ./jemacs-qt                # launch Qt editor~n")
diff --git a/jemacs-qt-main.c b/jemacs-qt-main.c
index c7e6e0a..d808f6f 100644
--- a/jemacs-qt-main.c
+++ b/jemacs-qt-main.c
@@ -1,30 +1,27 @@
 /*
  * jemacs-qt-main.c — Custom entry point for jemacs-qt (jerboa-emacs Qt frontend).
  *
- * Same threading workaround as jemacs-main.c:
  *   - Boot file contains only libraries (no program)
- *   - Program is loaded via Sscheme_script on a memfd
+ *   - Program is loaded via Sscheme_script from an in-memory file
+ *     (memfd on Linux, temp file on macOS)
  *
- * The Qt event loop runs on its own pthread (created by qt-app-create in
- * qt_shim.cpp). That pthread registers via Sactivate_thread/Sdeactivate_thread
- * (implemented in qt_chez_shim.c) before invoking Chez foreign-callable
- * trampolines.  No special handling is needed here.
- *
- * The CHEZ_QT_LIB env var points qt_chez_shim to where qt_chez_shim.so
- * lives (for load-shared-object). We point it at the binary's own directory
- * since qt_chez_shim is compiled into the binary itself — but we still need
- * the libqt_shim.so (gerbil-qt vendor shim) to be loadable.
- * Set CHEZ_QT_SHIM_DIR to where libqt_shim.so lives (same dir as binary,
- * or let the rpath handle it).
+ * On macOS, Qt runs on the main thread (Cocoa requirement). The event loop
+ * is driven by repeated processEvents() calls from the Scheme polling loop.
  */
 
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#else
 #define _GNU_SOURCE
+#include <sys/mman.h>
+#endif
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <libgen.h>
-#include <sys/mman.h>
+#include <fcntl.h>
 #include "scheme.h"
 #include "jemacs_qt_program.h"         /* jemacs_qt_program_data[], jemacs_qt_program_size */
 #include "jemacs_qt_petite_boot.h"     /* petite_boot_data[], petite_boot_size */
@@ -35,16 +32,27 @@ int main(int argc, char *argv[]) {
     /* Resolve real executable path */
     char exe_buf[4096];
     char exe_dir[4096];
+    int got_exe = 0;
+
+#ifdef __APPLE__
+    uint32_t exe_size = sizeof(exe_buf);
+    if (_NSGetExecutablePath(exe_buf, &exe_size) == 0) {
+        got_exe = 1;
+    }
+#else
     ssize_t len = readlink("/proc/self/exe", exe_buf, sizeof(exe_buf) - 1);
     if (len > 0) {
         exe_buf[len] = '\0';
+        got_exe = 1;
+    }
+#endif
+
+    if (got_exe) {
         setenv("JEMACS_EXE", exe_buf, 1);
         strncpy(exe_dir, exe_buf, sizeof(exe_dir) - 1);
         exe_dir[sizeof(exe_dir) - 1] = '\0';
         char *dir = dirname(exe_dir);
-        /* Point FFI shim loaders at the binary's directory.
-         * The shims (qt_chez_shim, pcre2_shim) are compiled into the binary,
-         * and libqt_shim.so must be alongside the binary (rpath or LD_LIBRARY_PATH). */
+        /* Point FFI shim loaders at the binary's directory */
         if (!getenv("CHEZ_QT_LIB"))
             setenv("CHEZ_QT_LIB", dir, 0);
         if (!getenv("CHEZ_QT_SHIM_DIR"))
@@ -55,20 +63,52 @@ int main(int argc, char *argv[]) {
             setenv("CHEZ_SCINTILLA_LIB", dir, 0);
     }
 
-    /* Create memfd for embedded program .so */
-    int fd = memfd_create("jemacs-qt-program", MFD_CLOEXEC);
+    /* Write embedded program .so to a file Chez can dlopen */
+    char prog_path[256];
+    int fd;
+
+#ifdef __APPLE__
+    /* macOS: no memfd — write to a temp file */
+    snprintf(prog_path, sizeof(prog_path), "/tmp/jemacs-qt-program.XXXXXX");
+    fd = mkstemp(prog_path);
+    if (fd < 0) {
+        perror("mkstemp");
+        return 1;
+    }
+    /* Rename to add .so extension so dlopen recognises it as a shared lib */
+    char prog_path_so[256];
+    snprintf(prog_path_so, sizeof(prog_path_so), "%s.so", prog_path);
+    if (rename(prog_path, prog_path_so) != 0) {
+        perror("rename");
+        close(fd);
+        return 1;
+    }
+    close(fd);
+    fd = open(prog_path_so, O_WRONLY | O_CREAT | O_TRUNC, 0700);
+    if (fd < 0) {
+        perror("open prog_path_so");
+        return 1;
+    }
+    strncpy(prog_path, prog_path_so, sizeof(prog_path) - 1);
+#else
+    /* Linux: memfd — anonymous in-memory file */
+    fd = memfd_create("jemacs-qt-program", MFD_CLOEXEC);
     if (fd < 0) {
         perror("memfd_create");
         return 1;
     }
+    snprintf(prog_path, sizeof(prog_path), "/proc/self/fd/%d", fd);
+#endif
+
     if (write(fd, jemacs_qt_program_data, jemacs_qt_program_size)
             != (ssize_t)jemacs_qt_program_size) {
-        perror("write memfd");
+        perror("write prog");
         close(fd);
         return 1;
     }
-    char prog_path[64];
-    snprintf(prog_path, sizeof(prog_path), "/proc/self/fd/%d", fd);
+#ifdef __APPLE__
+    close(fd);  /* file is on disk — close write fd before dlopen */
+#endif
 
     /* Initialize Chez Scheme */
     Sscheme_init(NULL);
@@ -79,30 +119,24 @@ int main(int argc, char *argv[]) {
     Sregister_boot_file_bytes("jemacs-qt", (void*)jemacs_qt_boot_data,  jemacs_qt_boot_size);
 
 #ifdef JEMACS_STATIC_BUILD
-    /* Tell Scheme libraries they are in a static build so they skip
-     * load-shared-object calls (dlopen("file.so") fails in musl static).
-     * Must be set before Sbuild_heap so library bodies see it. */
     setenv("JEMACS_STATIC", "1", 1);
 #endif
 
     /* Build heap from libraries only — no program */
     Sbuild_heap(NULL, NULL);
 
-#ifdef JEMACS_STATIC_BUILD
-    /* Register all FFI symbols after heap is built.
-     * Sforeign_symbol requires an initialized Scheme heap.
-     * foreign-procedure in library bodies uses dlsym(RTLD_DEFAULT) first;
-     * this call supplements that for any symbols missed by dlsym. */
+#if defined(JEMACS_STATIC_BUILD) || defined(__APPLE__)
     extern void register_static_foreign_symbols(void);
     register_static_foreign_symbols();
 #endif
 
-    /* Run via Sscheme_script so fork-thread works.
-     * Pass full argv so argv[0]=binary name and argv[1..]=user args.
-     * (command-line-arguments) returns ("./jemacs-qt" args...) — member checks work. */
     int status = Sscheme_script(prog_path, argc, (const char **)argv);
 
+#ifdef __APPLE__
+    unlink(prog_path);  /* clean up temp file */
+#else
     close(fd);
+#endif
     Sscheme_deinit();
     return status;
 }