diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index 702ee119a1..df4a546a4d 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -22,6 +22,30 @@ on: # yamllint disable-line rule:truthy - 'version-check.py' - 'MANIFEST.in' - '.github/workflows/test-library.yml' + 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 + release-commitish: + # github.event_name == 'workflow_dispatch' + # && github.event.inputs.release-commitish + default: '' + description: >- + The commit to be released to PyPI and tagged + in Git as `release-version`. Normally, you + should keep this empty. + YOLO: + default: false + description: >- + Flag whether test results should block the + release (true/false). Only use this under + extraordinary circumstances to ignore the + test failures and cut the release regardless. concurrency: group: >- @@ -33,12 +57,166 @@ concurrency: cancel-in-progress: true jobs: + pre-setup: + name: Pre-set global build settings + runs-on: ubuntu-latest + 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 + }} + 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.9 by default + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - 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: >- + print('::set-output name=is-untagged-devel::true') + - name: Mark the build as "release request" + id: request-check + if: github.event_name == 'workflow_dispatch' + run: >- + print('::set-output name=release-requested::true') + - name: Check out src from Git + if: >- + steps.request-check.outputs.release-requested != 'true' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.release-commitish }} + - 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 sys import version + hash = sha512(version.encode()).hexdigest() + print(f'::set-output name=py-hash-key::{hash}') + - 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: | + print( + "::set-output name=files-hash-key::${{ + hashFiles( + 'setup.cfg', 'tox.ini', 'pyproject.toml', + '.pre-commit-config.yaml', 'pytest.ini' + ) + }}", + ) + - name: Get pip cache dir + id: pip-cache-dir + if: >- + steps.request-check.outputs.release-requested != 'true' + run: >- + echo "::set-output name=dir::$(python -m pip cache dir)" + shell: bash + - name: Set up pip cache + if: >- + steps.request-check.outputs.release-requested != 'true' + uses: actions/cache@v2.1.5 + 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: | + import setuptools_scm + ver = setuptools_scm.get_version( + ${{ + steps.untagged-check.outputs.is-untagged-devel == 'true' + && 'local_scheme="no-local-version"' || '' + }} + ) + print('::set-output name=dist-version::{ver}'.format(ver=ver)) + - name: Set the target Git tag + id: git-tag + run: >- + print('::set-output name=tag::v${{ + steps.request-check.outputs.release-requested == 'true' + && github.event.inputs.release-version + || steps.scm-version.outputs.dist-version + }}') + - name: Set the expected dist artifact names + id: artifact-name + run: | + print('::set-output name=sdist::proxy.py-${{ + steps.request-check.outputs.release-requested == 'true' + && github.event.inputs.release-version + || steps.scm-version.outputs.dist-version + }}.tar.gz') + print('::set-output name=wheel::proxy.py-${{ + steps.request-check.outputs.release-requested == 'true' + && github.event.inputs.release-version + || steps.scm-version.outputs.dist-version + }}-py3-none-any.whl') + integration: runs-on: ${{ matrix.os }}-latest name: >- e2e: 🐍${{ matrix.python }} @ ${{ matrix.os }} needs: - build + - pre-setup # transitive, for accessing settings strategy: matrix: os: [Ubuntu, macOS] @@ -47,6 +225,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} - name: Make the env clean of non-test files run: | shopt -s extglob @@ -64,7 +244,7 @@ jobs: path: dist/ - name: Integration testing run: | - pip install dist/proxy.py-*-py3-none-any.whl + pip install 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}' proxy \ --hostname 127.0.0.1 \ --enable-web-server \ @@ -75,6 +255,8 @@ jobs: build: name: build-dists + needs: + - pre-setup # transitive, for accessing settings runs-on: Ubuntu-latest @@ -125,6 +307,8 @@ jobs: - name: Grab the source from Git uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} - name: Pre-populate the tox env run: >- @@ -145,13 +329,20 @@ jobs: uses: actions/upload-artifact@v2 with: name: python-package-distributions - path: dist + # 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: 30 # Defaults to 90 lint: name: ${{ matrix.toxenv }} needs: - build + - pre-setup # transitive, for accessing settings runs-on: Ubuntu-latest strategy: @@ -208,6 +399,8 @@ jobs: - name: Grab the source from Git uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} - name: Make the env clean of non-test files if: matrix.toxenv == 'metadata-validation' @@ -244,6 +437,7 @@ jobs: name: 🐍${{ matrix.python }} @ ${{ matrix.os }} needs: - build + - pre-setup # transitive, for accessing settings runs-on: ${{ matrix.os }}-latest strategy: @@ -264,6 +458,14 @@ jobs: - 3.7 max-parallel: 4 + continue-on-error: >- + ${{ + ( + needs.pre-setup.outputs.release-requested == 'true' && + !toJSON(github.event.inputs.YOLO) + ) && true || false + }} + env: PY_COLORS: 1 TOX_PARALLEL_NO_SPINNER: 1 @@ -312,6 +514,8 @@ jobs: - name: Grab the source from Git uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} - name: Download all the dists uses: actions/download-artifact@v2 @@ -326,7 +530,7 @@ jobs: --parallel auto --parallel-live --skip-missing-interpreters false - --installpkg dist/proxy.py-*-py3-none-any.whl + --installpkg 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}' --notest shell: bash - name: Run the testing @@ -356,4 +560,97 @@ jobs: run: >- print("All's good") shell: python + + 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: release + url: >- + https://pypi.org/project/proxy.py/${{ + needs.pre-setup.outputs.dist-public-version + }} + + steps: + - name: Download all the dists + uses: actions/download-artifact@v2 + with: + name: python-package-distributions + path: dist/ + - name: >- + Publish 🐍📦 v${{ needs.pre-setup.outputs.git-tag }} to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_TOKEN }} + + + publish-testpypi: + name: Publish 🐍📦 to TestPyPI + needs: + - check + - pre-setup # transitive, for accessing settings + if: >- + fromJSON(needs.pre-setup.outputs.is-untagged-devel) + runs-on: Ubuntu-latest + + environment: + name: release-testpypi + url: >- + https://test.pypi.org/project/proxy.py/${{ + needs.pre-setup.outputs.dist-public-version + }} + + steps: + - name: Download all the dists + uses: actions/download-artifact@v2 + with: + name: python-package-distributions + path: dist/ + - name: >- + Publish 🐍📦 v${{ needs.pre-setup.outputs.git-tag }}to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TESTPYPI_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: Fetch the src snapshot + uses: actions/checkout@v2 + with: + fetch-depth: 1 + ref: ${{ github.event.inputs.release-commitish }} + - name: Setup git user as [bot] + run: > + git config --local user.email + 'github-actions[bot]@users.noreply.github.com' + + git config --local user.name 'github-actions[bot]' + + - name: >- + Tag the release in the local Git repo + as v${{ needs.pre-setup.outputs.git-tag }} + run: >- + git tag '${{ needs.pre-setup.outputs.git-tag }}' + ${{ github.event.inputs.release-commitish }} + - name: >- + Push ${{ needs.pre-setup.outputs.git-tag }} tag corresponding + to the just published release back to GitHub + run: >- + git push --atomic origin '${{ needs.pre-setup.outputs.git-tag }}' ...