Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
118 lines (104 loc) · 6.03 KB
/
Copy pathMakefile
File metadata and controls
118 lines (104 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# template-python-package's implementation of template-base's release
# contract (docs/TEMPLATE.md, "Release: a Makefile contract"). release.yml
# sets RELEASE_VERSION / RELEASE_TAG / RELEASE_ARCH -- see there. A pure-Python
# wheel is built once, on one arch. PYPI_PUBLISH (a repo variable) selects
# where `publish-packages` uploads: unset = skip with a warning, `testpypi` or
# `pypi`. Uploads use PyPI trusted publishing (GitHub OIDC, the
# `publish-packages` job's `id-token: write`), so there is no stored token.
# `publish` is the per-arch image publish and is a no-op here (template-base
# >= 1.9.0).
.PHONY: release-arches build sbom release-assets publish publish-packages verify-published release-check finalize
ARCHES := amd64
# Distribution name from pyproject.toml, so an instance that renames the
# package needs no change here.
NAME := $(shell python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["name"])')
ASSET := dist/$(NAME)-$(RELEASE_VERSION)
# Pinned by tag and digest; Renovate updates both together (the
# SYFT_IMAGE/UV_IMAGE manager in .github/renovate.json). It runs as root on
# dist/ between `build` and the upload, so a retagged image must not be able
# to rewrite the wheel and sdist.
SYFT_IMAGE := anchore/syft:v1.52.0@sha256:500e2d872ac019436926e8322b4fc1f39441d94d21f6f4046c6ff29b30e8cb02
# release.yml runs build/sbom/publish-packages on the bare runner, which has docker but
# no uv; release-check runs in the devcontainer, which has uv. So use the
# host's uv when there is one, else the pinned uv image (same uv and Python
# minor as .devcontainer/Dockerfile; tests/unit/test_release_contract.py
# fails if the uv version drifts from it). The OIDC variables are forwarded so
# `uv publish --trusted-publishing always` works inside the container.
# UV_IMAGE is pinned by tag and digest (a retagged image would receive the OIDC
# request token); Renovate updates both. UV_VERSION and PYTHON_VERSION above
# stay the source the devcontainer test compares, and the guard below fails the
# Makefile if the image tag drifts from them.
# renovate: datasource=github-releases depName=astral-sh/uv
UV_VERSION := 0.13.6
# renovate: datasource=python-version depName=python versioning=docker
PYTHON_VERSION := 3.14
UV_IMAGE := ghcr.io/astral-sh/uv:0.13.6-python3.14-trixie-slim@sha256:637619278855a77885a94312efa7decd8479ca6cec5f959e134cbab96582821e
ifeq ($(findstring :$(UV_VERSION)-python$(PYTHON_VERSION)-,$(UV_IMAGE)),)
$(error UV_IMAGE ($(UV_IMAGE)) does not match UV_VERSION $(UV_VERSION) and PYTHON_VERSION $(PYTHON_VERSION); update the three together)
endif
UV := $(shell command -v uv >/dev/null 2>&1 && echo uv || echo \
docker run --rm -u $$(id -u):$$(id -g) -v "$(CURDIR):/src" -w /src \
-e UV_CACHE_DIR=/tmp/uv-cache -e UV_PYTHON_DOWNLOADS=never \
-e GITHUB_ACTIONS -e ACTIONS_ID_TOKEN_REQUEST_URL -e ACTIONS_ID_TOKEN_REQUEST_TOKEN \
$(UV_IMAGE) uv)
TESTPYPI_UPLOAD := https://test.pypi.org/legacy/
TESTPYPI_CHECK := https://test.pypi.org/simple/
PYPI_UPLOAD := https://upload.pypi.org/legacy/
PYPI_CHECK := https://pypi.org/simple/
release-arches:
@python3 -c 'import json,sys; print(json.dumps(sys.argv[1:]))' $(ARCHES)
# Stamps RELEASE_VERSION into pyproject.toml only for the duration of the
# build (restored on exit), so the checkout stays clean.
build:
rm -rf dist
mkdir -p dist
@set -e; bak=$$(mktemp); cp pyproject.toml "$$bak"; \
trap 'cp "$$bak" pyproject.toml; rm -f "$$bak"' EXIT; \
$(if $(RELEASE_VERSION),$(UV) version --frozen "$(RELEASE_VERSION)";) \
$(UV) build --no-sources
sbom:
@set -e; whl=$$(ls dist/*.whl); \
docker run --rm -v "$(CURDIR)/dist:/dist" $(SYFT_IMAGE) \
"file:/$$whl" -o "spdx-json=/$${whl%.whl}.spdx.json"
release-assets:
@set -e; for pat in 'dist/*.tar.gz' 'dist/*.whl' 'dist/*.spdx.json'; do \
ls $$pat >/dev/null 2>&1 || { echo "missing release asset: $$pat" >&2; exit 1; }; \
done; ls dist
# Per arch, image registries only: this template ships no image.
publish:
@echo "Python package -- no image to publish (see publish-packages)."
# Once, in release.yml's `publish-packages` job, which exists only when a
# *_PUBLISH variable is set and has `id-token: write`. That job is a bare
# runner: a checkout plus dist/ (the wheel and sdist `build` produced, which
# `sbom` described), so nothing is rebuilt. Only the wheel and sdist go up;
# dist/ also holds the SBOM. Add `--dry-run` to `uv publish` to check the
# files without uploading.
publish-packages:
ifeq ($(PYPI_PUBLISH),)
ifneq ($(NPM_PUBLISH)$(CARGO_PUBLISH),)
@echo "PYPI_PUBLISH is not set -- skipping the PyPI upload (another registry is publishing)."
else
@echo "::warning title=PyPI publish skipped::PYPI_PUBLISH is not set, so this package was NOT published to PyPI. To enable it, set the PYPI_PUBLISH repository variable to 'testpypi' or 'pypi' (Settings > Secrets and variables > Actions > Variables) and re-run the release."
endif
else ifeq ($(PYPI_PUBLISH),pypi)
$(UV) publish --trusted-publishing always --publish-url $(PYPI_UPLOAD) --check-url $(PYPI_CHECK) dist/*.whl dist/*.tar.gz
else ifeq ($(PYPI_PUBLISH),testpypi)
$(UV) publish --trusted-publishing always --publish-url $(TESTPYPI_UPLOAD) --check-url $(TESTPYPI_CHECK) dist/*.whl dist/*.tar.gz
else
@echo "PYPI_PUBLISH must be 'pypi' or 'testpypi', got '$(PYPI_PUBLISH)'." >&2; exit 1
endif
# Once, in release.yml's `verify-published` job, after `publish-packages`
# succeeded and only when a *_PUBLISH variable is set: installs the published
# release from the index. A bare runner with no secrets and an older python3,
# so the script builds its venv with the pinned uv (the host's, else the
# UV_IMAGE container) -- see scripts/release-verify-published.sh.
verify-published:
UV_IMAGE=$(UV_IMAGE) scripts/release-verify-published.sh
# Runs once after the arch leg, inside the devcontainer, and gates the GitHub
# release and publishing; dist/ holds the downloaded wheel. Checks the local
# wheel only. See scripts/release-check.sh.
release-check:
scripts/release-check.sh
# Nothing to combine: a single wheel is built once.
finalize:
@echo "Single pure-Python wheel -- nothing to finalize."