diff --git a/host/chez/build-smoke.sh b/host/chez/build-smoke.sh index 68480df..b5c4e5f 100755 --- a/host/chez/build-smoke.sh +++ b/host/chez/build-smoke.sh @@ -129,4 +129,20 @@ if [ "$got_dr" != "42" ]; then echo " FAIL: built #code data reader — want 42, got \`$got_dr\`"; exit 1 fi -echo "build smoke: passed (release + optimized + direct-link + tree-shake + compiler+core shake + data-reader)" +# A script namespace with no -main (just top-level side effects) must build and +# run its top-level forms, then exit cleanly — not crash calling a nil -main. +nomain="$(dirname "$out")/nomain" +mkdir -p "$nomain/src" +printf '{:paths ["src"]}\n' > "$nomain/deps.edn" +printf '(ns script)\n(println "no-main script ran")\n' > "$nomain/src/script.clj" +nmout="$(dirname "$out")/nomain-bin" +if ! JOLT_PWD="$nomain" bin/joltc build -m script -o "$nmout" >/dev/null 2>&1; then + echo " FAIL: jolt build of a no-main script exited non-zero"; exit 1 +fi +got_nm="$(cd / && "$nmout" 2>&1)"; rc_nm=$? +if [ "$got_nm" != "no-main script ran" ] || [ "$rc_nm" != "0" ]; then + echo " FAIL: no-main script binary — want 'no-main script ran' rc 0, got \`$got_nm\` rc $rc_nm" + exit 1 +fi + +echo "build smoke: passed (release + optimized + direct-link + tree-shake + compiler+core shake + data-reader + no-main)" diff --git a/host/chez/build.ss b/host/chez/build.ss index 1fb4812..4803f41 100644 --- a/host/chez/build.ss +++ b/host/chez/build.ss @@ -571,11 +571,17 @@ "))\n" " (list \"jolt-core\" \"stdlib\"))))\n")) (put-string out (string-append - " (let ((mainv (var-deref " (ei-str-lit entry-ns) " \"-main\")))\n" + ;; Call -main only if the entry namespace defines one; + ;; a script ns (top-level side effects, no -main) has + ;; already run its forms at heap build, so invoking a nil + ;; -main would crash ("nil cannot be cast to IFn") — just + ;; exit cleanly instead. + " (let ((maincell (var-cell-lookup " (ei-str-lit entry-ns) " \"-main\")))\n" ;; render an uncaught throw (+ Clojure backtrace) instead ;; of Chez's opaque dump, then exit non-zero. " (guard (v (#t (jolt-report-throwable v (current-error-port)) (exit 1)))\n" - " (apply jolt-invoke mainv args)))\n" + " (when (and maincell (var-cell-defined? maincell))\n" + " (apply jolt-invoke (var-cell-root maincell) args))))\n" " (exit 0)))\n")) (close-port out)) ;; 4. compile -> boot -> link. Two paths, chosen by whether this process