diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c64a74..5bb592f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated +## [4.34.0] - 2026-03-04 + +### Fixed + +- Fixed f2py and f2py3 detection in Spack environments where the `f2py` + executable lives in the `py-numpy` package's `bin/` directory rather than + alongside the Python interpreter. `FindF2PY.cmake` and `FindF2PY3.cmake` now + ask Python directly (`import numpy.f2py`) to locate the correct `f2py` + binary. A two-pass search is used: the preferred locations (Python `bin/` and + numpy-derived `bin/`) are checked first with `NO_DEFAULT_PATH`, then a normal + `PATH` search is used as a fallback to preserve behaviour on non-Spack + (e.g. module-based) systems. +- Fixed `find_package(Python3)` picking up a different Python 3 interpreter + (e.g. a newer Homebrew Python) instead of the one already found by + `find_package(Python)` in Spack environments. `esma_python.cmake` now pins + `Python3_EXECUTABLE` to `Python_EXECUTABLE` when `find_package(Python)` has + already resolved a Python 3 interpreter, ensuring both find the same Python. + +### Changed + +- Added `Python_FIND_VIRTUALENV FIRST` and `Python3_FIND_VIRTUALENV FIRST` to + `esma_python.cmake` so that an active Spack `python-venv` on `PATH` is + preferred over system or framework Pythons. + ## [4.33.1] - 2026-03-03 ### Fixed diff --git a/python/esma_python.cmake b/python/esma_python.cmake index 2ae14c1..70b5d0c 100644 --- a/python/esma_python.cmake +++ b/python/esma_python.cmake @@ -16,6 +16,9 @@ endif () set(Python_FIND_STRATEGY LOCATION) set(Python_FIND_UNVERSIONED_NAMES FIRST) set(Python_FIND_FRAMEWORK LAST) +# FIRST: respect an active virtualenv (e.g. Spack python-venv) on PATH before +# falling back to system/Homebrew Python. +set(Python_FIND_VIRTUALENV FIRST) find_package(Python COMPONENTS Interpreter) list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/f2py") include (esma_find_python_module) @@ -28,6 +31,17 @@ endif () set(Python3_FIND_STRATEGY LOCATION) set(Python3_FIND_UNVERSIONED_NAMES FIRST) set(Python3_FIND_FRAMEWORK LAST) +# FIRST: respect an active virtualenv (e.g. Spack python-venv) on PATH before +# falling back to system/Homebrew Python. +set(Python3_FIND_VIRTUALENV FIRST) +# If find_package(Python) already found a Python 3 interpreter (e.g. via +# Python_ROOT_DIR set by a Spack environment), pin Python3 to the exact same +# executable so it doesn't wander off and find a different Python 3 (e.g. a +# newer Homebrew Python). Python3_EXECUTABLE bypasses all discovery logic. +if (Python_EXECUTABLE AND Python_VERSION_MAJOR EQUAL 3) + message(DEBUG "[esma_python]: Pinning Python3_EXECUTABLE to ${Python_EXECUTABLE} (already found by find_package(Python))") + set(Python3_EXECUTABLE "${Python_EXECUTABLE}") +endif () find_package(Python3 COMPONENTS Interpreter) list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/f2py3") include (esma_find_python3_module) diff --git a/python/f2py/FindF2PY.cmake b/python/f2py/FindF2PY.cmake index ac21b2f..2318b8e 100644 --- a/python/f2py/FindF2PY.cmake +++ b/python/f2py/FindF2PY.cmake @@ -41,15 +41,49 @@ message(DEBUG "[F2PY]: Searching for f2py executable associated with Python_EXEC get_filename_component(Python_EXECUTABLE_DIR ${Python_EXECUTABLE} DIRECTORY) message(DEBUG "[F2PY]: Python executable directory: ${Python_EXECUTABLE_DIR}") +# In Spack environments, f2py lives in the py-numpy package's bin dir, which is +# separate from the Python interpreter's bin dir. Ask Python/numpy directly for +# the path to f2py so we always get the one matching the active numpy. +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import numpy.f2py; import os; print(os.path.dirname(numpy.f2py.__file__))" + OUTPUT_VARIABLE _numpy_f2py_dir + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if (_numpy_f2py_dir) + # numpy.f2py package dir is e.g. .../lib/pythonX.Y/site-packages/numpy/f2py + # Walking up 5 levels reaches the package prefix; bin/ lives alongside lib/. + # Layout: /bin/f2py + # lib/pythonX.Y/site-packages/numpy/f2py <- _numpy_f2py_dir + get_filename_component(_numpy_bin_dir "${_numpy_f2py_dir}/../../../../.." ABSOLUTE) + set(_numpy_bin_dir "${_numpy_bin_dir}/bin") + message(DEBUG "[F2PY]: numpy f2py package dir: ${_numpy_f2py_dir}") + message(DEBUG "[F2PY]: numpy-derived bin hint: ${_numpy_bin_dir}") +endif () + find_program(F2PY_EXECUTABLE NAMES "f2py${Python_VERSION_MAJOR}" "f2py${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}" "f2py-${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}" "f2py" PATHS ${Python_EXECUTABLE_DIR} + ${_numpy_bin_dir} HINTS ${Python_EXECUTABLE_DIR} + ${_numpy_bin_dir} + NO_DEFAULT_PATH ) +# If not found in the preferred locations, fall back to a normal PATH search. +# This handles non-Spack environments where f2py may be elsewhere on PATH. +if (NOT F2PY_EXECUTABLE) + find_program(F2PY_EXECUTABLE + NAMES "f2py${Python_VERSION_MAJOR}" + "f2py${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}" + "f2py-${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}" + "f2py" + ) +endif () + message(DEBUG "[F2PY]: Found f2py executable: ${F2PY_EXECUTABLE}") # Now as a sanity check, we need to make sure that the f2py executable is diff --git a/python/f2py3/FindF2PY3.cmake b/python/f2py3/FindF2PY3.cmake index baad05a..5ee58f1 100644 --- a/python/f2py3/FindF2PY3.cmake +++ b/python/f2py3/FindF2PY3.cmake @@ -41,15 +41,49 @@ message(DEBUG "[F2PY3]: Searching for f2py3 executable associated with Python3_E get_filename_component(Python3_EXECUTABLE_DIR ${Python3_EXECUTABLE} DIRECTORY) message(DEBUG "[F2PY3]: Python3 executable directory: ${Python3_EXECUTABLE_DIR}") +# In Spack environments, f2py lives in the py-numpy package's bin dir, which is +# separate from the Python interpreter's bin dir. Ask Python/numpy directly for +# the path to f2py so we always get the one matching the active numpy. +execute_process( + COMMAND "${Python3_EXECUTABLE}" -c "import numpy.f2py; import os; print(os.path.dirname(numpy.f2py.__file__))" + OUTPUT_VARIABLE _numpy_f2py_dir + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if (_numpy_f2py_dir) + # numpy.f2py package dir is e.g. .../lib/pythonX.Y/site-packages/numpy/f2py + # Walking up 5 levels reaches the package prefix; bin/ lives alongside lib/. + # Layout: /bin/f2py + # lib/pythonX.Y/site-packages/numpy/f2py <- _numpy_f2py_dir + get_filename_component(_numpy_bin_dir "${_numpy_f2py_dir}/../../../../.." ABSOLUTE) + set(_numpy_bin_dir "${_numpy_bin_dir}/bin") + message(DEBUG "[F2PY3]: numpy f2py package dir: ${_numpy_f2py_dir}") + message(DEBUG "[F2PY3]: numpy-derived bin hint: ${_numpy_bin_dir}") +endif () + find_program(F2PY3_EXECUTABLE NAMES "f2py${Python3_VERSION_MAJOR}" "f2py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" "f2py-${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" "f2py" PATHS ${Python3_EXECUTABLE_DIR} + ${_numpy_bin_dir} HINTS ${Python3_EXECUTABLE_DIR} + ${_numpy_bin_dir} + NO_DEFAULT_PATH ) +# If not found in the preferred locations, fall back to a normal PATH search. +# This handles non-Spack environments where f2py may be elsewhere on PATH. +if (NOT F2PY3_EXECUTABLE) + find_program(F2PY3_EXECUTABLE + NAMES "f2py${Python3_VERSION_MAJOR}" + "f2py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" + "f2py-${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" + "f2py" + ) +endif () + message(DEBUG "[F2PY3]: Found f2py3 executable: ${F2PY3_EXECUTABLE}") # Now as a sanity check, we need to make sure that the f2py3 executable is