@@ -218,8 +218,10 @@ jobs:
218218 "$PY" -m build --wheel --outdir "$PWD/dist" "$PWD/pypi"
219219 ls -l "$PWD/dist"
220220
221+ # musl wheels can't be pip-installed on this glibc build host; they're
222+ # smoke-tested in a real Alpine (musl) container by the smoke-musl job.
221223 - name : Smoke test wheel
222- if : matrix.rosetta != true
224+ if : matrix.rosetta != true && matrix.musl != true
223225 shell : bash
224226 env :
225227 PYTHON : ${{ runner.os == 'Linux' && '/opt/python/cp312-cp312/bin/python' || 'python3' }}
@@ -246,10 +248,14 @@ jobs:
246248 shasum -a 256 dist/*.whl
247249 fi
248250
251+ # Non-musl wheels are uploaded under the publishable `wheel-*` name and go
252+ # straight to release/publish. musl wheels are staged under a separate
253+ # name and only promoted to `wheel-*` by smoke-musl once they pass the
254+ # Alpine smoke test — so an unverified musl wheel is never published.
249255 - name : Upload wheel artifact
250256 uses : actions/upload-artifact@v4
251257 with :
252- name : wheel- ${{ matrix.name }}
258+ name : ${{ matrix.musl && format('musl-staging-{0}', matrix. name) || format('wheel-{0}', matrix.name) }}
253259 path : dist/*.whl
254260 if-no-files-found : error
255261
@@ -283,23 +289,94 @@ jobs:
283289 path : dist/*.tar.gz
284290 if-no-files-found : error
285291
292+ # --------------------------------------------------------------------------
293+ # Validate the musl wheels in a real Alpine (musl) container — they can't be
294+ # pip-installed on the glibc build host. Runs per-arch on native runners,
295+ # where docker is available (unlike inside the manylinux build container).
296+ # Only wheels that pass here are promoted to the publishable `wheel-*` name,
297+ # so an unverified musl wheel never reaches PyPI. Experimental: a musl
298+ # failure must never block the glibc/macOS release.
299+ # --------------------------------------------------------------------------
300+ smoke-musl :
301+ name : smoke-musl (${{ matrix.arch }})
302+ needs : build-wheels
303+ continue-on-error : true
304+ strategy :
305+ fail-fast : false
306+ matrix :
307+ include :
308+ - arch : x86_64
309+ runs-on : ubuntu-22.04
310+ name : musllinux-x86_64
311+ - arch : aarch64
312+ runs-on : ubuntu-22.04-arm
313+ name : musllinux-aarch64
314+ runs-on : ${{ matrix.runs-on }}
315+ steps :
316+ - name : Download staged musl wheel
317+ uses : actions/download-artifact@v4
318+ with :
319+ name : musl-staging-${{ matrix.name }}
320+ path : dist
321+
322+ - name : Smoke test in Alpine (musl)
323+ shell : bash
324+ run : |
325+ set -euo pipefail
326+ docker run --rm -v "$PWD/dist:/dist:ro" python:3.12-alpine sh -euc '
327+ wheel=$(ls /dist/*.whl | head -n1)
328+ echo "smoke-musl: testing $wheel"
329+ python -m venv /tmp/venv
330+ /tmp/venv/bin/pip install --upgrade pip >/dev/null
331+ /tmp/venv/bin/pip install "$wheel"
332+ echo "smoke-musl: codajv --version"
333+ /tmp/venv/bin/codajv --version
334+ echo "smoke-musl: codajv -s (level-1 source analysis, exercises bundled jmods)"
335+ out=$(/tmp/venv/bin/codajv -s "public class Smoke { public int answer() { return 42; } }")
336+ printf "%s" "$out" | head -c 2000; echo
337+ printf "%s" "$out" | grep -q Smoke || { echo "smoke-musl: FAILED — expected class Smoke in output"; exit 1; }
338+ echo "smoke-musl: OK"
339+ '
340+
341+ - name : Promote verified wheel for publishing
342+ uses : actions/upload-artifact@v4
343+ with :
344+ name : wheel-${{ matrix.name }}
345+ path : dist/*.whl
346+ if-no-files-found : error
347+
286348 # --------------------------------------------------------------------------
287349 # GitHub Release (tag pushes only): attach every wheel + sdist + checksums.
288350 # --------------------------------------------------------------------------
289351 github-release :
290352 name : github-release
291- needs : [build-wheels, build-sdist]
292- if : github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
353+ needs : [build-wheels, build-sdist, smoke-musl]
354+ # Wait for smoke-musl (so verified musl wheels are attached) but never block
355+ # the release on it — only the essential build jobs must have succeeded.
356+ if : >-
357+ always() &&
358+ needs.build-wheels.result == 'success' &&
359+ needs.build-sdist.result == 'success' &&
360+ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
293361 runs-on : ubuntu-22.04
294362 permissions :
295363 contents : write
296364 steps :
297- - name : Download all artifacts
365+ # Pull only publishable artifacts: `wheel-*` (incl. musl wheels promoted
366+ # by smoke-musl) and the sdist — never the `musl-staging-*` artifacts.
367+ - name : Download wheels
298368 uses : actions/download-artifact@v4
299369 with :
370+ pattern : wheel-*
300371 path : dist
301372 merge-multiple : true
302373
374+ - name : Download sdist
375+ uses : actions/download-artifact@v4
376+ with :
377+ name : sdist
378+ path : dist
379+
303380 - name : Generate checksums
304381 run : |
305382 cd dist
@@ -321,23 +398,38 @@ jobs:
321398 # --------------------------------------------------------------------------
322399 publish-pypi :
323400 name : publish-pypi
324- needs : [build-wheels, build-sdist]
401+ needs : [build-wheels, build-sdist, smoke-musl]
402+ # Wait for smoke-musl (so verified musl wheels publish too) but never block
403+ # the release on it — only the essential build jobs must have succeeded.
325404 if : >-
326- (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
327- (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi')
405+ always() &&
406+ needs.build-wheels.result == 'success' &&
407+ needs.build-sdist.result == 'success' &&
408+ (
409+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
410+ (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi')
411+ )
328412 runs-on : ubuntu-22.04
329413 environment :
330414 name : pypi
331415 url : https://pypi.org/p/codeanalyzer-java
332416 permissions :
333417 id-token : write
334418 steps :
335- - name : Download all artifacts
419+ # `wheel-*` (incl. smoke-musl-promoted musl wheels) + sdist; never staging.
420+ - name : Download wheels
336421 uses : actions/download-artifact@v4
337422 with :
423+ pattern : wheel-*
338424 path : dist
339425 merge-multiple : true
340426
427+ - name : Download sdist
428+ uses : actions/download-artifact@v4
429+ with :
430+ name : sdist
431+ path : dist
432+
341433 - name : Keep only distributables
342434 run : find dist -type f ! -name '*.whl' ! -name '*.tar.gz' -delete && ls -l dist
343435
@@ -352,21 +444,33 @@ jobs:
352444 # --------------------------------------------------------------------------
353445 publish-testpypi :
354446 name : publish-testpypi
355- needs : [build-wheels, build-sdist]
356- if : github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi'
447+ needs : [build-wheels, build-sdist, smoke-musl]
448+ if : >-
449+ always() &&
450+ needs.build-wheels.result == 'success' &&
451+ needs.build-sdist.result == 'success' &&
452+ github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi'
357453 runs-on : ubuntu-22.04
358454 environment :
359455 name : testpypi
360456 url : https://test.pypi.org/p/codeanalyzer-java
361457 permissions :
362458 id-token : write
363459 steps :
364- - name : Download all artifacts
460+ # `wheel-*` (incl. smoke-musl-promoted musl wheels) + sdist; never staging.
461+ - name : Download wheels
365462 uses : actions/download-artifact@v4
366463 with :
464+ pattern : wheel-*
367465 path : dist
368466 merge-multiple : true
369467
468+ - name : Download sdist
469+ uses : actions/download-artifact@v4
470+ with :
471+ name : sdist
472+ path : dist
473+
370474 - name : Keep only distributables
371475 run : find dist -type f ! -name '*.whl' ! -name '*.tar.gz' -delete && ls -l dist
372476
0 commit comments