Repository navigation
CI: publish releases to PyPI #107
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
Changes from all commits
69467df
e7ef2b3
42994d7
7e1d6b8
fb70abe
4189102
216465e
eac2c66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| name: release | ||
|
|
||
| # Publishes benchx to PyPI and creates the GitHub release, when the tag v<version> is pushed. | ||
| # docs/releasing.md is the checklist. A tag such as v0.1.0rc1 makes a pre-release. | ||
| # | ||
| # The package is tested before anything is uploaded. Conda packages are not built here: they | ||
| # come from the benchx feedstock on conda-forge, which follows the releases on PyPI. | ||
| # | ||
| # Only a tag publishes. A pull request that changes the packaging or the version runs the build | ||
| # job and uploads nothing. | ||
| # | ||
| # This needs, on GitHub, the pypi environment with its required reviewers, and on PyPI a trusted | ||
| # publisher for this file and that environment. The actions are pinned to commits since they | ||
| # handle the uploads. | ||
|
|
||
| on: | ||
| push: | ||
| tags: ["v*"] | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - ".github/workflows/release.yml" | ||
| - "pyproject.toml" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this correct: pyproject.toml gets the version from |
||
| - "src/benchx/__init__.py" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| version: ${{ steps.version.outputs.version }} | ||
| prerelease: ${{ steps.version.outputs.prerelease }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | ||
|
|
||
| - name: Build the sdist, and the wheel from it | ||
| run: uv build | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL of an alternative to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's see :D Haven't heard of it either.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| - name: Read the version from the sdist | ||
| id: version | ||
| run: | | ||
| version=$(basename dist/benchx-*.tar.gz .tar.gz) | ||
| version=${version#benchx-} | ||
| echo "version=$version" >> "$GITHUB_OUTPUT" | ||
| if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | ||
| echo "prerelease=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "prerelease=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Check that the tag names this version | ||
| if: github.ref_type == 'tag' | ||
| env: | ||
| TAG: ${{ github.ref_name }} | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| if [ "$TAG" != "v$VERSION" ]; then | ||
| echo "::error::tag $TAG does not match the package version $VERSION" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Check the package metadata | ||
| run: uvx twine check --strict dist/* | ||
|
|
||
| - name: Install the wheel into a clean environment and test it | ||
| run: | | ||
| uv venv --python 3.13 "$RUNNER_TEMP/venv" | ||
| source "$RUNNER_TEMP/venv/bin/activate" | ||
| uv pip install "$(ls dist/*.whl)[test]" | ||
| # Away from the checkout, so that nothing can import it instead of the installed package. | ||
| cd "$RUNNER_TEMP" | ||
| bx --help | ||
| python -m pytest --pyargs benchx -ra | ||
|
|
||
| - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | ||
| with: | ||
| name: dist | ||
| path: dist/ | ||
| if-no-files-found: error | ||
|
|
||
| publish-pypi: | ||
| needs: build | ||
| # Also what keeps the job after this one off pull requests and forks: it needs this one. | ||
| if: github.repository == 'benchx-dev/benchx' && github.ref_type == 'tag' | ||
| runs-on: ubuntu-latest | ||
| environment: | ||
| name: pypi | ||
| url: https://pypi.org/project/benchx/${{ needs.build.outputs.version }}/ | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | ||
| with: | ||
| name: dist | ||
| path: dist | ||
|
|
||
| - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3 | ||
|
|
||
| github-release: | ||
| needs: [build, publish-pypi] | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | ||
| with: | ||
| name: dist | ||
| path: release-files | ||
|
|
||
| - name: Create the release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| TAG: ${{ github.ref_name }} | ||
| PRERELEASE: ${{ needs.build.outputs.prerelease }} | ||
| run: gh release create "$TAG" release-files/* --verify-tag --generate-notes --prerelease="$PRERELEASE" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Releasing benchx | ||
|
|
||
| Pushing a tag `v<version>` runs `.github/workflows/release.yml`. It builds and tests the | ||
| package, waits for approval, publishes to PyPI, and creates the GitHub release. Below, | ||
| `upstream` is your remote for `benchx-dev/benchx`, and `0.1.0` stands for the version. | ||
|
|
||
| ## Checklist | ||
|
|
||
| 1. **Check CI.** The release workflow runs its own tests, but it does not look at the other | ||
| workflows, so check that the latest commit on `main` is green. | ||
| 2. **Prepare the release in a pull request, and merge it.** | ||
| - Set `__version__` in `src/benchx/__init__.py`; `pyproject.toml` reads it from there. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dynamic versioning, ok :-). |
||
| - Update `CHANGELOG.md`, including the list of contributors. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So how does the workflow work exactly: send the release PR, merge it, then push the tag? Or send the PR, push the tag, then merge the PR?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per releasing.md: $ git fetch upstream
$ git tag -a v0.1.0 -m "benchx 0.1.0" upstream/main
$ git push upstream v0.1.0
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The approval is through GitHub UI I believe. |
||
| 3. **Tag the merged commit and push the tag.** The tag must be `v` plus exactly the version in | ||
| `__version__`, or the workflow stops before uploading anything. | ||
|
|
||
| ```console | ||
| $ git fetch upstream | ||
| $ git tag -a v0.1.0 -m "benchx 0.1.0" upstream/main | ||
| $ git push upstream v0.1.0 | ||
| ``` | ||
|
|
||
| 4. **Approve the `publish-pypi` job.** Once the tests pass, the | ||
| [workflow run](https://github.com/benchx-dev/benchx/actions/workflows/release.yml) waits and | ||
| shows a "Review deployments" button. A required reviewer of the `pypi` environment other than | ||
| whoever pushed the tag has to approve it, so a release takes two people. | ||
| 5. **Check the result.** The run should be green, with the version on | ||
| [PyPI](https://pypi.org/project/benchx/) and in the | ||
| [GitHub releases](https://github.com/benchx-dev/benchx/releases). | ||
| 6. **Test the install in a new environment.** Activate it, because the tests start benchmarks | ||
| with whichever `python` is on `PATH`. | ||
|
|
||
| ```console | ||
| $ python -m venv venv && source venv/bin/activate | ||
| $ pip install 'benchx[test]==0.1.0' | ||
| $ bx --help | ||
| $ python -m pytest --pyargs benchx | ||
| ``` | ||
|
|
||
| 7. **Merge the conda-forge update.** Conda packages come from the benchx feedstock on | ||
| conda-forge, not from this repository. Once that feedstock exists, conda-forge's bot opens a | ||
| pull request on it after a new version reaches PyPI; review and merge that pull request. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is conda-forge set up already, or do we need to set it up first?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do that afterwards, opened #108 |
||
| 8. **Start the next development version.** In a pull request, set `__version__` to the next | ||
| version with a `.dev0` suffix: after `0.1.0`, that is `0.2.0.dev0`. Results record this version, | ||
| so without this step, results from `main` would claim to come from the release. | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose the last step is to bump the version in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added 8 above |
||
| ## Pre-releases | ||
|
|
||
| A version such as `0.1.0rc1`, tagged `v0.1.0rc1`, follows steps 1 to 6. It is marked as a | ||
| pre-release on GitHub, and `pip install benchx` skips it unless the version is named or | ||
| `--pre` is given. | ||
|
|
||
| ## If something fails | ||
|
|
||
| - **Before the upload:** nothing is published. Delete the tag, fix the problem, and tag again: | ||
|
|
||
| ```console | ||
| $ git push upstream --delete v0.1.0 | ||
| $ git tag -d v0.1.0 | ||
| ``` | ||
|
|
||
| - **During the upload to PyPI:** check the [PyPI project](https://pypi.org/project/benchx/) | ||
| first. If the version is not there, re-run the failed job. | ||
| - **After the upload to PyPI, in `github-release`:** the package is published, so re-run the | ||
| failed job. | ||
| - **A published version is wrong:** PyPI does not allow a version to be uploaded again, even | ||
| after it is deleted, so release a new version. Yanking the bad one on PyPI stops new installs | ||
| from picking it up by default. | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, there's no need to enforce specific versioning (v0.1.0 vs v2026.11.05 vs whatever other scheme)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which shall we do thought? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0.1.0, 0.2.0, 0.2.1 (bugfix) etc sounds fine to me :-).