diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index a9605b98..22f3fbe1 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -58,3 +58,17 @@ jobs: pip install -r test-requirements.txt pip install --no-index --find-links=./dist swmm_toolkit pytest + + # Regression test for the Python3_SOABI fallback. Builds with + # NO_STABLE_ABI=1 and runs the ctest suffix guardrail to catch the + # double-dot filename bug (e.g. _solver..so) that occurs when + # CMake's FindPython3 fails to populate Python3_SOABI. + - name: Build + ctest (NO_STABLE_ABI path) + shell: bash + env: + NO_STABLE_ABI: "1" + run: | + pip install cmake swig + cmake -S . -B build_cmake + cmake --build build_cmake --config Release + ctest --test-dir build_cmake --output-on-failure -C Release diff --git a/swmm-toolkit/CMakeLists.txt b/swmm-toolkit/CMakeLists.txt index 7b35d564..48e873dd 100644 --- a/swmm-toolkit/CMakeLists.txt +++ b/swmm-toolkit/CMakeLists.txt @@ -36,6 +36,8 @@ cmake_policy(SET CMP0078 NEW) cmake_policy(SET CMP0086 NEW) include(${SWIG_USE_FILE}) +enable_testing() + # Add project subdirectories add_subdirectory(swmm-solver) diff --git a/swmm-toolkit/src/swmm/toolkit/CMakeLists.txt b/swmm-toolkit/src/swmm/toolkit/CMakeLists.txt index 8b05bd17..106a05af 100644 --- a/swmm-toolkit/src/swmm/toolkit/CMakeLists.txt +++ b/swmm-toolkit/src/swmm/toolkit/CMakeLists.txt @@ -28,6 +28,14 @@ set(PY_LIMITED_API_DEF "Py_LIMITED_API=0x03090000") string(TOLOWER "$ENV{NO_STABLE_ABI}" _abi_val) if(_abi_val STREQUAL "1" OR _abi_val STREQUAL "true") set(PY_LIMITED_API_DEF "") + if(NOT Python3_SOABI) + execute_process( + COMMAND ${Python3_EXECUTABLE} -c + "import sysconfig; print(sysconfig.get_config_var('SOABI') or '')" + OUTPUT_VARIABLE Python3_SOABI + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() set(SOABI ".${Python3_SOABI}") endif() @@ -188,6 +196,31 @@ install( COMPONENT python ) +############################################################# +#################### SUFFIX TESTS #################### +############################################################# + +# Verify that each extension module was linked with a valid Python ABI suffix. +# Guards against the double-dot bug (_solver..so) that occurs when +# CMake's FindPython3 fails to populate Python3_SOABI. +# Run with: cmake --build && ctest --test-dir + +foreach(_ext_target output solver) + add_test( + NAME ExtensionSuffix_${_ext_target} + COMMAND ${Python3_EXECUTABLE} -c + "import importlib.machinery, os; \ +p = r'$'; \ +valid = importlib.machinery.EXTENSION_SUFFIXES; \ +assert os.path.exists(p), f'Extension not found: {p!r}'; \ +assert '..' not in os.path.basename(p), \ + f'Double-dot ABI bug in {p!r} -- Python3_SOABI was empty at configure time'; \ +assert any(p.endswith(s) for s in valid), \ + f'Invalid suffix in {p!r}, expected one of {valid}'; \ +print('PASS:', p)" + ) +endforeach() + # Copy libomp.dylib on macOS if using scikit-build-core if(APPLE AND DEFINED SKBUILD_PLATLIB_DIR) install(