ci(PLT-1250): tag-triggered PyPI release pipeline#133
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR introduces a tag-triggered release pipeline to publish orcapod to TestPyPI/PyPI using OIDC Trusted Publishing, while migrating packaging to hatchling + hatch-vcs for tag-based versioning.
Changes:
- Switch build backend from
setuptools/setuptools-scmtohatchling/hatch-vcsand update project homepage URL. - Add tag-triggered
publish.ymlworkflow with a test → license check → build → publish (TestPyPI then PyPI) → GitHub Release chain. - Add license checking and dependency review jobs to the main CI workflow; add
pip-licensesto dev dependencies/lockfile.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
pyproject.toml |
Migrates build backend to hatch + VCS-based versioning; adds pip-licenses dev dependency; updates homepage URL. |
uv.lock |
Locks pip-licenses (+ transitive prettytable) and updates extras resolution entries. |
.github/workflows/run-tests.yml |
Adds license-check and dependency-review jobs; adjusts checkout depth for hatch-vcs. |
.github/workflows/publish.yml |
Adds tag-triggered release workflow for build/publish/release using OIDC. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| on: | ||
| push: | ||
| tags: | ||
| - "v[0-9]+.[0-9]+.[0-9]+*" |
There was a problem hiding this comment.
The tag filter uses a regex-like pattern (with +), but GitHub Actions tag filters are glob patterns (no + quantifier). As written, this will only match tags that literally contain + characters (e.g., v1+.2+.3+...) and will not trigger for tags like v0.1.0 or v0.1.0rc1. Use a glob-compatible pattern (e.g., v[0-9]*.[0-9]*.[0-9]* or a simpler v* if acceptable).
| - "v[0-9]+.[0-9]+.[0-9]+*" | |
| - "v[0-9]*.[0-9]*.[0-9]*" |
| [build-system] | ||
| requires = ["setuptools>=64", "wheel", "setuptools-scm>=8"] | ||
| build-backend = "setuptools.build_meta" | ||
| requires = ["hatchling", "hatch-vcs"] |
There was a problem hiding this comment.
build-system.requires no longer specifies minimum versions. Since the previous config pinned minimums (e.g., setuptools>=64, setuptools-scm>=8), consider adding minimum versions for hatchling/hatch-vcs as well to avoid builds breaking on older environments (and to document the expected feature set for source = "vcs" and the vcs hook).
| requires = ["hatchling", "hatch-vcs"] | |
| requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"] |
…cense allowlist) - Pin hatchling>=1.21.0 and hatch-vcs>=0.4.0 in build-system.requires - Fix publish.yml tag glob: replace regex `+` quantifier with glob `*` (GitHub Actions uses fnmatch globs, not regex; `v[0-9]+` matched literal `+`) - Expand pip-licenses --allow-only to cover legacy PyPI metadata strings: "Apache Software License", "BSD License", "BSD", "Python Software Foundation License" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| name: Publish to PyPI | ||
|
|
||
| on: | ||
| push: |
There was a problem hiding this comment.
Fixed: changed to "v[0-9]*.[0-9]*.[0-9]*" using glob * quantifiers. GitHub Actions tag filters use fnmatch-style globs, not regex — the previous + was treated as a literal character and would never have triggered. The updated pattern matches v0.1.0, v1.2.3, and pre-releases like v0.1.0rc1.
| @@ -1,6 +1,6 @@ | |||
| [build-system] | |||
| requires = ["setuptools>=64", "wheel", "setuptools-scm>=8"] | |||
There was a problem hiding this comment.
Fixed: pinned hatchling>=1.21.0 and hatch-vcs>=0.4.0. These minimums ensure the source = "vcs" option (added in hatch-vcs 0.3.0) and the VCS hook are available, matching the spirit of the previous setuptools>=64/setuptools-scm>=8 pins.
Review response — dbd8724All Copilot review comments addressed in commit 1. Tag filter glob pattern ( 2. Build tool minimum versions ( 3. License allowlist expanded (both workflows) |
Several packages report non-SPDX license strings that --partial-match couldn't match against the original allowlist: - ray, msgpack, asttokens: "Apache 2.0" (not "Apache-2.0") - filelock: "Unlicense" (public domain dedication) - hypothesis: "MPL-2.0" - certifi: "Mozilla Public License 2.0 (MPL 2.0)" - psycopg: "LGPL-3.0-only" (existing intentional dep, safe under Python dynamic linking) Added all required strings to both publish.yml and run-tests.yml. Verified locally: exit code 0 with no violations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
License check takes ~5s vs 3+ minutes for tests. Failing fast on a disallowed dependency before burning CI minutes is the right order. - publish.yml: license-check → test → build → publish - run-tests.yml: license-check → test → spiral-integration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates the duplicated license-check job definition across publish.yml and run-tests.yml. Both now delegate to .github/workflows/_license-check.yml via workflow_call, so the allowlist only needs updating in one place. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
setuptools+setuptools-scmtohatchling+hatch-vcs(identical auto-versioning from git tags, matchesstarfix-pythonpattern)walkerlab→nauticalab) inpyproject.tomlpublish.yml: tag-triggered pipeline (v[0-9]+.[0-9]+.[0-9]+*) with sequential job chain: test → license-check → build → publish-testpypi → publish-pypi → GitHub Release (OIDC, no secrets)license-checkanddependency-reviewjobs torun-tests.ymlCI (mirrorsstarfix-pythonci.yml)pip-licenses>=5.0.0added to dev depsLicense gate
All dependency licenses are checked against an approved permissive allowlist (MIT, Apache-2.0, BSD variants, ISC, PSF) on every push, PR, and tag. Copyleft licenses (GPL, AGPL, LGPL) are denied on PRs via
actions/dependency-review-action@v4. The--partial-matchflag handles free-form license strings from packages.Note: the license gate uses
uv sync --locked --dev(no optional extras). If a new optional extra introduces a copyleft transitive dependency, it would not be caught by this gate. This is a documented trade-off.Manual setup (already done by @eywalker)
publish.yml, environmentpypi)publish.yml, environmenttestpypi)pypiandtestpypicreated in repo settingsTest plan
dev, push av0.1.0rc1tag and verify the publish workflow triggers and completespip install orcapod==0.1.0rc1works from PyPICloses PLT-1250