summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSviatoslav Sydorenko <wk@sydorenko.org.ua>2023-01-01 05:29:53 +0100
committerSviatoslav Sydorenko <wk@sydorenko.org.ua>2023-01-01 05:29:53 +0100
commitc3d86617593915581dc266b383729f28f2d79e1b (patch)
tree15a5ee83c23b929ae35b549ecd8261805124bea7
parent8b8cba952ceabccfa88d34cb3e0b2263c0d94ad7 (diff)
downloadcherrypy-git-c3d86617593915581dc266b383729f28f2d79e1b.tar.gz
๐Ÿงช๐Ÿ”ง Set up a GitHub Actions CI/CD workflow
-rw-r--r--.github/workflows/ci-cd.yml1183
-rw-r--r--setup.py1
2 files changed, 1184 insertions, 0 deletions
diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
new file mode 100644
index 00000000..0ae6636b
--- /dev/null
+++ b/.github/workflows/ci-cd.yml
@@ -0,0 +1,1183 @@
+---
+
+name: CI/CD
+
+on: # yamllint disable-line rule:truthy
+ push:
+ branches-ignore:
+ - dependabot/**
+ - maintenance/pip-tools-constraint-lockfiles
+ - maintenance/pip-tools-constraint-lockfiles-**
+ - patchback/backports/**
+ - pre-commit-ci-update-config
+ pull_request:
+ 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: >-
+ Flag whether test results should block the
+ release. Only use this under extraordinary
+ circumstances to ignore the test failures
+ and cut the release regardless.
+ type: boolean
+ schedule:
+ - cron: 1 0 * * * # Run daily at 0:01 UTC
+
+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
+ dists-name: cherrypy
+
+ 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
+ PYTHONLEGACYWINDOWSSTDIO: 1 # Python 3.6 hack
+ PYTHONUTF8: 1
+ TOX_PARALLEL_NO_SPINNER: 1
+ TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests
+ FORCE_COLOR
+ MYPY_FORCE_COLOR
+ NO_COLOR
+ PRE_COMMIT_COLOR
+ PY_COLORS
+ PYTEST_THEME
+ PYTEST_THEME_MODE
+ PYTHONIOENCODING
+ PYTHONLEGACYWINDOWSSTDIO
+ PYTHONUTF8
+ TOX_VERSION: tox < 4
+
+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: โš™๏ธ Mode
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: python
+ outputs:
+ dist-name: ${{ steps.dist.outputs.name }}
+ 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 }}
+ steps:
+ - name: Switch to using Python 3.11 by default
+ uses: actions/setup-python@v4
+ 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@v3
+ 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(
+ 'setup.cfg', 'setup.py', '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@v3
+ 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)
+ - 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 name
+ id: dist
+ 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("name=${{ env.dists-name }}", 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=${{ steps.dist.outputs.name }}-${{
+ 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=${{ steps.dist.outputs.name }}-${{
+ 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,
+ )
+
+ 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:
+ - pre-setup # transitive, for accessing settings
+
+ runs-on: ubuntu-latest
+
+ env:
+ TOXENV: cleanup-dists,build-dists
+
+ steps:
+ - name: Switch to using Python 3.11
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.11
+
+ - name: Grab the source from Git
+ uses: actions/checkout@v3
+ 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@v3
+ 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@v1.1.0
+ - 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:
+ - build-docs
+ - dist-check
+ # - doctest-docs
+ # - linkcheck-docs
+ # - metadata-validation
+ - pre-commit
+ - pre-commit-pep257
+ - setup-check
+ # - 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@v4
+ 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@v3
+ 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@v3
+ 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.11
+ - pypy-3.9
+ - >-
+ 3.10
+ - 3.9
+ - 3.8
+ - 3.7
+ - 3.6
+ - pypy-3.6
+ - ~3.12
+ os:
+ - ubuntu-22.04
+ - macos-12
+ - windows-2022
+ - ubuntu-20.04
+ - macos-11
+ - windows-2019
+ exclude:
+ # NOTE: Windows PyPy jobs are excluded to address the tox bug
+ # NOTE: https://github.com/tox-dev/tox/issues/1704.
+ # NOTE: They should be re-added once it's fixed.
+ - os: windows-2022
+ python-version: pypy-3.6
+ - os: windows-2019
+ python-version: pypy-3.6
+ # NOTE: Windows PyPy 3.7 jobs are excluded because of the lack
+ # NOTE: of the build deps to compile cryptography.
+ # NOTE: They should be re-added once it's fixed.
+ - os: windows-2022
+ python-version: pypy-3.9
+ - os: windows-2019
+ python-version: pypy-3.9
+ # NOTE: macOS PyPy jobs are excluded because installing cryptography
+ # NOTE: needs openssl headers that aren't present at the moment.
+ # TODO: Remove the exclusions once this is addressed.
+ - os: macos-11
+ python-version: pypy-3.6
+ - os: macos-latest
+ python-version: pypy-3.6
+ - os: macos-11
+ python-version: pypy-3.9
+ - os: macos-12
+ python-version: pypy-3.9
+ # TODO: include TOXENV=cheroot-master
+
+ 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@v4
+ 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@v3
+ 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: Patch tox.ini for Python 3.6 under Windows
+ if: >-
+ runner.os == 'Windows'
+ && matrix.python-version == '3.6'
+ run: >-
+ sed -i
+ 's/^package_env\(\s\)\?=.*/package_env = py36-win-dummy/g'
+ tox.ini
+ shell: bash
+
+ - 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 }}'
+ - name: Produce markdown test summary from JUnit
+ if: >-
+ !cancelled()
+ uses: test-summary/action@v2.0
+ with:
+ paths: .test-results/pytest/results.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/CodeCoverageSummary@v1.3.0
+ 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
+ }},
+ ${{ needs.pre-setup.outputs.wheel-artifact-name }}
+
+ 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/${{
+ needs.pre-setup.outputs.dist-name
+ }}/${{
+ needs.pre-setup.outputs.dist-version
+ }}
+
+ 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
+ with:
+ password: ${{ secrets.PYPI_API_TOKEN }}
+
+ 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://pypi.org/project/${{
+ needs.pre-setup.outputs.dist-name
+ }}/${{
+ needs.pre-setup.outputs.dist-version
+ }}
+
+ 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:
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ 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
+
+ steps:
+ - 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: |
+ REMOTE_TAGGED_COMMIT_SHA="$(
+ git ls-remote --tags --refs $(git remote get-url origin) '${{
+ needs.pre-setup.outputs.git-tag
+ }}' | awk '{print $1}'
+ )"
+
+ if [[ "${REMOTE_TAGGED_COMMIT_SHA}" == '${{
+ github.event.inputs.release-committish
+ }}' ]]
+ then
+ echo "already-exists=true" >> "${GITHUB_OUTPUT}"
+ fi
+
+ - name: Fetch the src snapshot
+ if: steps.existing-remote-tag-check.outputs.already-exists == 'true'
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event.inputs.release-committish }}
+ - name: Setup git user as [bot]
+ if: steps.existing-remote-tag-check.outputs.already-exists == 'true'
+ uses: fregante/setup-git-user@v1.1.0
+
+ - 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/${{
+ needs.pre-setup.outputs.dist-name
+ }}/${{
+ 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 }}'
+ --
+ ${{ github.event.inputs.release-committish }}
+ - 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 '${{ needs.pre-setup.outputs.git-tag }}'
+
+ 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
+
+ steps:
+ - name: Download all the dists
+ uses: actions/download-artifact@v3
+ with:
+ name: ${{ env.dists-artifact-name }}
+ path: dist/
+
+ - name: >-
+ Publish a GitHub Release for
+ ${{ needs.pre-setup.outputs.git-tag }}
+ uses: ncipollo/release-action@v1.11.1
+ with:
+ allowUpdates: false
+ artifactErrorsFailBuild: false
+ artifacts: |
+ dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}
+ dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}
+ artifactContentType: raw # Because whl and tgz are of different types
+ body: >
+ # Release ${{ needs.pre-setup.outputs.git-tag }}
+
+
+ This release is published to
+ https://pypi.org/project/${{
+ needs.pre-setup.outputs.dist-name
+ }}/${{
+ needs.pre-setup.outputs.dist-version
+ }}.
+
+
+ This release has been produced by the following workflow run: ${{
+ github.server_url
+ }}/${{
+ github.repository
+ }}/actions/runs/${{
+ github.run_id
+ }}.
+ # bodyFile: # FIXME: Use once Towncrier is integrated.
+ commit: ${{ github.event.inputs.release-committish }}
+ discussionCategory: Announcements
+ draft: false
+ name: ${{ needs.pre-setup.outputs.git-tag }}
+ # omitBody: false
+ omitBodyDuringUpdate: true
+ omitName: false
+ omitNameDuringUpdate: true
+ omitPrereleaseDuringUpdate: true
+ prerelease: false
+ removeArtifacts: false
+ replacesArtifacts: false
+ tag: ${{ needs.pre-setup.outputs.git-tag }}
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+...
diff --git a/setup.py b/setup.py
index c77b28fc..5b1f2aab 100644
--- a/setup.py
+++ b/setup.py
@@ -42,6 +42,7 @@ params = dict(
'CI: AppVeyor': 'https://ci.appveyor.com/project/{}'.format(repo_slug),
'CI: Travis': 'https://travis-ci.org/{}'.format(repo_slug),
'CI: Circle': 'https://circleci.com/gh/{}'.format(repo_slug),
+ 'CI: GitHub': 'https://github.com/{}/actions'.format(repo_slug),
'Docs: RTD': 'https://docs.cherrypy.dev',
'GitHub: issues': '{}/issues'.format(repo_url),
'GitHub: repo': repo_url,