11name : Build, validate & Release
22
3+ # Usage:
4+ # - For PRs: this workflow runs automatically to validate the package builds and installs correctly on multiple Python versions. No artifacts are published for PRs.
5+ # - For releases: when you push a tag like v1.2.3, this workflow runs the full matrix validation, then builds the release artifacts, and finally publishes to PyPI if all checks pass.
6+
37on :
8+ # Release pipeline: run only when pushing a version-like tag (e.g. v1.2.3)
49 push :
5- tags : [ 'v*.*.*' ]
10+ tags :
11+ - " v*.*.*"
12+
13+ # Validation pipeline: run on PRs targeting main/master (no publishing)
614 pull_request :
7- branches : [ main, master ]
8- types : [ labeled, opened, edited, synchronize, reopened ]
15+ branches : [main, master]
16+ types : [opened, edited, synchronize, reopened]
17+
18+ # This workflow only needs to read repo contents
19+ permissions :
20+ contents : read
921
1022jobs :
11- test :
12- name : Test / smoke (matrix)
23+ test_matrix :
24+ # PR + tag validation: ensure the project builds and installs on multiple Pythons
25+ name : Test install & smoke (Py ${{ matrix.python-version }})
1326 runs-on : ubuntu-latest
1427 strategy :
28+ # Run all versions even if one fails (helps spot version-specific issues)
1529 fail-fast : false
1630 matrix :
17- python-version : [ "3.10", "3.11", "3.12" ]
31+ python-version : ["3.10", "3.11", "3.12"]
32+
1833 steps :
19- - uses : actions/checkout@v6
20- - uses : actions/setup-python@v6
34+ # Fetch repository sources so we can build/test
35+ - name : Checkout sources
36+ uses : actions/checkout@v6
37+
38+ - name : Set up Python
39+ uses : actions/setup-python@v6
2140 with :
2241 python-version : ${{ matrix.python-version }}
2342
@@ -31,82 +50,99 @@ jobs:
3150 libxkbcommon-x11-0 \
3251 libxcb-cursor0
3352
34- - name : Install tools
35- run : |
36- python -m pip install --upgrade pip
37- python -m pip install build twine wheel "packaging>=24.2"
53+ # Install packaging toolchain:
54+ # - build: creates wheel + sdist
55+ # - twine: validates metadata and can upload (upload only happens in publish job)
56+ - name : Install build tools
57+ run : python -m pip install -U pip build twine
3858
39- - name : Build distributions (sdist + wheel)
59+ # Build distributions just to verify packaging config works on this Python
60+ - name : Build (for validation only)
4061 run : python -m build
4162
42- - name : Inspect dist
43- run : |
44- ls -lah dist/
45- echo "sdist contents (first ~200 entries):"
46- tar -tf dist/*.tar.gz | sed -n '1,200p'
47-
48- - name : Twine metadata & README check
63+ # Validate dist metadata (README rendering, required fields, etc.)
64+ - name : Twine check
4965 run : python -m twine check dist/*
5066
67+ # Smoke test: install the built wheel and verify the package imports
5168 - name : Install from wheel & smoke test
5269 run : |
53- python -m pip install dist/*.whl
54- python - <<'PY'
55- import importlib
56- pkg_name = "dlclivegui"
57- m = importlib.import_module(pkg_name)
58- print("Imported:", m.__name__, "version:", getattr(m, "__version__", "n/a"))
59- PY
60-
61- if ! command -v dlclivegui >/dev/null 2>&1; then
62- echo "CLI entry point 'dlclivegui' not found in PATH; skipping CLI smoke test."
63- else
64- if command -v dlclivegui >/dev/null 2>&1; then
65- echo "Running 'dlclivegui --help' smoke test..."
66- if ! dlclivegui --help >/dev/null 2>&1; then
67- echo "::error::'dlclivegui --help' failed; this indicates a problem with the installed CLI package."
68- exit 1
69- fi
70-
71- build :
72- name : Build release artifacts (single)
70+ WHEEL=$(ls -1 dist/*.whl | head -n 1)
71+ echo "Using wheel: $WHEEL"
72+ python -m pip install \
73+ --extra-index-url https://download.pytorch.org/whl/cpu \
74+ "deeplabcut-live-gui[pytorch] @ file://$(pwd)/${WHEEL}"
75+ python -c "import dlclivegui; print('Imported dlclivegui OK')"
76+ QT_QPA_PLATFORM=offscreen dlclivegui --help
77+
78+ build_release :
79+ # Tag-only build: produce the "official" release artifacts once matrix passed
80+ name : Build release artifacts
7381 runs-on : ubuntu-latest
74- needs : test
75- if : ${{ startsWith(github.ref, 'refs/tags/v') }}
82+ needs : test_matrix
83+ # Safety gate: only run for version tags, never for PRs/branches
84+ if : startsWith(github.ref, 'refs/tags/v')
85+
7686 steps :
77- - uses : actions/checkout@v6
78- - uses : actions/setup-python@v6
87+ # Fetch sources for the tagged revision
88+ - name : Checkout sources
89+ uses : actions/checkout@v6
90+
91+ # Use a single, modern Python for the canonical release build
92+ - name : Set up Python (release build)
93+ uses : actions/setup-python@v6
7994 with :
8095 python-version : " 3.12"
8196
82- - name : Build distributions (sdist + wheel)
83- run : |
84- python -m pip install --upgrade pip
85- python -m pip install build twine wheel "packaging>=24.2"
86- python -m build
87- python -m twine check dist/*
97+ # Install build + validation tooling
98+ - name : Install build tools
99+ run : python -m pip install -U pip build twine
100+
101+ # Produce both sdist and wheel in dist/
102+ - name : Build distributions
103+ run : python -m build
104+
105+ # Re-check metadata on the final artifacts we intend to publish
106+ - name : Twine check
107+ run : python -m twine check dist/*
88108
109+ # Store dist/ outputs so the publish job uploads exactly what we built here
89110 - name : Upload dist artifacts
90111 uses : actions/upload-artifact@v4
91112 with :
92113 name : dist
93114 path : dist/*
94- if-no-files-found : error
95115
96116 publish :
97- name : Publish to PyPI (OIDC)
117+ # Tag-only publish: download built artifacts and upload them to PyPI
118+ name : Publish to PyPI (API token)
98119 runs-on : ubuntu-latest
99- needs : build
100- if : ${{ startsWith(github.ref, 'refs/tags/v') }}
101- environment : pypi
102- permissions :
103- id-token : write
120+ needs : build_release
121+ # Safety gate: only run for version tags
122+ if : startsWith(github.ref, 'refs/tags/v')
123+
104124 steps :
125+ # Retrieve the exact distributions produced in build_release
105126 - name : Download dist artifacts
106127 uses : actions/download-artifact@v4
107128 with :
108129 name : dist
109130 path : dist
110131
132+ # Set up Python (only needed to run Twine)
133+ - name : Set up Python (publish)
134+ uses : actions/setup-python@v6
135+ with :
136+ python-version : " 3.x"
137+
138+ # Install twine for uploading
139+ - name : Install Twine
140+ run : python -m pip install -U twine
141+
142+ # Upload to PyPI using an API token stored in repo secrets.
143+ # --skip-existing avoids failing if you re-run a workflow for the same version.
111144 - name : Publish to PyPI
112- uses : pypa/gh-action-pypi-publish@release/v1
145+ env :
146+ TWINE_USERNAME : __token__
147+ TWINE_PASSWORD : ${{ secrets.TWINE_API_KEY }}
148+ run : python -m twine upload --non-interactive --verbose --skip-existing dist/*
0 commit comments