Caching (layer blobs, downloaded wheels, base interpreter detection) is now
enabled by default at the per-user cache dir (or --cache-dir / $PYMAGE_CACHE_DIR).
--no-cache disables it: layer/meta caches off and wheels downloaded into an
ephemeral temp dir that's cleaned up after the build.
- setupCaches centralizes cache wiring (one cache.Cache serves layers + meta);
--no-cache uses a temp wheel dir with cleanup
- DefaultCacheDir honors $PYMAGE_CACHE_DIR; cli tests point it at a temp dir so
the on-by-default cache doesn't touch the user's cache
- tests: setupCaches (default + --no-cache cleanup); README/config updated
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
A no-op build's ~2-3s was almost all registry I/O:
- the base index was fetched 3x (BasePlatforms + remote.Image per platform)
- the base's top layer was downloaded once per platform every build to read
/etc/apko.json for interpreter detection (Chainguard has no PYTHON_VERSION)
Changes:
- build.BaseSet resolves the base reference once and serves per-platform child
images memoized (index fetched once; verified by TestBaseSet)
- cache the detected interpreter version by base digest in a default per-user
metadata cache (cache.GetText/PutText), so repeat builds skip the apko layer
download
- lazy wheel-cache dir creation; wheelhouse.Resolve defaults to the per-user
cache dir
Tests: TestBaseSet (index fetched once + per-platform memoized), TestCachedInterpreter
(write + cache-hit short-circuit), cache text round-trip.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
- parsePythonTag now rejects tags without the 'python' prefix (e.g. '3.12'),
which would otherwise produce a bogus .../lib/3.12/site-packages layout
- parseConsoleScripts iterates with strings.Split instead of bufio.Scanner, so
a long line/large entry_points.txt can't silently truncate the launcher set
- tests for both
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
Replace per-tag remote.Write/WriteIndex with a single remote.NewPusher reused
for every tag. The pusher caches per-repo upload state, so identical blobs are
checked/uploaded only once even when pushing the same digest to multiple tags
(previously each tag re-HEADed every blob). Still uploads layers/index children
concurrently via WithJobs.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
go-containerregistry already uploads layers (and index children) concurrently,
but only at its default of 4. Set remote.WithJobs explicitly with an auto
default that scales with CPU count (floor 4), plus a --push-concurrency flag and
[tool.pymage] push-concurrency config to tune it.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
Default --layer-strategy is now 'auto'. Adds max-layers (default 127) and
max-wheel-layers config keys/flags with validation. Documents the layer budget
and reuse-stable bin packing in README and DESIGN.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
When no platform is given, inspect the base reference: a multi-arch base index
yields a multi-arch build (filtering out attestation/unknown entries), a
single-arch base yields one image. Explicit --platform still overrides. Adds
build.BasePlatforms with tests + a CLI default-platforms test; documents it.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
- output() writes the single by-digest reference (repo@sha256:...) to stdout so
'docker run "$(pymage build)"' works; per-tag pointers and ggcr's per-blob
progress (pushed/existing/mounted blob) go to stderr via logs.Progress
- adds TestBuildStdoutIsImageRef asserting the stdout contract and that blob
logs land on stderr; documents it in the README
- completes and removes todo.txt
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
If --python is given it must match the base's detected Python version; if
omitted, the version is auto-detected (PYTHON_VERSION env, else apko.json) and
used for both wheel selection and the site-packages layout. Errors clearly when
the base version can't be determined and --python is unset. CI now relies on
auto-detection (drops --python). Adds resolveInterpreter tests and asserts the
auto-detected version flows into the image.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
Reads the base image's advertised PYTHON_VERSION (config env, no layer
download) and fails the build when it doesn't match --python, so a base tag
that slides to a new Python version can't silently produce a broken image.
Documents base-pinning tradeoffs in the README. Adds unit + CLI tests.
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
- --platform is now repeatable/comma-separated; >1 platform builds one image
per platform and assembles them into an OCI image index (remote.WriteIndex /
OCI layout). SBOM aggregates wheels across platforms.
- adds TestBuildMultiArchIndex (2-platform index, reproducible) against an
in-process registry; documents multi-arch in the README
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
- add internal/cache content-addressed layer store; build per-wheel layers in
parallel and reuse cached compressed blobs across rebuilds (--cache-dir)
- thread platform/python target into wheel resolution; validate --env
- add common secret patterns to the default source ignore list
- README: copy/paste-safe usage example + new flags; DESIGN status updated
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>