Skip to content

Commit dce2472

Browse files
Secboneclaude
andcommitted
fix: rewrite publish workflow using official actions/download-artifact@v4
Replace third-party dawidd6/action-download-artifact and fountainhead/action-wait-for-check with: - actions/github-script@v7 for polling workflow completion - actions/download-artifact@v4 with run-id + merge-multiple This fixes the subdirectory extraction issue that caused previous publish attempts to find 0 wheels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0555c3c commit dce2472

1 file changed

Lines changed: 107 additions & 152 deletions

File tree

.github/workflows/publish.yml

Lines changed: 107 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -3,162 +3,135 @@ name: Publish to PyPI
33
on:
44
push:
55
tags: ['*']
6-
workflow_dispatch: # Allow manual triggering
6+
workflow_dispatch:
77

88
concurrency:
99
group: ${{ github.workflow }}-${{ github.ref }}
10-
cancel-in-progress: false # Don't cancel publish jobs
10+
cancel-in-progress: false
1111

1212
jobs:
13-
# Wait for all platform test workflows to complete successfully
14-
wait-for-tests:
15-
name: Wait for test workflows
13+
collect-wheels:
14+
name: Collect tested wheels
1615
runs-on: ubuntu-latest
16+
permissions:
17+
actions: read
1718
steps:
18-
- name: Wait for Linux tests
19-
uses: fountainhead/action-wait-for-check@v1.2.0
20-
id: wait-linux
21-
with:
22-
token: ${{ secrets.GITHUB_TOKEN }}
23-
checkName: "Test py 3.10"
24-
ref: ${{ github.sha }}
25-
timeoutSeconds: 1800
26-
27-
- name: Fail if Linux tests failed
28-
if: steps.wait-linux.outputs.conclusion != 'success'
29-
run: |
30-
echo "❌ Linux tests failed or timed out (conclusion: ${{ steps.wait-linux.outputs.conclusion }})"
31-
exit 1
32-
33-
- name: Wait for macOS tests
34-
uses: fountainhead/action-wait-for-check@v1.2.0
35-
id: wait-macos
36-
with:
37-
token: ${{ secrets.GITHUB_TOKEN }}
38-
checkName: "Test py 3.10 macos-latest"
39-
ref: ${{ github.sha }}
40-
timeoutSeconds: 1800
41-
42-
- name: Fail if macOS tests failed
43-
if: steps.wait-macos.outputs.conclusion != 'success'
44-
run: |
45-
echo "❌ macOS tests failed or timed out (conclusion: ${{ steps.wait-macos.outputs.conclusion }})"
46-
exit 1
47-
48-
- name: Wait for Windows tests
49-
uses: fountainhead/action-wait-for-check@v1.2.0
50-
id: wait-windows
19+
- name: Wait for platform workflows and get run IDs
20+
uses: actions/github-script@v7
21+
id: wait
5122
with:
52-
token: ${{ secrets.GITHUB_TOKEN }}
53-
checkName: "Test py 3.10"
54-
ref: ${{ github.sha }}
55-
timeoutSeconds: 1800
23+
script: |
24+
const workflows = {
25+
linux: 'linux.yml',
26+
macos: 'macos.yml',
27+
windows: 'windows.yml'
28+
};
29+
const sha = context.sha;
30+
const timeoutMs = 30 * 60 * 1000;
31+
const pollMs = 30 * 1000;
32+
const start = Date.now();
33+
const results = {};
34+
35+
for (const [platform, filename] of Object.entries(workflows)) {
36+
core.info(`Waiting for ${platform} workflow (${filename})...`);
37+
38+
while (Date.now() - start < timeoutMs) {
39+
const { data } = await github.rest.actions.listWorkflowRuns({
40+
owner: context.repo.owner,
41+
repo: context.repo.repo,
42+
workflow_id: filename,
43+
head_sha: sha,
44+
per_page: 1
45+
});
46+
47+
if (data.workflow_runs.length > 0) {
48+
const run = data.workflow_runs[0];
49+
if (run.status === 'completed') {
50+
if (run.conclusion === 'success') {
51+
core.info(` ✓ ${platform} succeeded (run ${run.id})`);
52+
results[platform] = run.id;
53+
break;
54+
} else {
55+
core.setFailed(` ✗ ${platform} ${run.conclusion} (run ${run.id})`);
56+
return;
57+
}
58+
}
59+
core.info(` ${platform}: ${run.status}, waiting...`);
60+
} else {
61+
core.info(` ${platform}: no runs found yet, waiting...`);
62+
}
63+
64+
await new Promise(r => setTimeout(r, pollMs));
65+
}
66+
67+
if (!results[platform]) {
68+
core.setFailed(`Timeout waiting for ${platform} workflow`);
69+
return;
70+
}
71+
}
72+
73+
core.setOutput('linux-run-id', String(results.linux));
74+
core.setOutput('macos-run-id', String(results.macos));
75+
core.setOutput('windows-run-id', String(results.windows));
5676
57-
- name: Fail if Windows tests failed
58-
if: steps.wait-windows.outputs.conclusion != 'success'
59-
run: |
60-
echo "❌ Windows tests failed or timed out (conclusion: ${{ steps.wait-windows.outputs.conclusion }})"
61-
exit 1
62-
63-
download-wheels:
64-
name: Download tested wheels
65-
needs: [wait-for-tests]
66-
runs-on: ubuntu-latest
67-
steps:
6877
- name: Download Linux wheels
69-
uses: dawidd6/action-download-artifact@v6
78+
uses: actions/download-artifact@v4
7079
with:
71-
workflow: linux.yml
72-
commit: ${{ github.sha }}
80+
run-id: ${{ steps.wait.outputs.linux-run-id }}
81+
github-token: ${{ secrets.GITHUB_TOKEN }}
82+
pattern: wheel-linux-*
7383
path: dist/
74-
name_is_regexp: true
75-
name: wheel-linux-.*
76-
search_artifacts: true
77-
if_no_artifact_found: fail
78-
workflow_conclusion: ""
84+
merge-multiple: true
7985

8086
- name: Download macOS wheels
81-
uses: dawidd6/action-download-artifact@v6
87+
uses: actions/download-artifact@v4
8288
with:
83-
workflow: macos.yml
84-
commit: ${{ github.sha }}
89+
run-id: ${{ steps.wait.outputs.macos-run-id }}
90+
github-token: ${{ secrets.GITHUB_TOKEN }}
91+
pattern: wheel-macos-*
8592
path: dist/
86-
name_is_regexp: true
87-
name: wheel-macos-.*
88-
search_artifacts: true
89-
if_no_artifact_found: fail
90-
workflow_conclusion: ""
93+
merge-multiple: true
9194

9295
- name: Download Windows wheels
93-
uses: dawidd6/action-download-artifact@v6
96+
uses: actions/download-artifact@v4
9497
with:
95-
workflow: windows.yml
96-
commit: ${{ github.sha }}
98+
run-id: ${{ steps.wait.outputs.windows-run-id }}
99+
github-token: ${{ secrets.GITHUB_TOKEN }}
100+
pattern: wheel-windows-*
97101
path: dist/
98-
name_is_regexp: true
99-
name: wheel-windows-.*
100-
search_artifacts: true
101-
if_no_artifact_found: fail
102-
workflow_conclusion: ""
102+
merge-multiple: true
103103

104-
- name: Flatten wheel directories
105-
run: |
106-
# action-download-artifact extracts into subdirectories; move all .whl to dist/
107-
find dist/ -name '*.whl' -exec mv {} dist/ \;
108-
# remove empty subdirectories
109-
find dist/ -mindepth 1 -type d -delete
110-
# deduplicate identical wheels (ABI3 builds are the same across Python versions)
111-
cd dist && md5sum *.whl | sort | awk 'seen[$1]++ {print $2}' | xargs -r rm -f && cd ..
112-
113-
- name: List downloaded wheels
104+
- name: Deduplicate ABI3 wheels
114105
run: |
115106
echo "Downloaded wheels:"
116107
ls -lh dist/
108+
# ABI3 builds are identical across Python versions; keep one per checksum
109+
cd dist && md5sum *.whl | sort | awk 'seen[$1]++ {print $2}' | xargs -r rm -f && cd ..
117110
echo ""
118-
echo "Wheel count:"
119-
ls dist/*.whl | wc -l
111+
echo "After dedup:"
112+
ls -lh dist/
120113
121-
- name: Verify wheel count
114+
- name: Verify wheels
122115
run: |
123-
wheel_count=$(ls dist/*.whl 2>/dev/null | wc -l)
124116
linux_count=$(ls dist/toad-*manylinux*.whl 2>/dev/null | wc -l)
125117
macos_count=$(ls dist/toad-*macosx*.whl 2>/dev/null | wc -l)
126118
windows_count=$(ls dist/toad-*win*.whl 2>/dev/null | wc -l)
119+
total=$((linux_count + macos_count + windows_count))
127120
128-
echo "Total wheels: $wheel_count"
129-
echo " Linux: $linux_count (expected >=1)"
130-
echo " macOS: $macos_count (expected >=1)"
131-
echo " Windows: $windows_count (expected >=1)"
132-
133-
# With ABI3, each platform produces one unique wheel after dedup
134-
if [ "$linux_count" -lt 1 ]; then
135-
echo "Error: No Linux wheels found"
136-
exit 1
137-
fi
138-
if [ "$macos_count" -lt 1 ]; then
139-
echo "Error: No macOS wheels found"
140-
exit 1
141-
fi
142-
if [ "$windows_count" -lt 1 ]; then
143-
echo "Error: No Windows wheels found"
144-
exit 1
145-
fi
146-
echo "All platforms have wheels"
147-
148-
- name: Verify wheel integrity
149-
run: |
150-
# Test installing one wheel from each platform
151-
echo "Testing Linux wheel..."
152-
pip install dist/toad-*manylinux*.whl --force-reinstall
153-
python -c "import toad; from toad.merge import _chi_merge_rust; assert _chi_merge_rust is not None; print('✓ Linux wheel OK')"
121+
echo "Total wheels: $total"
122+
echo " Linux: $linux_count"
123+
echo " macOS: $macos_count"
124+
echo " Windows: $windows_count"
154125
155-
echo "Testing macOS wheel..."
156-
pip install dist/toad-*macosx*.whl --force-reinstall || echo "⚠️ macOS wheel test skipped (wrong platform)"
126+
[ "$linux_count" -ge 1 ] || { echo "Error: No Linux wheels"; exit 1; }
127+
[ "$macos_count" -ge 1 ] || { echo "Error: No macOS wheels"; exit 1; }
128+
[ "$windows_count" -ge 1 ] || { echo "Error: No Windows wheels"; exit 1; }
157129
158-
echo "Testing Windows wheel..."
159-
pip install dist/toad-*win*.whl --force-reinstall || echo "⚠️ Windows wheel test skipped (wrong platform)"
130+
echo "✓ All platforms have wheels"
160131
161-
echo "✓ Wheel verification complete"
132+
# Quick install test for Linux wheel
133+
pip install dist/toad-*manylinux*.whl --force-reinstall
134+
python -c "import toad; print('✓ Linux wheel installs OK')"
162135
163136
- name: Upload combined wheels
164137
uses: actions/upload-artifact@v4
@@ -169,27 +142,14 @@ jobs:
169142

170143
build-sdist:
171144
name: Build source distribution
172-
needs: [wait-for-tests]
173145
runs-on: ubuntu-latest
174146
steps:
175147
- uses: actions/checkout@v4
176-
177-
- name: Setup Python
178-
uses: actions/setup-python@v5
148+
- uses: actions/setup-python@v5
179149
with:
180150
python-version: '3.10'
181-
182-
- name: Install build tools
183-
run: pip install build
184-
185-
- name: Build sdist
186-
run: python -m build --sdist
187-
188-
- name: Verify sdist
189-
run: |
190-
tar -tzf dist/*.tar.gz | head -20
191-
echo "✓ Source distribution built successfully"
192-
151+
- run: pip install build
152+
- run: python -m build --sdist
193153
- name: Upload sdist
194154
uses: actions/upload-artifact@v4
195155
with:
@@ -199,38 +159,33 @@ jobs:
199159

200160
publish:
201161
name: Publish to PyPI
202-
needs: [download-wheels, build-sdist]
162+
needs: [collect-wheels, build-sdist]
203163
runs-on: ubuntu-latest
204164
environment:
205165
name: pypi
206166
url: https://pypi.org/p/toad
207167
permissions:
208-
id-token: write # IMPORTANT: mandatory for trusted publishing
168+
id-token: write
209169

210170
steps:
211-
- name: Download all wheels
212-
uses: actions/download-artifact@v4
171+
- uses: actions/download-artifact@v4
213172
with:
214173
name: all-tested-wheels
215174
path: dist/
216175

217-
- name: Download sdist
218-
uses: actions/download-artifact@v4
176+
- uses: actions/download-artifact@v4
219177
with:
220178
name: sdist
221179
path: dist/
222180

223-
- name: List all distributions
181+
- name: List distributions
224182
run: |
225-
echo "📦 Packages to publish:"
183+
echo "Packages to publish:"
226184
ls -lh dist/
227-
echo ""
228-
echo "Summary:"
229-
echo " Wheels: $(ls dist/*.whl 2>/dev/null | wc -l)"
230-
echo " Sdist: $(ls dist/*.tar.gz 2>/dev/null | wc -l)"
185+
echo "Wheels: $(ls dist/*.whl 2>/dev/null | wc -l)"
186+
echo "Sdist: $(ls dist/*.tar.gz 2>/dev/null | wc -l)"
231187
232-
- name: Publish to PyPI
233-
uses: pypa/gh-action-pypi-publish@release/v1
188+
- uses: pypa/gh-action-pypi-publish@release/v1
234189
with:
235190
skip-existing: true
236191
verbose: true

0 commit comments

Comments
 (0)