Skip to content

Commit a9c1fe465a8642a16cb290d9667d4c624d80b306 triggered by: schedule of refs/heads/main branch (workflow run ID: 11904022319; number: 3413; attempt: 1) #3413

Commit a9c1fe465a8642a16cb290d9667d4c624d80b306 triggered by: schedule of refs/heads/main branch (workflow run ID: 11904022319; number: 3413; attempt: 1)

Commit a9c1fe465a8642a16cb290d9667d4c624d80b306 triggered by: schedule of refs/heads/main branch (workflow run ID: 11904022319; number: 3413; attempt: 1) #3413

Workflow file for this run

---
name: CI/CD
on: # yamllint disable-line rule:truthy
merge_group:
push:
branches-ignore:
- dependabot/**
- gh-readonly-queue/** # Temporary merge queue-related GH-made branches
- maintenance/pip-tools-constraint-lockfiles
- maintenance/pip-tools-constraint-lockfiles-**
- patchback/backports/**
- pre-commit-ci-update-config
pull_request:
schedule:
- cron: 1 0 * * * # Run daily at 0:01 UTC
workflow_dispatch:
inputs:
release-version:
# github.event_name == 'workflow_dispatch'
# && github.event.inputs.release-version
description: >-
Target PEP440-compliant version to release.
Please, don't prepend `v`.
required: true
type: string
release-committish:
# github.event_name == 'workflow_dispatch'
# && github.event.inputs.release-committish
default: ''
description: >-
The commit to be released to PyPI and tagged
in Git as `release-version`. Normally, you
should keep this empty.
type: string
YOLO:
default: false
description: >-
Set this flag to disregard the outcome of the
test stage. The test results will block the
release otherwise. Only use this under
extraordinary circumstances to ignore the test
failures and cut the release regardless.
type: boolean
concurrency:
group: >-
${{
github.workflow
}}-${{
github.ref_type
}}-${{
github.event.pull_request.number || github.sha
}}
cancel-in-progress: true
env:
dists-artifact-name: python-package-distributions
FORCE_COLOR: 1 # Request colored output from CLI tools supporting it
MYPY_FORCE_COLOR: 1 # MyPy's color enforcement
PIP_DISABLE_PIP_VERSION_CHECK: 1
PIP_NO_PYTHON_VERSION_WARNING: 1
PIP_NO_WARN_SCRIPT_LOCATION: 1
PRE_COMMIT_COLOR: always
PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest`
PYTHONIOENCODING: utf-8
PYTHONUTF8: 1
TOX_PARALLEL_NO_SPINNER: 1
TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests
FORCE_COLOR
MYPY_FORCE_COLOR
NO_COLOR
PIP_DISABLE_PIP_VERSION_CHECK
PIP_NO_PYTHON_VERSION_WARNING
PIP_NO_WARN_SCRIPT_LOCATION
PRE_COMMIT_COLOR
PY_COLORS
PYTEST_THEME
PYTEST_THEME_MODE
PYTHONIOENCODING
PYTHONLEGACYWINDOWSSTDIO
PYTHONUTF8
TOX_VERSION: tox < 4.12
run-name: >-
${{
github.event_name == 'workflow_dispatch'
&& format('📦 Releasing v{0}...', github.event.inputs.release-version)
|| ''
}}
${{
github.event.pull_request.number && 'PR' || ''
}}${{
!github.event.pull_request.number && 'Commit' || ''
}}
${{ github.event.pull_request.number || github.sha }}
triggered by: ${{ github.event_name }} of ${{
github.ref
}} ${{
github.ref_type
}}
(workflow run ID: ${{
github.run_id
}}; number: ${{
github.run_number
}}; attempt: ${{
github.run_attempt
}})
jobs:
pre-setup:
name: ⚙️ Pre-set global build settings
runs-on: ubuntu-latest
timeout-minutes: 1
defaults:
run:
shell: python
outputs:
dist-version: >-
${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version
}}
is-untagged-devel: >-
${{ steps.untagged-check.outputs.is-untagged-devel || false }}
release-requested: >-
${{
steps.request-check.outputs.release-requested || false
}}
is-yolo-mode: >-
${{
(
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.YOLO
)
&& true || false
}}
cache-key-files: >-
${{ steps.calc-cache-key-files.outputs.files-hash-key }}
git-tag: ${{ steps.git-tag.outputs.tag }}
sdist-artifact-name: ${{ steps.artifact-name.outputs.sdist }}
wheel-artifact-name: ${{ steps.artifact-name.outputs.wheel }}
changelog-patch-name: ${{ steps.changelog-patch-name.outputs.filename }}
changelog-draft-name-md: >-
${{ steps.changelog-draft-name.outputs.filename-base }}.md
changelog-draft-name-rst: >-
${{ steps.changelog-draft-name.outputs.filename-base }}.rst
steps:
- name: Switch to using Python 3.11 by default
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: >-
Mark the build as untagged '${{
github.event.repository.default_branch
}}' branch build
id: untagged-check
if: >-
github.event_name == 'push' &&
github.ref == format(
'refs/heads/{0}', github.event.repository.default_branch
)
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print('is-untagged-devel=true', file=outputs_file)
- name: Mark the build as "release request"
id: request-check
if: github.event_name == 'workflow_dispatch'
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print('release-requested=true', file=outputs_file)
- name: Check out src from Git
if: >-
steps.request-check.outputs.release-requested != 'true'
uses: actions/checkout@v4
with:
fetch-depth: >-
${{
steps.request-check.outputs.release-requested == 'true'
&& 1 || 0
}}
ref: ${{ github.event.inputs.release-committish }}
- name: >-
Calculate Python interpreter version hash value
for use in the cache key
if: >-
steps.request-check.outputs.release-requested != 'true'
id: calc-cache-key-py
run: |
from hashlib import sha512
from os import environ
from pathlib import Path
from sys import version
FILE_APPEND_MODE = 'a'
hash = sha512(version.encode()).hexdigest()
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'py-hash-key={hash}', file=outputs_file)
- name: >-
Calculate dependency files' combined hash value
for use in the cache key
if: >-
steps.request-check.outputs.release-requested != 'true'
id: calc-cache-key-files
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(
"files-hash-key=${{
hashFiles(
'tox.ini', 'pyproject.toml',
'.pre-commit-config.yaml', 'pytest.ini'
)
}}",
file=outputs_file,
)
- name: Get pip cache dir
id: pip-cache-dir
if: >-
steps.request-check.outputs.release-requested != 'true'
run: >-
echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}"
shell: bash
- name: Set up pip cache
if: >-
steps.request-check.outputs.release-requested != 'true'
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: >-
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key }}-${{
steps.calc-cache-key-files.outputs.files-hash-key }}
restore-keys: |
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key
}}-
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Drop Git tags from HEAD for non-release requests
if: >-
steps.request-check.outputs.release-requested != 'true'
run: >-
git tag --points-at HEAD
|
xargs git tag --delete
shell: bash
- name: Set up versioning prerequisites
if: >-
steps.request-check.outputs.release-requested != 'true'
run: >-
python -m
pip install
--user
setuptools-scm
shell: bash
- name: Set the current dist version from Git
if: steps.request-check.outputs.release-requested != 'true'
id: scm-version
run: |
from os import environ
from pathlib import Path
import setuptools_scm
FILE_APPEND_MODE = 'a'
ver = setuptools_scm.get_version(
${{
steps.untagged-check.outputs.is-untagged-devel == 'true'
&& 'local_scheme="no-local-version"' || ''
}}
)
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'dist-version={ver}', file=outputs_file)
print(
f'dist-version-for-filenames={ver.replace("+", "-")}',
file=outputs_file,
)
- name: Set the target Git tag
id: git-tag
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(
"tag=v${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version
}}",
file=outputs_file,
)
- name: Set the expected dist artifact names
id: artifact-name
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(
"sdist=cheroot-${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version
}}.tar.gz",
file=outputs_file,
)
print(
"wheel=cheroot-${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version
}}-py3-none-any.whl",
file=outputs_file,
)
- name: Set the expected changelog patch filename
id: changelog-patch-name
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print('filename=0001-Generate-a-change-log-entry-for-v${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version-for-filenames
}}.patch', file=outputs_file)
- name: Set the expected changelog draft filename
id: changelog-draft-name
run: |
from os import environ
from pathlib import Path
FILE_APPEND_MODE = 'a'
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print('filename-base=change-notes-v${{
steps.request-check.outputs.release-requested == 'true'
&& github.event.inputs.release-version
|| steps.scm-version.outputs.dist-version-for-filenames
}}', file=outputs_file)
build-changelog:
name: >-
👷📝 ${{ needs.pre-setup.outputs.git-tag }} changelog
[mode: ${{
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
&& 'nightly' || ''
}}${{
fromJSON(needs.pre-setup.outputs.release-requested)
&& 'release' || ''
}}${{
(
!fromJSON(needs.pre-setup.outputs.is-untagged-devel)
&& !fromJSON(needs.pre-setup.outputs.release-requested)
) && 'test' || ''
}}]
needs:
- pre-setup
runs-on: ubuntu-latest
env:
TOXENV: make-changelog
steps:
- name: Switch to using Python 3.11
uses: actions/[email protected]
with:
python-version: 3.11
- name: Grab the source from Git
uses: actions/[email protected]
with:
fetch-depth: 1 # Enough for this job to generate the changelog
ref: ${{ github.event.inputs.release-committish }}
- name: >-
Calculate Python interpreter version hash value
for use in the cache key
id: calc-cache-key-py
run: |
from hashlib import sha512
from os import environ
from pathlib import Path
from sys import version
FILE_APPEND_MODE = 'a'
hash = sha512(version.encode()).hexdigest()
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'py-hash-key={hash}', file=outputs_file)
shell: python
- name: Set up pip cache
uses: actions/[email protected]
with:
path: >-
${{
runner.os == 'Linux'
&& '~/.cache/pip'
|| '~/Library/Caches/pip'
}}
key: >-
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key }}-${{
needs.pre-setup.outputs.cache-key-files }}
restore-keys: |
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key
}}-
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Install tox
run: >-
python -m
pip install
--user
'${{ env.TOX_VERSION }}'
- name: Pre-populate the tox env
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--notest
- name: Drop Git tags from HEAD for non-tag-create events
if: >-
!fromJSON(needs.pre-setup.outputs.release-requested)
run: >-
git tag --points-at HEAD
|
xargs git tag --delete
shell: bash
- name: Setup git user as [bot]
# Refs:
# * https://github.community/t/github-actions-bot-email-address/17204/6
# * https://github.com/actions/checkout/issues/13#issuecomment-724415212
uses: fregante/[email protected]
- name: Generate changelog draft to a temporary file
run: >-
2>/dev/null
python -m
tox
--skip-missing-interpreters false
--skip-pkg-install
-qq
--
'${{ needs.pre-setup.outputs.dist-version }}'
--draft
|
tee
'${{ needs.pre-setup.outputs.changelog-draft-name-rst }}'
shell: bash
- name: Sanitize the markdown changelog version
run: >-
sed
-i
-e 's/:commit:`\([0-9a-f]\+\)`/${{
''
}}https:\/\/github.com\/cherrypy\/cheroot\/commit\/\1/g'
-e 's/:gh:`\([-.a-zA-Z0-9]\+\)`/https:\/\/github.com\/\1/g'
-e 's/:\(issue\|pr\):`\([0-9]\+\)`/#\2/g'
-e 's/:user:`\([-.a-zA-Z0-9]\+\)`/@\1/g'
'${{ needs.pre-setup.outputs.changelog-draft-name-rst }}'
shell: bash
- name: Install pandoc via apt
run: sudo apt install -y pandoc
- name: >-
Convert ${{ needs.pre-setup.outputs.changelog-draft-name-rst }}
into ${{ needs.pre-setup.outputs.changelog-draft-name-md }}
with a native pandoc run
run: >-
pandoc
--from=rst
--to=gfm
--output='${{ needs.pre-setup.outputs.changelog-draft-name-md }}'
'${{ needs.pre-setup.outputs.changelog-draft-name-rst }}'
- name: Render the changelog draft in the GitHub Job Summary
run: |
echo "# Changelog for ${{
needs.pre-setup.outputs.git-tag
}}" >> "${GITHUB_STEP_SUMMARY}"
echo >> "${GITHUB_STEP_SUMMARY}"
echo >> "${GITHUB_STEP_SUMMARY}"
cat '${{
needs.pre-setup.outputs.changelog-draft-name-md
}}' >> "${GITHUB_STEP_SUMMARY}"
shell: bash
- name: Generate changelog update with tox and stage it in Git
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--skip-pkg-install
--
'${{ needs.pre-setup.outputs.dist-version }}'
--yes
- name: >-
Commit the changelog updates for release
${{ needs.pre-setup.outputs.git-tag }} in the local Git repo
run: >-
git commit -m
'Generate a change log entry for ${{
needs.pre-setup.outputs.git-tag
}}'
- name: Log the changelog commit
run: git show --color
- name: Create a changelog update patch from the last Git commit
run: >-
git format-patch
--output='${{ needs.pre-setup.outputs.changelog-patch-name }}'
-1 HEAD
- name: Verify that expected patch got created
run: ls -1 '${{ needs.pre-setup.outputs.changelog-patch-name }}'
- name: Save the package bump patch as a GHA artifact
uses: actions/upload-artifact@v3
with:
name: changelog
path: |
${{ needs.pre-setup.outputs.changelog-patch-name }}
${{ needs.pre-setup.outputs.changelog-draft-name-md }}
${{ needs.pre-setup.outputs.changelog-draft-name-rst }}
build:
name: >-
👷 dists ${{ needs.pre-setup.outputs.git-tag }}
[mode: ${{
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
&& 'nightly' || ''
}}${{
fromJSON(needs.pre-setup.outputs.release-requested)
&& 'release' || ''
}}${{
(
!fromJSON(needs.pre-setup.outputs.is-untagged-devel)
&& !fromJSON(needs.pre-setup.outputs.release-requested)
) && 'test' || ''
}}]
needs:
- build-changelog
- pre-setup # transitive, for accessing settings
runs-on: ubuntu-latest
timeout-minutes: 2
env:
TOXENV: cleanup-dists,build-dists
steps:
- name: Switch to using Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Grab the source from Git
uses: actions/checkout@v4
with:
fetch-depth: >-
${{
fromJSON(needs.pre-setup.outputs.release-requested)
&& 1 || 0
}}
ref: ${{ github.event.inputs.release-committish }}
- name: >-
Calculate Python interpreter version hash value
for use in the cache key
id: calc-cache-key-py
run: |
from hashlib import sha512
from os import environ
from pathlib import Path
from sys import version
FILE_APPEND_MODE = 'a'
hash = sha512(version.encode()).hexdigest()
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'py-hash-key={hash}', file=outputs_file)
shell: python
- name: Get pip cache dir
id: pip-cache-dir
run: >-
echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}"
- name: Set up pip cache
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: >-
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key }}-${{
needs.pre-setup.outputs.cache-key-files }}
restore-keys: |
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key
}}-
${{ runner.os }}-pip-
- name: Install tox
run: >-
python -m
pip install
--user
'${{ env.TOX_VERSION }}'
- name: Pre-populate the tox env
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--notest
- name: Drop Git tags from HEAD for non-tag-create events
if: >-
!fromJSON(needs.pre-setup.outputs.release-requested)
run: >-
git tag --points-at HEAD
|
xargs git tag --delete
shell: bash
- name: Setup git user as [bot]
if: >-
fromJSON(needs.pre-setup.outputs.release-requested)
|| fromJSON(needs.pre-setup.outputs.is-untagged-devel)
uses: fregante/setup-git-user@v2
- name: >-
Tag the release in the local Git repo
as ${{ needs.pre-setup.outputs.git-tag }}
for setuptools-scm to set the desired version
if: >-
fromJSON(needs.pre-setup.outputs.release-requested)
run: >-
git tag
-m '${{ needs.pre-setup.outputs.git-tag }}'
'${{ needs.pre-setup.outputs.git-tag }}'
--
${{
fromJSON(needs.pre-setup.outputs.release-requested)
&& github.event.inputs.release-committish || ''
}}
- name: Install tomlkit Python distribution package
if: >-
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
run: >-
python -m pip install --user tomlkit
- name: Instruct setuptools-scm not to add a local version part
if: >-
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
run: |
from pathlib import Path
import tomlkit
pyproject_toml_path = Path.cwd() / 'pyproject.toml'
pyproject_toml_txt = pyproject_toml_path.read_text()
pyproject_toml = tomlkit.loads(pyproject_toml_txt)
setuptools_scm_section = pyproject_toml['tool']['setuptools_scm']
setuptools_scm_section['local_scheme'] = 'no-local-version'
patched_pyproject_toml_txt = tomlkit.dumps(pyproject_toml)
pyproject_toml_path.write_text(patched_pyproject_toml_txt)
shell: python
- name: Pretend that pyproject.toml is unchanged
if: >-
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
run: |
git diff --color=always
git update-index --assume-unchanged pyproject.toml
- name: Build dists
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--skip-pkg-install
- name: Verify that the artifacts with expected names got created
run: >-
ls -1
'dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}'
'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}'
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
# NOTE: Exact expected file names are specified here
# NOTE: as a safety measure — if anything weird ends
# NOTE: up being in this dir or not all dists will be
# NOTE: produced, this will fail the workflow.
path: |
dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}
dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}
retention-days: >-
${{
(
fromJSON(needs.pre-setup.outputs.release-requested)
) && 90 || 30
}}
lint:
name: 🧹 ${{ matrix.toxenv }}
needs:
- build
- pre-setup # transitive, for accessing settings
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os:
- ubuntu
python-version:
- 3.11
toxenv:
- pre-commit
- metadata-validation
- build-docs
- doctest-docs
- linkcheck-docs
- spellcheck-docs
fail-fast: false
env:
TOXENV: ${{ matrix.toxenv }}
steps:
- name: >-
Switch to using Python v${{ matrix.python-version }}
by default
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
# NOTE: `pre-commit --show-diff-on-failure` and `sphinxcontrib-spellcheck`
# NOTE: with Git authors allowlist enabled both depend on the presence of a
# NOTE: Git repository.
- name: Grab the source from Git
if: >-
contains(fromJSON('["pre-commit", "spellcheck-docs"]'), matrix.toxenv)
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.release-committish }}
- name: Retrieve the project source from an sdist inside the GHA artifact
if: >-
!contains(fromJSON('["pre-commit", "spellcheck-docs"]'), matrix.toxenv)
uses: re-actors/checkout-python-sdist@release/v1
with:
source-tarball-name: ${{ needs.pre-setup.outputs.sdist-artifact-name }}
workflow-artifact-name: ${{ env.dists-artifact-name }}
- name: >-
Calculate Python interpreter version hash value
for use in the cache key
id: calc-cache-key-py
run: |
from hashlib import sha512
from os import environ
from pathlib import Path
from sys import version
FILE_APPEND_MODE = 'a'
hash = sha512(version.encode()).hexdigest()
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'py-hash-key={hash}', file=outputs_file)
shell: python
- name: Get pip cache dir
id: pip-cache
run: >-
echo "dir=$(pip cache dir)" >> "${GITHUB_OUTPUT}"
- name: Set up pip cache
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: >-
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key }}-${{
needs.pre-setup.outputs.cache-key-files }}
restore-keys: |
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key
}}-
${{ runner.os }}-pip-
- name: Install tox
run: >-
python -m
pip install
--user
'${{ env.TOX_VERSION }}'
- name: Make the env clean of non-test files
if: matrix.toxenv == 'metadata-validation'
run: |
shopt -s extglob
rm -rf !tox.ini
shell: bash
- name: Download all the dists
if: matrix.toxenv == 'metadata-validation'
uses: actions/download-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
path: dist/
- name: >-
Pre-populate tox envs: `${{ env.TOXENV }}`
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--notest
- name: Initialize pre-commit envs if needed
run: >-
test -d .tox/pre-commit
&& .tox/pre-commit/bin/python -m pre_commit install-hooks
|| :
- name: >-
Run tox envs: `${{ env.TOXENV }}`
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--skip-pkg-install
tests:
name: >-
🧪 🐍${{
matrix.python-version
}} @ ${{
matrix.os
}}
needs:
- build
- pre-setup # transitive, for accessing settings
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version:
# NOTE: The latest and the lowest supported Pythons are prioritized
# NOTE: to improve the responsiveness. It's nice to see the most
# NOTE: important results first.
- 3.12
- 3.11
- >-
3.10
- 3.9
- 3.8
os:
- ubuntu-22.04
- ubuntu-20.04
- macos-11.0
- macos-13
- windows-2019
- windows-2022
continue-on-error: >-
${{
(
fromJSON(needs.pre-setup.outputs.is-yolo-mode) ||
(
startsWith(matrix.python-version, '~')
) ||
contains(matrix.python-version, 'alpha')
) && true || false
}}
env:
TOXENV: python
steps:
- name: Set up Python ${{ matrix.python-version }}
id: python-install
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Retrieve the project source from an sdist inside the GHA artifact
uses: re-actors/checkout-python-sdist@release/v1
with:
source-tarball-name: ${{ needs.pre-setup.outputs.sdist-artifact-name }}
workflow-artifact-name: ${{ env.dists-artifact-name }}
- name: Figure out if the interpreter ABI is stable
id: py-abi
run: |
from os import environ
from sys import version_info
FILE_APPEND_MODE = 'a'
is_stable_abi = version_info.releaselevel == 'final'
with open(
environ['GITHUB_OUTPUT'],
mode=FILE_APPEND_MODE,
) as outputs_file:
print(
'is-stable-abi={is_stable_abi}'.
format(is_stable_abi=str(is_stable_abi).lower()),
file=outputs_file,
)
shell: python
- name: >-
Calculate Python interpreter version hash value
for use in the cache key
if: fromJSON(steps.py-abi.outputs.is-stable-abi)
id: calc-cache-key-py
run: |
from hashlib import sha512
from os import environ
from sys import version
FILE_APPEND_MODE = 'a'
hash = sha512(version.encode()).hexdigest()
with open(
environ['GITHUB_OUTPUT'], mode=FILE_APPEND_MODE,
) as outputs_file:
print(f'py-hash-key={hash}', file=outputs_file)
shell: python
- name: Get pip cache dir
if: fromJSON(steps.py-abi.outputs.is-stable-abi)
id: pip-cache-dir
run: >-
echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}"
shell: bash
- name: Set up pip cache
if: fromJSON(steps.py-abi.outputs.is-stable-abi)
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: >-
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key }}-${{
needs.pre-setup.outputs.cache-key-files }}
restore-keys: |
${{ runner.os }}-pip-${{
steps.calc-cache-key-py.outputs.py-hash-key
}}-
${{ runner.os }}-pip-
- name: Upgrade pip with `requires_python`
run: >-
python -m
pip install
--user
--upgrade
--force-reinstall
pip-with-requires-python
- name: Install tox
run: >-
python -m
pip install
--user
'${{ env.TOX_VERSION }}'
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
path: dist/
- name: >-
Pre-populate tox env:
${{ env.TOXENV }}
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--installpkg 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}'
--notest
- name: Windows system info
run: systeminfo
if: >-
startsWith(matrix.os, 'windows-')
- name: >-
Log platform.platform()
run: >-
python -m platform
- name: >-
Log platform.version()
run: >-
python -c "import platform;
print(platform.version())"
- name: >-
Log platform.uname()
run: >-
python -c "import platform;
print(platform.uname())"
- name: >-
Log platform.release()
run: >-
python -c "import platform;
print(platform.release())"
- name: Log stdlib OpenSSL version
run: >-
python -c
"import ssl; print('\nOPENSSL_VERSION: '
+ ssl.OPENSSL_VERSION + '\nOPENSSL_VERSION_INFO: '
+ repr(ssl.OPENSSL_VERSION_INFO)
+ '\nOPENSSL_VERSION_NUMBER: '
+ repr(ssl.OPENSSL_VERSION_NUMBER))"
- name: Run the testing
run: >-
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
--installpkg 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}'
--skip-pkg-install
- name: Produce markdown test summary from JUnit
if: >-
!cancelled()
uses: test-summary/[email protected]
with:
paths: .test-results/pytest/test.xml
- name: Check if Cobertura XML coverage files exist
if: >-
!cancelled()
&& runner.os == 'Linux'
id: coverage-files
run: >-
compgen -G '.test-results/pytest/cov.xml'
&& ( echo 'present=true' >> ${GITHUB_OUTPUT} )
;
exit 0
shell: bash
- name: Produce markdown test summary from Cobertura XML
if: steps.coverage-files.outputs.present == 'true'
uses: irongut/[email protected]
with:
badge: true
filename: .test-results/pytest/cov.xml
format: markdown
output: both
# Ref: https://github.com/irongut/CodeCoverageSummary/issues/66
- name: Append coverage results to Job Summary
if: steps.coverage-files.outputs.present == 'true'
run: >-
cat code-coverage-results.md >> "${GITHUB_STEP_SUMMARY}"
- name: Re-run the failing tests with maximum verbosity
if: >-
!cancelled()
&& failure()
run: >- # `exit 1` makes sure that the job remains red with flaky runs
python -m
tox
--parallel auto
--parallel-live
--skip-missing-interpreters false
-vvvvv
--installpkg 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}'
--
--no-cov -vvvvv --lf
&& exit 1
shell: bash
- name: Send coverage data to Codecov
if: >-
!cancelled()
uses: codecov/codecov-action@v3
with:
files: .test-results/pytest/cov.xml
flags: >-
CI-GHA,
OS-${{
runner.os
}},
VM-${{
matrix.os
}},
Py-${{
steps.python-install.outputs.python-version
}}
check: # This job does nothing and is only used for the branch protection
if: always()
needs:
- lint
- pre-setup # transitive, for accessing settings
- tests
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
allowed-failures: >-
${{
fromJSON(needs.pre-setup.outputs.is-yolo-mode)
&& 'lint, tests' || ''
}}
jobs: ${{ toJSON(needs) }}
publish-pypi:
name: Publish 🐍📦 ${{ needs.pre-setup.outputs.git-tag }} to PyPI
needs:
- check
- pre-setup # transitive, for accessing settings
if: >-
fromJSON(needs.pre-setup.outputs.release-requested)
runs-on: ubuntu-latest
environment:
name: pypi
url: >-
https://pypi.org/project/cheroot/${{
needs.pre-setup.outputs.dist-version
}}
permissions:
contents: read # This job doesn't need to `git push` anything
id-token: write # PyPI Trusted Publishing (OIDC)
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
path: dist/
- name: >-
Publish 🐍📦 ${{ needs.pre-setup.outputs.git-tag }} to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
publish-testpypi:
name: Publish 🐍📦 ${{ needs.pre-setup.outputs.git-tag }} to TestPyPI
needs:
- check
- pre-setup # transitive, for accessing settings
if: >-
fromJSON(needs.pre-setup.outputs.is-untagged-devel)
|| fromJSON(needs.pre-setup.outputs.release-requested)
runs-on: ubuntu-latest
environment:
name: testpypi
url: >-
https://test.pypi.org/project/cheroot/${{
needs.pre-setup.outputs.dist-version
}}
permissions:
contents: read # This job doesn't need to `git push` anything
id-token: write # PyPI Trusted Publishing (OIDC)
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
path: dist/
- name: >-
Publish 🐍📦 ${{ needs.pre-setup.outputs.git-tag }} to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
post-release-repo-update:
name: >-
Publish post-release Git tag
for ${{ needs.pre-setup.outputs.git-tag }}
needs:
- publish-pypi
- pre-setup # transitive, for accessing settings
runs-on: ubuntu-latest
outputs:
pull_request_url: ${{ steps.pr.outputs.pull_request_url }}
permissions:
contents: write # Mandatory for `git push` to work
pull-requests: write
steps:
- name: Fetch the src snapshot # IMPORTANT: Must be before the tag check
uses: actions/checkout@v4
with:
fetch-depth: 2
ref: ${{ github.event.inputs.release-committish }}
- name: >-
Check if the requested tag ${{ needs.pre-setup.outputs.git-tag }}
is present and is pointing at the required commit ${{
github.event.inputs.release-committish
}}
id: existing-remote-tag-check
run: |
set -eEuo pipefail
REMOTE_TAGGED_COMMIT_SHA="$(
git ls-remote --tags --refs $(git remote get-url origin) '${{
needs.pre-setup.outputs.git-tag
}}' | awk '{print $1}'
)"
# NOTE: Since we're making a new change log commit on top of the one
# NOTE: that is the release workflow trigger, it'll be the one that's
# NOTE: tagged if the workflow run succeeded previously. So we need to
# NOTE: grab its parent for comparison, since new workflow runs will
# NOTE: generate a new changelog update commit and while its diff and
# NOTE: the parent may be the same, the metadata like the timestamp
# NOTE: would differ causing it to have a different commit SHA. But the
# NOTE: parent would be immutable so that's what we'll compare to the
# NOTE: release committish.
if [[ "${REMOTE_TAGGED_COMMIT_SHA}" == '' ]]
then
LAST_HUMAN_COMMIT_SHA=
else
LAST_HUMAN_COMMIT_SHA=$(git rev-parse "${REMOTE_TAGGED_COMMIT_SHA}"^)
fi
RELEASE_REQUEST_COMMIT_SHA=$(git rev-parse '${{
github.event.inputs.release-committish || 'HEAD'
}}')
if [[ "${LAST_HUMAN_COMMIT_SHA}" == "${RELEASE_REQUEST_COMMIT_SHA}" ]]
then
echo "already-exists=true" >> "${GITHUB_OUTPUT}"
fi
- name: Setup git user as [bot]
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
# Refs:
# * https://github.community/t/github-actions-bot-email-address/17204/6
# * https://github.com/actions/checkout/issues/13#issuecomment-724415212
uses: fregante/setup-git-user@v2
- name: Fetch the GHA artifact with the version patch
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
uses: actions/download-artifact@v3
with:
name: changelog
- name: Apply the changelog patch
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
run: git am '${{ needs.pre-setup.outputs.changelog-patch-name }}'
shell: bash
- name: >-
Create a local 'release/${{
needs.pre-setup.outputs.dist-version
}}' branch
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
run: >-
git checkout -b 'release/${{
needs.pre-setup.outputs.dist-version
}}'
- name: >-
Tag the release in the local Git repo
as ${{ needs.pre-setup.outputs.git-tag }}
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
run: >-
git tag
-m '${{ needs.pre-setup.outputs.git-tag }}'
-m 'Published at https://pypi.org/project/cheroot/${{
needs.pre-setup.outputs.dist-version
}}'
-m 'This release has been produced by the following workflow run: ${{
github.server_url
}}/${{
github.repository
}}/actions/runs/${{
github.run_id
}}'
'${{ needs.pre-setup.outputs.git-tag }}'
- name: >-
Push ${{ needs.pre-setup.outputs.git-tag }} tag corresponding
to the just published release back to GitHub
if: steps.existing-remote-tag-check.outputs.already-exists != 'true'
run: >-
git push --atomic origin
'release/${{ needs.pre-setup.outputs.dist-version }}'
'${{ needs.pre-setup.outputs.git-tag }}'
- name: pull-request-action
id: pr
uses: vsoch/[email protected]
env:
BRANCH_PREFIX: ''
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BODY: >-
Automated changelog generation with the version
${{ needs.pre-setup.outputs.dist-version }}.
PULL_REQUEST_BRANCH: ${{ github.event.repository.default_branch }}
PULL_REQUEST_FROM_BRANCH: >-
release/${{ needs.pre-setup.outputs.dist-version }}
PULL_REQUEST_TITLE: >-
🔖 Release ${{ needs.pre-setup.outputs.git-tag }}
- name: Log the pull request details
run: |
echo "PR number: ${{ steps.pr.outputs.pull_request_number }}"
echo "PR URL: ${{ steps.pr.outputs.pull_request_url }}"
publish-github-release:
name: >-
Publish a GitHub Release for
${{ needs.pre-setup.outputs.git-tag }}
needs:
- post-release-repo-update
- pre-setup # transitive, for accessing settings
runs-on: ubuntu-latest
permissions:
contents: write
discussions: write
id-token: write # IMPORTANT: mandatory for Sigstore signing
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: ${{ env.dists-artifact-name }}
path: dist/
- name: Fetch the GHA artifact with the version patch
uses: actions/download-artifact@v3
with:
name: changelog
- name: Figure out if the current version is a pre-release
id: release-maturity-check
run: |
from os import environ
from pathlib import Path
release_version = '${{
needs.pre-setup.outputs.dist-version
}}'
FILE_APPEND_MODE = 'a'
is_pre_release = any(
hint_char in release_version
for hint_char in {'a', 'b', 'd', 'r'}
)
with Path(environ['GITHUB_OUTPUT']).open(
mode=FILE_APPEND_MODE,
) as outputs_file:
print(
f'is-pre-release={is_pre_release !s}'.lower(),
file=outputs_file,
)
shell: python
- name: Prepare the release notes file for the GitHub Releases
run: |
echo '## 📝 Release notes' | tee -a release-notes.md
echo | tee -a release-notes.md
echo | tee -a release-notes.md
echo '📦 PyPI page: https://pypi.org/project/cheroot/${{
needs.pre-setup.outputs.dist-version
}}' | tee -a release-notes.md
echo | tee -a release-notes.md
echo | tee -a release-notes.md
echo '${{
steps.release-maturity-check.outputs.is-pre-release == 'true'
&& format(
'🚧 {0} is marked as a pre-release.',
needs.pre-setup.outputs.git-tag
)
|| format(
'🌱 {0} is marked as a stable release.',
needs.pre-setup.outputs.git-tag
)
}}' | tee -a release-notes.md
echo | tee -a release-notes.md
echo | tee -a release-notes.md
echo '🔗 This release has been produced by ' \
'the following workflow run: ${{
github.server_url
}}/${{
github.repository
}}/actions/runs/${{
github.run_id
}}' | tee -a release-notes.md
echo | tee -a release-notes.md
echo | tee -a release-notes.md
cat '${{
needs.pre-setup.outputs.changelog-draft-name-md
}}' | tee -a release-notes.md
shell: bash
- name: Sign the dists with Sigstore
uses: sigstore/[email protected]
with:
inputs: >-
dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}
dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}
- name: >-
Publish a GitHub Release for
${{ needs.pre-setup.outputs.git-tag }}
with Sigstore-signed artifacts
uses: ncipollo/release-action@v1
with:
allowUpdates: false
artifactErrorsFailBuild: false
artifacts: |
dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}
dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}.sigstore
dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}
dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}.sigstore
artifactContentType: raw # Because whl and tgz are of different types
bodyFile: release-notes.md
discussionCategory: Announcements
draft: false
name: ${{ needs.pre-setup.outputs.git-tag }}
# omitBody: false
omitBodyDuringUpdate: true
omitName: false
omitNameDuringUpdate: true
omitPrereleaseDuringUpdate: true
prerelease: ${{ steps.release-maturity-check.outputs.is-pre-release }}
removeArtifacts: false
replacesArtifacts: false
tag: ${{ needs.pre-setup.outputs.git-tag }}
token: ${{ secrets.GITHUB_TOKEN }}
...