-
Notifications
You must be signed in to change notification settings - Fork 1
Automate the release process #485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d46ca43
Automate the release with two workflows
kozlov721 d7b7c13
Group the generated release notes by label
kozlov721 c05886b
Describe the tag suffix accurately
kozlov721 960768d
Make the release workflows reusable
kozlov721 503f7fd
Pass only the secret that the release workflows need
kozlov721 28a76d3
Hold the PyPI upload for an approval
kozlov721 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Groups the notes that `gh release create --generate-notes` writes. | ||
| changelog: | ||
| exclude: | ||
| labels: | ||
| - release | ||
| categories: | ||
| - title: New features | ||
| labels: | ||
| - enhancement | ||
| - title: Bug fixes | ||
| labels: | ||
| - fix | ||
| - hotfix | ||
| - bug | ||
| - title: Documentation | ||
| labels: | ||
| - documentation | ||
| - title: Maintenance | ||
| labels: | ||
| - DevOps | ||
| - tests | ||
| - automated | ||
| - title: Other changes | ||
| labels: | ||
| - "*" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| name: Release PR | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: "major, minor, patch, or an explicit version such as 1.2.3" | ||
| type: string | ||
| default: patch | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| release-pr: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | ||
| with: | ||
| # A release always comes from `main`. The full history and the tags | ||
| # give the previous tag and the changes after it. | ||
| ref: main | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Set the new version | ||
| id: version | ||
| env: | ||
| REQUEST: ${{ inputs.version }} | ||
| run: | | ||
| version="$(python3 tools/version.py --set "$REQUEST")" | ||
| if git tag --list "v$version" "v$version-*" | grep -q .; then | ||
| echo "::error::A tag for version $version exists already." | ||
| exit 1 | ||
| fi | ||
| { | ||
| echo "version=$version" | ||
| echo "previous=$(git describe --tags --abbrev=0)" | ||
| } >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Write the pull request body | ||
| env: | ||
| BODY_PATH: ${{ runner.temp }}/release-pr-body.md | ||
| PREVIOUS: ${{ steps.version.outputs.previous }} | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| # The heredoc expands shell variables only. A commit subject cannot | ||
| # inject a command, because the shell never re-reads an expansion. | ||
| changelog="$(git log --pretty=format:'- %s' "$PREVIOUS"..HEAD)" | ||
| cat >"$BODY_PATH" <<BODY | ||
| ## Purpose | ||
| Release LuxonisML \`v${VERSION}\`. The \`Release PR\` workflow opened this | ||
| pull request, and the merge of it starts the release. | ||
|
|
||
| ## Specification | ||
| The version in \`luxonis_ml/__init__.py\` becomes \`${VERSION}\`. | ||
|
|
||
| Changes after \`${PREVIOUS}\`: | ||
|
|
||
| ${changelog} | ||
|
|
||
| ## Dependencies & Potential Impact | ||
| This pull request changes the version only. The new version then goes to | ||
| PyPI under the same package name. | ||
|
|
||
| ## Deployment Plan | ||
| 1. Approve and merge this pull request. | ||
| 2. The \`Release Tag\` workflow creates the tag, the release, and the notes. | ||
| 3. The release event starts \`Upload Python Package\` and | ||
| \`Deploy Documentation\`. | ||
|
|
||
| ## Testing & Validation | ||
| CI runs on this pull request. It includes \`check-requirements\`, which | ||
| only runs on \`release/*\` branches. | ||
|
|
||
| ## AI Usage | ||
| The \`Release PR\` workflow generated this pull request. No AI tool wrote it. | ||
|
|
||
| Submitted code was reviewed by a human: YES/NO | ||
|
|
||
| The author is taking the responsibility for the contribution: YES/NO | ||
| BODY | ||
|
|
||
| - name: Create the pull request | ||
| uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 | ||
| with: | ||
| # A pull request from the default token does not start CI. The | ||
| # personal token makes the checks run, `check-requirements` included. | ||
| token: ${{ secrets.WORKFLOW_SECRET }} | ||
| base: main | ||
| branch: release/v${{ steps.version.outputs.version }} | ||
| delete-branch: true | ||
| add-paths: luxonis_ml/__init__.py | ||
| commit-message: "chore: bump the version to ${{ steps.version.outputs.version }}" | ||
| title: "LuxonisML `v${{ steps.version.outputs.version }}`" | ||
| body-path: ${{ runner.temp }}/release-pr-body.md | ||
| labels: release |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| name: Release Tag | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [closed] | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| # A tag-name convention that every release since v0.3.0 keeps. It does not | ||
| # mark the GitHub release as a prerelease, and the PyPI version stays | ||
| # `X.Y.Z`. Change both deliberately, not through this constant alone. | ||
| TAG_SUFFIX: "-beta" | ||
|
|
||
| jobs: | ||
| release: | ||
| if: >- | ||
| github.event.pull_request.merged && | ||
| startsWith(github.event.pull_request.head.ref, 'release/') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | ||
| with: | ||
| ref: ${{ github.event.pull_request.merge_commit_sha }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Read the version | ||
| id: version | ||
| run: echo "version=$(python3 tools/version.py)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Create the release | ||
| env: | ||
| # The default token cannot start another workflow. The personal | ||
| # token makes the release event start the PyPI upload and the | ||
| # documentation deployment. | ||
| GH_TOKEN: ${{ secrets.WORKFLOW_SECRET }} | ||
| TAG: v${{ steps.version.outputs.version }}${{ env.TAG_SUFFIX }} | ||
| TARGET: ${{ github.event.pull_request.merge_commit_sha }} | ||
| run: | | ||
| gh release create "$TAG" \ | ||
| --target "$TARGET" \ | ||
| --title "$TAG" \ | ||
| --generate-notes | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env python3 | ||
| """Read or change the version in ``luxonis_ml/__init__.py``. | ||
|
|
||
| Run it from the repository root. | ||
| """ | ||
|
|
||
| import argparse | ||
| import ast | ||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| INIT_PATH = Path("luxonis_ml/__init__.py") | ||
| VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+$") | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = parse_args() | ||
| current = read_version() | ||
|
|
||
| if args.new_version is None: | ||
| sys.stdout.write(f"{current}\n") | ||
| return | ||
|
|
||
| new_version = resolve_version(current, args.new_version) | ||
| write_version(new_version) | ||
| sys.stdout.write(f"{new_version}\n") | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description="Read or change the LuxonisML version." | ||
| ) | ||
| parser.add_argument( | ||
| "--set", | ||
| dest="new_version", | ||
| metavar="VERSION", | ||
| help=( | ||
| "Increase one part of the version with 'major', 'minor', or " | ||
| "'patch', or give an explicit version such as '1.2.3'. Without " | ||
| "this option the tool only reports the current version." | ||
| ), | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def read_version() -> str: | ||
| return str(_version_node(INIT_PATH.read_text(encoding="utf-8")).value) | ||
|
|
||
|
|
||
| def resolve_version(current: str, request: str) -> str: | ||
| major, minor, patch = parse_version(current) | ||
| if request == "major": | ||
| new_version = (major + 1, 0, 0) | ||
| elif request == "minor": | ||
| new_version = (major, minor + 1, 0) | ||
| elif request == "patch": | ||
| new_version = (major, minor, patch + 1) | ||
| else: | ||
| new_version = parse_version(request) | ||
|
|
||
| if new_version <= (major, minor, patch): | ||
| raise SystemExit( | ||
| f"Version {_format_version(new_version)} is not above the " | ||
| f"current version {current}." | ||
| ) | ||
| return _format_version(new_version) | ||
|
|
||
|
|
||
| def parse_version(version: str) -> tuple[int, int, int]: | ||
| if not VERSION_PATTERN.match(version): | ||
| raise SystemExit( | ||
| f"Expected 'major', 'minor', 'patch', or a version of the form " | ||
| f"'1.2.3'. Got {version!r}." | ||
| ) | ||
| major, minor, patch = (int(part) for part in version.split(".")) | ||
| return major, minor, patch | ||
|
|
||
|
|
||
| def write_version(new_version: str) -> None: | ||
| text = INIT_PATH.read_text(encoding="utf-8") | ||
| node = _version_node(text) | ||
| lines = text.splitlines(keepends=True) | ||
| line = lines[node.lineno - 1] | ||
| prefix = line[: node.col_offset] | ||
| suffix = line[node.end_col_offset :] | ||
| lines[node.lineno - 1] = f'{prefix}"{new_version}"{suffix}' | ||
| INIT_PATH.write_text("".join(lines), encoding="utf-8") | ||
|
|
||
|
|
||
| def _format_version(version: tuple[int, int, int]) -> str: | ||
| return ".".join(str(part) for part in version) | ||
|
|
||
|
|
||
| def _version_node(text: str) -> ast.Constant: | ||
| for node in ast.parse(text).body: | ||
| if ( | ||
| isinstance(node, ast.AnnAssign) | ||
| and isinstance(node.target, ast.Name) | ||
| and node.target.id == "__version__" | ||
| and isinstance(node.value, ast.Constant) | ||
| and isinstance(node.value.value, str) | ||
| ): | ||
| return node.value | ||
| raise SystemExit(f"Found no `__version__` string in {INIT_PATH}.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.