mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-07 23:35:16 +00:00
ci: build & run a real uv project (compiled wheels numpy/pydantic-core, console scripts) and assert imports/metadata/dev-exclusion; docs: note uv export delegation + conformance
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
This commit is contained in:
parent
80ff593fc9
commit
cb303c1577
3 changed files with 78 additions and 2 deletions
63
.github/workflows/pymage.yaml
vendored
63
.github/workflows/pymage.yaml
vendored
|
|
@ -184,3 +184,66 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
echo "Confirmed: adding a dependency reused all existing layers and added exactly one."
|
||||
|
||||
# Build a real uv project with *compiled* wheels (numpy, pydantic-core) and
|
||||
# a console-script package, then actually run the image: import the native
|
||||
# extensions, read installed metadata, and check the generated launcher.
|
||||
# This exercises the `uv export` resolution path and install fidelity
|
||||
# end-to-end against a real Docker runtime.
|
||||
- name: Build & run a real uv project (compiled wheels + metadata)
|
||||
if: runner.os == 'Linux'
|
||||
working-directory: pymage
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf realapp && mkdir -p realapp/src/realapp
|
||||
cat > realapp/pyproject.toml <<'TOML'
|
||||
[project]
|
||||
name = "realapp"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12,<3.13"
|
||||
dependencies = ["numpy==2.1.3", "pydantic==2.9.2", "rich==13.9.4", "cowsay==6.1"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ruff==0.7.4"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
TOML
|
||||
echo "" > realapp/src/realapp/__init__.py
|
||||
uv lock --project realapp
|
||||
go run . build ./realapp \
|
||||
--base python:3.12-slim --platform linux/amd64 \
|
||||
--repo "$REGISTRY/realapp" -t v1 --insecure \
|
||||
--entrypoint python --entrypoint -c --entrypoint "print('ok')"
|
||||
|
||||
# dev-group tool must NOT be in the image (uv export --no-dev)
|
||||
if crane manifest --insecure "$REGISTRY/realapp:v1" \
|
||||
| jq -r '.layers[].annotations["dev.pymage.wheels"] // empty' | grep -qi '^ruff=='; then
|
||||
echo "::error::dev dependency 'ruff' leaked into the runtime image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
crane pull --insecure --platform linux/amd64 "$REGISTRY/realapp:v1" /tmp/realapp.tar
|
||||
loaded=$(docker load -i /tmp/realapp.tar | sed -E 's/^Loaded image( ID)?: //' | tail -1)
|
||||
echo "loaded image: $loaded"
|
||||
|
||||
# Import compiled extensions, exercise them, and read installed metadata.
|
||||
docker run --rm --entrypoint python "$loaded" -c "
|
||||
import numpy as np, pydantic, rich # compiled + pure imports
|
||||
assert np.zeros(3).sum() == 0.0 # runs the numpy C ext
|
||||
from pydantic import BaseModel
|
||||
class M(BaseModel):
|
||||
x: int
|
||||
assert M(x=2).x == 2 # runs pydantic-core (Rust ext)
|
||||
import importlib.metadata as md
|
||||
for p in ('numpy', 'pydantic', 'rich', 'cowsay'):
|
||||
print(p, md.version(p)) # installed metadata present
|
||||
assert 'ruff' not in {d.metadata['Name'].lower() for d in md.distributions()}
|
||||
print('runtime import + metadata OK')
|
||||
"
|
||||
|
||||
# The console-script launcher was generated and is a valid script.
|
||||
docker run --rm --entrypoint sh "$loaded" -c \
|
||||
'ls /app/.venv/bin && test -s /app/.venv/bin/cowsay && head -1 /app/.venv/bin/cowsay'
|
||||
echo "Confirmed: compiled wheels import, metadata is present, dev tools excluded, launcher generated."
|
||||
|
|
|
|||
|
|
@ -31,7 +31,14 @@ because it exploits content-addressed layering:
|
|||
When a lock has no compatible wheel, pymage fails fast and directs the user to
|
||||
pre-build a wheel out-of-band and supply it via `--find-links`.
|
||||
- Replacing dependency *resolution*. We delegate resolution to existing,
|
||||
correct tools (`uv` / `pip`) and consume their lockfile output.
|
||||
correct tools and consume their output: for uv projects we shell out to
|
||||
`uv export --frozen --no-dev` (when uv is available) to get the exact
|
||||
per-target requirement set — closure, extras, groups, and workspace selection
|
||||
are uv's responsibility, not ours. pymage only evaluates the markers uv emits
|
||||
and maps each pin to a wheel. A built-in lock walker remains as a fallback for
|
||||
environments without uv. Likewise, install layout is validated against a real
|
||||
`uv pip install` in a conformance test (installing a wheel runs no code, so
|
||||
there is no RCE risk in using it as an oracle).
|
||||
- A general-purpose Dockerfile interpreter. This is a focused Python app builder
|
||||
(think `ko`, but for Python wheels).
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,13 @@ pymage build \
|
|||
### Optional dependencies, workspaces, and markers (uv.lock)
|
||||
|
||||
pymage installs the project's **runtime closure** from `uv.lock` (the deps you'd
|
||||
get from `uv sync --no-dev`), not every package in the lock:
|
||||
get from `uv sync --no-dev`), not every package in the lock. When `uv` is
|
||||
installed and a `pyproject.toml` is present, resolution is delegated to
|
||||
`uv export --frozen --no-dev` — uv is the source of truth for the closure,
|
||||
extras, groups, and workspace selection, so pymage doesn't reimplement its
|
||||
resolver. pymage then evaluates the environment markers uv emits for the target
|
||||
and attaches wheel URLs from the lock. (Without uv, a built-in closure walker is
|
||||
used as a fallback.) The selectors below map onto `uv export` flags:
|
||||
|
||||
- `--extra <group>` enables one of the project's own
|
||||
`[project.optional-dependencies]` groups (repeatable). Extras requested *by*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue