@@ -285,14 +285,12 @@ jobs:
285285 # would break the minos guarantee of macosx_10_15 / macosx_11_0 wheels).
286286 #
287287 # Previously this caused "symbol not found in flat namespace
288- # '___kmpc_dispatch_deinit'" at import time. Root cause: that symbol is
289- # an Intel-private OpenMP extension emitted by clang's -fopenmp when
290- # dynamic/guided schedules are used, but the LLVM openmp *runtime* we build
291- # from source deliberately omits it (it was removed in LLVM 13).
292- # Fix: pass -fno-openmp-extensions at compile time so clang never emits a
293- # reference to __kmpc_dispatch_deinit (or any other Intel-private symbol).
294- # The resulting .so is pure OpenMP-standard and links cleanly against our
295- # self-built libomp whose minos == the wheel deployment target.
288+ # '___kmpc_dispatch_deinit'" at import time. Root cause: Apple Clang emits
289+ # calls to __kmpc_dispatch_deinit for schedule(dynamic/guided) loops, but the
290+ # LLVM openmp runtime we build from source omits it (removed in LLVM 13).
291+ # -fno-openmp-extensions does NOT suppress this symbol on modern Apple Clang.
292+ # Fix: inject a no-op __kmpc_dispatch_deinit stub into the CI libomp build
293+ # (see build_macos_ci_libomp.sh) so bundled libomp satisfies the reference.
296294 - name : Cache CI libomp (macOS)
297295 if : runner.os == 'macOS'
298296 uses : actions/cache@v4
@@ -613,10 +611,9 @@ jobs:
613611 #
614612 # Previously this command pre-loaded Homebrew libomp via DYLD_LIBRARY_PATH,
615613 # which masked a "symbol not found in flat namespace '___kmpc_dispatch_deinit'"
616- # bug: delocate bundles the LLVM-source libomp (no dispatch_deinit), but
617- # clang emits a call to dispatch_deinit unless -fno-openmp-extensions is used.
618- # The pre-load caused CI tests to pass while users hit the error locally.
619- # Fix: test without any system-libomp fallback so the CI gate is honest.
614+ # bug: delocate bundles the LLVM-source libomp unless our dispatch_deinit stub
615+ # is present. The pre-load caused CI tests to pass while users hit the error
616+ # locally. Fix: test without any system-libomp fallback so the CI gate is honest.
620617 CIBW_TEST_COMMAND_MACOS : |
621618 echo "=== Testing macOS wheel (bundled libomp only — no DYLD_LIBRARY_PATH fallback) ==="
622619 echo "Architecture: $(uname -m)"
@@ -653,18 +650,17 @@ jobs:
653650 print(f'[ERROR] Failed to import sequenzo: {e}')
654651 sys.exit(1)
655652
656- # clustering_c_code uses OpenMP — this is the module that previously
657- # triggered "symbol not found in flat namespace '___kmpc_dispatch_deinit'".
658- # If -fno-openmp-extensions was not applied at compile time, this will fail
659- # even with the bundled libomp.
653+ # clustering_c_code uses OpenMP dynamic schedules — this module previously
654+ # triggered "symbol not found in flat namespace '___kmpc_dispatch_deinit'"
655+ # when bundled libomp lacked the dispatch_deinit compatibility stub.
660656 try:
661657 import sequenzo.clustering.clustering_c_code
662658 print('[OK] clustering_c_code loaded (bundled libomp sufficient)')
663659 except ImportError as e:
664660 print(f'[ERROR] clustering_c_code failed to load: {e}')
665661 if 'kmpc_dispatch_deinit' in str(e):
666662 print('[ERROR] ___kmpc_dispatch_deinit missing from bundled libomp.')
667- print('[ERROR] Ensure -fno-openmp-extensions is passed at compile time .')
663+ print('[ERROR] Ensure build_macos_ci_libomp.sh injects the dispatch_deinit stub .')
668664 sys.exit(1)
669665
670666 try:
@@ -813,39 +809,65 @@ jobs:
813809 run : ls -lah dist/
814810 shell : bash
815811
816- # Verify that macOS .so files do NOT reference ___kmpc_dispatch_deinit .
817- # If this symbol appears as an undefined reference it means -fno-openmp-extensions
818- # was not applied correctly; the bundled LLVM libomp built from source never exports
819- # it (removed in LLVM 13), so any user who installs the wheel would hit a dlopen
820- # "symbol not found in flat namespace" error at runtime .
821- - name : Verify no Intel-private OpenMP symbols in macOS .so files
812+ # Verify macOS wheels can resolve ___kmpc_dispatch_deinit at load time .
813+ # Apple Clang emits calls to __kmpc_dispatch_deinit for schedule(dynamic/guided)
814+ # loops; -fno-openmp-extensions does NOT suppress this on modern Apple Clang.
815+ # nm -u on a linked . so lists the symbol as undefined (U) because it is resolved
816+ # from bundled libomp at dlopen time — that is expected and OK when libomp exports it .
817+ - name : Verify OpenMP dispatch_deinit resolvable in macOS wheels
822818 if : runner.os == 'macOS'
823819 run : |
824820 set -euo pipefail
825- echo "=== Checking for ___kmpc_dispatch_deinit in built .so files ==="
821+ echo "=== Checking ___kmpc_dispatch_deinit resolution in macOS wheels ==="
826822 FOUND_BAD=0
827823 for whl in dist/*.whl; do
828824 echo "Inspecting: $whl"
829825 TMPDIR_WHL=$(mktemp -d)
830826 unzip -q "$whl" -d "$TMPDIR_WHL"
827+
828+ LIBOMP=""
829+ while IFS= read -r -d '' dylib; do
830+ case "$(basename "$dylib")" in
831+ libomp*.dylib) LIBOMP="$dylib"; break ;;
832+ esac
833+ done < <(find "$TMPDIR_WHL" -path '*/.dylibs/*.dylib' -print0)
834+
835+ if [ -z "$LIBOMP" ]; then
836+ echo "[ERROR] No bundled libomp.dylib found in $whl"
837+ FOUND_BAD=1
838+ rm -rf "$TMPDIR_WHL"
839+ continue
840+ fi
841+
842+ if nm -g "$LIBOMP" 2>/dev/null | grep -q '__kmpc_dispatch_deinit'; then
843+ echo "[OK] Bundled libomp exports __kmpc_dispatch_deinit: $LIBOMP"
844+ LIBOMP_HAS_DEINIT=1
845+ else
846+ echo "[ERROR] Bundled libomp missing __kmpc_dispatch_deinit stub: $LIBOMP"
847+ LIBOMP_HAS_DEINIT=0
848+ FOUND_BAD=1
849+ fi
850+
831851 while IFS= read -r -d '' so_file; do
832852 if nm -u "$so_file" 2>/dev/null | grep -q '___kmpc_dispatch_deinit'; then
833- echo "[ERROR] ___kmpc_dispatch_deinit found as undefined in: $so_file"
834- echo " This means -fno-openmp-extensions was not applied."
835- echo " Users will see 'symbol not found in flat namespace' at import time."
836- FOUND_BAD=1
853+ if [ "$LIBOMP_HAS_DEINIT" -eq 1 ]; then
854+ echo "[OK] $so_file references dispatch_deinit; bundled libomp provides it"
855+ else
856+ echo "[ERROR] $so_file references dispatch_deinit but libomp lacks stub"
857+ FOUND_BAD=1
858+ fi
837859 else
838- echo "[OK] $so_file — no Intel-private OMP symbols "
860+ echo "[OK] $so_file — no dispatch_deinit reference (static OpenMP schedules) "
839861 fi
840862 done < <(find "$TMPDIR_WHL" -name "*.so" -print0)
841863 rm -rf "$TMPDIR_WHL"
842864 done
843865 if [ "$FOUND_BAD" -eq 1 ]; then
844- echo "[FATAL] One or more .so files reference ___kmpc_dispatch_deinit ."
845- echo " Ensure -fno-openmp-extensions is passed at compile time ."
866+ echo "[FATAL] macOS wheel OpenMP symbol check failed ."
867+ echo " Ensure build_macos_ci_libomp.sh injects the dispatch_deinit stub ."
846868 exit 1
847869 fi
848- echo "=== All macOS .so files are clean ==="
870+ echo "=== All macOS wheels pass OpenMP symbol verification ==="
849871 shell : bash
850872
851873 - name : Check wheels
0 commit comments