Read ~/.gitlab.yaml for config; isolate cross-build objects
ober
d11ffd7ad07ede126164ce0e4e155c3b15eb330b
--- 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/ --- 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 --- 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) --- 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))]))) --- 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) "")))