jpkg: install host tools without a project
ober
2e7988939e3f311ce879934bbe9b5f309e2aceb2
--- a/docs/jpkg-guide.md +++ b/docs/jpkg-guide.md @@ -83,8 +83,8 @@ the store = ~/.jerboa/pkg — verified artifacts, shared across projects - `add` / `update` resolve ranges → write `jpkg.lock`. - Bare `install` reads `jpkg.lock` and only the lock — deterministic, - offline-friendly, CI-safe. `install PKG` is the explicit resolve-and-lock - convenience form. + offline-friendly, CI-safe. `install PKG` installs a signed executable + package into the user's bin directory without requiring a project. - Installing copies verified files; it never executes them. `build` runs code, in a sandbox, under policy. @@ -211,7 +211,7 @@ Global: `jpkg --help`, `jpkg --version`. Every command also works as | `jpkg new NAME` | Scaffold a new package directory with manifest, strict policy, lockfile, `src/main.ss`, README, and `.build.yml`. | | `jpkg add PKG[@VERSION]` | Add a dependency (range), resolve, write lock, install. | | `jpkg remove PKG` | Drop a dependency, re-resolve, prune the environment. | -| `jpkg install [PKG[@VERSION]]` | Install a package for this host, or with no argument install **exactly** `jpkg.lock`. | +| `jpkg install [PKG[@VERSION]]` | Install a host-specific executable globally, or with no argument install **exactly** `jpkg.lock`. | | `jpkg update [PKG ...]` | Re-resolve to newer allowed versions. | | `jpkg uninstall PKG` | Remove a package from the project environment only. | | `jpkg list` | List locked packages. | --- a/lib/std/pkg/commands.ss +++ b/lib/std/pkg/commands.ss @@ -16,7 +16,8 @@ (only (jerboa core) def try catch) (only (std pkg util) jpkg-error mkdir-p path-concat path-basename remove-tree - write-file-bytevector string-suffix-of? string-join-list) + read-file-bytevector write-file-bytevector + string-prefix-of? string-suffix-of? string-join-list) (only (std pkg manifest) manifest-template parse-manifest-file manifest-name manifest-version valid-package-name?) @@ -32,7 +33,8 @@ (only (std pkg project) project-add project-remove project-install project-update project-uninstall project-list project-env-paths - project-link project-unlink project-verify-lock) + project-link project-unlink project-verify-lock env-dir-name) + (only (std pkg store) jpkg-home) (only (std pkg ed25519) hex->bytes) (only (std pkg publish) publish-keygen publish-load-key publish-key-public-hex @@ -258,6 +260,48 @@ (say " ~a ~a (~a)" (locked-package-name p) (locked-package-version p) (locked-package-registry p)))) pkgs)) + (def (global-bin-dir) + (or (getenv "JERBOA_INSTALL_DIR") + (let ([home (or (getenv "HOME") + (jpkg-error "install: HOME not set"))]) + (path-concat home ".local/bin")))) + (def (install-global-tool spec) + (unless (or (string-prefix-of? "jerboa-code" spec) + (string-prefix-of? "@ober/jerboa-code" spec)) + (jpkg-error "install: package argument is only supported for jerboa-code")) + (let ([tools-dir (path-concat (jpkg-home) "tools")] + [original-dir (current-directory)]) + (mkdir-p tools-dir) + (dynamic-wind + (lambda () (current-directory tools-dir)) + (lambda () + (unless (file-exists? "jpkg.sexp") + (cmd-init '("@ober/global-tools"))) + (let* ([pkgs (project-add spec)] + [jcode-pkg + (let loop ([rest pkgs]) + (cond + [(null? rest) + (jpkg-error "install: resolved package has no jcode executable")] + [(string-prefix-of? "@ober/jerboa-code-" + (locked-package-name (car rest))) + (car rest)] + [else (loop (cdr rest))]))] + [source + (path-concat + (path-concat ".jpkg/deps" + (env-dir-name + (locked-package-name jcode-pkg))) + "jcode")] + [bin-dir (global-bin-dir)] + [destination (path-concat bin-dir "jcode")]) + (unless (file-exists? source) + (jpkg-error "install: package does not contain jcode")) + (mkdir-p bin-dir) + (write-file-bytevector destination (read-file-bytevector source)) + (chmod destination #o755) + pkgs)) + (lambda () (current-directory original-dir))))) (def (cmd-add args) (unless (and (pair? args) (null? (cdr args))) @@ -282,7 +326,7 @@ (jpkg-error "usage: jpkg install [PKG[@VERSION]]")) (let ([pkgs (if (null? args) (project-install) - (project-add (car args)))]) + (install-global-tool (car args)))]) (say "installed ~a package~a~a" (length pkgs) (if (= (length pkgs) 1) "" "s") (if (null? args) " from jpkg.lock" "")) --- a/tests/test-jpkg-project.ss +++ b/tests/test-jpkg-project.ss @@ -57,6 +57,7 @@ (mkdir-p world) (putenv "JERBOA_PKG_HOME" (path-concat world "pkghome")) +(putenv "JERBOA_INSTALL_DIR" (path-concat world "bin")) (putenv "JERBOA_PKG_REGISTRIES" (format "main=~a" reg1)) ;; build a package source dir + artifact, publish into registry @@ -75,6 +76,11 @@ (write-file-bytevector (path-concat dir "src/lib.ss") (string->utf8 (format ";; ~a ~a~%" name version))) + (when (s-contains? name "@ober/jerboa-code-") + (write-file-bytevector + (path-concat dir "jcode") + (string->utf8 "#!/bin/sh\nexit 0\n")) + (chmod (path-concat dir "jcode") #o755)) (let-values ([(path digest size) (pack-project dir (path-concat world "tmp.jpkg"))]) (registry-add-package! reg path) @@ -210,15 +216,12 @@ (let ([r2 (run-jpkg '("install"))]) (and (= (car r2) 0) (not (file-exists? ".jpkg/deps/lib-extra" #f))))))) -(check "install-selects-host-package" +(check "install-selects-host-global-tool" (let ([r (run-jpkg '("install" "jerboa-code@0.1.1"))]) (and (= (car r) 0) (s-contains? (cadr r) host-jcode-package) - (equal? (map locked-package-name (lock-parse-file "jpkg.lock")) - (list host-jcode-package)) - (file-directory? - (path-concat ".jpkg/deps" (env-dir-name host-jcode-package))) - (= 0 (car (run-jpkg '("remove" "jerboa-code"))))))) + (null? (lock-parse-file "jpkg.lock")) + (file-exists? (path-concat world "bin/jcode"))))) ;; ── dependency-confusion guard ────────────────────────────────────────── ;; @lib/base gets locked from "main"; a higher-priority registry "evil"