From 4c32c15f57ad344917d59694d9661e39e3feb5e1 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Tue, 11 Aug 2026 14:02:40 -0500 Subject: [PATCH] Fix macOS pybind11 module segfault: pin Python_FIND_STRATEGY=LOCATION On macOS, Python_EXECUTABLE alone doesn't stop FindPython's separate Development.Module/.Embed library lookups from independently resolving to a different Homebrew framework Python than the pinned interpreter (e.g. python@3.14 present on the macos-14 runner image, vs. the 3.12 actions/setup-python provisions). This produced pybind11 extension modules compiled against 3.12 headers but linked against 3.14's libpython, which crashed at import time inside pybind11's own version-mismatch error-reporting path. Root-caused and verified via NWChemEx/SCF's macOS CI (previously segfaulting deterministically on both compiler legs, now passing 4/4 tests including the C++ binaries that embed their own interpreter). Full investigation: scf-macos-pybind11-segfault.md in SCF's repo. Explicitly forcing Python_LIBRARY was also tried and rejected: it fixed the pybind11 module link but broke a dependency's separate Development.Embed lookup. Python_FIND_STRATEGY=LOCATION -- CMake's own documented mechanism for this exact scenario -- fixes both cleanly. --- .github/actions/cmake_build/action.yml | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/actions/cmake_build/action.yml b/.github/actions/cmake_build/action.yml index 9e45783..50ebe4d 100644 --- a/.github/actions/cmake_build/action.yml +++ b/.github/actions/cmake_build/action.yml @@ -91,6 +91,36 @@ runs: # ambient interpreter (e.g. a macOS Homebrew framework Python), # leaving configure-time-installed packages invisible to build-time # codegen scripts that embed a Python interpreter. + # + # Python_EXECUTABLE alone only pins the interpreter/header search -- + # on macOS, FindPython's separate Development.Module/.Embed library + # lookups can still independently resolve to a *different* Homebrew + # framework Python (e.g. python@3.14, present on GitHub's macos-14 + # runner image regardless of what this job's own `brew install` + # pulls in) even though the headers matched the pinned 3.12 + # interpreter. This produced a pybind11 module compiled against 3.12 + # headers but linked against 3.14's libpython -- confirmed via + # `otool -L` showing a hardcoded link to + # /opt/homebrew/opt/python@3.14/.../Python -- which crashed at + # import time inside pybind11's own version-mismatch error path (see + # scf-macos-pybind11-segfault.md in NWChemEx/SCF for the full + # investigation). Python_FIND_STRATEGY=LOCATION is CMake's own + # documented mechanism for tying *all* Development component lookups + # to wherever the interpreter itself was found, instead of a + # separate framework search; explicitly forcing Python_LIBRARY + # instead was also tried and rejected -- it fixed the pybind11 + # module link but broke a different dependency's + # Development.Embed lookup. Both Python_ and Python3_ spellings are + # set since different dependencies in the FetchContent graph call + # find_package() with either casing. + python_pin_opts=(-DPython_EXECUTABLE="$(command -v python3)") + if [ "$RUNNER_OS" = "macOS" ]; then + python_pin_opts+=( + -DPython_FIND_STRATEGY=LOCATION + -DPython3_FIND_STRATEGY=LOCATION + ) + fi + cmake -Bbuild -H. -GNinja \ -DCMAKE_INSTALL_PREFIX=./install \ -DCMAKE_C_COMPILER="${CC}" \ @@ -98,7 +128,7 @@ runs: -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DBUILD_TESTING="${build_testing}" \ - -DPython_EXECUTABLE="$(command -v python3)" \ + "${python_pin_opts[@]}" \ ${{ inputs.cmake_opts }} - name: Build