From a3400f138a7179e177e8c27202183406149ef1c3 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 1 May 2025 18:14:56 +1000 Subject: [PATCH 01/24] Publish to PyPI via GitHub CI --- .github/workflows/publish.yml | 36 ++++++++++++++++ docs/development/release-process.rst | 4 +- noxfile.py | 64 ++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..a38ed6dd2 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,36 @@ +name: Publish + +on: + release: + types: [created] + +env: + FORCE_COLOR: 1 + +jobs: + publish: + environment: + name: pypi + url: https://pypi.org/p/packaging + permissions: + id-token: write + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + name: Install Python + with: + python-version: "3.12" + cache: "pip" + allow-prereleases: false + + - name: Build distribution via nox + run: pipx run nox --error-on-missing-interpreters -s release_build + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1 + with: + print-hash: true diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index e4e40d256..4a0084fc6 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -11,10 +11,12 @@ Release Process $ nox -s release -- YY.N - You will need the password for your GPG key as well as an API token for PyPI. + This creates and pushes a new tag for the release #. Add a `release on GitHub `__. + This triggers a CI workflow which builds and publishes the package to PyPI + #. Notify the other project owners of the release. .. note:: diff --git a/noxfile.py b/noxfile.py index 3d666e88b..b786aa5b4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,7 +12,6 @@ import tempfile import textwrap import time -import webbrowser from pathlib import Path import nox @@ -141,8 +140,48 @@ def release(session): next_version = f"{major}.{minor + 1}.dev0" _bump(session, version=next_version, file=version_file, kind="development") - # Checkout the git tag. - session.run("git", "checkout", "-q", release_version, external=True) + # Push the commits and tag. + # NOTE: The following fails if pushing to the branch is not allowed. This can + # happen on GitHub, if the main branch is protected, there are required + # CI checks and "Include administrators" is enabled on the protection. + session.run("git", "push", "upstream", "main", release_version, external=True) + + +@nox.session +def release_build(session): + package_name = "packaging" + + # Parse version from command-line arguments, if provided, otherwise get + # from Git tag. + try: + release_version = _get_version_from_arguments(session.posargs) + except ValueError as e: + if session.posargs: + session.error(f"Invalid arguments: {e}") + + release_version = session.run( + "git", "describe", "--exact-match", silent=True, external=True + ) + release_version = release_version.strip() + session.debug(f"version: {release_version}") + checkout = False + else: + checkout = True + + # Check state of working directory. + _check_working_directory_state(session) + + # Ensure there are no uncommitted changes. + result = subprocess.run( + ["git", "status", "--porcelain"], capture_output=True, encoding="utf-8" + ) + if result.stdout: + print(result.stdout, end="", file=sys.stderr) + session.error("The working tree has uncommitted changes") + + # Checkout the git tag, if provided. + if checkout: + session.run("git", "checkout", "-q", release_version, external=True) session.install("build", "twine") @@ -162,24 +201,13 @@ def release(session): diff = "\n".join(diff_generator) session.error(f"Got the wrong files:\n{diff}") - # Get back out into main. - session.run("git", "checkout", "-q", "main", external=True) + # Get back out into main, if we checked out before. + if checkout: + session.run("git", "checkout", "-q", "main", external=True) - # Check and upload distribution files. + # Check distribution files. session.run("twine", "check", *files) - # Push the commits and tag. - # NOTE: The following fails if pushing to the branch is not allowed. This can - # happen on GitHub, if the main branch is protected, there are required - # CI checks and "Include administrators" is enabled on the protection. - session.run("git", "push", "upstream", "main", release_version, external=True) - - # Upload the distribution. - session.run("twine", "upload", *files) - - # Open up the GitHub release page. - webbrowser.open("https://github.com/pypa/packaging/releases") - @nox.session def update_licenses(session: nox.Session) -> None: From b7f3f7e1215e51b3a58ef018046eeb32a83f1ea0 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sat, 3 May 2025 15:53:19 +1000 Subject: [PATCH 02/24] Don't cache pip in publish CI job Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a38ed6dd2..225d5cbac 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,7 +24,6 @@ jobs: name: Install Python with: python-version: "3.12" - cache: "pip" allow-prereleases: false - name: Build distribution via nox From 759c36f36778721c79c7da23a5763118f20547c2 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sat, 3 May 2025 15:53:55 +1000 Subject: [PATCH 03/24] Improve language in noxfile comment Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index b786aa5b4..62128f6ec 100644 --- a/noxfile.py +++ b/noxfile.py @@ -179,7 +179,7 @@ def release_build(session): print(result.stdout, end="", file=sys.stderr) session.error("The working tree has uncommitted changes") - # Checkout the git tag, if provided. + # Check out the Git tag, if provided. if checkout: session.run("git", "checkout", "-q", release_version, external=True) From 70457e5009a57efa22abb1bbc20ad9fe53d03f4d Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sat, 3 May 2025 15:52:18 +1000 Subject: [PATCH 04/24] Use latest Python version available --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 225d5cbac..72d2dc880 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 name: Install Python with: - python-version: "3.12" + python-version: "3.x" allow-prereleases: false - name: Build distribution via nox From 671d6abb635625aecd479fdd922adb5716dc12fd Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sat, 3 May 2025 16:03:12 +1000 Subject: [PATCH 05/24] Switch PyPA action version comment to tag From branch --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 72d2dc880..cb980c203 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,6 +30,6 @@ jobs: run: pipx run nox --error-on-missing-interpreters -s release_build - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: print-hash: true From 944e4eecbdf8af2a4ada9941b1dd10895cdc55ba Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sat, 3 May 2025 16:10:31 +1000 Subject: [PATCH 06/24] Split build out from release CI job Upload/download distributions between jobs using respective GitHub actions --- .github/workflows/publish.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cb980c203..bae59c98f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,13 +8,7 @@ env: FORCE_COLOR: 1 jobs: - publish: - environment: - name: pypi - url: https://pypi.org/p/packaging - permissions: - id-token: write - + build: runs-on: ubuntu-latest steps: @@ -29,6 +23,30 @@ jobs: - name: Build distribution via nox run: pipx run nox --error-on-missing-interpreters -s release_build + - name: Upload distribution + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 + with: + name: packages + path: dist/ + if-no-files-found: error + compression-level: 0 + + publish: + environment: + name: pypi + url: https://pypi.org/p/packaging + permissions: + id-token: write + + runs-on: ubuntu-latest + + steps: + - name: Download distribution + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 4.3.0 + with: + name: packages + path: dist/ + - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: From cc9692b6608c15733ba684cb8b9adfc33b45c96f Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sun, 4 May 2025 15:35:56 +1000 Subject: [PATCH 07/24] Document approvals required for release --- docs/development/release-process.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index 4a0084fc6..8d5316319 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -15,7 +15,8 @@ Release Process #. Add a `release on GitHub `__. - This triggers a CI workflow which builds and publishes the package to PyPI + This schedules a CI workflow which will build and publish the package to + PyPI, after any required approvals are made #. Notify the other project owners of the release. From d855d627ac79868f80bce257e8493d365daff109 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Sun, 4 May 2025 15:44:47 +1000 Subject: [PATCH 08/24] Link to version page in release deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bae59c98f..ee099a81d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: publish: environment: name: pypi - url: https://pypi.org/p/packaging + url: https://pypi.org/project/packaging/${{ github.ref_name }} permissions: id-token: write From 6d6ba8ee1ac2368f681c17585f767c49ae29064d Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 May 2025 19:21:27 +1000 Subject: [PATCH 09/24] Separate install from build in release CI job --- .github/workflows/publish.yml | 5 ++++- noxfile.py | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee099a81d..24e83b319 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,8 +20,11 @@ jobs: python-version: "3.x" allow-prereleases: false + - name: Provision nox environment + run: pipx run nox --install-only + - name: Build distribution via nox - run: pipx run nox --error-on-missing-interpreters -s release_build + run: pipx run nox --no-install --error-on-missing-interpreters -s release_build - name: Upload distribution uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 diff --git a/noxfile.py b/noxfile.py index 62128f6ec..6b20c9764 100644 --- a/noxfile.py +++ b/noxfile.py @@ -151,6 +151,9 @@ def release(session): def release_build(session): package_name = "packaging" + # Determine if we're in install-only mode. + install_only = session.run("python", "--version", silent=True) is None + # Parse version from command-line arguments, if provided, otherwise get # from Git tag. try: @@ -162,7 +165,7 @@ def release_build(session): release_version = session.run( "git", "describe", "--exact-match", silent=True, external=True ) - release_version = release_version.strip() + release_version = "" if install_only else release_version.strip() session.debug(f"version: {release_version}") checkout = False else: @@ -194,7 +197,7 @@ def release_build(session): f"dist/{package_name}-{release_version}-py3-none-any.whl", f"dist/{package_name}-{release_version}.tar.gz", ] - if files != expected: + if files != expected and not install_only: diff_generator = difflib.context_diff( expected, files, fromfile="expected", tofile="got", lineterm="" ) From ed64a232c23cace637650ae2ebe407a5706d2f46 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 May 2025 19:22:23 +1000 Subject: [PATCH 10/24] Link to CI job approval documentation --- docs/development/release-process.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index 8d5316319..fa1d6cf95 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -16,7 +16,7 @@ Release Process #. Add a `release on GitHub `__. This schedules a CI workflow which will build and publish the package to - PyPI, after any required approvals are made + PyPI. Publishing will wait for any `required approvals`_ #. Notify the other project owners of the release. @@ -27,3 +27,5 @@ Release Process - PyPI maintainer (or owner) access to ``packaging`` - push directly to the ``main`` branch on the source repository - push tags directly to the source repository + +.. _required approvals: https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job From b2a96521d344bbebfc2023adaec3b429ca06f9fe Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 May 2025 19:23:11 +1000 Subject: [PATCH 11/24] Add punctuation to release process documentation --- docs/development/release-process.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index fa1d6cf95..c70581678 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -11,12 +11,12 @@ Release Process $ nox -s release -- YY.N - This creates and pushes a new tag for the release + This creates and pushes a new tag for the release. #. Add a `release on GitHub `__. This schedules a CI workflow which will build and publish the package to - PyPI. Publishing will wait for any `required approvals`_ + PyPI. Publishing will wait for any `required approvals`_. #. Notify the other project owners of the release. From 94b65760f73588dc8ed0dda3de583bbea05dfa6e Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 May 2025 19:48:29 +1000 Subject: [PATCH 12/24] Check built distribution during release --- noxfile.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/noxfile.py b/noxfile.py index 6b20c9764..ed5aa1e34 100644 --- a/noxfile.py +++ b/noxfile.py @@ -122,6 +122,9 @@ def release(session): session.run("git", "add", str(changelog_file), external=True) _bump(session, version=release_version, file=version_file, kind="release") + # Check the built distribution. + _build_and_check(session, release_version, remove=True) + # Tag the release commit. # fmt: off session.run( @@ -149,11 +152,6 @@ def release(session): @nox.session def release_build(session): - package_name = "packaging" - - # Determine if we're in install-only mode. - install_only = session.run("python", "--version", silent=True) is None - # Parse version from command-line arguments, if provided, otherwise get # from Git tag. try: @@ -165,7 +163,7 @@ def release_build(session): release_version = session.run( "git", "describe", "--exact-match", silent=True, external=True ) - release_version = "" if install_only else release_version.strip() + release_version = "" if release_version is None else release_version.strip() session.debug(f"version: {release_version}") checkout = False else: @@ -186,8 +184,22 @@ def release_build(session): if checkout: session.run("git", "checkout", "-q", release_version, external=True) + # Build the distribution. + _build_and_check(session, release_version) + + # Get back out into main, if we checked out before. + if checkout: + session.run("git", "checkout", "-q", "main", external=True) + + +def _build_and_check(session, release_version, remove=False): + package_name = "packaging" + session.install("build", "twine") + # Determine if we're in install-only mode. + install_only = session.run("python", "--version", silent=True) is None + # Build the distribution. session.run("python", "-m", "build") @@ -204,13 +216,13 @@ def release_build(session): diff = "\n".join(diff_generator) session.error(f"Got the wrong files:\n{diff}") - # Get back out into main, if we checked out before. - if checkout: - session.run("git", "checkout", "-q", "main", external=True) - # Check distribution files. session.run("twine", "check", *files) + # Remove distribution files, if requested. + if remove and not install_only: + shutil.rmtree("dist", ignore_errors=True) + @nox.session def update_licenses(session: nox.Session) -> None: From a0fc13eed2f0c951435707c1e157febfe21b18e6 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 May 2025 19:49:28 +1000 Subject: [PATCH 13/24] Run twine check in strict mode during release Converts warnings to errors --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index ed5aa1e34..c91e03f7f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -217,7 +217,7 @@ def _build_and_check(session, release_version, remove=False): session.error(f"Got the wrong files:\n{diff}") # Check distribution files. - session.run("twine", "check", *files) + session.run("twine", "check", "--strict", *files) # Remove distribution files, if requested. if remove and not install_only: From f2505451afa41706d1c24e8d1883cb58e88cbc60 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Fri, 9 May 2025 11:52:10 +1000 Subject: [PATCH 14/24] Run twine-check in linting CI workflow --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b57ee770c..13a325877 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -51,6 +51,9 @@ jobs: - name: Build run: pipx run build + - name: Build + run: pipx run twine check --strict + - name: Archive files uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: From 9edb7a5a69e103c0d769229a43f366203b809c87 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Fri, 9 May 2025 11:52:48 +1000 Subject: [PATCH 15/24] Remove redundant Python-setup step in release-build CI job --- .github/workflows/publish.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 24e83b319..81496e94e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,11 +14,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 - name: Install Python - with: - python-version: "3.x" - allow-prereleases: false + # pipx is pre-installed in 'ubuntu' VMs - name: Provision nox environment run: pipx run nox --install-only From ef36fbdcc4da55b0996b4df57a333bbbb9e4caaa Mon Sep 17 00:00:00 2001 From: Laurie O Date: Tue, 29 Jul 2025 11:23:38 +1000 Subject: [PATCH 16/24] Switch to 'git switch' for switching branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index c91e03f7f..2a7664ac1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -182,14 +182,14 @@ def release_build(session): # Check out the Git tag, if provided. if checkout: - session.run("git", "checkout", "-q", release_version, external=True) + session.run("git", "switch", "-q", release_version, external=True) # Build the distribution. _build_and_check(session, release_version) # Get back out into main, if we checked out before. if checkout: - session.run("git", "checkout", "-q", "main", external=True) + session.run("git", "switch", "-q", "main", external=True) def _build_and_check(session, release_version, remove=False): From 5f364189173e6140f30923cf97536665a33c6477 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Tue, 29 Jul 2025 11:26:46 +1000 Subject: [PATCH 17/24] Switch to release 'published' event for CI trigger --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 81496e94e..ef102aa42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,7 +2,7 @@ name: Publish on: release: - types: [created] + types: [published] env: FORCE_COLOR: 1 From 75cee2c27876eb45bb94bb1624277b76e3574c4a Mon Sep 17 00:00:00 2001 From: Laurie O Date: Wed, 6 Aug 2025 17:39:05 +1000 Subject: [PATCH 18/24] Revert "Run twine-check in linting CI workflow" This reverts commit f2505451afa41706d1c24e8d1883cb58e88cbc60. --- .github/workflows/lint.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 13a325877..b57ee770c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -51,9 +51,6 @@ jobs: - name: Build run: pipx run build - - name: Build - run: pipx run twine check --strict - - name: Archive files uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: From d81fd829c78beb135ab858c94102fa53a557b5f5 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 7 Aug 2025 13:25:26 +1000 Subject: [PATCH 19/24] Name checkout step --- .github/workflows/publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ef102aa42..59d17e5f8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # pipx is pre-installed in 'ubuntu' VMs From 100ed2f4a4135631dc5d822632f773a297267c6f Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 7 Aug 2025 13:31:29 +1000 Subject: [PATCH 20/24] Document install-only detection --- noxfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 374158ae2..caf0f58b4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -199,7 +199,12 @@ def _build_and_check(session, release_version, remove=False): session.install("build", "twine") - # Determine if we're in install-only mode. + # Determine if we're in install-only mode. This works as `python --version` + # should always succeed when running `nox`, but in install-only mode + # `session.run(..., silent=True)` always immediately returns `None` instead + # of invoking the command and returning the command's output. See the + # documentation at: + # https://nox.thea.codes/en/stable/usage.html#skipping-everything-but-install-commands install_only = session.run("python", "--version", silent=True) is None # Build the distribution. From f5494b8d44dc92bfa29237eae2fd32499b62a2a5 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Mon, 15 Sep 2025 15:26:34 +1000 Subject: [PATCH 21/24] Use 'pypa/gh-action-pypi-publish' GitHub action v1.13 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 59d17e5f8..7e92bdc02 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,6 +48,6 @@ jobs: path: dist/ - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: print-hash: true From 7b7127ea78e12d12af9ecefda378b6f446b9113f Mon Sep 17 00:00:00 2001 From: Laurie O Date: Wed, 19 Nov 2025 17:18:42 +1000 Subject: [PATCH 22/24] Make publish job require build job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7e92bdc02..389f69dc4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,6 +32,8 @@ jobs: compression-level: 0 publish: + needs: + - build environment: name: pypi url: https://pypi.org/project/packaging/${{ github.ref_name }} From 2c405b994b5d0f27c9c2aa1cff9e5ab50d0e8913 Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 20 Nov 2025 15:28:49 +1000 Subject: [PATCH 23/24] Format and add type annotations --- noxfile.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index 35bd43037..2a4932e1c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -157,9 +157,10 @@ def release(session: nox.Session) -> None: @nox.session -def release_build(session): +def release_build(session: nox.Session) -> None: # Parse version from command-line arguments, if provided, otherwise get # from Git tag. + release_version: str | None try: release_version = _get_version_from_arguments(session.posargs) except ValueError as e: @@ -180,7 +181,10 @@ def release_build(session): # Ensure there are no uncommitted changes. result = subprocess.run( - ["git", "status", "--porcelain"], capture_output=True, encoding="utf-8" + ["git", "status", "--porcelain"], + check=False, + capture_output=True, + encoding="utf-8", ) if result.stdout: print(result.stdout, end="", file=sys.stderr) @@ -198,7 +202,11 @@ def release_build(session): session.run("git", "switch", "-q", "main", external=True) -def _build_and_check(session, release_version, remove=False): +def _build_and_check( + session: nox.Session, + release_version: str, + remove: bool = False, +) -> None: package_name = "packaging" session.install("build", "twine") From 861784fb0d9fb9770b55a3d57509fa1871b268dd Mon Sep 17 00:00:00 2001 From: Laurie O Date: Wed, 3 Dec 2025 15:13:37 +1000 Subject: [PATCH 24/24] Switch to manual workflow run --- .github/workflows/publish.yml | 25 +++++++++++++++++++++++-- docs/development/release-process.rst | 5 +++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 389f69dc4..553244d66 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,8 +1,13 @@ name: Publish on: - release: - types: [published] + workflow_dispatch: + inputs: + ref: + # require SHA as tags and branches are mutable + description: Git commit SHA (not branch/tag) + required: true + type: string env: FORCE_COLOR: 1 @@ -12,8 +17,24 @@ jobs: runs-on: ubuntu-latest steps: + - name: Log inputs + run: echo "${{ inputs }}" + - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.ref }} + fetch-depth: 2 # shouldn't need much, if any, history + + - name: Log Git tag + run: git describe --always --tags + + - name: Check ref is the commit SHA + # require SHA as tags and branches are mutable + run: test "${{ inputs.ref }}" + = "$(git git log --max-count=1 --format=format:%h)" + || test "${{ inputs.ref }}" + = "$(git git log --max-count=1 --format=format:%H)" # pipx is pre-installed in 'ubuntu' VMs diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index c70581678..5e9b52511 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -15,8 +15,9 @@ Release Process #. Add a `release on GitHub `__. - This schedules a CI workflow which will build and publish the package to - PyPI. Publishing will wait for any `required approvals`_. +#. Run the 'Publish' manual GitHub workflow, specifying the Git tag's commit + SHA. This will build and publish the package to PyPI. Publishing will wait + for any `required approvals`_. #. Notify the other project owners of the release.