diff --git a/.github/workflows/build-wheels-metal.yaml b/.github/workflows/build-wheels-metal.yaml index 892787ee1..797c91970 100644 --- a/.github/workflows/build-wheels-metal.yaml +++ b/.github/workflows/build-wheels-metal.yaml @@ -7,11 +7,10 @@ permissions: jobs: build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-14, macos-15] + # Two runners produced the same py3-none wheel filename, which collided + # when their artifacts were downloaded into one release directory. + name: Build wheels on macos-14 + runs-on: macos-14 steps: - uses: actions/checkout@v6 @@ -38,14 +37,18 @@ jobs: CIBW_REPAIR_WHEEL_COMMAND: "" CIBW_ARCHS: "arm64" CIBW_ENVIRONMENT: CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_APPLE_SILICON_PROCESSOR=arm64 -DGGML_METAL=on -DCMAKE_CROSSCOMPILING=ON" - CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*" + # wheel.py-api = py3 produces one wheel for all supported Python versions. + CIBW_BUILD: "cp39-*" with: package-dir: . output-dir: wheelhouse2 + - name: Verify wheel archives + run: python3 scripts/verify-wheel-archives.py --expected-count 1 wheelhouse2/*.whl + - uses: actions/upload-artifact@v7 with: - name: wheels-mac_${{ matrix.os }} + name: wheels-mac_macos-14 path: ./wheelhouse2/*.whl release: @@ -54,11 +57,20 @@ jobs: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + - uses: actions/download-artifact@v8 with: - merge-multiple: true + name: wheels-mac_macos-14 path: dist2 + - name: Verify release wheel archives + run: python3 scripts/verify-wheel-archives.py --expected-count 1 dist2/*.whl + - uses: softprops/action-gh-release@v3 with: files: dist2/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c34e2729..8da7f5da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- fix(ci): prevent corrupted Metal wheel releases by @provoke210 in #2379 + ## [0.3.36] - feat: update llama.cpp to ggml-org/llama.cpp@0c1e57098 diff --git a/scripts/verify-wheel-archives.py b/scripts/verify-wheel-archives.py new file mode 100644 index 000000000..c1e63d073 --- /dev/null +++ b/scripts/verify-wheel-archives.py @@ -0,0 +1,40 @@ +"""Reject damaged wheel archives before uploading or publishing them.""" + +import argparse +import sys +import zipfile +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--expected-count", type=int) + parser.add_argument("wheels", nargs="+") + args = parser.parse_args() + + if args.expected_count is not None and len(args.wheels) != args.expected_count: + print( + f"Expected {args.expected_count} wheel archive(s), found {len(args.wheels)}", + file=sys.stderr, + ) + return 1 + + failed = False + for name in args.wheels: + path = Path(name) + try: + with zipfile.ZipFile(path) as archive: + bad_file = archive.testzip() + if bad_file is not None: + raise zipfile.BadZipFile(f"bad CRC in {bad_file}") + except (OSError, zipfile.BadZipFile) as exc: + print(f"Invalid wheel archive {path}: {exc}", file=sys.stderr) + failed = True + else: + print(f"Valid wheel archive: {path}") + + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main())