From be907c978a0ee9ca12c1a6299c7317fda9509fc8 Mon Sep 17 00:00:00 2001 From: Horde Date: Tue, 1 Sep 2026 02:55:32 +0000 Subject: [PATCH 1/3] CI: build wheels natively per arch (fix duplicate/colliding wheels); add win_arm64 The build matrix had a cross `arch: [x86_64, arm64]` dimension, but only CIBW_ARCHS_MACOS was wired to it. On the x86 ubuntu-24.04 / windows-latest runners the `arch: arm64` leg therefore ignored the arch and rebuilt the native x86_64 / amd64 wheel, just under an artifact named "...-arm64-...". That caused: - duplicate manylinux-x86_64 and win_amd64 wheels (10 of them), which collided during the release job's `download-artifact --merge-multiple` and intermittently produced a truncated wheel -> `twine check` BadZipFile -> failed PyPI publish; - no real win_arm64 wheels ever being built. Replace the broken cross-arch dimension with one runner per native target architecture (cibuildwheel builds the native arch by default), and add a native windows-11-arm leg for real win_arm64 wheels (3.11+ only; marked continue-on-error since embree/predicates use x86 SIMD and may not build yet). Artifact names are now unique per (os, python), so nothing collides on merge. Bump version to 2.6.4.dev0 so merging this exercises the fixed publish pipeline end-to-end (a pre-release upload to PyPI). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/wheels.yml | 40 +++++++++++++++++++++++------------- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 200fb9d3..2356027b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -25,26 +25,37 @@ jobs: build_wheels: runs-on: ${{ matrix.os }} + # windows-11-arm is newly added; its native toolchain may not build every + # dependency yet (embree / predicates use x86 SIMD), so don't let that leg + # block a release if it fails. + continue-on-error: ${{ matrix.os == 'windows-11-arm' }} strategy: fail-fast: false + # One runner per *native* target architecture; cibuildwheel builds the + # runner's native arch by default. Do NOT reintroduce a cross `arch` + # dimension: previously `arch: arm64` on the x86 ubuntu-24.04 / windows-latest + # runners silently built x86_64 / amd64 wheels (only CIBW_ARCHS_MACOS was + # wired up), producing duplicate wheels that collided during the release + # artifact download and left win_arm64 uncovered. matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest] # Use specific versions for clarity - arch: [x86_64, arm64] - pybuilds: [cp38, cp39, cp310, cp311, cp312] # Define pybuilds at the top level + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest, windows-11-arm] + pybuilds: [cp38, cp39, cp310, cp311, cp312] exclude: - - os: ubuntu-24.04-arm # No need to specify arch, it's already implicit - arch: x86_64 # Exclude x86_64 on ARM + # No macOS CPython 3.8 wheels are shipped. - os: macos-latest - arch: x86_64 # Exclude x86_64 explicitly - - os: macos-latest # Exclude cp38 pybuilds: cp38 - os: macos-15-intel - arch: arm64 # Exclude arm64 on macOS 13 - - os: macos-15-intel pybuilds: cp38 + # Windows on ARM64 has official CPython only for 3.11+. + - os: windows-11-arm + pybuilds: cp38 + - os: windows-11-arm + pybuilds: cp39 + - os: windows-11-arm + pybuilds: cp310 name: > - ${{ matrix.pybuilds }} ${{ matrix.os }} ${{ matrix.arch }} + ${{ matrix.pybuilds }} ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -59,15 +70,16 @@ jobs: CIBW_ENVIRONMENT_LINUX: "CC=clang CXX=clang++" CIBW_MANYLINUX_X86_64_IMAGE: "manylinux_2_28" CIBW_MANYLINUX_AARCH64_IMAGE: "manylinux_2_28" - CIBW_BUILD: "${{ matrix.pybuilds }}-*" # Use matrix.pybuilds and matrix.arch - CIBW_ARCHS_MACOS: ${{ matrix.arch }} + CIBW_BUILD: "${{ matrix.pybuilds }}-*" + # No CIBW_ARCHS_* override: each runner builds its native architecture. CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" - name: Upload Artifact uses: actions/upload-artifact@v4 continue-on-error: true # Important: Continue if upload fails with: - # Include pybuilds in the artifact name - name: wheels-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.pybuilds }} + # Unique per (os, python); one native arch per runner so wheel + # filenames never collide when the release job merges artifacts. + name: wheels-${{ matrix.os }}-${{ matrix.pybuilds }} path: ./wheelhouse/*.whl if-no-files-found: error # Fail if no wheels are found compression-level: 6 diff --git a/pyproject.toml b/pyproject.toml index b0cc59c2..efbeeeba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ build-backend = "scikit_build_core.build" [project] name = "libigl" -version = "2.6.3" +version = "2.6.4.dev0" description = "libigl: A simple C++ geometry processing library" readme = "README.md" requires-python = ">=3.8" From de2ff00e17a5763d3f603b6c75a661faf6c0491e Mon Sep 17 00:00:00 2001 From: Horde Date: Tue, 1 Sep 2026 13:52:12 +0000 Subject: [PATCH 2/3] CI: disable embree on win_arm64 wheels (embree has no MSVC-ARM64 build) The windows-11-arm leg fails building embree: embree only enables its NEON (sse2neon) code path when its CMake detects an ARM target, and on Windows/MSVC ARM64 that detection falls back to SSE2 ("Detected default ISA: SSE2"), so it compiles x86 SSE sources and MSVC's immintrin.h rejects them on ARM64. (macOS arm64 works because embree's arm/NEON path is detected and supported there.) Pass CMAKE_ARGS=-DLIBIGL_EMBREE=OFF only on the windows-11-arm leg so win_arm64 wheels build without the igl.embree submodule. Other platforms keep embree. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/wheels.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2356027b..cd14a157 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -73,6 +73,11 @@ jobs: CIBW_BUILD: "${{ matrix.pybuilds }}-*" # No CIBW_ARCHS_* override: each runner builds its native architecture. CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" + # embree only enables its NEON (sse2neon) path when its build detects an + # ARM target; on Windows/MSVC-ARM64 that detection falls back to SSE2 and + # fails to compile (x86 immintrin.h). Drop embree on win_arm64 (the + # igl.embree submodule is simply absent from that wheel). + CIBW_ENVIRONMENT_WINDOWS: ${{ matrix.os == 'windows-11-arm' && 'CMAKE_ARGS=-DLIBIGL_EMBREE=OFF' || '' }} - name: Upload Artifact uses: actions/upload-artifact@v4 continue-on-error: true # Important: Continue if upload fails From 579c9f21ff34652934bbdd917219cc30fa5705f2 Mon Sep 17 00:00:00 2001 From: Horde Date: Tue, 1 Sep 2026 15:47:33 +0000 Subject: [PATCH 3/3] tests: make igl.embree import optional; skip embree tests when absent cibuildwheel runs `pytest {project}/tests` on every wheel, and the win_arm64 wheel is built without embree (no MSVC/ARM64 embree). The unconditional `import igl.embree` at module scope aborted collection there. Guard the import (HAS_EMBREE) and skip test_embree / test_new_embree_algorithms when it's absent. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_all.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_all.py b/tests/test_all.py index ed893aa5..868622de 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -10,11 +10,18 @@ import igl.copyleft import igl.copyleft.tetgen import igl.copyleft.cgal -import igl.embree import igl.spectra import igl.predicates import igl.cycodebase +# embree is not built on every platform (e.g. Windows/ARM64, where upstream +# embree has no MSVC build), so treat it as optional. +try: + import igl.embree + HAS_EMBREE = True +except ImportError: + HAS_EMBREE = False + @pytest.fixture def icosahedron(): V,F = igl.icosahedron() @@ -569,6 +576,7 @@ def test_cgal(): R = igl.copyleft.cgal.oriented_bounding_box(VC) +@pytest.mark.skipif(not HAS_EMBREE, reason="igl.embree not built (e.g. Windows/ARM64)") def test_embree(): # octahedron V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) @@ -1392,6 +1400,7 @@ def test_new_tetgen_algorithms(): assert FF_sk.shape[1] == 3 +@pytest.mark.skipif(not HAS_EMBREE, reason="igl.embree not built (e.g. Windows/ARM64)") def test_new_embree_algorithms(): V_oct = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]], dtype=np.float64) F_oct = np.array([[0,1,2],[0,2,4],[0,4,5],[0,5,1],[1,3,2],[1,5,3],[2,3,4],[3,5,4]], dtype=np.int64)