jpkg: select host-specific jerboa-code package
ober
5e00c7c4261b449d3f0e1a1e06362bf3478531a9
--- a/docs/jpkg-guide.md +++ b/docs/jpkg-guide.md @@ -32,8 +32,9 @@ defaults, enforced by code with tests. files, setuid bits, and oversize archives are rejected up front. - **The lockfile is the security boundary.** `jpkg.lock` pins exact versions, registries, artifact digests, and manifest digests. - `jpkg install` materializes *exactly* the lock — it never silently - resolves something new. + Bare `jpkg install` materializes *exactly* the lock — it never silently + resolves something new. Supplying a package name explicitly requests + resolution and writes the resulting exact lock. - **Signed metadata, not trusted transport.** Registries use [TUF](https://theupdateframework.io/): threshold-signed root/targets/ snapshot/timestamp roles with **rollback** and **freeze** protection and @@ -81,8 +82,9 @@ the store = ~/.jerboa/pkg — verified artifacts, shared across projects ``` - `add` / `update` resolve ranges → write `jpkg.lock`. -- `install` reads `jpkg.lock` and only the lock — deterministic, offline- - friendly, CI-safe. +- Bare `install` reads `jpkg.lock` and only the lock — deterministic, + offline-friendly, CI-safe. `install PKG` is the explicit resolve-and-lock + convenience form. - Installing copies verified files; it never executes them. `build` runs code, in a sandbox, under policy. @@ -180,7 +182,7 @@ registry, artifact-sha256, artifact-size, manifest-sha256, dependencies, yank status — or, for a dev link, a `link` path with no digests (flagged non-reproducible). Entries are sorted; the file round-trips deterministically. -`jpkg install` installs **exactly** this and nothing else. +Bare `jpkg install` installs **exactly** this and nothing else. ### 4.3 `jpkg.policy.sexp` — optional project policy override @@ -209,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` | Install **exactly** `jpkg.lock` (no resolution). | +| `jpkg install [PKG[@VERSION]]` | Install a package for this host, 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/cli.ss +++ b/lib/std/pkg/cli.ss @@ -56,8 +56,8 @@ "add dependency and update lockfile" cmd-add) (list "remove" "jpkg remove PKG" "remove dependency and update lockfile" cmd-remove) - (list "install" "jpkg install" - "install exactly what jpkg.lock describes" cmd-install) + (list "install" "jpkg install [PKG[@VERSION]]" + "install a package, or exactly replay jpkg.lock" cmd-install) (list "update" "jpkg update [PKG ...]" "resolve newer allowed versions" cmd-update) (list "uninstall" "jpkg uninstall PKG" --- a/lib/std/pkg/commands.ss +++ b/lib/std/pkg/commands.ss @@ -277,11 +277,15 @@ 0)) (def (cmd-install args) - (unless (null? args) - (jpkg-error "usage: jpkg install (installs exactly jpkg.lock)")) - (let ([pkgs (project-install)]) - (say "installed ~a package~a from jpkg.lock" (length pkgs) - (if (= (length pkgs) 1) "" "s")) + (unless (or (null? args) + (and (pair? args) (null? (cdr args)))) + (jpkg-error "usage: jpkg install [PKG[@VERSION]]")) + (let ([pkgs (if (null? args) + (project-install) + (project-add (car args)))]) + (say "installed ~a package~a~a" (length pkgs) + (if (= (length pkgs) 1) "" "s") + (if (null? args) " from jpkg.lock" "")) (say-packages pkgs) 0)) --- a/lib/std/pkg/project.ss +++ b/lib/std/pkg/project.ss @@ -394,6 +394,23 @@ (char=? (string-ref env 0) #\@)) env (string-append "@" (or env "ober"))))) + (def (host-package-name name) + ;; Logical packages may publish host-specific binary variants. Keep the + ;; physical tuple in the lockfile so subsequent installs remain exact. + (if (string=? name "@ober/jerboa-code") + (string-append + name + (case (machine-type) + [(a6le ta6le) "-linux-amd64"] + [(arm64le tarm64le) "-linux-arm64"] + [(a6fb ta6fb) "-freebsd-amd64"] + [(arm64fb tarm64fb) "-freebsd-arm64"] + [(a6osx ta6osx) "-macos-amd64"] + [(arm64osx tarm64osx) "-macos-arm64"] + [else + (jpkg-error "no jerboa-code artifact for host machine ~a" + (machine-type))])) + name)) (def (parse-pkg-spec spec) ;; "PKG" or "PKG@VERSION-OR-RANGE" -> (values name range-or-#f) @@ -407,9 +424,10 @@ (string-append (default-scope) "/" spec))]) (let loop ([i 1]) ;; skip the leading @ of the scope (cond - [(>= i (string-length spec)) (values spec #f)] + [(>= i (string-length spec)) + (values (host-package-name spec) #f)] [(char=? (string-ref spec i) #\@) - (values (substring spec 0 i) + (values (host-package-name (substring spec 0 i)) (substring spec (+ i 1) (string-length spec)))] [else (loop (+ i 1))])))) --- a/tests/test-jpkg-project.ss +++ b/tests/test-jpkg-project.ss @@ -42,6 +42,17 @@ (define world (format "/tmp/jpkg-proj-~a" (random-suffix))) (define reg1 (path-concat world "registry1")) (define reg2 (path-concat world "registry2")) +(define host-jcode-package + (string-append + "@ober/jerboa-code" + (case (machine-type) + [(a6le ta6le) "-linux-amd64"] + [(arm64le tarm64le) "-linux-arm64"] + [(a6fb ta6fb) "-freebsd-amd64"] + [(arm64fb tarm64fb) "-freebsd-arm64"] + [(a6osx ta6osx) "-macos-amd64"] + [(arm64osx tarm64osx) "-macos-arm64"] + [else (error 'test-jpkg-project "unsupported test host" (machine-type))]))) (define orig-dir (current-directory)) (mkdir-p world) @@ -74,6 +85,7 @@ (publish! reg1 "@lib/base" "1.0.0" '()) (publish! reg1 "@lib/base" "1.5.0" '()) (publish! reg1 "@lib/extra" "1.0.0" '(("@lib/base" . "^1.0.0"))) +(publish! reg1 host-jcode-package "0.1.1" '()) ;; ── project flow ──────────────────────────────────────────────────────── @@ -198,6 +210,15 @@ (let ([r2 (run-jpkg '("install"))]) (and (= (car r2) 0) (not (file-exists? ".jpkg/deps/lib-extra" #f))))))) +(check "install-selects-host-package" + (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"))))))) ;; ── dependency-confusion guard ────────────────────────────────────────── ;; @lib/base gets locked from "main"; a higher-priority registry "evil"