Publish #24
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
name: Publish | |
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: | |
# 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 ref | |
run: | | |
echo "event_ref=`echo '${{ github.event.release.tag_name }}${{ github.event.ref }}'`" >> $GITHUB_ENV | |
- 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 'Ref `${{ env.event_ref }}`' | |
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: | |
ref: ${{ env.event_ref }} | |
- 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 developmental release for this version from Test PyPI | |
run: | | |
curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; from packaging import version; contents=sys.stdin.read(); parsed = json.loads(contents); new_release_number = version.parse("${{ env.version_from_release_tag }}"); existing_releases = [version.parse(x) for x in parsed["releases"].keys()]; existing_developmental_release_version = max([x for x in existing_releases if x.release == new_release_number.release and x.is_devrelease], default=version.parse("0.0.0")); print(f"test_pypi_developmental_version={existing_developmental_release_version}")' >> $GITHUB_ENV | |
- name: Generate the developmental release version | |
# 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_developmental_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 'Ref `${{ env.event_ref }}`' | |
echo 'release event tag `${{ env.release_tag }}`' | |
echo 'release event version `${{ env.version_from_release_tag }}`' | |
echo 'Version in pyproject.toml `${{ env.backports_version }}`' | |
echo 'Developmental release version in Test PyPI `${{ env.test_pypi_developmental_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 "event_ref=${{ env.event_ref }}\nversion_from_release_tag=${{env.version_from_release_tag}}\nversion_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, macos-latest] | |
steps: | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: | | |
cat release_values.txt | xargs -I{} bash -c 'echo {} >> $GITHUB_ENV' | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- name: Replace version in pyproject.toml | |
# Required for developmental releases | |
run: | | |
awk '{sub(/^version = ".*?"$/,"version = \"${{ env.version_to_use }}\""); print}' pyproject.toml > temp && mv temp pyproject.toml | |
- name: Build wheels | |
uses: pypa/[email protected] | |
env: | |
CIBW_BUILD: "cp37* cp38* cp39* cp310* pp37* pp38* pp39* pp310*" | |
CIBW_SKIP: "pp*-macosx* *-win32 *-manylinux_i686" | |
CIBW_ARCHS_MACOS: x86_64 arm64 universal2 | |
- uses: actions/upload-artifact@v3 | |
with: | |
name: dist | |
path: | | |
./wheelhouse/*.whl | |
!./wheelhouse/UNKNOWN*.whl | |
build_wheels_windows: | |
name: Build wheel on windows-latest | |
needs: [pre_build_sanity_check] | |
runs-on: windows-latest | |
steps: | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: foreach($line in [System.IO.File]::ReadLines("release_values.txt")){ echo $line >> $env:GITHUB_ENV } | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- name: Replace version in pyproject.toml | |
# Required for developmental releases | |
run: (Get-Content -path "pyproject.toml") | % { $_ -Replace '^version = ".*?"$', "version = `"$env:version_to_use`"" } | Out-File "pyproject.toml" | |
- name: Build wheels | |
uses: pypa/[email protected] | |
env: | |
CIBW_BUILD: "cp37* cp38* cp39* cp310* pp37* pp38* pp39* pp310*" | |
CIBW_SKIP: "pp*-macosx* pp*-win* *-win32 *-manylinux_i686" | |
CIBW_ARCHS_MACOS: x86_64 arm64 universal2 | |
- uses: actions/upload-artifact@v3 | |
with: | |
name: dist | |
path: | | |
./wheelhouse/*.whl | |
!./wheelhouse/UNKNOWN*.whl | |
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' | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: | | |
cat release_values.txt | xargs -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- name: Replace version in pyproject.toml | |
# Required for developmental releases | |
run: sed -i -e 's/^version = ".*\?"$/version = "${{ env.version_to_use }}"/g' pyproject.toml | |
- name: Get build tool | |
run: pip install --upgrade build | |
- name: Build sdist | |
run: python -m build | |
- uses: actions/upload-artifact@v3 | |
with: | |
name: dist | |
path: dist/*.tar.gz | |
publish_to_test_pypi: | |
needs: [pre_build_sanity_check, build_wheels, build_wheels_windows, build_sdist] | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: | | |
cat release_values.txt | xargs -I{} bash -c 'echo {} >> $GITHUB_ENV' | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- uses: actions/download-artifact@v3 | |
id: download | |
with: | |
name: dist | |
path: dist/ | |
- 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: | |
needs: [publish_to_test_pypi] | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: | | |
cat release_values.txt | xargs -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | |
- name: Verify that the `version_from_release_tag` is not a "developmental release" | |
run: | | |
python -c 'import sys; from packaging import version; code = 1 if version.parse("${{ env.version_from_release_tag }}").is_devrelease else 0; sys.exit(code)' | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- name: Get the latest developmental release for this version from Test PyPI | |
run: | | |
curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; from packaging import version; contents = sys.stdin.read(); parsed = json.loads(contents); new_release_number = version.parse("${{ env.version_from_release_tag }}"); existing_releases = [version.parse(x) for x in parsed["releases"].keys()]; developmental_release_version = max([x for x in existing_releases if x.release == new_release_number.release and x.is_devrelease], default=version.parse("0.0.0")); test_pypi_release_exists = new_release_number in existing_releases; print(f"test_pypi_developmental_release_version={developmental_release_version}\ntest_pypi_release_exists={test_pypi_release_exists}"); ' >> $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 'Ref: `${{ env.event_ref }}`' | |
echo 'Version to use: `${{ env.version_to_use }}`' | |
echo 'Developmental release version in Test PyPI `${{ env.test_pypi_developmental_release_version }}`' | |
echo 'Version exists in Test PyPI? `${{ env.test_pypi_release_exists }}`' | |
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: | | |
[[ ${{ env.test_pypi_developmental_release_version }} != 0.0.0 ]] | |
- name: Verify that the release has been uploaded to Test PyPI | |
run: | | |
[[ ${{ env.test_pypi_release_exists }} == True ]] | |
- name: Verify that the `version_from_release_tag` is present in the CHANGELOG | |
# TODO: Use something like `changelog-cli` to extract the correct version number | |
run: | | |
grep ${{ env.version_from_release_tag }} CHANGELOG.md | |
- name: Verify that the `version_from_release_tag` is larger/newer than the existing release in PyPI | |
# Note: This precludes making releases that are for old versions | |
run: | | |
python -c 'import sys; from packaging import version; code = 0 if version.parse("${{ env.pypi_version }}") < version.parse("${{ env.version_from_release_tag }}") else 1; sys.exit(code)' | |
publish: | |
if: github.event_name == 'release' | |
needs: [pre_build_sanity_check, build_wheels, build_wheels_windows, build_sdist, publish_to_test_pypi, pre_publish_sanity_check] | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/download-artifact@v3 | |
with: | |
name: release_values | |
- name: Load normalized release values | |
run: | | |
cat release_values.txt | xargs -l -I{} bash -c 'echo {} >> $GITHUB_ENV' | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.event_ref }} | |
- uses: actions/download-artifact@v3 | |
id: download | |
with: | |
name: dist | |
path: dist/ | |
- name: Publish package to PyPI | |
uses: pypa/[email protected] | |
with: | |
# TODO: Change to use "Trusted publishing"? | |
user: __token__ | |
password: ${{ secrets.pypi_password }} |