From 105591723b1f3d97c66cc56fe75503b26a5e4331 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Wed, 16 Sep 2026 12:21:18 -0500 Subject: [PATCH] bumps the wheel container for ABI --- .github/actions/build_pypi_dist/action.yml | 92 +++++++++++++++----- .github/actions/cmake_build/action.yml | 2 +- .github/actions/pip_build/action.yml | 2 +- .github/actions/setup_nwx_dev_env/action.yml | 28 ++++-- .github/workflows/platform_matrix.yaml | 23 ++++- 5 files changed, 115 insertions(+), 32 deletions(-) diff --git a/.github/actions/build_pypi_dist/action.yml b/.github/actions/build_pypi_dist/action.yml index 7a565cc..6d70413 100644 --- a/.github/actions/build_pypi_dist/action.yml +++ b/.github/actions/build_pypi_dist/action.yml @@ -212,8 +212,11 @@ runs: # Built from source at the *exact* version Ubuntu 24.04's apt # package provides (see setup_nwx_dev_env), not EPEL's openmpi3 # (3.1.3) as before -- OpenMPI's ABI is not guaranteed compatible - # across major versions. EPEL7 (manylinux2014's package repo) has - # no openmpi4 package at all, so this can't be a yum install. Still + # across major versions. (manylinux_2_28's AppStream does carry + # an openmpi 4.1.1, but a source build pins the exact version + # instead of whatever the distro floats to, so this stays as it + # is; EPEL7, the package repo before the 2_28 switch, had no + # openmpi4 at all and left no choice in the matter.) Still # needed even though the wheel no longer vendors MPI (see # CIBW_REPAIR_WHEEL_COMMAND_LINUX below): the extension must be # *compiled* against a matching OpenMPI to link correctly against @@ -248,19 +251,22 @@ runs: # provides ambient (libopenmpi-dev/openmpi-bin) and which end users # need to install themselves (documented as a runtime requirement). # - # Exact sonames (libmpi.so.40), NOT a glob (libmpi*): cibuildwheel - # v2.22.0's manylinux2014 image (quay.io/pypa/manylinux2014_x86_64: - # 2024.11.16-1) bakes in auditwheel 6.1.0, whose --exclude does a + # Exact sonames (libmpi.so.40), NOT a glob (libmpi*). The old + # manylinux2014 image (quay.io/pypa/manylinux2014_x86_64: + # 2024.11.16-1) baked in auditwheel 6.1.0, whose --exclude did a # plain `soname in exclude` set-membership check (confirmed by - # reading that exact container's lddtree.py) -- fnmatch/glob - # support for --exclude was only added in a later auditwheel - # release. A glob pattern silently matches nothing and bundles the - # library anyway (reproduced locally via Docker: 'libmpi*' bundled - # libmpi/libopen-rte/libopen-pal same as no --exclude at all; - # 'libmpi.so.40' correctly excluded it, and since libopen-rte/ - # libopen-pal are only reachable *through* libmpi in this tree, - # excluding libmpi.so.40 alone is sufficient -- auditwheel doesn't - # recurse into an excluded library's own dependencies). + # reading that exact container's lddtree.py); fnmatch/glob support + # for --exclude only arrived in a later release, so a glob + # silently matched nothing and bundled the library anyway + # (reproduced via Docker against 6.1.0: 'libmpi*' bundled + # libmpi/libopen-rte/libopen-pal same as no --exclude at all, + # while 'libmpi.so.40' correctly excluded it). manylinux_2_28 + # ships auditwheel 6.7.0, which does support globs, but the exact + # sonames are kept: they work under both, and they name precisely + # what is meant. Since libopen-rte/libopen-pal are only reachable + # *through* libmpi in this tree, excluding libmpi.so.40 alone is + # sufficient -- auditwheel doesn't recurse into an excluded + # library's own dependencies. { echo "CIBW_REPAIR_WHEEL_COMMAND_LINUX=auditwheel repair --exclude 'libmpi.so.40' --exclude 'libopen-rte.so.40' --exclude 'libopen-pal.so.40' -w {dest_dir} {wheel}" } >> "$GITHUB_ENV" @@ -276,13 +282,23 @@ runs: fi if [ "${{ inputs.needs_boost }}" = "true" ]; then - # manylinux2014's plain boost-devel is 1.53.0, too old for headers - # some repos need (e.g. boost/container_hash/hash.hpp). EPEL's - # boost169-devel (parallel-installable, doesn't replace the base - # boost-devel) is the newest available via yum there, but installs - # under a versioned, non-default include path, so CMake's FindBoost - # needs to be pointed at it explicitly via BOOST_INCLUDEDIR. - before_linux+=("yum install -y boost169-devel") + # The image's base boost-devel is too old for headers some repos + # need: boost/container_hash/hash.hpp arrived in Boost 1.67, and + # the base package is 1.53.0 on manylinux2014 and 1.66.0 on + # manylinux_2_28 -- verified absent in both. EPEL's boost169-devel + # (1.69.0, parallel-installable, doesn't replace the base + # boost-devel) carries it, and installs at the same + # /usr/include/boost169 path under EPEL7 and EPEL8 alike. That + # versioned, non-default include path is why CMake's FindBoost has + # to be pointed at it explicitly via BOOST_INCLUDEDIR. + # + # epel-release is installed here rather than assumed: EPEL came + # pre-enabled on manylinux2014, but NOT on the manylinux_2_28 + # image cibuildwheel v2.22.0 pins (2024.11.16-1) -- verified, it + # ships only baseos/appstream/extras/powertools, and this + # yum install fails without it. Harmlessly repeated by the + # needs_blas and needs_eigen branches below. + before_linux+=("yum install -y epel-release" "yum install -y boost169-devel") env_linux+=("BOOST_INCLUDEDIR=/usr/include/boost169") before_macos+=("brew install boost") fi @@ -369,6 +385,40 @@ runs: env: CIBW_BUILD: ${{ inputs.cibw_build }} CIBW_BUILD_VERBOSITY: "1" + # Overrides cibuildwheel v2.22.0's default manylinux2014 image. + # That image is CentOS 7 + devtoolset-10, whose libstdc++ is built + # with _GLIBCXX_USE_DUAL_ABI 0 -- its does an + # unconditional "#undef _GLIBCXX_USE_CXX11_ABI" and pins the macro + # to 0, so it *discards* any -D_GLIBCXX_USE_CXX11_ABI=1 passed in. + # The new ABI is not merely off there, it is compiled out of the + # toolchain. Every C++ library in a Linux wheel therefore exported + # the pre-C++11 COW std::string ABI (pluginplay::ModuleManager::at + # taking RKSs rather than RKNSt7__cxx11...), which nothing built on + # a modern distro can link against: the dev matrix + # (ubuntu-24.04, gcc-14/clang-18) defaults to the new ABI, so + # SCF's test_cmake_build died at link with hundreds of undefined + # references to ecosystem symbols once it finally got far enough to + # link at all. + # + # manylinux_2_28 (AlmaLinux 8, gcc 14.2.1) defaults to + # _GLIBCXX_USE_CXX11_ABI 1, and its auditwheel policy permits the + # GLIBCXX_3.4.21 symbols the new ABI needs -- manylinux_2_17 caps + # at 3.4.19, which is the other half of why the flag route is a + # dead end rather than merely risky. Its gcc 14.2.1 also happens to + # match the dev matrix's gcc-14 exactly. + # + # Verified against the exact image cibuildwheel v2.22.0 pins + # (quay.io/pypa/manylinux_2_28_x86_64:2024.11.16-1, NOT :latest, + # which differs): yum is present, PowerTools is enabled out of the + # box so openblas-devel/lapack-devel/eigen3-devel resolve, and + # boost169-devel resolves at the same /usr/include/boost169 path + # once epel-release is installed -- EPEL is pre-enabled on + # manylinux2014 but not here, which is why the needs_boost branch + # above now installs it explicitly. + # + # Raises the minimum glibc for a consumer of these wheels from 2.17 + # (CentOS 7, long EOL) to 2.28 (RHEL 8 / Ubuntu 18.10 and newer). + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - name: Collect cibuildwheel Output if: inputs.cibw_build != '' diff --git a/.github/actions/cmake_build/action.yml b/.github/actions/cmake_build/action.yml index 8f0e8ac..dc47f7b 100644 --- a/.github/actions/cmake_build/action.yml +++ b/.github/actions/cmake_build/action.yml @@ -21,7 +21,7 @@ description: >- inputs: compiler: - description: "One of: gcc-14, clang-18. Installed via the native package manager (apt on Linux, Homebrew on macOS)." + description: "One of: gcc-14, clang-18 (Linux); appleclang (macOS, i.e. /usr/bin/clang++, the compiler that builds the wheels). Installed via the native package manager (apt on Linux, Homebrew on macOS)." required: true run_cmake_tests: description: "Whether to build and run the CTest (C++) test suite (sets -DBUILD_TESTING)." diff --git a/.github/actions/pip_build/action.yml b/.github/actions/pip_build/action.yml index c13e17a..94cf1de 100644 --- a/.github/actions/pip_build/action.yml +++ b/.github/actions/pip_build/action.yml @@ -20,7 +20,7 @@ description: >- inputs: compiler: - description: "One of: gcc-14, clang-18. Installed via the native package manager (apt on Linux, Homebrew on macOS)." + description: "One of: gcc-14, clang-18 (Linux); appleclang (macOS, i.e. /usr/bin/clang++, the compiler that builds the wheels). Installed via the native package manager (apt on Linux, Homebrew on macOS)." required: true run_cmake_tests: description: "Whether to build and run the CTest (C++) test suite (sets -DBUILD_TESTING)." diff --git a/.github/actions/setup_nwx_dev_env/action.yml b/.github/actions/setup_nwx_dev_env/action.yml index 60a6a62..0dbe356 100644 --- a/.github/actions/setup_nwx_dev_env/action.yml +++ b/.github/actions/setup_nwx_dev_env/action.yml @@ -21,7 +21,7 @@ description: >- inputs: compiler: - description: "One of: gcc-14, clang-18." + description: "One of: gcc-14, clang-18 (Linux); appleclang (macOS)." required: true needs_nwchem: description: >- @@ -62,14 +62,28 @@ runs: echo "CXX=g++-14" >> "$GITHUB_ENV" echo "CMAKE_PREFIX_PATH=$(brew --prefix openblas)" >> "$GITHUB_ENV" ;; - macOS_clang-18) - brew install llvm@18 libomp cmake ninja ccache open-mpi boost openblas eigen - prefix=$(brew --prefix llvm@18) + macOS_appleclang) + # /usr/bin/clang++ -- the SAME compiler cibuildwheel uses to build + # the macOS wheels (build_pypi_dist logs "AppleClang 15.0.0" on + # macos-14). Deliberately not Homebrew llvm@18, which was used + # here before: that ships its OWN libc++ (_LIBCPP_VERSION 18.01.00) + # while the wheels are built against the SDK's, and mixing two + # libc++ implementations links cleanly and then dies at dyld with + # "symbol not found in flat namespace" -- the SCF#76 segfault. + # + # It also closes a real gap: AppleClang builds every released + # macOS wheel but was compiled by no CI job, which is how a C++20 + # P1091 (structured binding captured in a lambda) use survived in + # grid_from_integratorxx.cpp while all legs stayed green. The + # project requires C++20 and AppleClang 15 compiles the whole tree + # at -std=c++20 today; testing with it makes the ceiling it + # already imposes on releases visible in PR CI. + brew install libomp cmake ninja ccache open-mpi boost openblas eigen libomp_prefix=$(brew --prefix libomp) - echo "CC=${prefix}/bin/clang" >> "$GITHUB_ENV" - echo "CXX=${prefix}/bin/clang++" >> "$GITHUB_ENV" + echo "CC=/usr/bin/clang" >> "$GITHUB_ENV" + echo "CXX=/usr/bin/clang++" >> "$GITHUB_ENV" echo "CMAKE_PREFIX_PATH=$(brew --prefix openblas);${libomp_prefix}" >> "$GITHUB_ENV" - # llvm@18's own clang has no bundled OpenMP runtime; find_package(OpenMP) + # AppleClang has no bundled OpenMP runtime either; find_package(OpenMP) # needs omp.h/libomp.dylib on the compiler's default search paths to pass # its "-fopenmp compiles" check and locate the library, and CPATH/LIBRARY_PATH # are how Clang picks those up without needing explicit -I/-L flags threaded diff --git a/.github/workflows/platform_matrix.yaml b/.github/workflows/platform_matrix.yaml index 65d3147..8041541 100644 --- a/.github/workflows/platform_matrix.yaml +++ b/.github/workflows/platform_matrix.yaml @@ -46,11 +46,30 @@ jobs: {"os": "ubuntu-latest", "cibw_build": "cp312-manylinux_x86_64"}, {"os": "macos-14", "cibw_build": "cp312-macosx_arm64"} ]' + # macos-14/gcc-14 is deliberately absent. The macOS wheels are + # built by cibuildwheel with AppleClang against libc++, while + # Homebrew's GCC uses libstdc++ and has no working libc++ mode -- + # they mangle std::string differently + # (NSt3__112basic_string... vs NSt7__cxx1112basic_string...), so a + # GCC build can never link them. That is a property of the + # platform, not a bug to be fixed, so the leg could only ever be + # red. macOS + GCC is supported only when the whole ecosystem is + # built from source (-DNWX_ECOSYSTEM_FROM_SOURCE=ON); NWXCMake's + # nwx_assert_wheel_toolchain_supported() fails loudly otherwise. + # + # The remaining macOS leg is AppleClang, not Homebrew llvm@18: the + # wheels are built by AppleClang, and llvm@18 carries its own libc++ + # (18.01.00 vs the SDK's), which links cleanly and then dies at dyld + # -- the SCF#76 segfault. Matching the release compiler also means + # PR CI finally compiles what actually ships. + # + # Linux keeps both compilers: clang there consumes GCC's own + # libstdc++ headers and links the same libstdc++.so.6, so the two + # share one C++ runtime and agree on the mangling. dev_matrix='[ {"os": "ubuntu-latest", "compiler": "gcc-14"}, {"os": "ubuntu-latest", "compiler": "clang-18"}, - {"os": "macos-14", "compiler": "gcc-14"}, - {"os": "macos-14", "compiler": "clang-18"} + {"os": "macos-14", "compiler": "appleclang"} ]' echo "release_matrix=$(echo "$release_matrix" | jq -c .)" >> "$GITHUB_OUTPUT" echo "dev_matrix=$(echo "$dev_matrix" | jq -c .)" >> "$GITHUB_OUTPUT"