From 08115506e00080afccaa360dd7ea7a50de9e7bdd Mon Sep 17 00:00:00 2001 From: Kevin Hendricks Date: Fri, 7 Feb 2025 10:33:29 -0500 Subject: [PATCH] fix cmake FindPython3 shortcomings for Mac Frameworks --- CMakeLists.txt | 15 +++- cmake_extras/FindPython3MacFramework.cmake | 98 ++++++++++++++++++++++ cmake_extras/FindPythonMacFramework.cmake | 92 ++++++++++++++++++++ 3 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 cmake_extras/FindPython3MacFramework.cmake create mode 100644 cmake_extras/FindPythonMacFramework.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c55885a883..6aab2d3486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -######################################################## +###A##################################################### # # This is a CMake configuration file. # To use it you need CMake which can be @@ -27,7 +27,7 @@ if ( NOT DEFINED TRY_NEWER_FINDPYTHON3 ) if ( NOT APPLE ) set ( TRY_NEWER_FINDPYTHON3 1 ) else() - set ( TRY_NEWER_FINDPYTHON3 0 ) + set ( TRY_NEWER_FINDPYTHON3 1 ) endif() endif() @@ -144,6 +144,9 @@ message(STATUS "CMake version in use: ${CMAKE_VERSION}") if (${USE_NEWER_FINDPYTHON3}) message(STATUS "Using newer findpython3 cmake module") if ( APPLE ) + # cmake's FindPython3 does *not* find frameworks based on precendence in PATH + # so set some pretty specific hints using our own FindPython3MacFramework.cmake first + find_package(Python3MacFramework 3.9 QUIET) find_package(Python3 3.9 COMPONENTS Interpreter Development) endif() if ( WIN32 ) @@ -181,8 +184,11 @@ if (${USE_NEWER_FINDPYTHON3}) else() message(STATUS "Using older findpython cmake module") if ( APPLE ) - find_package(PythonInterp 3.11) - find_package (PythonLibs 3.11) + find_package(PythonMacFramework 3.9 QUIET) + if (NOT PythonMacFramework_FOUND) + find_package(PythonInterp 3.9) + find_package(PythonLibs 3.9) + endif() endif() if ( WIN32 ) find_package(PythonInterp 3.9) @@ -201,3 +207,4 @@ add_subdirectory( internal/gumbo ) add_subdirectory( 3rdparty/ ) add_subdirectory( src/ ) + diff --git a/cmake_extras/FindPython3MacFramework.cmake b/cmake_extras/FindPython3MacFramework.cmake new file mode 100644 index 0000000000..45baea962f --- /dev/null +++ b/cmake_extras/FindPython3MacFramework.cmake @@ -0,0 +1,98 @@ +#.rst: +# FindPython3MacFramework +# ---------------------- +# +# Find first Python.framework from version 3 found in your system PATH +# +# This module finds if a Python.framework is installed and determines +# where the interpreter (executable) and libraries are. +# This code sets the following variables: +# +# :: +# Python3MacFramework_FOUND +# Python3_EXECUTABLE - path to the Python interpreter +# Python3_LIBRARIES - path to the python library +# Python3_INCLUDE_DIRS - path to where Python.h is found +# Python3_VERSION_STRING - version of the Python libs found + +set(_PYTHON_VERSIONS 3.14 3.13 3.12 3.11 3.10 3.9) + +find_program(_PYTHON_EXECUTABLE python3 PATHS ENV PATH) + +# determine python version string +if(_PYTHON_EXECUTABLE) + execute_process(COMMAND "${_PYTHON_EXECUTABLE}" -c + "import sys; sys.stdout.write(';'.join([str(x) for x in sys.version_info[:3]]))" + OUTPUT_VARIABLE _VERSION + RESULT_VARIABLE _PYTHON_VERSION_RESULT + ERROR_QUIET) + + if(NOT _PYTHON_VERSION_RESULT) + list(GET _VERSION 0 _PYTHON_VERSION_MAJOR) + list(GET _VERSION 1 _PYTHON_VERSION_MINOR) + list(GET _VERSION 2 _PYTHON_VERSION_PATCH) + list(JOIN _VERSION "." _PYTHON_VERSION_STRING) + set(_VERSION_STRING "${_PYTHON_VERSION_MAJOR}.${_PYTHON_VERSION_MINOR}") + # it must be an acceptable version + list( FIND _PYTHON_VERSIONS ${_VERSION_STRING} _ACC_VER) + if ( ${_ACC_VER} GREATER -1 ) + # determine if this is a Python.framework + # ie. /Library/Frameworks/Python.framework/Versions/3.13/bin/python3 + # ie. /Users/kbhend/ndevpython/libraries/Frameworks/Python.framework/Versions/3.13/bin/python3 + + string(REPLACE "/" ";" _PATH_SEGMENTS ${_PYTHON_EXECUTABLE}) + set(_PATH_LIST ${_PATH_SEGMENTS} ) + # message("Excutable Path: " ${_PYTHON_EXECUTABLE} ) + list( FIND _PATH_LIST "Python.framework" _ISFWK ) + if( ${_ISFWK} GREATER -1 ) + # message("Found framework: " ${_ISFWK} ) + list(POP_BACK _PATH_LIST) # remove python3 + list(POP_BACK _PATH_LIST) # remove bin + list(POP_BACK _PATH_LIST _CURRENT_VERSION) + list(POP_BACK _PATH_LIST) # remove Versions + list(JOIN _PATH_LIST "/" _PYTHON_FRAMEWORK_BASE) + + # Now we have found a valid Python3 Framework from the PATH + set(Python3_VERSION_STRING ${_PYTHON_VERSION_STRING}) + set(Python3_EXECUTABLE "${_PYTHON_EXECUTABLE}") + # Remember to add back the leading "/" since these are absolute paths + set(Python3_LIBRARIES "/${_PYTHON_FRAMEWORK_BASE}/Versions/${_CURRENT_VERSION}/lib/libpython${_VERSION_STRING}.dylib") + set(Python3_INCLUDE_DIRS "/${_PYTHON_FRAMEWORK_BASE}/Versions/${_CURRENT_VERSION}/include/python${_VERSION_STRING}") + # message("Python3_LIBRARIES: " ${Python3_LIBRARIES}) + # message("Python3_INCLUDE_DIRS: " ${Python3_INCLUDE_DIRS}) + # clean up + unset(_CURRENT_VERSION) + unset(_PYTHON_VERSION_STRING) + unset(_PYTHON_FRAMEWORK_BASE) + endif() + # clean up + unset(_IS_FWK) + unset(_PATH_SEGMENTS) + unset(_PATH_LIST) + endif() + # clean up + unset(_PYTHON_VERSION_MAJOR) + unset(_PYTHON_VERSION_MINOR) + unset(_PYTHON_VERSION_PATCH) + unset(_VERSION_STRING) + unset(_ACC_VER) + endif() + # clean up + unset(_VERSION) + unset(_PYTHON_VERSION_RESULT) +endif() + +# cleanup +unset(_PYTHON_VERSIONS) +unset(_PYTHON_EXECUTABLE) + +# handle the QUIETLY and REQUIRED arguments and set Python3MacFramework_FOUND to TRUE if +# all listed variables are TRUE +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python3MacFramework REQUIRED_VARS Python3_EXECUTABLE + Python3_LIBRARIES + Python3_INCLUDE_DIRS + VERSION_VAR Python3_VERSION_STRING) +mark_as_advanced(Python3_EXECUTABLE) +mark_as_advanced(Python3_LIBRARIES) +mark_as_advanced(Python3_INCLUDE_DIRS) diff --git a/cmake_extras/FindPythonMacFramework.cmake b/cmake_extras/FindPythonMacFramework.cmake new file mode 100644 index 0000000000..4d641155da --- /dev/null +++ b/cmake_extras/FindPythonMacFramework.cmake @@ -0,0 +1,92 @@ +#.rst: +# FindPythonMacFramework +# ---------------------- +# +# Find first Python.framework found in Path +# +# This module finds if a Python.framework is installed and determines +# where the interpreter (executable) and libraries are. +# This code sets the following variables: +# +# :: +# PYTHONMACFRAMEWORK_FOUND +# PYTHON_EXECUTABLE - path to the Python interpreter +# PYTHON_VERSION_STRING - Python version found e.g. 3.13.2 +# PYTHON_VERSION_MAJOR - Python major version found e.g. 3 +# PYTHON_VERSION_MINOR - Python minor version found e.g. 13 +# PYTHON_VERSION_PATCH - Python patch version found e.g. 2 +# PYTHON_LIBRARIES - path to the python library +# PYTHON_INCLUDE_DIRS - path to where Python.h is found +# PYTHONLIBS_VERSION_STRING - version of the Python libs found + +set(_PYTHON_VERSIONS 3.14 3.13 3.12 3.11 3.10 3.9) + +find_program(PYTHON_EXECUTABLE python3 PATHS ENV PATH) + +# determine python version string +if(PYTHON_EXECUTABLE) + execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c + "import sys; sys.stdout.write(';'.join([str(x) for x in sys.version_info[:3]]))" + OUTPUT_VARIABLE _VERSION + RESULT_VARIABLE _PYTHON_VERSION_RESULT + ERROR_QUIET) + + if(NOT _PYTHON_VERSION_RESULT) + list(GET _VERSION 0 _PYTHON_VERSION_MAJOR) + list(GET _VERSION 1 _PYTHON_VERSION_MINOR) + list(GET _VERSION 2 _PYTHON_VERSION_PATCH) + list(JOIN _VERSION "." _PYTHON_VERSION_STRING) + set(_VERSION_STRING "${_PYTHON_VERSION_MAJOR}.${_PYTHON_VERSION_MINOR}") + unset(_VERSION) + unset(_PYTHON_VERSION_RESULT) + # it must be an acceptable version + list( FIND _PYTHON_VERSIONS ${_VERSION_STRING} _ACC_VER) + if ( ${_ACC_VER} GREATER -1 ) + # determine if this is a Python.framework + # ie. /Library/Frameworks/Python.framework/Versions/3.13/bin/python3 + # ie. /Users/kbhend/ndevpython/libraries/Frameworks/Python.framework/Versions/3.13/bin/python3 + + string(REPLACE "/" ";" _PATH_SEGMENTS ${PYTHON_EXECUTABLE}) + set(_PATH_LIST ${_PATH_SEGMENTS} ) + unset(_PATH_SEGMENTS) + # message("Excutable Path: " ${PYTHON_EXECUTABLE} ) + list( FIND _PATH_LIST "Python.framework" _ISFWK ) + if( ${_ISFWK} GREATER -1 ) + # message("Found framework: " ${_ISFWK} ) + set(PYTHON_VERSION_MAJOR ${_PYTHON_VERSION_MAJOR}) + set(PYTHON_VERSION_MINOR ${_PYTHON_VERSION_MINOR}) + set(PYTHON_VERSION_PATCH ${_PYTHON_VERSION_PATCH}) + set(PYTHON_VERSION_STRING ${_PYTHON_VERSION_STRING}) + list(POP_BACK _PATH_LIST) # remove python3 + list(POP_BACK _PATH_LIST) # remove bin + list(POP_BACK _PATH_LIST _CURRENT_VERSION) + list(POP_BACK _PATH_LIST) # remove Versions + list(JOIN _PATH_LIST "/" PYTHON_FRAMEWORK_BASE) + # Remember to add back the leading "/" since these are absolute paths + set(PYTHON_LIBRARIES "/${PYTHON_FRAMEWORK_BASE}/Versions/${_CURRENT_VERSION}/lib/libpython${_VERSION_STRING}.dylib") + set(PYTHON_INCLUDE_DIRS "/${PYTHON_FRAMEWORK_BASE}/Versions/${_CURRENT_VERSION}/include/python${_VERSION_STRING}") + # message("PYTHON_LIBRARIES: " ${PYTHON_LIBRARIES}) + # message("PYTHON_INCLUDE_DIRS: " ${PYTHON_INCLUDE_DIRS}) + endif() + unset(_PYTHON_VERSION_MAJOR) + unset(_PYTHON_VERSION_MINOR) + unset(_PYTHON_VERSION_PATCH) + unset(_VERSION_STRING) + unset(_PATH_LIST) + endif() + endif() +endif() + +# handle the QUIETLY and REQUIRED arguments and set PYTHONMACFRAMEWORK_FOUND to TRUE if +# all listed variables are TRUE +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PythonMacFramework REQUIRED_VARS PYTHON_EXECUTABLE + PYTHON_VERSION_MAJOR + PYTHON_VERSION_MINOR + PYTHON_VERSION_PATCH + PYTHON_LIBRARIES + PYTHON_INCLUDE_DIRS + VERSION_VAR PYTHON_VERSION_STRING) +mark_as_advanced(PYTHON_EXECUTABLE) +mark_as_advanced(PYTHON_LIBRARIES) +mark_as_advanced(PYTHON_INCLUDE_DIRS)