Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Release automation: build the source distribution, create a GitHub release
# with it and publish it to PyPI.
#
# This runs when a release tag is pushed. There are no wheels: borghash is a
# Cython extension, a wheel built on a GH runner would be platform specific and
# only the sdist has ever been published.
#
# The GitHub release is created as a *draft* on purpose: the release notes want
# a human. Publishing the draft is a single click in the GitHub UI.
#
# The upload to PyPI is a separate job only so that the "pypi" environment gate
# applies to the upload alone - that is the last chance to stop a release before
# the irreversible step.
#
# One-time setup, so that no API token has to be stored anywhere:
# - on pypi.org, add a trusted publisher to the "borghash" project:
# owner "borgbackup", repository "borghash", workflow "release.yml",
# environment "pypi".
# - create the "pypi" environment in the repository settings. Configuring
# required reviewers for it makes the upload wait for an approval.

name: Release

on:
push:
# borghash tags have no "v" prefix: 0.2.0, and 0.3.0b1 for a pre-release.
tags:
- '*.*.*'

permissions:
contents: read

jobs:
release:
name: Build the sdist and draft the GitHub release
runs-on: ubuntu-24.04
timeout-minutes: 30

permissions:
contents: write # to create the release

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Just fetching one commit is not enough for setuptools-scm, so we fetch all.
fetch-depth: 0
fetch-tags: true

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'

- name: Build the sdist
# The .c files are not in git and Cython is not in build-system.requires,
# so "python -m build" can not cythonize in its isolated environment: the
# extensions have to be cythonized first, like the CI workflow does it.
# This is also what "cythonize with the latest Cython release" in
# CHANGES.rst has always meant.
run: |
set -euxo pipefail
python -m pip install --upgrade pip setuptools build twine Cython
python setup.py build_ext --inplace
python -m build --sdist
twine check dist/*
ls -l dist/

- name: Check that the sdist is the one for this tag
# A missing or unfetched tag makes setuptools-scm silently produce a dev
# version - much cheaper to notice here than on PyPI.
env:
TAG: ${{ github.ref_name }}
run: |
set -euxo pipefail
test -f "dist/borghash-$TAG.tar.gz"

- name: Check that the sdist is complete and installable
# A release that can not be installed from PyPI is the worst kind of
# release. Nothing else installs borghash from a sdist, and no other
# workflow runs on a tag push, so this is the release gate: the sdist
# must build without Cython (only the generated .c files are in it) and
# the test suite must pass against what it installs.
run: |
set -euxo pipefail
python -m venv "$RUNNER_TEMP/venv-sdist"
"$RUNNER_TEMP/venv-sdist/bin/pip" install --upgrade pip
"$RUNNER_TEMP/venv-sdist/bin/pip" install dist/borghash-*.tar.gz
"$RUNNER_TEMP/venv-sdist/bin/pip" install pytest pytest-benchmark
# tests/ is not in the sdist, it comes from the checkout - but borghash
# is imported from the venv, the checkout only has it below src/.
"$RUNNER_TEMP/venv-sdist/bin/pytest" -v -rs tests/
"$RUNNER_TEMP/venv-sdist/bin/borghash-demo"

- name: Create the draft release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euxo pipefail
# 0.3.0b1 and friends are pre-releases, 0.3.0 is not.
prerelease=""
case "$TAG" in *a*|*b*|*rc*) prerelease="--prerelease" ;; esac
cat > release-notes.md <<EOF
See the [changelog](https://github.com/borgbackup/borghash/blob/$TAG/CHANGES.rst) for what changed in this release.

### Installation

\`pip install borghash==$TAG\`

borghash is a Cython extension: installing the source distribution below
needs a C compiler, but no Cython - the generated C files are included.
EOF
if gh release view "$TAG" > /dev/null 2>&1; then
# a re-run of this job: keep the (possibly already edited) release and
# just replace its assets.
gh release upload "$TAG" --clobber dist/*.tar.gz
else
gh release create "$TAG" \
--draft $prerelease \
--title "borghash $TAG" \
--notes-file release-notes.md \
dist/*.tar.gz
fi
gh release view "$TAG" --json isDraft,isPrerelease,assets

- name: Keep the sdist for the PyPI upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sdist
path: dist/*.tar.gz
if-no-files-found: error

pypi:
name: Upload the sdist to PyPI
needs: [release]

runs-on: ubuntu-24.04
timeout-minutes: 30

environment:
name: pypi
url: https://pypi.org/project/borghash/

permissions:
contents: read
id-token: write # trusted publishing

steps:
- name: Get the sdist built by the release job
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdist
path: dist

- name: What we are about to upload
run: ls -l dist/

- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,26 @@ The generated C files are included in the sdist, thus installing the built
package does not require Cython.


Making a release
----------------

Update ``CHANGES.rst`` (the heading of the new section belongs onto the last
commit that goes into the release) and merge that via a pull request. Then put
an annotated, signed tag named like the version (no ``v`` prefix) onto the
"update CHANGES" commit and push it::

git tag -s -m "tagged/signed release 0.3.0" 0.3.0
git push origin 0.3.0

Pushing the tag runs ``.github/workflows/release.yml``, which builds the sdist,
checks that it is complete and installable, and creates a *draft* GitHub
release with it. The upload to PyPI happens in the ``pypi`` job, which uses
trusted publishing (no API token) and waits for an approval if the ``pypi``
environment has required reviewers configured.

Finally, write the release notes and publish the draft release.


Want a demo?
------------

Expand Down
Loading