Skip to content

Commit 7cd3f20

Browse files
authored
Add publish-collect workflow for PyPI deployment
This workflow triggers after the completion of three build workflows, checks their statuses, and publishes the package to PyPI if all builds are successful.
1 parent ea450ad commit 7cd3f20

1 file changed

Lines changed: 216 additions & 0 deletions

File tree

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# publish-collect.yml — wakes when all three builds finish, collects and publishes
2+
name: Collect & Publish uv-ffi to PyPI
3+
on:
4+
workflow_run:
5+
workflows:
6+
- "🔧 Build uv-ffi Wheels"
7+
- "🔧 Build uv-ffi Wheels (Extended)"
8+
- "👽 Build uv-ffi Wheels (Ultra-Exotic & Source)"
9+
types: [completed]
10+
branches: [omnipkg-ffi-v0.10.8]
11+
12+
permissions:
13+
contents: write
14+
id-token: write
15+
actions: read
16+
17+
jobs:
18+
check-all-done:
19+
name: "🔍 Check all builds complete"
20+
runs-on: ubuntu-latest
21+
outputs:
22+
all_done: ${{ steps.check.outputs.all_done }}
23+
std_run_id: ${{ steps.check.outputs.std_run_id }}
24+
ext_run_id: ${{ steps.check.outputs.ext_run_id }}
25+
exo_run_id: ${{ steps.check.outputs.exo_run_id }}
26+
steps:
27+
- name: Check completion of all three builds
28+
id: check
29+
env:
30+
GH_TOKEN: ${{ github.token }}
31+
run: |
32+
BRANCH="omnipkg-ffi-v0.10.8"
33+
TRIGGERING_RUN="${{ github.event.workflow_run.id }}"
34+
TRIGGERING_WF="${{ github.event.workflow_run.name }}"
35+
echo "Triggered by: $TRIGGERING_WF ($TRIGGERING_RUN)"
36+
37+
# Get the input tag from the triggering run
38+
TAG=$(gh api "repos/${{ github.repository }}/actions/runs/$TRIGGERING_RUN" \
39+
--jq '.inputs.tag // ""' 2>/dev/null)
40+
echo "Tag from triggering run: $TAG"
41+
42+
# If no tag in inputs (e.g. triggered without tag), abort
43+
if [ -z "$TAG" ]; then
44+
echo "No tag found on triggering run — skipping"
45+
echo "all_done=false" >> $GITHUB_OUTPUT
46+
exit 0
47+
fi
48+
49+
find_completed_run() {
50+
local WF=$1
51+
gh api "repos/${{ github.repository }}/actions/workflows/$WF/runs?branch=$BRANCH&per_page=20" \
52+
--jq ".workflow_runs[] | select(.status == \"completed\") | .id" 2>/dev/null | \
53+
while read RUN_ID; do
54+
INPUT_TAG=$(gh api "repos/${{ github.repository }}/actions/runs/$RUN_ID" --jq '.inputs.tag // ""' 2>/dev/null)
55+
[ "$INPUT_TAG" = "$TAG" ] && echo "$RUN_ID" && return
56+
done
57+
}
58+
59+
STD=$(find_completed_run "build-wheels.yml")
60+
EXT=$(find_completed_run "build-wheels-extended.yml")
61+
EXO=$(find_completed_run "build-wheels-exotic.yml")
62+
63+
echo "std=$STD ext=$EXT exo=$EXO"
64+
65+
if [ -n "$STD" ] && [ -n "$EXT" ] && [ -n "$EXO" ]; then
66+
echo "✅ All three builds complete"
67+
echo "all_done=true" >> $GITHUB_OUTPUT
68+
echo "std_run_id=$STD" >> $GITHUB_OUTPUT
69+
echo "ext_run_id=$EXT" >> $GITHUB_OUTPUT
70+
echo "exo_run_id=$EXO" >> $GITHUB_OUTPUT
71+
else
72+
echo "⏳ Not all builds done yet — waiting for next workflow_run event"
73+
echo "all_done=false" >> $GITHUB_OUTPUT
74+
fi
75+
76+
publish:
77+
name: "Upload release to PyPI"
78+
needs: check-all-done
79+
if: needs.check-all-done.outputs.all_done == 'true'
80+
runs-on: ubuntu-latest
81+
environment:
82+
name: pypi
83+
url: https://pypi.org/p/uv-ffi
84+
permissions:
85+
id-token: write
86+
actions: write
87+
contents: write
88+
steps:
89+
- name: Download standard wheels
90+
uses: dawidd6/action-download-artifact@v3
91+
with:
92+
workflow: build-wheels.yml
93+
run_id: ${{ needs.check-all-done.outputs.std_run_id }}
94+
name: wheels-*
95+
name_is_regexp: true
96+
path: dist/
97+
if_no_artifact_found: ignore
98+
continue-on-error: true
99+
100+
- name: Download extended wheels
101+
uses: dawidd6/action-download-artifact@v3
102+
with:
103+
workflow: build-wheels-extended.yml
104+
run_id: ${{ needs.check-all-done.outputs.ext_run_id }}
105+
name: ext-wheels-*
106+
name_is_regexp: true
107+
path: dist/
108+
if_no_artifact_found: ignore
109+
continue-on-error: true
110+
111+
- name: Download exotic wheels
112+
uses: dawidd6/action-download-artifact@v3
113+
with:
114+
workflow: build-wheels-exotic.yml
115+
run_id: ${{ needs.check-all-done.outputs.exo_run_id }}
116+
name: exotic-wheels-*
117+
name_is_regexp: true
118+
path: dist/
119+
if_no_artifact_found: ignore
120+
continue-on-error: true
121+
122+
- name: Download sdist
123+
uses: dawidd6/action-download-artifact@v3
124+
with:
125+
workflow: publish.yml
126+
run_id: ${{ github.run_id }}
127+
name: sdist
128+
path: dist/
129+
if_no_artifact_found: ignore
130+
continue-on-error: true
131+
132+
# ── Everything below is identical to your existing publish logic ──
133+
- name: Flatten all wheels into dist/
134+
run: |
135+
find dist/ -name "*.whl" -exec mv {} dist/ \; 2>/dev/null || true
136+
find dist/ -mindepth 1 -type d -exec rm -rf {} + 2>/dev/null || true
137+
find dist/ -name "*.attestation" -delete 2>/dev/null || true
138+
echo "Total: $(find dist/ -name '*.whl' | wc -l) wheels"
139+
ls -lh dist/ || true
140+
141+
- name: Split wheels — PyPI vs GH Releases
142+
run: |
143+
mkdir -p dist-pypi dist-ghrelease
144+
PYPI_KEEP=(
145+
"*manylinux_2_28_x86_64*" "*manylinux_2_28_aarch64*"
146+
"*manylinux_2_17_x86_64*" "*manylinux_2_17_aarch64*"
147+
"*musllinux_1_2_x86_64*" "*musllinux_1_2_aarch64*"
148+
"*macosx_11_0_arm64*" "*macosx_10_12_x86_64*"
149+
"*win_amd64*" "*win_arm64*"
150+
"*.tar.gz"
151+
)
152+
for f in dist/*.whl dist/*.tar.gz; do
153+
[ -f "$f" ] || continue
154+
name=$(basename "$f")
155+
case "$name" in
156+
*-pp[0-9]*|*-graalpy*|*-cp313t-*|*-cp3[0-9][0-9]-cp3[0-9][0-9]-*)
157+
cp "$f" dist-ghrelease/; continue ;;
158+
esac
159+
matched=false
160+
for pat in "${PYPI_KEEP[@]}"; do
161+
case "$name" in $pat) matched=true; break ;; esac
162+
done
163+
$matched && cp "$f" dist-pypi/ || cp "$f" dist-ghrelease/
164+
done
165+
echo "PyPI: $(ls dist-pypi/ | wc -l) GH: $(ls dist-ghrelease/ | wc -l)"
166+
167+
- name: Upload exotic wheels to GitHub Release
168+
if: hashFiles('dist-ghrelease/*.whl') != ''
169+
uses: softprops/action-gh-release@v2
170+
with:
171+
tag_name: ${{ github.event.workflow_run.head_branch }}
172+
files: dist-ghrelease/*.whl
173+
fail_on_unmatched_files: false
174+
env:
175+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176+
177+
- name: Remove wheels already on PyPI
178+
run: |
179+
find dist-pypi/ -name "*.attestation" -delete 2>/dev/null || true
180+
VERSION=$(find dist-pypi/ -name "*.whl" | head -1 | grep -oP '(?<=uv_ffi-)[0-9][^-]+' || true)
181+
[ -z "$VERSION" ] && echo "Nothing to upload" && exit 0
182+
EXISTING=$(curl -s "https://pypi.org/pypi/uv-ffi/$VERSION/json" \
183+
| python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin).get('urls',[])]" 2>/dev/null || true)
184+
while IFS= read -r fname; do
185+
[ -f "dist-pypi/$fname" ] && rm -f "dist-pypi/$fname" && echo " skip: $fname"
186+
done <<< "$EXISTING"
187+
echo "Uploading $(ls dist-pypi/*.whl 2>/dev/null | wc -l) wheels"
188+
189+
- name: Publish via OIDC
190+
id: oidc
191+
continue-on-error: true
192+
uses: pypa/gh-action-pypi-publish@release/v1
193+
with:
194+
packages-dir: dist-pypi/
195+
skip-existing: true
196+
197+
- name: Clean attestations after OIDC failure
198+
if: steps.oidc.outcome == 'failure'
199+
run: find dist-pypi/ -name "*.attestation" -delete 2>/dev/null || true
200+
201+
- name: Publish via API token (fallback)
202+
if: steps.oidc.outcome == 'failure'
203+
uses: pypa/gh-action-pypi-publish@release/v1
204+
with:
205+
packages-dir: dist-pypi/
206+
password: ${{ secrets.PYPI_API_TOKEN }}
207+
208+
update-wheel-index:
209+
name: "📄 Update GH Pages wheel index"
210+
needs: publish
211+
runs-on: ubuntu-latest
212+
if: always()
213+
permissions:
214+
contents: write
215+
steps:
216+
# ... identical to your existing update-wheel-index job

0 commit comments

Comments
 (0)