@@ -3,162 +3,135 @@ name: Publish to PyPI
33on :
44 push :
55 tags : ['*']
6- workflow_dispatch : # Allow manual triggering
6+ workflow_dispatch :
77
88concurrency :
99 group : ${{ github.workflow }}-${{ github.ref }}
10- cancel-in-progress : false # Don't cancel publish jobs
10+ cancel-in-progress : false
1111
1212jobs :
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