jerbuild binary: --rust-target + --csv-dir for cross-builds

ober

fb74ab1e840da579f27433228e634e82e6f3cd01

diff --git a/jerbuild.ss b/jerbuild.ss
index 20121cc..cc2723e 100644
--- a/jerbuild.ss
+++ b/jerbuild.ss
@@ -1427,6 +1427,24 @@
              acc)]
       [else (loop (cdr args) cc (cons (car args) acc))])))
 
+(define (parse-single-flag args flag-name)
+  ;; Scan for FLAG <value> (or FLAG=<value>). Returns (values value remaining).
+  ;; Last occurrence wins. Returns #f if absent.
+  (let* ([eq-prefix (string-append flag-name "=")]
+         [eq-len (string-length eq-prefix)])
+    (let loop ([args args] [value #f] [acc '()])
+      (cond
+        [(null? args) (values value (reverse acc))]
+        [(string=? (car args) flag-name)
+         (when (null? (cdr args))
+           (error 'jerbuild (format "~a requires a value" flag-name)))
+         (loop (cddr args) (cadr args) acc)]
+        [(string-starts-with? (car args) eq-prefix)
+         (loop (cdr args)
+               (substring (car args) eq-len (string-length (car args)))
+               acc)]
+        [else (loop (cdr args) value (cons (car args) acc))]))))
+
 (define (shell-quote s)
   ;; Wrap s in single quotes, escaping any embedded single quote.
   (let* ([len (string-length s)]
@@ -1645,9 +1663,11 @@ int main(int argc, const char *argv[]) {
                        (directory-list dir))))
     '()))
 
-(define (build-rust-crate spec)
+(define (build-rust-crate spec rust-target)
   ;; spec: "path/Cargo.toml[:features]"
-  ;; Drives `cargo build --release [--features F]`; returns list of .a paths.
+  ;; rust-target: triple string (e.g., "aarch64-unknown-linux-musl") or #f
+  ;; Drives `cargo build --release [--features F] [--target T]`; returns
+  ;; list of .a paths under target/[<triple>/]release/.
   (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)))
@@ -1655,15 +1675,20 @@ int main(int argc, const char *argv[]) {
            [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)])
+           [target-arg (if rust-target
+                         (format " --target ~a" (shell-quote rust-target))
+                         "")]
+           [cmd (format "cargo build --release --manifest-path ~a~a~a"
+                        (shell-quote cargo-toml) features-arg target-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)]
+      (let* ([release-dir (if rust-target
+                            (format "~a/target/~a/release" manifest-dir rust-target)
+                            (format "~a/target/release" manifest-dir))]
              [archives (list-archives-in release-dir)])
         (when (null? archives)
           (error 'jerbuild
@@ -1709,6 +1734,8 @@ int main(int argc, const char *argv[]) {
   ;;                 [--extra-ldflag F]                    (repeatable)
   ;;                 [--rust-crate Cargo.toml[:features]]  (repeatable)
   ;;                 [--main-c FILE]                       (single)
+  ;;                 [--rust-target TRIPLE]                (single)
+  ;;                 [--csv-dir PATH]                      (single)
   ;;                 <entry.ss> <output>
   ;;
   ;; Builds a standalone executable that bundles Chez + the user's entry script
@@ -1720,31 +1747,41 @@ int main(int argc, const char *argv[]) {
         (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 args) (parse-multi-flag args "--rust-crate")])
-              (let-values ([(main-cs rest) (parse-multi-flag args "--main-c")])
-                (let ([main-c-override
-                       (cond
-                         [(null? main-cs) #f]
-                         [else (car (reverse main-cs))])]
-                      ;; Wrap each extra-source path as (path . #f) — no
-                      ;; per-source cflags supported on the CLI; use .jerbuild
-                      ;; for that.
-                      [extra-sources-pairs
-                       (map (lambda (s) (cons s #f)) extra-sources)])
-                  (do-binary-build libs cc-arg extra-archives
-                                   extra-sources-pairs
-                                   extra-ldflags rust-crates main-c-override
-                                   ;; CLI form: no ffi-symbols, pre-build, or
-                                   ;; pre-link — those are config-only.
-                                   #f '() '() (current-directory)
-                                   rest))))))))))
+              (let-values ([(main-cs args) (parse-multi-flag args "--main-c")])
+                (let-values ([(rust-target args) (parse-single-flag args "--rust-target")])
+                  (let-values ([(csv-dir rest) (parse-single-flag args "--csv-dir")])
+                    (let ([main-c-override
+                           (cond
+                             [(null? main-cs) #f]
+                             [else (car (reverse main-cs))])]
+                          ;; Wrap each extra-source path as (path . #f) — no
+                          ;; per-source cflags supported on the CLI; use .jerbuild
+                          ;; for that.
+                          [extra-sources-pairs
+                           (map (lambda (s) (cons s #f)) extra-sources)])
+                      (do-binary-build libs cc-arg extra-archives
+                                       extra-sources-pairs
+                                       extra-ldflags rust-crates main-c-override
+                                       ;; CLI form: no ffi-symbols, pre-build, or
+                                       ;; pre-link — those are config-only.
+                                       #f '() '()
+                                       rust-target csv-dir
+                                       (current-directory)
+                                       rest))))))))))))
 
 (define (do-binary-build libs cc-arg extra-archives extra-sources
                          extra-ldflags rust-crates main-c-override
-                         ffi-symbols pre-build pre-link config-dir rest)
+                         ffi-symbols pre-build pre-link
+                         rust-target csv-dir-override
+                         config-dir rest)
   ;; extra-sources is a list of pairs (abs-path . cflags-or-#f).
+  ;; rust-target: triple passed to `cargo build --target=` or #f for host.
+  ;; csv-dir-override: full path to a csv-dir (containing petite.boot,
+  ;;   scheme.boot, libkernel.a, scheme.h). When set, replaces the default
+  ;;   bundle path. Lets cross-builds point at a Chez built for the target.
   (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>"))
+           "binary: usage: jerbuild binary [--libdirs P] [--cc CC] [--extra-archive A] [--extra-source S.c] [--extra-ldflag F] [--rust-crate Cargo.toml[:features]] [--rust-target T] [--csv-dir D] [--main-c F] <entry.ss> <output>"))
   (let* ([entry      (car rest)]
          [output     (cadr rest)]
          [cc         (or cc-arg (or (getenv "CC") "cc"))]
@@ -1752,7 +1789,7 @@ int main(int argc, const char *argv[]) {
                          (error 'jerbuild
                                 "binary: JERBUILD_BUNDLE_DIR not set (jerbuild internal bug)"))]
          [mt         (machine-type)]
-         [csv-dir    (format "~a/csv/~a" bundle-dir mt)]
+         [csv-dir    (or csv-dir-override (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)]
@@ -1813,7 +1850,10 @@ int main(int argc, const char *argv[]) {
       (printf "    Output: ~a\n" output)
       (printf "    Host:   ~a\n" mt)
       (printf "    CC:     ~a\n" cc)
-      (printf "    Chez:   ~a\n" csv-dir)
+      (printf "    Chez:   ~a~a\n" csv-dir
+              (if csv-dir-override " (override)" ""))
+      (when rust-target
+        (printf "    Rust target: ~a\n" rust-target))
       (unless (null? rust-crates)
         (printf "    Rust crates:    ~a\n" (length rust-crates)))
       (unless (null? extra-archives)
@@ -1850,7 +1890,9 @@ int main(int argc, const char *argv[]) {
                 [(null? rust-crates) '()]
                 [else
                  (printf "==> [2/5] Build Rust crates (~a)\n" (length rust-crates))
-                 (apply append (map build-rust-crate rust-crates))])]
+                 (apply append
+                   (map (lambda (spec) (build-rust-crate spec rust-target))
+                        rust-crates))])]
              [user-objs
               (cond
                 [(null? extra-sources) '()]
@@ -1976,6 +2018,20 @@ int main(int argc, const char *argv[]) {
 ;;   (pre-link "rust-dedup.sh")          ; optional; sh commands run AFTER C
 ;;                                       ;   compile but BEFORE final link.
 ;;                                       ;   Env: JERBUILD_OBJ_DIR, JERBUILD_OUTPUT
+;;   (rust-target "aarch64-unknown-linux-musl")  ; optional; cross target.
+;;                                       ;   Passed to `cargo build --target=…`
+;;                                       ;   and switches the archive search to
+;;                                       ;   target/<triple>/release/.
+;;                                       ;   CLI --rust-target overrides.
+;;   (csv-dir "vendor/chez/aarch64-linux/csv/tarm64le")  ; optional override of
+;;                                       ;   the Chez csv-dir (default: bundle
+;;                                       ;   csv for the host machine-type). Use
+;;                                       ;   for cross-builds — point at csv/
+;;                                       ;   files (petite.boot, scheme.boot,
+;;                                       ;   libkernel.a, scheme.h, optionally
+;;                                       ;   liblz4.a/libz.a) built for the
+;;                                       ;   target machine. CLI --csv-dir
+;;                                       ;   overrides.
 ;;
 ;; All paths are resolved relative to the .jerbuild file's directory.
 
@@ -2041,7 +2097,9 @@ int main(int argc, const char *argv[]) {
 (define (load-jerbuild-config path)
   ;; Returns (values entry output libdirs cc rust-crates
   ;;                  extra-sources extra-archives extra-ldflags main-c
-  ;;                  ffi-symbols pre-build pre-link).
+  ;;                  ffi-symbols pre-build pre-link
+  ;;                  rust-target csv-dir
+  ;;                  config-dir).
   ;; extra-sources entries are pairs (abs-path . cflags-or-#f).
   (let* ([config-dir (path-dirname path)]
          [base (if (absolute-path? config-dir)
@@ -2051,7 +2109,8 @@ int main(int argc, const char *argv[]) {
          [entry #f] [output #f] [cc #f] [main-c #f] [ffi-symbols #f]
          [libdirs '()] [rust-crates '()] [extra-sources '()]
          [extra-archives '()] [extra-ldflags '()]
-         [pre-build '()] [pre-link '()])
+         [pre-build '()] [pre-link '()]
+         [rust-target #f] [csv-dir #f])
     (for-each
       (lambda (form)
         (unless (and (pair? form) (symbol? (car form)))
@@ -2096,9 +2155,17 @@ int main(int argc, const char *argv[]) {
            (set! pre-build (append pre-build (cdr form)))]
           [(pre-link)
            (set! pre-link (append pre-link (cdr form)))]
+          [(rust-target)
+           (unless (= (length form) 2)
+             (error 'jerbuild "config: (rust-target TRIPLE) takes one value"))
+           (set! rust-target (cadr form))]
+          [(csv-dir)
+           (unless (= (length form) 2)
+             (error 'jerbuild "config: (csv-dir PATH) takes one value"))
+           (set! csv-dir (resolve-config-path (cadr form) base))]
           [else
            (error 'jerbuild
-                  (format "config: unknown key ~a in ~a (valid: entry output cc libdirs rust-crates extra-sources extra-archives extra-ldflags main-c ffi-symbols pre-build pre-link)"
+                  (format "config: unknown key ~a in ~a (valid: entry output cc libdirs rust-crates extra-sources extra-archives extra-ldflags main-c ffi-symbols pre-build pre-link rust-target csv-dir)"
                           (car form) path))]))
       forms)
     (unless entry
@@ -2107,40 +2174,50 @@ int main(int argc, const char *argv[]) {
       (error 'jerbuild (format "config: missing required (output PATH) in ~a" path)))
     (values entry output libdirs cc rust-crates
             extra-sources extra-archives extra-ldflags main-c
-            ffi-symbols pre-build pre-link base)))
+            ffi-symbols pre-build pre-link
+            rust-target csv-dir
+            base)))
 
 (define (run-build args)
   ;; jerbuild build [--cc CC] [--config PATH]
+  ;;                [--rust-target TRIPLE] [--csv-dir PATH]
   ;; Reads .jerbuild from cwd (or any parent) and runs do-binary-build.
-  ;; --cc overrides the config's (cc ...) setting.
+  ;; --cc / --rust-target / --csv-dir override the config's settings.
   (let-values ([(cc-arg rest1) (parse-cc-flag args)])
-    (let-values ([(config-paths rest) (parse-multi-flag rest1 "--config")])
-      (unless (null? rest)
-        (error 'jerbuild
-               (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 main-c
-                       ffi-symbols pre-build pre-link config-dir)
-                      (load-jerbuild-config config-path)])
-          (do-binary-build libdirs
-                           (or cc-arg cfg-cc)
-                           extra-archives extra-sources extra-ldflags
-                           rust-crates main-c
-                           ffi-symbols pre-build pre-link config-dir
-                           (list entry output)))))))
+    (let-values ([(config-paths rest2) (parse-multi-flag rest1 "--config")])
+      (let-values ([(rt-arg rest3) (parse-single-flag rest2 "--rust-target")])
+        (let-values ([(csv-arg rest) (parse-single-flag rest3 "--csv-dir")])
+          (unless (null? rest)
+            (error 'jerbuild
+                   (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 main-c
+                           ffi-symbols pre-build pre-link
+                           cfg-rust-target cfg-csv-dir
+                           config-dir)
+                          (load-jerbuild-config config-path)])
+              (do-binary-build libdirs
+                               (or cc-arg cfg-cc)
+                               extra-archives extra-sources extra-ldflags
+                               rust-crates main-c
+                               ffi-symbols pre-build pre-link
+                               (or rt-arg cfg-rust-target)
+                               (or csv-arg cfg-csv-dir)
+                               config-dir
+                               (list entry output)))))))))
 
 ;;;; ============================================================
 ;;;; Entry point
diff --git a/support/build-jerbuild.sh b/support/build-jerbuild.sh
index 2e44169..22246ab 100755
--- a/support/build-jerbuild.sh
+++ b/support/build-jerbuild.sh
@@ -352,9 +352,10 @@ int main(int argc, const char *argv[]) {
             "  jerbuild binary [--libdirs <p>] [--cc CC]\n"
             "          [--extra-archive A] [--extra-source S.c]\n"
             "          [--extra-ldflag F] [--rust-crate Cargo.toml[:features]]\n"
-            "          [--main-c FILE]\n"
+            "          [--main-c FILE] [--rust-target T] [--csv-dir D]\n"
             "          <entry.ss> <output>                # build standalone binary\n"
-            "  jerbuild build [--cc CC] [--config PATH]   # read .jerbuild, build\n"
+            "  jerbuild build [--cc CC] [--config PATH]\n"
+            "          [--rust-target T] [--csv-dir D]    # read .jerbuild, build\n"
             "  jerbuild --jerboa-home                     # extract+print stdlib path\n"
             "  jerbuild --version\n",
             stdout);