Read ~/.gitlab.yaml for config; isolate cross-build objects

ober

d11ffd7ad07ede126164ce0e4e155c3b15eb330b

diff --git a/.gitignore b/.gitignore
index b445fb1..d00bfe3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,9 @@ jgl-*-main.c
 *_boot.h
 *_program.h
 
+# Cross-build isolated object dir
+/.build/
+
 # Default export output
 /issues/
 
diff --git a/README.md b/README.md
index 4a9807b..5470c34 100644
--- a/README.md
+++ b/README.md
@@ -62,11 +62,17 @@ jgl <command> [options]
 | `--status STATUS` | pipelines: filter by status                             |
 | `--pipeline ID`   | jobs: restrict to a pipeline                            |
 | `-o, --output DIR`| export: output directory (default `./issues`)           |
+| `--config PATH`   | Config file (default `~/.gitlab.yaml`; also `GITLAB_CONFIG`) |
 | `-h, --help`      | Show help                                               |
 | `--version`       | Show version                                            |
 
 A token with `read_api` scope is needed for private projects; public projects
-read without one. Set credentials once via the environment:
+read without one.
+
+### Configuration
+
+`jgl` resolves each setting with the precedence **flag › environment ›
+`~/.gitlab.yaml` › default**. Use whichever fits:
 
 ```sh
 export GITLAB_URL="https://gitlab.com"
@@ -74,6 +80,15 @@ export GITLAB_TOKEN="glpat-xxxxxxxxxxxx"
 export GITLAB_PROJECT="group/project"
 ```
 
+or a flat `~/.gitlab.yaml` (override the path with `--config` or `GITLAB_CONFIG`):
+
+```yaml
+url: https://gitlab.com
+token: glpat-xxxxxxxxxxxx
+project: group/project
+format: text
+```
+
 ### Examples
 
 ```sh
diff --git a/build-jgl-cross.ss b/build-jgl-cross.ss
index a4dbf0d..058c79d 100644
--- a/build-jgl-cross.ss
+++ b/build-jgl-cross.ss
@@ -110,28 +110,34 @@
 (printf "    cross csv-dir: ~a~n    native .a:     ~a~n    symbols:       ~a jerboa_* + ~a libc~n    output:        ~a~n~n"
         cross-csv-dir native-a (length jerboa-symbols) (length libc-symbols) output)
 
+;; ── Stage 0.5: isolate compiled objects ────────────────────────────────────
+;; Never patch source libraries in place. Chez maps each library source dir to
+;; a writable object dir; point them ALL at obj-dir so the ta6le .so/.wpo land
+;; there instead of clobbering lib/ or $JERBOA_HOME/lib (host objects).
+(define obj-dir   (format "~a/.build/~a-obj" jgl-repo output))
+(define entry-so  (format "~a/program.so"  obj-dir))
+(define entry-wpo (format "~a/program.wpo" obj-dir))
+(system (format "rm -rf '~a' && mkdir -p '~a'" obj-dir obj-dir))
+
 ;; ── Stage 1: load xpatch (target=ta6le emit mode) ──────────────────────────
 (define orig-libdirs (library-directories))
 (printf "==> [1/6] loading xpatch (compiler -> ta6le emit mode)~n")
 (load xpatch)
 (library-directories
-  (append
-    (list (cons (format "~a/lib" jgl-repo)    (format "~a/lib" jgl-repo))
-          (cons (format "~a/lib" jerboa-home) (format "~a/lib" jerboa-home)))
-    orig-libdirs))
+  (map (lambda (pair) (cons (if (pair? pair) (car pair) pair) obj-dir)) orig-libdirs))
 (compile-imported-libraries #t)
 (generate-wpo-files #t)
 
 ;; ── Stage 2: compile-program ────────────────────────────────────────────────
-(printf "==> [2/6] compile-program ~a~n" entry-script)
-(compile-program entry-script)
-(define entry-wpo
-  (string-append (substring entry-script 0 (- (string-length entry-script) 3)) ".wpo"))
+(printf "==> [2/6] compile-program ~a -> ~a~n" entry-script entry-so)
+(guard (e (#t (system (format "rm -rf '~a'" obj-dir)) (raise e)))
+  (compile-program entry-script entry-so))
 
 ;; ── Stage 3: whole-program optimize → single .so ────────────────────────────
 (define wpo-output (string-append output ".wp.so"))
-(printf "==> [3/6] compile-whole-program -> ~a~n" wpo-output)
-(compile-whole-program entry-wpo wpo-output #t)
+(printf "==> [3/6] compile-whole-program ~a -> ~a~n" entry-wpo wpo-output)
+(guard (e (#t (system (format "rm -rf '~a'" obj-dir)) (raise e)))
+  (compile-whole-program entry-wpo wpo-output #t))
 
 ;; ── Stage 4: embed boots + program as C arrays ──────────────────────────────
 (define (embed in-path var out-path)
@@ -229,4 +235,5 @@
 (printf "    ~a~n" link-cmd)
 (unless (zero? (system link-cmd))
   (error 'build-jgl-cross "link failed"))
+(system (format "rm -rf '~a'" obj-dir))   ;; drop isolated objects
 (printf "~n==> done: ~a~n" output)
diff --git a/lib/jgl/cli.ss b/lib/jgl/cli.ss
index c79c67b..203dca2 100644
--- a/lib/jgl/cli.ss
+++ b/lib/jgl/cli.ss
@@ -38,6 +38,7 @@
       [(member name '("--status"))         "status"]
       [(member name '("--output" "-o"))    "output"]
       [(member name '("--pipeline"))       "pipeline"]
+      [(member name '("--config"))         "config"]
       [else #f]))
 
   (def (parse-args argv)
@@ -75,7 +76,7 @@
   ;; A project is always required. A token is optional: public projects read
   ;; fine without one, and private projects return a 401 we report clearly.
   (def (require-conn o)
-    (need o opts-project "a project (--project / GITLAB_PROJECT)"))
+    (need o opts-project "a project (--project, GITLAB_PROJECT, or project: in ~/.gitlab.yaml)"))
 
   ;; --- Rendering ---
 
@@ -195,9 +196,13 @@ Options:
   --status STATUS    pipelines: filter by status
   --pipeline ID      jobs: restrict to a pipeline
   -o, --output DIR   export: output directory (default ./issues)
+  --config PATH      Config file (default ~/.gitlab.yaml; also GITLAB_CONFIG)
   -h, --help         Show this help
   --version          Show version
 
+Config: flags override env vars override ~/.gitlab.yaml (keys: url, token,
+project, format) override defaults.
+
 Examples:
   jgl issues -p group/proj --state opened
   jgl issue 42 -p 123 --token glpat-xxxx
@@ -297,7 +302,8 @@ Examples:
         [else
          (guard (e (#t (displayln "jgl: error: " (err-string e)) (exit 1)))
            (let* ([o (resolve-opts (opt opts "url") (opt opts "token")
-                                   (opt opts "project") (opt opts "format"))]
+                                   (opt opts "project") (opt opts "format")
+                                   (opt opts "config"))]
                   [c (opts->client o)])
              (dispatch command positionals opts o c))
            (exit 0))])))
diff --git a/lib/jgl/config.ss b/lib/jgl/config.ss
index 1dbebfd..e3234fd 100644
--- a/lib/jgl/config.ss
+++ b/lib/jgl/config.ss
@@ -1,15 +1,27 @@
 #!chezscheme
-;;; (jgl config) -- Resolve connection options from CLI flags + environment.
+;;; (jgl config) -- Resolve connection options from CLI flags, environment,
+;;; and a ~/.gitlab.yaml config file.
 ;;;
-;;; Precedence: explicit CLI flag > environment variable > built-in default.
-;;;   --url   / GITLAB_URL     (default https://gitlab.com)
-;;;   --token / GITLAB_TOKEN   (required for authenticated calls)
-;;;   --project / GITLAB_PROJECT
-;;; A personal access token with read_api scope is expected.
+;;; Precedence (highest first):
+;;;   CLI flag  >  environment variable  >  config file  >  built-in default
+;;;     --url   / GITLAB_URL     / url:      (default https://gitlab.com)
+;;;     --token / GITLAB_TOKEN   / token:
+;;;     --project / GITLAB_PROJECT / project:
+;;;     --format  / GITLAB_FORMAT  / format:  (default text)
+;;;
+;;; The config file is ~/.gitlab.yaml by default (override with --config PATH
+;;; or GITLAB_CONFIG). It is a flat YAML map, e.g.:
+;;;
+;;;     url: https://gitlab.com
+;;;     token: glpat-xxxxxxxxxxxx
+;;;     project: group/project
+;;;     format: text
+;;;
+;;; A personal access token with read_api scope is expected for private projects.
 
 (library (jgl config)
   (export make-opts opts? opts-base-url opts-token opts-project opts-format
-          resolve-opts opts->client)
+          resolve-opts opts->client default-config-path)
   (import (except (chezscheme)
                   make-hash-table hash-table?
                   sort sort!
@@ -21,6 +33,8 @@
                   make-date make-time)
           (except (jerboa prelude) meta atom?)
           (std misc string)
+          (std misc ports)
+          (std text yaml)
           (jgl client))
 
   (defstruct opts (base-url token project format))
@@ -35,13 +49,43 @@
       (substring s 0 (- (string-length s) 1))
       s))
 
-  ;; Build resolved options from CLI-provided values (any of which may be #f).
-  (def (resolve-opts url token project format)
-    (make-opts
-      (strip-trailing-slash (or url (env "GITLAB_URL") "https://gitlab.com"))
-      (or token (env "GITLAB_TOKEN") #f)
-      (or project (env "GITLAB_PROJECT") #f)
-      (or format "text")))
+  ;; ~/.gitlab.yaml unless overridden by --config / GITLAB_CONFIG.
+  (def (default-config-path)
+    (let ([h (getenv "HOME")])
+      (if (and h (not (string=? h ""))) (string-append h "/.gitlab.yaml") #f)))
+
+  ;; Load the YAML config into a flat alist of string->value. Missing file or a
+  ;; parse error yields '() (config is best-effort; flags/env still work).
+  (def (load-yaml-config path)
+    (if (and path (file-exists? path))
+      (guard (e (#t '()))
+        (let ([parsed (yaml-load-string (read-file-string path))])
+          (if (and (list? parsed) (or (null? parsed) (pair? (car parsed)))) parsed '())))
+      '()))
+
+  ;; Look up a string key; coerce scalars to a non-empty string, else #f.
+  (def (cfg-ref alist key)
+    (let ([p (and (pair? alist) (assoc key alist))])
+      (if p
+        (let ([v (cdr p)])
+          (cond
+            [(string? v)  (if (string=? v "") #f v)]
+            [(number? v)  (number->string v)]
+            [(symbol? v)  (symbol->string v)]
+            [(boolean? v) (if v "true" "false")]
+            [else #f]))
+        #f)))
+
+  ;; Build resolved options. CLI values (any may be #f) win, then env, then the
+  ;; config file at `config-path` (or ~/.gitlab.yaml), then defaults.
+  (def (resolve-opts url token project format config-path)
+    (let ([cfg (load-yaml-config (or config-path (env "GITLAB_CONFIG") (default-config-path)))])
+      (make-opts
+        (strip-trailing-slash
+          (or url (env "GITLAB_URL") (cfg-ref cfg "url") "https://gitlab.com"))
+        (or token   (env "GITLAB_TOKEN")   (cfg-ref cfg "token")   #f)
+        (or project (env "GITLAB_PROJECT") (cfg-ref cfg "project") #f)
+        (or format  (env "GITLAB_FORMAT")  (cfg-ref cfg "format")  "text"))))
 
   (def (opts->client o)
     (make-client (opts-base-url o) (or (opts-token o) "")))