Skip to content

Commit 6b0fb90

Browse files
authored
Merge pull request #13048 from sbidoul/trusted-publisher-sbi
Add trusted publisher release workfiow
2 parents c7fb1e1 + 1801d83 commit 6b0fb90

9 files changed

+154
-9
lines changed

.github/dependabot.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ updates:
33
- package-ecosystem: "github-actions"
44
directory: "/"
55
schedule:
6-
interval: "monthly"
6+
interval: "weekly"
77
groups:
88
github-actions:
99
patterns:
1010
- "*"
11+
- package-ecosystem: "pip"
12+
directory: "/"
13+
schedule:
14+
interval: "weekly"

.github/workflows/release.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Publish Python 🐍 distribution 📦 to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
jobs:
9+
build:
10+
name: Build distribution 📦
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
15+
with:
16+
persist-credentials: false
17+
- name: Build a binary wheel and a source tarball
18+
run: ./build-project.py
19+
- name: Store the distribution packages
20+
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
21+
with:
22+
name: python-package-distributions
23+
path: dist/
24+
25+
publish-to-pypi:
26+
name: >-
27+
Publish Python 🐍 distribution 📦 to PyPI
28+
needs:
29+
- build
30+
runs-on: ubuntu-latest
31+
environment:
32+
name: pypi
33+
url: https://pypi.org/project/pip/${{ github.ref_name }}
34+
permissions:
35+
id-token: write # IMPORTANT: mandatory for trusted publishing
36+
37+
steps:
38+
- name: Download all the dists
39+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
40+
with:
41+
name: python-package-distributions
42+
path: dist/
43+
- name: Publish distribution 📦 to PyPI
44+
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # release/v1

MANIFEST.in

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ include README.rst
55
include SECURITY.md
66
include pyproject.toml
77

8+
include build-requirements.in
9+
include build-requirements.txt
10+
include build-project.py
11+
812
include src/pip/_vendor/README.rst
913
include src/pip/_vendor/vendor.txt
1014
recursive-include src/pip/_vendor *LICENSE*

build-project.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
"""Build pip using pinned build requirements."""
3+
4+
import subprocess
5+
import tempfile
6+
import venv
7+
from os import PathLike
8+
from pathlib import Path
9+
from types import SimpleNamespace
10+
11+
12+
class EnvBuilder(venv.EnvBuilder):
13+
"""A subclass of venv.EnvBuilder that exposes the python executable command."""
14+
15+
def ensure_directories(
16+
self, env_dir: str | bytes | PathLike[str] | PathLike[bytes]
17+
) -> SimpleNamespace:
18+
context = super().ensure_directories(env_dir)
19+
self.env_exec_cmd = context.env_exec_cmd
20+
return context
21+
22+
23+
def get_git_head_timestamp() -> str:
24+
return subprocess.run(
25+
[
26+
"git",
27+
"log",
28+
"-1",
29+
"--pretty=format:%ct",
30+
],
31+
text=True,
32+
stdout=subprocess.PIPE,
33+
).stdout.strip()
34+
35+
36+
def main() -> None:
37+
with tempfile.TemporaryDirectory() as build_env:
38+
env_builder = EnvBuilder(with_pip=True)
39+
env_builder.create(build_env)
40+
subprocess.run(
41+
[
42+
env_builder.env_exec_cmd,
43+
"-Im",
44+
"pip",
45+
"install",
46+
"--no-deps",
47+
"--only-binary=:all:",
48+
"--require-hashes",
49+
"-r",
50+
Path(__file__).parent / "build-requirements.txt",
51+
],
52+
check=True,
53+
)
54+
subprocess.run(
55+
[
56+
env_builder.env_exec_cmd,
57+
"-Im",
58+
"build",
59+
"--no-isolation",
60+
],
61+
check=True,
62+
env={"SOURCE_DATE_EPOCH": get_git_head_timestamp()},
63+
)
64+
65+
66+
if __name__ == "__main__":
67+
main()

build-requirements.in

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
setuptools

build-requirements.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.12
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --generate-hashes build-requirements.in
6+
#
7+
build==1.2.2.post1 \
8+
--hash=sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5 \
9+
--hash=sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7
10+
# via -r build-requirements.in
11+
packaging==24.2 \
12+
--hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \
13+
--hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f
14+
# via build
15+
pyproject-hooks==1.2.0 \
16+
--hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \
17+
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
18+
# via build
19+
20+
# The following packages are considered to be unsafe in a requirements file:
21+
setuptools==75.8.0 \
22+
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \
23+
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3
24+
# via -r build-requirements.in

docs/html/development/release-process.rst

+2-5
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,8 @@ Creating a new release
146146
This will update the relevant files and tag the correct commit.
147147
#. Submit the ``release/YY.N`` branch as a pull request and ensure CI passes.
148148
Merge the changes back into ``main`` and pull them back locally.
149-
#. Build the release artifacts using ``nox -s build-release -- YY.N``.
150-
This will checkout the tag, generate the distribution files to be
151-
uploaded and checkout the main branch again.
152-
#. Upload the release to PyPI using ``nox -s upload-release -- YY.N``.
153-
#. Push the tag created by ``prepare-release``.
149+
#. Push the tag created by ``prepare-release``. This will trigger the release
150+
workflow on GitHub and publish to PyPI.
154151
#. Regenerate the ``get-pip.py`` script in the `get-pip repository`_ (as
155152
documented there) and commit the results.
156153
#. Submit a Pull Request to `CPython`_ adding the new version of pip

news/13048.process.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Started releasing to PyPI from a GitHub Actions CI/CD workflow that implements trusted publishing and bundles :pep:`740` digital attestations.
2+
3+
In addition to being signed, the released distribution packages are now reproducible through the commit timestamp.

noxfile.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def build_release(session: nox.Session) -> None:
339339
)
340340

341341
session.log("# Install dependencies")
342-
session.install("build", "twine")
342+
session.install("twine")
343343

344344
with release.isolated_temporary_checkout(session, version) as build_dir:
345345
session.log(
@@ -375,11 +375,11 @@ def build_dists(session: nox.Session) -> List[str]:
375375
)
376376

377377
session.log("# Build distributions")
378-
session.run("python", "-m", "build", silent=True)
378+
session.run("python", "build-project.py", silent=True)
379379
produced_dists = glob.glob("dist/*")
380380

381381
session.log(f"# Verify distributions: {', '.join(produced_dists)}")
382-
session.run("twine", "check", *produced_dists, silent=True)
382+
session.run("twine", "check", "--strict", *produced_dists, silent=True)
383383

384384
return produced_dists
385385

0 commit comments

Comments
 (0)