diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b98d6de79a..a6fb96e4f6 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -142,6 +142,11 @@ jobs: 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 @@ -296,6 +301,10 @@ jobs: 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: | @@ -342,6 +351,228 @@ jobs: }}-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-changelog-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/setup-python@v5.0.0 + with: + python-version: 3.11 + + - name: Grab the source from Git + uses: actions/checkout@v4.1.1 + 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/cache@v4.0.0 + 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/setup-git-user@v2.0.1 + + - 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 changelog 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: >- @@ -359,6 +590,7 @@ jobs: ) && 'test' || '' }}] needs: + - build-changelog - pre-setup # transitive, for accessing settings runs-on: ubuntu-latest @@ -1036,6 +1268,26 @@ jobs: # * 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 @@ -1055,14 +1307,15 @@ jobs: 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 }}' + git push --atomic origin + 'release/${{ needs.pre-setup.outputs.dist-version }}' + '${{ needs.pre-setup.outputs.git-tag }}' publish-github-release: name: >- @@ -1083,6 +1336,35 @@ jobs: 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: 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 '🔗 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: >- Publish a GitHub Release for @@ -1095,25 +1377,7 @@ jobs: 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/cheroot/${{ - 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 }} + bodyFile: release-notes.md discussionCategory: Announcements draft: false name: ${{ needs.pre-setup.outputs.git-tag }} diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 2a6696106d..fe34b5182c 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -10,6 +10,7 @@ conftest conn chunked docstrings +downstreams ef environ Faux @@ -32,6 +33,7 @@ noqa PIL pipelining positionally +pre py pytest pythonic @@ -49,11 +51,13 @@ stdout subclasses submodules subpackages +symlinked syscall systemd threadpool Tidelift TLS +Towncrier tracebacks tuple tuples diff --git a/requirements/tox-check-changelog-cp310-linux-x86_64.txt b/requirements/tox-check-changelog-cp310-linux-x86_64.txt new file mode 120000 index 0000000000..7ae658e5cc --- /dev/null +++ b/requirements/tox-check-changelog-cp310-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp310-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-check-changelog-cp311-linux-x86_64.txt b/requirements/tox-check-changelog-cp311-linux-x86_64.txt new file mode 120000 index 0000000000..e84f76d584 --- /dev/null +++ b/requirements/tox-check-changelog-cp311-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp311-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-check-changelog-cp312-linux-x86_64.txt b/requirements/tox-check-changelog-cp312-linux-x86_64.txt new file mode 120000 index 0000000000..79587652f0 --- /dev/null +++ b/requirements/tox-check-changelog-cp312-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp312-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-check-changelog-cp313-linux-x86_64.txt b/requirements/tox-check-changelog-cp313-linux-x86_64.txt new file mode 120000 index 0000000000..61ba303ace --- /dev/null +++ b/requirements/tox-check-changelog-cp313-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp313-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-check-changelog-cp38-linux-x86_64.txt b/requirements/tox-check-changelog-cp38-linux-x86_64.txt new file mode 120000 index 0000000000..0c71a1aee9 --- /dev/null +++ b/requirements/tox-check-changelog-cp38-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp38-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-check-changelog-cp39-linux-x86_64.txt b/requirements/tox-check-changelog-cp39-linux-x86_64.txt new file mode 120000 index 0000000000..a821f37958 --- /dev/null +++ b/requirements/tox-check-changelog-cp39-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp39-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp310-linux-x86_64.txt b/requirements/tox-draft-changelog-cp310-linux-x86_64.txt new file mode 120000 index 0000000000..7ae658e5cc --- /dev/null +++ b/requirements/tox-draft-changelog-cp310-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp310-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp311-linux-x86_64.txt b/requirements/tox-draft-changelog-cp311-linux-x86_64.txt new file mode 120000 index 0000000000..e84f76d584 --- /dev/null +++ b/requirements/tox-draft-changelog-cp311-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp311-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp312-linux-x86_64.txt b/requirements/tox-draft-changelog-cp312-linux-x86_64.txt new file mode 120000 index 0000000000..79587652f0 --- /dev/null +++ b/requirements/tox-draft-changelog-cp312-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp312-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp313-linux-x86_64.txt b/requirements/tox-draft-changelog-cp313-linux-x86_64.txt new file mode 120000 index 0000000000..61ba303ace --- /dev/null +++ b/requirements/tox-draft-changelog-cp313-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp313-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp38-linux-x86_64.txt b/requirements/tox-draft-changelog-cp38-linux-x86_64.txt new file mode 120000 index 0000000000..0c71a1aee9 --- /dev/null +++ b/requirements/tox-draft-changelog-cp38-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp38-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-draft-changelog-cp39-linux-x86_64.txt b/requirements/tox-draft-changelog-cp39-linux-x86_64.txt new file mode 120000 index 0000000000..a821f37958 --- /dev/null +++ b/requirements/tox-draft-changelog-cp39-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp39-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp310-linux-x86_64.txt b/requirements/tox-make-changelog-cp310-linux-x86_64.txt new file mode 120000 index 0000000000..7ae658e5cc --- /dev/null +++ b/requirements/tox-make-changelog-cp310-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp310-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp311-linux-x86_64.txt b/requirements/tox-make-changelog-cp311-linux-x86_64.txt new file mode 120000 index 0000000000..e84f76d584 --- /dev/null +++ b/requirements/tox-make-changelog-cp311-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp311-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp312-linux-x86_64.txt b/requirements/tox-make-changelog-cp312-linux-x86_64.txt new file mode 120000 index 0000000000..79587652f0 --- /dev/null +++ b/requirements/tox-make-changelog-cp312-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp312-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp313-linux-x86_64.txt b/requirements/tox-make-changelog-cp313-linux-x86_64.txt new file mode 120000 index 0000000000..61ba303ace --- /dev/null +++ b/requirements/tox-make-changelog-cp313-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp313-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp38-linux-x86_64.txt b/requirements/tox-make-changelog-cp38-linux-x86_64.txt new file mode 120000 index 0000000000..0c71a1aee9 --- /dev/null +++ b/requirements/tox-make-changelog-cp38-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp38-linux-x86_64.txt \ No newline at end of file diff --git a/requirements/tox-make-changelog-cp39-linux-x86_64.txt b/requirements/tox-make-changelog-cp39-linux-x86_64.txt new file mode 120000 index 0000000000..a821f37958 --- /dev/null +++ b/requirements/tox-make-changelog-cp39-linux-x86_64.txt @@ -0,0 +1 @@ +tox-build-docs-cp39-linux-x86_64.txt \ No newline at end of file diff --git a/tox.ini b/tox.ini index 49dd345e17..0dd093aed8 100644 --- a/tox.ini +++ b/tox.ini @@ -103,8 +103,11 @@ commands = `file://\{index_file\}`\n\nTo serve docs, use \ `python3 -m http.server --directory \ \N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0`\n")' +depends = + make-changelog deps = -rrequirements{/}tox-docs.in +envdir = {toxworkdir}/build-docs [testenv:doctest-docs] allowlist_externals = @@ -131,6 +134,7 @@ commands = . "{toxworkdir}{/}docs_out" deps = {[testenv:build-docs]deps} +envdir = {[testenv:build-docs]envdir} [testenv:linkcheck-docs] allowlist_externals = @@ -157,6 +161,7 @@ commands = . "{toxworkdir}{/}docs_out" deps = {[testenv:build-docs]deps} +envdir = {[testenv:build-docs]envdir} [testenv:spellcheck-docs] allowlist_externals = @@ -183,6 +188,7 @@ commands = . "{toxworkdir}{/}docs_out" deps = -rrequirements{/}tox-docs-linkcheck.in +envdir = {[testenv:build-docs]envdir} [testenv:watch] commands = ptw --runner=pytest @@ -193,6 +199,81 @@ deps = commands_pre = commands = pre-commit run --all-files --show-diff-on-failure {posargs} + +[testenv:check-changelog] +basepython = {[testenv:make-changelog]basepython} +description = + Check Towncrier change notes +commands_pre = +commands = + {envpython} \ + {[python-cli-options]byteerrors} \ + {[python-cli-options]isolate} \ + -m towncrier.check \ + --compare-with origin/devel {posargs:} +deps = + {[testenv:make-changelog]deps} +envdir = {[testenv:make-changelog]envdir} +isolated_build = {[testenv:make-changelog]isolated_build} +skip_install = {[testenv:make-changelog]skip_install} + + +[testenv:make-changelog] +basepython = python3 +depends = + check-changelog +description = + Generate a changelog from fragments using Towncrier. Getting an + unreleased changelog preview does not require extra arguments. + When invoking to update the changelog, pass the desired version as an + argument after `--`. For example, `tox -e {envname} -- 1.3.2`. +envdir = {[testenv:build-docs]envdir} +commands_pre = +commands = + {envpython} \ + {[python-cli-options]byteerrors} \ + {[python-cli-options]isolate} \ + -m towncrier.build \ + --version \ + {posargs:'[UNRELEASED DRAFT]' --draft} +deps = + {[testenv:build-docs]deps} + # -r{toxinidir}/docs/requirements.txt + # FIXME: re-enable the "-r" + "-c" paradigm once the pip bug is fixed. + # Ref: https://github.com/pypa/pip/issues/9243 + # towncrier + # -r{toxinidir}/docs/requirements.in + # -c{toxinidir}/docs/requirements.txt +isolated_build = true +skip_install = true + + +[testenv:draft-changelog] +allowlist_externals = + sh +basepython = {[testenv:make-changelog]basepython} +description = + Print out the Towncrier-managed change notes + draft for the next release to stdout +commands_pre = +commands = + # NOTE: `sh` invocation is required to suppress stderr from + # NOTE: towncrier since it does not have own CLI flags for + # NOTE: doing this. + sh -c "2>/dev/null \ + {envpython} \ + {[python-cli-options]byteerrors} \ + {[python-cli-options]isolate} \ + -m towncrier.build \ + --version '[UNRELEASED DRAFT]' \ + --draft" +envdir = {[testenv:make-changelog]envdir} +deps = + {[testenv:make-changelog]deps} +isolated_build = {[testenv:make-changelog]isolated_build} +skip_install = {[testenv:make-changelog]skip_install} + + [testenv:cleanup-dists] description = Wipe the the dist{/} folder