-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add infra for testing release build CI before a real release
- Loading branch information
1 parent
0ce5d38
commit 2974a21
Showing
2 changed files
with
305 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,34 +4,261 @@ on: | |
release: | ||
types: [released] | ||
workflow_dispatch: | ||
inputs: | ||
requested_release_tag: | ||
description: 'The tag to use for this developmental release (without `.dev` suffix) (e.g., `v2.0.1`)' | ||
required: true | ||
|
||
jobs: | ||
publish: | ||
# Responsible for validating inputs and generating release values for the rest of the workflow | ||
# Takes in the tag from the GitHub release, or a manually provided one for developmental releases (i.e., tests of the CI pipeline) | ||
pre_build_sanity_check: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/setup-python@v4 | ||
name: Install Python | ||
with: | ||
python-version: '3.12' | ||
|
||
- run: | | ||
pip install packaging | ||
- name: Capture the release tag | ||
run: | | ||
echo "release_tag=`echo '${{ github.event.release.tag_name }}${{ github.event.inputs.requested_release_tag }}'`" >> $GITHUB_ENV | ||
- name: Normalize the release tag into a version | ||
run: | | ||
echo "version_from_release_tag=`echo '${{ env.release_tag }}' | sed 's/^v//'`" >> $GITHUB_ENV | ||
- name: Log all the things | ||
run: | | ||
echo 'release event's tag `${{ env.release_tag }}`' | ||
echo 'release event's version `${{ env.version_from_release_tag }}`' | ||
- name: Verify that the release's tag matches the format we expect ("v" + Python version number) | ||
# https://peps.python.org/pep-0440/ | ||
run: | | ||
echo "${{ env.release_tag }}" | sed '/^v\([1-9][0-9]*!\)\?\(0\|[1-9][0-9]*\)\(\.\(0\|[1-9][0-9]*\)\)*\(\(a\|b\|rc\)\(0\|[1-9][0-9]*\)\)\?\(\.post\(0\|[1-9][0-9]*\)\)\?\(\.dev\(0\|[1-9][0-9]*\)\)\?$/!{q1}' | ||
- uses: actions/checkout@v4 | ||
# with: # TODO: Releases aren't necessarily on the default branch | ||
# ref: ${{ env.release_tag }} | ||
|
||
- name: Get the version from pyproject.toml | ||
run: | | ||
echo "backports_version=`grep -Po 'version = "\K[^"]*' pyproject.toml`" >> $GITHUB_ENV | ||
- name: Log all the things | ||
run: | | ||
echo 'version in pyproject.toml `${{ env.backports_version }}`' | ||
- name: Verify that the release version matches the version in pyproject.toml | ||
run: | | ||
[[ ${{ env.version_from_release_tag }} == ${{ env.backports_version }} ]] | ||
- name: Get the latest version from Test PyPI | ||
# TODO: New releases aren't necessarily for the latest code. Change this to get the most recent developmental version for a release instead | ||
if: github.event_name == 'workflow_dispatch' | ||
run: | | ||
curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; contents=sys.stdin.read(); parsed = json.loads(contents); print("test_pypi_version=" + parsed["info"]["version"])' >> $GITHUB_ENV | ||
- name: Generate the developmental release version | ||
if: github.event_name == 'workflow_dispatch' | ||
# If there is a developmental release in Test PyPI for the version in pyproject.toml, increment the number. Else 1. Save in $GITHUB_ENV | ||
run: | | ||
python -c 'from packaging import version; new = version.parse("${{ env.version_from_release_tag }}"); existing = version.parse("${{ env.test_pypi_version }}"); dev_number = existing.dev + 1 if existing.is_devrelease and new.release == existing.release else 1; epoch = f"{new.epoch}!" if new.epoch else ""; release = ".".join([str(x) for x in new.release]); pre = f"{new.pre[0]}{new.pre[1]}" if new.pre else ""; post = f".post{new.post}" if new.post else ""; dev = f".dev{dev_number}"; developmental_release_version=f"{epoch}{release}{pre}{post}{dev}"; print(f"developmental_release_version={developmental_release_version}")' >> $GITHUB_ENV | ||
- name: Determine which version to use | ||
run: echo "version_to_use=`if [ '${{ github.event_name }}' == 'workflow_dispatch' ]; then echo '${{ env.developmental_release_version }}'; else echo '${{ env.version_from_release_tag }}'; fi`" >> $GITHUB_ENV | ||
|
||
- name: Log all the things | ||
run: | | ||
echo 'release event's tag `${{ env.release_tag }}`' | ||
echo 'release event's version `${{ env.version_from_release_tag }}`' | ||
echo 'Version in pyproject.toml `${{ env.backports_version }}`' | ||
echo 'Version in Test PyPI `${{ env.test_pypi_version }}`' | ||
echo 'New developmental version `${{ env.developmental_release_version }}`' | ||
echo 'Version to use `${{ env.version_to_use }}`' | ||
- name: Verify that the version string we produced looks like a Python version string | ||
# https://peps.python.org/pep-0440/ | ||
run: | | ||
echo "${{ env.version_to_use }}" | sed '/^\([1-9][0-9]*!\)\?\(0\|[1-9][0-9]*\)\(\.\(0\|[1-9][0-9]*\)\)*\(\(a\|b\|rc\)\(0\|[1-9][0-9]*\)\)\?\(\.post\(0\|[1-9][0-9]*\)\)\?\(\.dev\(0\|[1-9][0-9]*\)\)\?$/!{q1}' | ||
- name: Serialize normalized release values | ||
run: | | ||
echo -e "version_to_use=${{ env.version_to_use }}" > release_values.txt | ||
- name: Share normalized release values | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: release_values | ||
path: release_values.txt | ||
|
||
build_wheels: | ||
name: Build wheel on ${{ matrix.os }} | ||
needs: [pre_build_sanity_check] | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, windows-latest, macos-latest] | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
# with: # TODO: Releases aren't necessarily on the default branch | ||
# ref: ${{ env.release_tag }} | ||
|
||
- uses: actions/download-artifact@v3 | ||
with: | ||
name: release_values | ||
|
||
- name: Load normalized release values | ||
run: | | ||
xargs -a release_values.txt -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | ||
- name: Replace version in pyproject.toml (developmental releases) | ||
run: sed -i -e 's/^version = ".*\?"$/version = "${{ env.version_to_use }}"/g' pyproject.toml | ||
|
||
- name: Build wheels | ||
uses: pypa/[email protected] | ||
env: | ||
CIBW_SKIP: "pp*-macosx* *-win32 *-manylinux_i686" | ||
CIBW_ARCHS_MACOS: x86_64 arm64 universal2 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
- uses: actions/upload-artifact@v3 | ||
with: | ||
python-version: 3.12 | ||
path: ./wheelhouse/*.whl | ||
|
||
- name: Run tests | ||
run: python setup.py test | ||
build_sdist: | ||
name: Build source distribution | ||
needs: [pre_build_sanity_check] | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/setup-python@v4 | ||
name: Install Python | ||
with: | ||
python-version: '3.12' | ||
|
||
- name: Build source dist | ||
- uses: actions/checkout@v4 | ||
# with: # TODO: Releases aren't necessarily on the default branch | ||
# ref: ${{ env.release_tag }} | ||
|
||
- uses: actions/download-artifact@v3 | ||
with: | ||
name: release_values | ||
|
||
- name: Load normalized release values | ||
run: | | ||
xargs -a release_values.txt -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | ||
- name: Replace version in pyproject.toml (developmental releases) | ||
run: sed -i -e 's/^version = ".*\?"$/version = "${{ env.version_to_use }}"/g' pyproject.toml | ||
|
||
- name: Build sdist | ||
run: python setup.py sdist | ||
|
||
- name: Publish package to TestPyPI | ||
uses: pypa/[email protected] | ||
- uses: actions/upload-artifact@v3 | ||
with: | ||
user: __token__ | ||
password: ${{ secrets.test_pypi_password }} | ||
repository_url: https://test.pypi.org/legacy/ | ||
path: dist/*.tar.gz | ||
|
||
# publish_to_test_pypi: | ||
# needs: [pre_build_sanity_check, build_wheels, build_sdist] | ||
# runs-on: ubuntu-latest | ||
|
||
- name: Publish package to PyPI | ||
uses: pypa/[email protected] | ||
# steps: | ||
# - uses: actions/checkout@v4 | ||
# # with: # TODO: Releases aren't necessarily on the default branch | ||
# # ref: ${{ env.release_tag }} | ||
|
||
# - name: Set up Python | ||
# uses: actions/setup-python@v4 | ||
# with: | ||
# python-version: 3.12 | ||
|
||
# - name: Publish package to TestPyPI | ||
# uses: pypa/[email protected] | ||
# with: | ||
# # TODO: Change to use "Trusted publishing"? | ||
# user: __token__ | ||
# password: ${{ secrets.test_pypi_password }} | ||
# repository_url: https://test.pypi.org/legacy/ | ||
|
||
pre_publish_sanity_check: | ||
if: github.event_name == 'release' | ||
needs: [pre_build_sanity_check] | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/download-artifact@v3 | ||
with: | ||
user: __token__ | ||
password: ${{ secrets.pypi_password }} | ||
name: release_values | ||
|
||
- name: Load normalized release values | ||
run: | | ||
xargs -a release_values.txt -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | ||
- name: Get the latest version from Test PyPI | ||
# TODO: New releases aren't necessarily for the latest code. Change this to get the most recent developmental version for a release instead | ||
run: | | ||
curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; contents=sys.stdin.read(); parsed = json.loads(contents); print("test_pypi_version=" + parsed["info"]["version"])' >> $GITHUB_ENV | ||
- name: Get the latest version from PyPI | ||
run: | | ||
curl https://pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; contents=sys.stdin.read(); parsed = json.loads(contents); print("pypi_version=" + parsed["info"]["version"])' >> $GITHUB_ENV | ||
- name: Log all the things | ||
run: | | ||
echo 'Version in Test PyPI `${{ env.test_pypi_version }}`' | ||
echo 'Version in PyPI `${{ env.pypi_version }}`' | ||
- name: Verify that there exists a developmental release for this version in Test PyPI | ||
# https://peps.python.org/pep-0440 | ||
# Meant to make sure that we aren't somehow skipping the "developmental release" phase of the release | ||
# (e.g., Publishing the GitHub release without first saving the draft) | ||
run: | | ||
python -c 'import sys; from packaging import version; code = 0 if version.parse("${{ env.test_pypi_version }}").is_devrelease else 1; sys.exit(code)' | ||
- name: Verify that the `version_to_use` is not a "developmental release" | ||
# https://peps.python.org/pep-0440 | ||
run: | | ||
python -c 'import sys; from packaging import version; code = 1 if version.parse("${{ env.version_to_use }}").is_devrelease else 0; sys.exit(code)' | ||
- name: Verify that the `version_to_use` is larger/newer than the existing release in PyPI | ||
run: | | ||
python -c 'import sys; from packaging import version; code = 0 if version.parse("${{ env.pypi_version }}") < version.parse("${{ env.version_to_use }}") else 1; sys.exit(code)' | ||
- name: Verify that the `version_to_use` is present in the CHANGELOG | ||
# TODO: Use something like `changelog-cli` to extract the correct version number | ||
run: | | ||
grep ${{ env.version_to_use }} CHANGELOG.md | ||
# publish: | ||
# needs: [pre_build_sanity_check, build_wheels, build_sdist, publish_to_test_pypi, pre_publish_sanity_check] | ||
# runs-on: ubuntu-latest | ||
|
||
# steps: | ||
# - uses: actions/checkout@v4 | ||
|
||
# - name: Set up Python | ||
# uses: actions/setup-python@v4 | ||
# with: | ||
# python-version: 3.12 | ||
|
||
# - uses: actions/download-artifact@v3 | ||
# with: | ||
# name: my-artifact | ||
|
||
# - name: Publish package to TestPyPI | ||
# uses: pypa/[email protected] | ||
# with: | ||
# user: __token__ | ||
# password: ${{ secrets.test_pypi_password }} | ||
# repository_url: https://test.pypi.org/legacy/ | ||
|
||
# - name: Publish package to PyPI | ||
# uses: pypa/[email protected] | ||
# with: | ||
# user: __token__ | ||
# password: ${{ secrets.pypi_password }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Release Process | ||
|
||
When you think you are ready to make a release of `backports.datetime_fromisoformat` follow this process. | ||
|
||
- [Release Process](#release-process) | ||
- [Update `pyproject.toml` and `CHANGELOG.md`](#update-pyprojecttoml-and-changelogmd) | ||
- [Create a developmental release](#create-a-developmental-release) | ||
- [Test the developmental release](#test-the-developmental-release) | ||
- [What if something went wrong with the developmental release?](#what-if-something-went-wrong-with-the-developmental-release) | ||
- [Create a GitHub Release](#create-a-github-release) | ||
- [What if something went wrong with the final release?](#what-if-something-went-wrong-with-the-final-release) | ||
|
||
## Update `pyproject.toml` and `CHANGELOG.md` | ||
|
||
Modify the `project.version` key in `pyproject.toml` to be the version you want to release. | ||
Make sure that there is a corresponding entry in `CHANGELOG.md` | ||
|
||
## Create a developmental release | ||
|
||
The first step to *any* release is to excercise our build pipeline to make sure that our systems are still working as expected. | ||
(We release so infrequently, that there is often something that has broken due to "bit rot". You know how it is.) | ||
|
||
This is done with a ["developmental release"](https://peps.python.org/pep-0440) that executes the entire publishing process, including being uploaded to the [Test PyPI server](https://test.pypi.org/). | ||
|
||
**Note:** Developmental releases are the first step to doing *any* sort of public release. Developmental releases are private, used purely for internal testing of our CI systems. **They aren't for external users at all!** | ||
Public "beta"/"pre-release" releases for external users use this same process as "final"/"production" releases: Start by creating a developmental release to make sure things are still working. | ||
|
||
Steps to creating a developmental release: | ||
|
||
1. Manually trigger the [`publish` GitHub Action workflow](.github/workflows/publish.yml) with the developmental release version number | ||
2. Verify that the workflow completes successfully | ||
|
||
If everything went well, your developmental release will be present in the [Test PyPI server](https://test.pypi.org/project/backports-datetime-fromisoformat/#history) | ||
|
||
## Test the developmental release | ||
|
||
``` | ||
TODO: How to download a version from the Test PyPI server | ||
``` | ||
|
||
TODO: What things to test. | ||
|
||
## What if something went wrong with the developmental release? | ||
|
||
🎉 This is why we have developmental releases! 🎉 | ||
|
||
1. Debug and correct the failure of the [`publish` GitHub Action workflow](https://github.com/movermeyer/backports.datetime_fromisoformat/actions/workflows/publish.yml) | ||
2. Try again by manually triggering the [`publish` GitHub Action workflow](https://github.com/movermeyer/backports.datetime_fromisoformat/actions/workflows/publish.yml) | ||
|
||
Only continue once you have a successful developmental release. | ||
|
||
## Create a GitHub Release | ||
|
||
Once you have sucessfully uploaded and tested a developmental release, it's time for the real thing! | ||
|
||
1. Create a new [GitHub release](https://github.com/movermeyer/backports.datetime_fromisoformat/releases/new) | ||
2. Click "Publish Release" | ||
3. Verify that the [`publish` GitHub Action workflow](.github/workflows/publish.yml) completes successfully | ||
|
||
## What if something went wrong with the final release? | ||
|
||
TODO. |