Skip to content

Commit d41fd62

Browse files
authored
Merge pull request #65 from petercorke/ci/pypi-trusted-publish
ci: publish to PyPI from a GitHub release (trusted publishing)
2 parents 6103438 + a5ef6e9 commit d41fd62

3 files changed

Lines changed: 118 additions & 13 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [main]
66
pull_request:
77
branches: [main]
8+
workflow_dispatch:
89

910
jobs:
1011
unittest:
@@ -81,7 +82,7 @@ jobs:
8182
verbose: true
8283

8384
docs:
84-
if: github.event_name == 'push'
85+
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
8586
needs: unittest
8687
runs-on: ubuntu-latest
8788
steps:

‎.github/workflows/publish.yml‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Build and publish to PyPI when a GitHub release is created, or on manual trigger.
2+
# Uses OIDC trusted publishing, so no PyPI token is stored. The trusted publisher
3+
# on PyPI is: owner petercorke, repository pgraph-python, workflow publish.yml,
4+
# environment pypi. Pure Python package, so one universal wheel and an sdist.
5+
#
6+
# To release: make sure main has the version in pyproject.toml, then create a
7+
# GitHub release whose tag is v<version> on main.
8+
9+
name: Publish to PyPI
10+
11+
on:
12+
release:
13+
types: [created]
14+
workflow_dispatch:
15+
16+
permissions:
17+
contents: read
18+
19+
jobs:
20+
verify_version:
21+
name: Verify tag matches package version
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v7
25+
with:
26+
fetch-depth: 0
27+
- name: Check version
28+
# Skip on manual workflow_dispatch runs (no release tag exists)
29+
if: github.event_name == 'release'
30+
run: |
31+
TAG="${GITHUB_REF_NAME#v}"
32+
PKG=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
33+
echo "Tag version : $TAG"
34+
echo "Package version: $PKG"
35+
[ "$TAG" = "$PKG" ] || { echo "ERROR: tag $GITHUB_REF_NAME does not match pyproject.toml version $PKG"; exit 1; }
36+
- name: Check the tagged commit is on main
37+
if: github.event_name == 'release'
38+
run: |
39+
git fetch origin main
40+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main \
41+
|| { echo "ERROR: $GITHUB_SHA is not on main, so it was not reviewed and tested there"; exit 1; }
42+
43+
build:
44+
name: Build sdist and wheel
45+
needs: verify_version
46+
runs-on: ubuntu-latest
47+
steps:
48+
- uses: actions/checkout@v7
49+
50+
- uses: actions/setup-python@v7
51+
with:
52+
python-version: "3.12"
53+
54+
- name: Build sdist and wheel
55+
run: pipx run build
56+
57+
- name: Check metadata
58+
run: pipx run twine check dist/*
59+
60+
- uses: actions/upload-artifact@v7
61+
with:
62+
name: dist
63+
path: dist/
64+
65+
smoke_test:
66+
name: Smoke-test wheel
67+
needs: build
68+
runs-on: ubuntu-latest
69+
steps:
70+
- uses: actions/setup-python@v7
71+
with:
72+
python-version: "3.12"
73+
74+
- uses: actions/download-artifact@v8
75+
with:
76+
name: dist
77+
path: dist
78+
79+
- name: Install the wheel (pulls dependencies from PyPI)
80+
run: pip install dist/*.whl
81+
82+
- name: Smoke test
83+
run: |
84+
python - <<'PY'
85+
from importlib.metadata import version
86+
import pgraph
87+
88+
g = pgraph.UGraph()
89+
g.add_vertex(coord=[0, 0], name="a")
90+
g.add_vertex(coord=[3, 4], name="b")
91+
g.add_edge("a", "b")
92+
path, length, _ = g.path_Astar("a", "b")
93+
assert length == 5.0, length
94+
print("OK", version("pgraph-python"), length)
95+
PY
96+
97+
upload_pypi:
98+
name: Publish to PyPI
99+
needs: [build, smoke_test]
100+
runs-on: ubuntu-latest
101+
environment:
102+
name: pypi
103+
url: https://pypi.org/project/pgraph-python/
104+
permissions:
105+
id-token: write # required for OIDC trusted publishing
106+
steps:
107+
- uses: actions/download-artifact@v8
108+
with:
109+
name: dist
110+
path: dist
111+
112+
# pinned to a commit, as this action can publish to PyPI; dependabot updates it
113+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
114+
with:
115+
skip_existing: true

‎Makefile‎

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ help:
99
@echo " make docs - build Sphinx documentation"
1010
@echo " make docupdate - upload Sphinx documentation to GitHub pages"
1111
@echo " make dist - build dist files"
12-
@echo " make upload - upload to PyPI"
1312
@echo " make clean - remove dist and docs build files"
13+
@echo " (PyPI publish is via GitHub Releases, see .github/workflows/publish.yml)"
1414
@echo " make help - this message$(BLACK)"
1515

1616
test:
@@ -34,17 +34,6 @@ dist: .FORCE
3434
#$(MAKE) test
3535
python -m build
3636

37-
pypi: .FORCE
38-
validate-pyproject pyproject.toml
39-
@if ! git diff --quiet || ! git diff --cached --quiet; then \
40-
echo "Error: uncommitted changes present, aborting upload"; exit 1; \
41-
fi
42-
$(eval VERSION := $(shell grep '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/'))
43-
python -m build
44-
twine upload dist/*
45-
git tag v$(VERSION)
46-
git push origin v$(VERSION)
47-
4837
clean: .FORCE
4938
(cd docs; make clean)
5039
-rm -r *.egg-info

0 commit comments

Comments
 (0)