Add Rust/C extras + .jerbuild config to `jerbuild binary`
ober
2452c2feeb1be87827f3ea37f2fe170e51b952e0
--- a/jerbuild.ss +++ b/jerbuild.ss @@ -1503,131 +1503,448 @@ int main(int argc, const char *argv[]) { } ") +;;;; ============================================================ +;;;; Helpers for binary subcommand extras (Rust crates, C shims) +;;;; ============================================================ + +(define (parse-multi-flag args flag-name) + ;; Collect every occurrence of --<flag-name> <value> (or =<value>). + ;; Returns (values values-list remaining-args), preserving order. + (let ([eq-prefix (string-append flag-name "=")]) + (let loop ([args args] [vals '()] [acc '()]) + (cond + [(null? args) (values (reverse vals) (reverse acc))] + [(string=? (car args) flag-name) + (when (null? (cdr args)) + (error 'jerbuild (format "~a requires a value" flag-name))) + (loop (cddr args) (cons (cadr args) vals) acc)] + [(string-starts-with? (car args) eq-prefix) + (let ([val (substring (car args) + (string-length eq-prefix) + (string-length (car args)))]) + (loop (cdr args) (cons val vals) acc))] + [else + (loop (cdr args) vals (cons (car args) acc))])))) + +(define (parse-rust-crate-spec spec) + ;; "path/Cargo.toml" -> (values "path/Cargo.toml" #f) + ;; "path/Cargo.toml:crypto,sqlite" -> (values "path/Cargo.toml" "crypto,sqlite") + ;; Splits on the FIRST colon. Cargo.toml paths with colons are not supported. + (let ([len (string-length spec)]) + (let loop ([i 0]) + (cond + [(>= i len) (values spec #f)] + [(char=? (string-ref spec i) #\:) + (values (substring spec 0 i) + (substring spec (+ i 1) len))] + [else (loop (+ i 1))])))) + +(define (path-dirname p) + (let loop ([i (- (string-length p) 1)]) + (cond + [(< i 0) "."] + [(char=? (string-ref p i) #\/) + (if (zero? i) "/" (substring p 0 i))] + [else (loop (- i 1))]))) + +(define (path-basename p) + (let loop ([i (- (string-length p) 1)]) + (cond + [(< i 0) p] + [(char=? (string-ref p i) #\/) + (substring p (+ i 1) (string-length p))] + [else (loop (- i 1))]))) + +(define (list-archives-in dir) + ;; Top-level .a files under dir as absolute paths. Empty list if dir missing. + (if (file-exists? dir) + (sort string<? + (map (lambda (n) (format "~a/~a" dir n)) + (filter (lambda (n) (string-ends-with? n ".a")) + (directory-list dir)))) + '())) + +(define (build-rust-crate spec) + ;; spec: "path/Cargo.toml[:features]" + ;; Drives `cargo build --release [--features F]`; returns list of .a paths. + (let-values ([(cargo-toml features) (parse-rust-crate-spec spec)]) + (unless (file-exists? cargo-toml) + (error 'jerbuild (format "rust-crate: Cargo.toml not found: ~a" cargo-toml))) + (let* ([manifest-dir (path-dirname cargo-toml)] + [features-arg (if features + (format " --features ~a" (shell-quote features)) + "")] + [cmd (format "cargo build --release --manifest-path ~a~a" + (shell-quote cargo-toml) features-arg)]) + (printf " ~a\n" cmd) + (let ([rc (system cmd)]) + (unless (zero? rc) + (error 'jerbuild + (format "rust-crate: cargo build failed (rc=~a) for ~a" + rc cargo-toml)))) + (let* ([release-dir (format "~a/target/release" manifest-dir)] + [archives (list-archives-in release-dir)]) + (when (null? archives) + (error 'jerbuild + (format "rust-crate: no .a files found in ~a after cargo build" + release-dir))) + (for-each (lambda (a) (printf " -> ~a\n" a)) archives) + archives)))) + +(define (compile-c-source src obj-dir cc csv-dir index) + ;; Compile src to obj-dir/extra-<index>-<basename-without-.c>.o + (let* ([base (path-basename src)] + [stem (if (string-ends-with? base ".c") + (substring base 0 (- (string-length base) 2)) + base)] + [out (format "~a/extra-~a-~a.o" obj-dir index stem)] + [cmd (format "~a -I~a -O2 -c ~a -o ~a" + cc + (shell-quote csv-dir) + (shell-quote src) + (shell-quote out))]) + (printf " ~a\n" cmd) + (let ([rc (system cmd)]) + (unless (zero? rc) + (error 'jerbuild + (format "extra-source: cc failed (rc=~a) for ~a" rc src)))) + out)) + +(define (join-quoted items) + (apply string-append + (map (lambda (x) (string-append " " (shell-quote x))) items))) + +(define (join-raw items) + (apply string-append + (map (lambda (x) (string-append " " x)) items))) + (define (run-binary args) - ;; jerbuild binary [--libdirs <p>] [--cc <prog>] <entry.ss> <output> + ;; jerbuild binary [--libdirs P] [--cc CC] + ;; [--extra-archive A] (repeatable) + ;; [--extra-source S.c] (repeatable) + ;; [--extra-ldflag F] (repeatable) + ;; [--rust-crate Cargo.toml[:features]] (repeatable) + ;; <entry.ss> <output> ;; ;; Builds a standalone executable that bundles Chez + the user's entry script - ;; (WPO compiled). The user gets a single binary with no external deps beyond - ;; system libc + libncurses. - (let-values ([(libs rest1) (parse-libdirs-flag args)]) - (let-values ([(cc-arg rest) (parse-cc-flag rest1)]) - (when (< (length rest) 2) + ;; (WPO compiled), plus any prebuilt archives, freshly-built Rust crates, and + ;; compiled C shims the project needs. + (let-values ([(libs args) (parse-libdirs-flag args)]) + (let-values ([(cc-arg args) (parse-cc-flag args)]) + (let-values ([(extra-archives args) (parse-multi-flag args "--extra-archive")]) + (let-values ([(extra-sources args) (parse-multi-flag args "--extra-source")]) + (let-values ([(extra-ldflags args) (parse-multi-flag args "--extra-ldflag")]) + (let-values ([(rust-crates rest) (parse-multi-flag args "--rust-crate")]) + (do-binary-build libs cc-arg extra-archives extra-sources + extra-ldflags rust-crates rest)))))))) + +(define (do-binary-build libs cc-arg extra-archives extra-sources + extra-ldflags rust-crates rest) + (when (< (length rest) 2) + (error 'jerbuild + "binary: usage: jerbuild binary [--libdirs P] [--cc CC] [--extra-archive A] [--extra-source S.c] [--extra-ldflag F] [--rust-crate Cargo.toml[:features]] <entry.ss> <output>")) + (let* ([entry (car rest)] + [output (cadr rest)] + [cc (or cc-arg (or (getenv "CC") "cc"))] + [bundle-dir (or (getenv "JERBUILD_BUNDLE_DIR") + (error 'jerbuild + "binary: JERBUILD_BUNDLE_DIR not set (jerbuild internal bug)"))] + [mt (machine-type)] + [csv-dir (format "~a/csv/~a" bundle-dir mt)] + [petite-boot (format "~a/petite.boot" csv-dir)] + [scheme-boot (format "~a/scheme.boot" csv-dir)] + [libkernel (format "~a/libkernel.a" csv-dir)] + [scheme-h (format "~a/scheme.h" csv-dir)]) + + (unless (file-exists? entry) + (error 'jerbuild (format "binary: entry not found: ~a" entry))) + (for-each + (lambda (a) + (unless (file-exists? a) + (error 'jerbuild (format "binary: --extra-archive not found: ~a" a)))) + extra-archives) + (for-each + (lambda (s) + (unless (file-exists? s) + (error 'jerbuild (format "binary: --extra-source not found: ~a" s)))) + extra-sources) + (unless (and (file-exists? libkernel) (file-exists? scheme-h) + (file-exists? petite-boot) (file-exists? scheme-boot)) + (error 'jerbuild + (format "binary: required Chez files missing under ~a (bundle was built without csv files for this machine-type)" + csv-dir))) + + (let* ([bundle-lib (format "~a/lib" bundle-dir)] + [user-libs (let loop ([xs libs] [seen '()] [acc '()]) + (cond + [(null? xs) (reverse acc)] + [(or (member (car xs) seen) + (string=? (car xs) bundle-lib)) + (loop (cdr xs) seen acc)] + [else + (loop (cdr xs) + (cons (car xs) seen) + (cons (car xs) acc))]))] + [obj-dir (format "/tmp/jerbuild-binary-~a" (get-process-id))] + [program-so (format "~a/program.so" obj-dir)] + [program-wpo (format "~a/program.wpo" obj-dir)] + [program-wp-so (format "~a/program.wp.so" obj-dir)] + [petite-hdr (format "~a/petite_boot.h" obj-dir)] + [scheme-hdr (format "~a/scheme_boot.h" obj-dir)] + [program-hdr (format "~a/program_boot.h" obj-dir)] + [main-c (format "~a/main.c" obj-dir)]) + + (printf "=== jerbuild binary build ===\n") + (printf " Entry: ~a\n" entry) + (printf " Output: ~a\n" output) + (printf " Host: ~a\n" mt) + (printf " CC: ~a\n" cc) + (printf " Chez: ~a\n" csv-dir) + (unless (null? rust-crates) + (printf " Rust crates: ~a\n" (length rust-crates))) + (unless (null? extra-archives) + (printf " Extra archives: ~a\n" (length extra-archives))) + (unless (null? extra-sources) + (printf " Extra sources: ~a\n" (length extra-sources))) + (newline) + + (system (format "mkdir -p ~a" (shell-quote obj-dir))) + + ;; Set library-directories: user libs redirect to obj-dir (so + ;; compile-imported-libraries doesn't pollute user source trees); + ;; bundle lib is plain (precompiled .so/.wpo live there already). + ;; + ;; jerbuild's own WPO image internalizes libraries reachable from + ;; (jerboa build) — (jerboa core), (std misc string), (std pregexp), etc. + ;; When user code imports any of these, Chez treats them as "already + ;; loaded" and skips compilation, so compile-whole-program needs their + ;; .wpo files on disk. build-jerbuild.sh stages them next to the .sls + ;; sources in the bundle. + (library-directories + (append (map (lambda (l) (cons l obj-dir)) user-libs) + (list bundle-lib))) + + (compile-imported-libraries #t) + (generate-wpo-files #t) + + (printf "==> [1/5] WPO compile ~a\n" entry) + (compile-program entry program-so) + (compile-whole-program program-wpo program-wp-so #t) + + (let* ([rust-archives + (cond + [(null? rust-crates) '()] + [else + (printf "==> [2/5] Build Rust crates (~a)\n" (length rust-crates)) + (apply append (map build-rust-crate rust-crates))])] + [user-objs + (cond + [(null? extra-sources) '()] + [else + (printf "==> [2/5] Compile C sources (~a)\n" (length extra-sources)) + (let loop ([srcs extra-sources] [i 0] [acc '()]) + (cond + [(null? srcs) (reverse acc)] + [else + (loop (cdr srcs) + (+ i 1) + (cons (compile-c-source + (car srcs) obj-dir cc csv-dir i) + acc))]))])]) + + (printf "==> [3/5] Embed boots + program as C arrays\n") + (embed-bytes-as-c-array petite-boot petite-hdr "petite_boot") + (embed-bytes-as-c-array scheme-boot scheme-hdr "scheme_boot") + (embed-bytes-as-c-array program-wp-so program-hdr "program_boot") + + (printf "==> [4/5] Generate main.c\n") + (call-with-output-file main-c + (lambda (port) (display *binary-main-c-template* port)) + 'replace) + + (printf "==> [5/5] Compile + link -> ~a\n" output) + (let* ([chez-archives + (apply string-append + (map (lambda (a) + (let ([p (format "~a/~a" csv-dir a)]) + (if (file-exists? p) + (string-append " " (shell-quote p)) + ""))) + '("liblz4.a" "libz.a")))] + ;; Link order: main.c -> user .o -> Rust .a -> extra .a -> + ;; libkernel -> lz4/z -> extra ldflags -> OS libs. + ;; Anything that uses Scheme_* symbols must come before libkernel; + ;; anything libkernel needs (lz4/z, ncurses) must come after. + [cc-cmd (format "~a -I~a -I~a -O2 -o ~a ~a~a~a~a ~a~a~a ~a" + cc + (shell-quote obj-dir) + (shell-quote csv-dir) + (shell-quote output) + (shell-quote main-c) + (join-quoted user-objs) + (join-quoted rust-archives) + (join-quoted extra-archives) + (shell-quote libkernel) + chez-archives + (join-raw extra-ldflags) + (machine-type->os-libs mt))]) + (printf " ~a\n" cc-cmd) + (let ([rc (system cc-cmd)]) + (unless (zero? rc) + (error 'jerbuild (format "binary: cc exited ~a" rc)))))) + + (system (format "rm -rf ~a" (shell-quote obj-dir))) + (printf "\n=== Build complete: ~a ===\n" output)))) + +;;;; ============================================================ +;;;; .jerbuild config file (per-repo build settings, s-expression) +;;;; ============================================================ +;; +;; Recognized top-level forms: +;; +;; (entry "path/main.ss") ; required +;; (output "binary-name") ; required +;; (libdirs "." "src") ; optional, zero or more +;; (cc "cc") ; optional; CLI --cc overrides +;; (rust-crates +;; "path/Cargo.toml" +;; ("other/Cargo.toml" features: "crypto,sqlite")) +;; (extra-sources "shim.c" "other.c") ; optional +;; (extra-archives "vendor/lib.a") ; optional +;; (extra-ldflags "-framework" "Security") ; optional, raw strings +;; +;; All paths are resolved relative to the .jerbuild file's directory. + +(define (read-all-sexps path) + (call-with-input-file path + (lambda (port) + (let loop ([acc '()]) + (let ([s (read port)]) + (if (eof-object? s) + (reverse acc) + (loop (cons s acc)))))))) + +(define (find-config-from dir) + ;; Walk up from dir looking for .jerbuild. Returns absolute path or #f. + (let ([candidate (format "~a/.jerbuild" dir)]) + (cond + [(file-exists? candidate) candidate] + [else + (let ([parent (path-dirname dir)]) + (if (or (string=? parent dir) (string=? parent "")) + #f + (find-config-from parent)))]))) + +(define (absolute-path? p) + (and (positive? (string-length p)) + (char=? (string-ref p 0) #\/))) + +(define (resolve-config-path p base) + (if (absolute-path? p) p (format "~a/~a" base p))) + +(define (find-keyword key plist) + ;; Property-list lookup: (k1: v1 k2: v2) -> v1 for k1:. + (let loop ([xs plist]) + (cond + [(or (null? xs) (null? (cdr xs))) #f] + [(eq? (car xs) key) (cadr xs)] + [else (loop (cddr xs))]))) + +(define (config-rust-crate->spec form base) + (cond + [(string? form) (resolve-config-path form base)] + [(and (pair? form) (string? (car form))) + (let* ([cargo-path (resolve-config-path (car form) base)] + [features (find-keyword 'features: (cdr form))]) + (if features + (format "~a:~a" cargo-path features) + cargo-path))] + [else + (error 'jerbuild (format "config: bad rust-crate entry: ~a" form))])) + +(define (load-jerbuild-config path) + ;; Returns (values entry output libdirs cc rust-crates + ;; extra-sources extra-archives extra-ldflags). + (let* ([config-dir (path-dirname path)] + [base (if (absolute-path? config-dir) + config-dir + (format "~a/~a" (current-directory) config-dir))] + [forms (read-all-sexps path)] + [entry #f] [output #f] [cc #f] + [libdirs '()] [rust-crates '()] [extra-sources '()] + [extra-archives '()] [extra-ldflags '()]) + (for-each + (lambda (form) + (unless (and (pair? form) (symbol? (car form))) + (error 'jerbuild (format "config: expected (key ...) form, got ~a" form))) + (case (car form) + [(entry) + (unless (= (length form) 2) + (error 'jerbuild "config: (entry PATH) takes one value")) + (set! entry (resolve-config-path (cadr form) base))] + [(output) + (unless (= (length form) 2) + (error 'jerbuild "config: (output PATH) takes one value")) + (set! output (resolve-config-path (cadr form) base))] + [(cc) + (unless (= (length form) 2) + (error 'jerbuild "config: (cc CMD) takes one value")) + (set! cc (cadr form))] + [(libdirs) + (set! libdirs (map (lambda (p) (resolve-config-path p base)) (cdr form)))] + [(rust-crates) + (set! rust-crates + (map (lambda (f) (config-rust-crate->spec f base)) (cdr form)))] + [(extra-sources) + (set! extra-sources + (map (lambda (p) (resolve-config-path p base)) (cdr form)))] + [(extra-archives) + (set! extra-archives + (map (lambda (p) (resolve-config-path p base)) (cdr form)))] + [(extra-ldflags) + (set! extra-ldflags (cdr form))] + [else + (error 'jerbuild + (format "config: unknown key ~a in ~a (valid: entry output cc libdirs rust-crates extra-sources extra-archives extra-ldflags)" + (car form) path))])) + forms) + (unless entry + (error 'jerbuild (format "config: missing required (entry PATH) in ~a" path))) + (unless output + (error 'jerbuild (format "config: missing required (output PATH) in ~a" path))) + (values entry output libdirs cc rust-crates + extra-sources extra-archives extra-ldflags))) + +(define (run-build args) + ;; jerbuild build [--cc CC] [--config PATH] + ;; Reads .jerbuild from cwd (or any parent) and runs do-binary-build. + ;; --cc overrides the config's (cc ...) setting. + (let-values ([(cc-arg rest1) (parse-cc-flag args)]) + (let-values ([(config-paths rest) (parse-multi-flag rest1 "--config")]) + (unless (null? rest) (error 'jerbuild - "binary: usage: jerbuild binary [--libdirs P] [--cc CC] <entry.ss> <output>")) - (let* ([entry (car rest)] - [output (cadr rest)] - [cc (or cc-arg (or (getenv "CC") "cc"))] - [bundle-dir (or (getenv "JERBUILD_BUNDLE_DIR") - (error 'jerbuild - "binary: JERBUILD_BUNDLE_DIR not set (jerbuild internal bug)"))] - [mt (machine-type)] - [csv-dir (format "~a/csv/~a" bundle-dir mt)] - [petite-boot (format "~a/petite.boot" csv-dir)] - [scheme-boot (format "~a/scheme.boot" csv-dir)] - [libkernel (format "~a/libkernel.a" csv-dir)] - [scheme-h (format "~a/scheme.h" csv-dir)]) - - (unless (file-exists? entry) - (error 'jerbuild (format "binary: entry not found: ~a" entry))) - (unless (and (file-exists? libkernel) (file-exists? scheme-h) - (file-exists? petite-boot) (file-exists? scheme-boot)) - (error 'jerbuild - (format "binary: required Chez files missing under ~a (bundle was built without csv files for this machine-type)" - csv-dir))) - - ;; Libdirs: user-supplied first (so they can shadow), then bundled stdlib. - ;; Dedupe so passing --libdirs $bundle/lib doesn't double-add. - ;; User libs are redirected to obj-dir so compile-imported-libraries - ;; writes .so/.wpo there instead of next to user sources. Bundle lib - ;; keeps its plain mapping because precompiled .so/.wpo are bundled - ;; alongside the .sls sources (see build-jerbuild.sh step 2). - (let* ([bundle-lib (format "~a/lib" bundle-dir)] - [user-libs (let loop ([xs libs] [seen '()] [acc '()]) - (cond - [(null? xs) (reverse acc)] - [(or (member (car xs) seen) - (string=? (car xs) bundle-lib)) - (loop (cdr xs) seen acc)] - [else - (loop (cdr xs) - (cons (car xs) seen) - (cons (car xs) acc))]))]) - - (let* ([obj-dir (format "/tmp/jerbuild-binary-~a" (get-process-id))] - [program-so (format "~a/program.so" obj-dir)] - [program-wpo (format "~a/program.wpo" obj-dir)] - [program-wp-so (format "~a/program.wp.so" obj-dir)] - [petite-hdr (format "~a/petite_boot.h" obj-dir)] - [scheme-hdr (format "~a/scheme_boot.h" obj-dir)] - [program-hdr (format "~a/program_boot.h" obj-dir)] - [main-c (format "~a/main.c" obj-dir)]) - - (printf "=== jerbuild binary build ===\n") - (printf " Entry: ~a\n" entry) - (printf " Output: ~a\n" output) - (printf " Host: ~a\n" mt) - (printf " CC: ~a\n" cc) - (printf " Chez: ~a\n\n" csv-dir) - - (system (format "mkdir -p ~a" (shell-quote obj-dir))) - - ;; Set library-directories: user libs redirect to obj-dir (so - ;; compile-imported-libraries doesn't pollute user source trees); - ;; bundle lib is plain (precompiled .so/.wpo live there already). - ;; - ;; jerbuild's own WPO image internalizes libraries reachable from - ;; (jerboa build) — (jerboa core), (std misc string), (std pregexp), etc. - ;; When user code imports any of these, Chez treats them as "already - ;; loaded" and skips compilation, so compile-whole-program needs their - ;; .wpo files on disk. build-jerbuild.sh stages them next to the .sls - ;; sources in the bundle. - (library-directories - (append (map (lambda (l) (cons l obj-dir)) user-libs) - (list bundle-lib))) - - (compile-imported-libraries #t) - (generate-wpo-files #t) - - (printf "==> [1/4] WPO compile ~a\n" entry) - (compile-program entry program-so) - (compile-whole-program program-wpo program-wp-so #t) - - (printf "==> [2/4] Embed boots + program as C arrays\n") - (embed-bytes-as-c-array petite-boot petite-hdr "petite_boot") - (embed-bytes-as-c-array scheme-boot scheme-hdr "scheme_boot") - (embed-bytes-as-c-array program-wp-so program-hdr "program_boot") - - (printf "==> [3/4] Generate main.c\n") - (call-with-output-file main-c - (lambda (port) (display *binary-main-c-template* port)) - 'replace) - - (printf "==> [4/4] Compile + link -> ~a\n" output) - (let* ([extra-archives - (apply string-append - (map (lambda (a) - (let ([p (format "~a/~a" csv-dir a)]) - (if (file-exists? p) - (string-append " " (shell-quote p)) - ""))) - '("liblz4.a" "libz.a")))] - [os-libs (machine-type->os-libs mt)] - [cc-cmd (format "~a -I~a -I~a -O2 -o ~a ~a ~a~a ~a" - cc - (shell-quote obj-dir) - (shell-quote csv-dir) - (shell-quote output) - (shell-quote main-c) - (shell-quote libkernel) - extra-archives - os-libs)]) - (printf " ~a\n" cc-cmd) - (let ([rc (system cc-cmd)]) - (unless (zero? rc) - (error 'jerbuild (format "binary: cc exited ~a" rc))))) - - (system (format "rm -rf ~a" (shell-quote obj-dir))) - (printf "\n=== Build complete: ~a ===\n" output))))))) + (format "build: unexpected positional args ~a" rest))) + (let ([config-path + (cond + [(not (null? config-paths)) + (let ([p (car (reverse config-paths))]) + (unless (file-exists? p) + (error 'jerbuild (format "build: --config not found: ~a" p))) + p)] + [else + (or (find-config-from (current-directory)) + (error 'jerbuild + "build: no .jerbuild found in cwd or any parent"))])]) + (printf "=== jerbuild build ===\n") + (printf " Config: ~a\n" config-path) + (let-values ([(entry output libdirs cfg-cc rust-crates + extra-sources extra-archives extra-ldflags) + (load-jerbuild-config config-path)]) + (do-binary-build libdirs + (or cc-arg cfg-cc) + extra-archives extra-sources extra-ldflags + rust-crates + (list entry output))))))) ;;;; ============================================================ ;;;; Entry point @@ -1641,6 +1958,8 @@ int main(int argc, const char *argv[]) { (run-compile (cdr args))] [(and (pair? args) (string=? (car args) "binary")) (run-binary (cdr args))] + [(and (pair? args) (string=? (car args) "build")) + (run-build (cdr args))] [(and (pair? args) (string=? (car args) "transpile")) (let-values ([(src-dir lib-dir) (parse-args (cdr args))]) (jerbuild src-dir lib-dir))] --- a/support/build-jerbuild.sh +++ b/support/build-jerbuild.sh @@ -349,18 +349,23 @@ int main(int argc, const char *argv[]) { " jerbuild compile --libdirs <p> [--wpo] FILE...\n" " # compile-imported-libs\n" " jerbuild exec --libdirs <p> FILE [args...] # load+run a Scheme script\n" - " jerbuild binary [--libdirs <p>] [--cc CC] <entry.ss> <output>\n" - " # build standalone binary\n" + " jerbuild binary [--libdirs <p>] [--cc CC]\n" + " [--extra-archive A] [--extra-source S.c]\n" + " [--extra-ldflag F] [--rust-crate Cargo.toml[:features]]\n" + " <entry.ss> <output> # build standalone binary\n" + " jerbuild build [--cc CC] [--config PATH] # read .jerbuild, build\n" " jerbuild --jerboa-home # extract+print stdlib path\n" " jerbuild --version\n", stdout); return 0; } - /* The `binary` subcommand needs petite.boot/scheme.boot/libkernel.a/scheme.h - * on disk so the Scheme handler can embed + link them. Pre-extract the - * bundle and expose the path via env so the Scheme side can find them. */ - if (argc >= 2 && strcmp(argv[1], "binary") == 0) { + /* The `binary` and `build` subcommands need petite.boot/scheme.boot/ + * libkernel.a/scheme.h on disk so the Scheme handler can embed + link + * them. Pre-extract the bundle and expose the path via env so the + * Scheme side can find them. */ + if (argc >= 2 && (strcmp(argv[1], "binary") == 0 || + strcmp(argv[1], "build") == 0)) { setenv("JERBUILD_BUNDLE_DIR", ensure_extracted(), 1); }