diff options
author | Florian Bruhin <me@the-compiler.org> | 2023-08-16 11:59:46 +0200 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2023-08-17 12:35:53 +0200 |
commit | 950d06ad5b54abfc3f0d4dffb6b4b890b029e9e8 (patch) | |
tree | c2ecd154b61fbbd89a2210c5de25c357070efe45 | |
parent | d7b33759e537a759fb4cf90adfcc98f1a3ec7626 (diff) | |
download | qutebrowser-950d06ad5b54abfc3f0d4dffb6b4b890b029e9e8.tar.gz qutebrowser-950d06ad5b54abfc3f0d4dffb6b4b890b029e9e8.zip |
ci: Initial automatic release support
See #3725
-rw-r--r-- | .github/workflows/release.yml | 137 | ||||
-rwxr-xr-x | scripts/dev/build_release.py | 29 | ||||
-rw-r--r-- | scripts/dev/update_version.py | 64 | ||||
-rw-r--r-- | tox.ini | 8 |
4 files changed, 214 insertions, 24 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..213078730 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,137 @@ +name: Release + +on: + workflow_dispatch: + inputs: + release_type: + description: 'Release type' + required: true + default: 'patch' + type: choice + options: + - 'patch' + - 'minor' + - 'major' + # FIXME do we want a possibility to do prereleases here? + python_version: + description: 'Python version' + required: true + default: '3.11' + type: choice + options: + - '3.8' + - '3.9' + - '3.10' + - '3.11' +jobs: + prepare: + runs-on: ubuntu-20.04 + timeout-minutes: 5 + outputs: + version: ${{ steps.bump.outputs.version }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + # Doesn't really matter what we prepare the release with, but let's + # use the same version for consistency. + python-version: ${{ github.event.inputs.python_version }} + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U -r misc/requirements/requirements-tox.txt + - name: Configure git + run: | + git config --global user.name "qutebrowser bot" + git config --global user.email "bot@qutebrowser.org" + - name: Switch to release branch + if: "${{ github.event.inputs.release_type }} == 'patch'" + run: | + git checkout "$(git branch --format='%(refname:short)' --list 'v*.*.x' | sort -V | tail -n1)" + # FIXME set up GPG for signed tag + - name: Bump version + id: bump + run: "tox -e update-version -- ${{ github.event.inputs.release_type }}" + - name: Push release commit/tag + run: | + git push origin main + git push origin v${{ steps.bump.outputs.version }} + - name: Cherry-pick release commit + if: "${{ github.event.inputs.release_type }} == 'patch'" + run: | + git checkout main + git cherry-pick v${{ steps.bump.outputs.version }} + git push origin main + git checkout v${{ steps.bump.outputs.version_x }} + - name: Create release branch + if: "${{ github.event.inputs.release_type }} != 'patch'" + run: | + git checkout -b v${{ steps.bump.outputs.version_x }} + git push --set-upstream origin v${{ steps.bump.outputs.version_x }} + - name: Create GitHub draft release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.bump.outputs.version }} + draft: true + body: "*Release artifacts for this release are currently being uploaded...*" + release: + strategy: + matrix: + include: + - os: macos-11 + - os: windows-2019 + - os: ubuntu-20.04 + runs-on: "${{ matrix.os }}" + timeout-minutes: 45 + needs: [prepare] + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ github.event.inputs.python_version }} + # FIXME set up GPG for signed releases (at least on Ubuntu) + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U -r misc/requirements/requirements-tox.txt + - name: Build and upload release + run: "tox -e build-release -- --upload --no-confirm --experimental --gh-token ${{ secrets.GITHUB_TOKEN }}" + finalize: + runs-on: ubuntu-20.04 + timeout-minutes: 5 + needs: [prepare, release] + steps: + - name: Publish final release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ needs.prepare.outputs.version }} + draft: false + # FIXME automatically cut relevant changes from changelog and add them here? + body: | + Check the [changelog](https://github.com/qutebrowser/qutebrowser/blob/master/doc/changelog.asciidoc) for changes in this release. + irc: + timeout-minutes: 2 + continue-on-error: true + runs-on: ubuntu-20.04 + needs: [prepare, release, finalize] + if: "${{ always() }}" + steps: + - name: Send success IRC notification + uses: Gottox/irc-message-action@v2 + if: "${{ needs.finalize.result == 'success' }}" + with: + server: irc.libera.chat + channel: '#qutebrowser-bots' + nickname: qutebrowser-bot + message: "[${{ github.workflow }}] \u00033Success:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})" + - name: Send non-success IRC notification + uses: Gottox/irc-message-action@v2 + if: "${{ needs.finalize.result != 'success' }}" + with: + server: irc.libera.chat + channel: '#qutebrowser-bots' + nickname: qutebrowser-bot + message: "[${{ github.workflow }}] \u00034FAIL:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})\n + prepare: ${{ needs.prepare.result }}, release: ${{ needs.release.result}}, finalize: ${{ needs.finalize.result }}" diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 55b3f5f1c..2038a7f67 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -544,13 +544,19 @@ def read_github_token( return token -def github_upload(artifacts: List[Artifact], tag: str, gh_token: str) -> None: +def github_upload( + artifacts: List[Artifact], + tag: str, + gh_token: str, + experimental: bool, +) -> None: """Upload the given artifacts to GitHub. Args: artifacts: A list of Artifacts to upload. tag: The name of the release tag gh_token: The GitHub token to use + experimental: Upload to the experiments repo """ # pylint: disable=broad-exception-raised import github3 @@ -558,7 +564,11 @@ def github_upload(artifacts: List[Artifact], tag: str, gh_token: str) -> None: utils.print_title("Uploading to github...") gh = github3.login(token=gh_token) - repo = gh.repository('qutebrowser', 'qutebrowser') + + if experimental: + repo = gh.repository('qutebrowser', 'experiments') + else: + repo = gh.repository('qutebrowser', 'qutebrowser') release = None # to satisfy pylint for release in repo.releases(): @@ -602,10 +612,13 @@ def github_upload(artifacts: List[Artifact], tag: str, gh_token: str) -> None: break -def pypi_upload(artifacts: List[Artifact]) -> None: +def pypi_upload(artifacts: List[Artifact], experimental: bool) -> None: """Upload the given artifacts to PyPI using twine.""" utils.print_title("Uploading to PyPI...") - run_twine('upload', artifacts) + if experimental: + run_twine('upload', artifacts, "-r", "testpypi") + else: + run_twine('upload', artifacts) def twine_check(artifacts: List[Artifact]) -> None: @@ -635,6 +648,8 @@ def main() -> None: help="Build a debug build.") parser.add_argument('--qt5', action='store_true', required=False, help="Build against PyQt5") + parser.add_argument('--experimental', action='store_true', required=False, + help="Upload to experiments repo and test PyPI") args = parser.parse_args() utils.change_cwd() @@ -647,6 +662,7 @@ def main() -> None: gh_token = read_github_token(args.gh_token) else: gh_token = read_github_token(args.gh_token, optional=True) + assert not args.experimental # makes no sense without upload if not misc_checks.check_git(): utils.print_error("Refusing to do a release with a dirty git tree") @@ -685,9 +701,10 @@ def main() -> None: input() assert gh_token is not None - github_upload(artifacts, version_tag, gh_token=gh_token) + github_upload( + artifacts, version_tag, gh_token=gh_token, experimental=args.experimental) if upload_to_pypi: - pypi_upload(artifacts) + pypi_upload(artifacts, experimental=args.experimental) else: print() utils.print_title("Artifacts") diff --git a/scripts/dev/update_version.py b/scripts/dev/update_version.py index ec1550414..c67873496 100644 --- a/scripts/dev/update_version.py +++ b/scripts/dev/update_version.py @@ -8,6 +8,7 @@ """Update version numbers using bump2version.""" +import re import sys import argparse import os.path @@ -19,6 +20,24 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, from scripts import utils +class Error(Exception): + """Base class for exceptions in this module.""" + + +def verify_branch(version_leap): + """Check that we're on the correct git branch.""" + proc = subprocess.run( + ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], + check=True, capture_output=True, text=True) + branch = proc.stdout.strip() + + if ( + version_leap == 'patch' and not re.fullmatch(r'v\d+\.\d+\.\*', branch) or + version_leap != 'patch' and branch != 'main' + ): + raise Error(f"Invalid branch for {version_leap} release: {branch}") + + def bump_version(version_leap="patch"): """Update qutebrowser release version. @@ -46,6 +65,7 @@ if __name__ == "__main__": utils.change_cwd() if not args.commands: + verify_branch(args.bump) bump_version(args.bump) show_commit() @@ -54,22 +74,30 @@ if __name__ == "__main__": x_version = '.'.join([str(p) for p in qutebrowser.__version_info__[:-1]] + ['x']) - print("Run the following commands to create a new release:") - print("* git push origin; git push origin v{v}".format(v=version)) - if args.bump == 'patch': - print("* git checkout main && git cherry-pick v{v} && " - "git push origin".format(v=version)) + if utils.ON_CI: + output_file = os.environ["GITHUB_OUTPUT"] + with open(output_file, "w", encoding="ascii") as f: + f.write(f"version={version}\n") + f.write(f"x_version={x_version}\n") + + print(f"Outputs for {version} written to GitHub Actions output file") else: - print("* git branch v{x} v{v} && git push --set-upstream origin v{x}" - .format(v=version, x=x_version)) - print("* Create new release via GitHub (required to upload release " - "artifacts)") - print("* Linux: git fetch && git checkout v{v} && " - "tox -e build-release -- --upload" - .format(v=version)) - print("* Windows: git fetch; git checkout v{v}; " - "py -3.9 -m tox -e build-release -- --upload" - .format(v=version)) - print("* macOS: git fetch && git checkout v{v} && " - "tox -e build-release -- --upload" - .format(v=version)) + print("Run the following commands to create a new release:") + print("* git push origin; git push origin v{v}".format(v=version)) + if args.bump == 'patch': + print("* git checkout main && git cherry-pick v{v} && " + "git push origin".format(v=version)) + else: + print("* git branch v{x} v{v} && git push --set-upstream origin v{x}" + .format(v=version, x=x_version)) + print("* Create new release via GitHub (required to upload release " + "artifacts)") + print("* Linux: git fetch && git checkout v{v} && " + "tox -e build-release -- --upload" + .format(v=version)) + print("* Windows: git fetch; git checkout v{v}; " + "py -3.9 -m tox -e build-release -- --upload" + .format(v=version)) + print("* macOS: git fetch && git checkout v{v} && " + "tox -e build-release -- --upload" + .format(v=version)) @@ -267,6 +267,14 @@ deps = commands = {envpython} -m sphinx -jauto -W --color {posargs} {toxinidir}/doc/extapi/ {toxinidir}/doc/extapi/_build/ +[testenv:update-version] +basepython = {env:PYTHON:python3} +passenv = + GITHUB_OUTPUT + CI +deps = -r{toxinidir}/misc/requirements/requirements-dev.txt +commands = {envpython} scripts/dev/update_version.py {posargs} + [testenv:build-release{,-qt5}] basepython = {env:PYTHON:python3} passenv = * |