66# Additionally, setting Python3_EXECUTABLE can be used to set the Python version explicitly, but
77# may become less reliable with newer versions of CMake (as opposed setting FindPython3 HINTS). Current
88# implementation gives preference to active virtualenvs.
9-
109cmake_policy (SET CMP0094 NEW) # makes FindPython3 prefer activated virtualenv Python to latest version
1110set (PYTHON_VERSION_MIN 3.9)
1211set (PYTHON_VERSION_MAX 3.999)
13-
14- if (PYTHON_DEVELOPMENT_REQUIRED)
15- if (DEFINED Python3_EXECUTABLE) # if already specified
16- set (_specified_Python3_EXECUTABLE ${Python3_EXECUTABLE} )
12+ if (DEFINED Python3_EXECUTABLE) # if already specified
13+ set (_specified_Python3_EXECUTABLE ${Python3_EXECUTABLE} )
14+ # If a specific Python executable is provided, lock the Python version range
15+ # to the exact version of that executable so FindPython3 searches that version.
16+ execute_process (
17+ COMMAND
18+ "${_specified_Python3_EXECUTABLE} " -c
19+ "import sys; print('{}.{}'.format(sys.version_info[0], sys.version_info[1]))"
20+ OUTPUT_VARIABLE _specified_Python3_VERSION_MM
21+ ERROR_VARIABLE _specified_Python3_VERSION_MM_ERR
22+ OUTPUT_STRIP_TRAILING_WHITESPACE
23+ )
24+ if (_specified_Python3_VERSION_MM)
25+ set (PYTHON_VERSION_MIN ${_specified_Python3_VERSION_MM} )
26+ set (PYTHON_VERSION_MAX ${_specified_Python3_VERSION_MM} )
1727 endif ()
28+ unset (_specified_Python3_VERSION_MM)
29+ unset (_specified_Python3_VERSION_MM_ERR)
30+ endif ()
31+
32+ # Tested in cmake 3.26-4.21. Python3_FIND_ABI causes the Development COMPONENTS to not be found
33+ unset (Python3_FIND_ABI)
34+
35+ if (NOT PYTHON_DEVELOPMENT_REQUIRED)
36+ # if not PYTHON_DEVELOPMENT_REQUIRED, just find some version of
37+ # Python (don't need to be as specific)
38+ find_package (
39+ Python3
40+ ${PYTHON_VERSION_MIN} ...${PYTHON_VERSION_MAX}
41+ COMPONENTS
42+ Interpreter
43+ )
44+ set (ITK_WRAP_PYTHON_VERSION "ITK_WRAP_PYTHON=OFF" )
45+ else ()
1846 # set(Python3_FIND_REGISTRY LAST) # default is FIRST. Do we need/want this?
1947 find_package (
2048 Python3
2149 ${PYTHON_VERSION_MIN} ...${PYTHON_VERSION_MAX}
2250 COMPONENTS
51+ Interpreter
52+ # NOTE: dockcross build environments do not supply
53+ # `Development` python-dev resources
54+ Development.Module
55+ Development.SABIModule
56+ NumPy
57+ )
58+ set (ITK_WRAP_PYTHON_VERSION "${Python3_VERSION} " )
59+
60+ # start section to define package components based on LIMITED_API support and choices
61+ set (_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION 3.11)
62+
63+ # Force ITK_WRAP_PYTHON_VERSION if SKBUILD_SABI_COMPONENT requests it
64+ string (
65+ FIND
66+ "${SKBUILD_SABI_COMPONENT} "
67+ "SABIModule"
68+ _SKBUILD_SABI_COMPONENT_REQUIRED
69+ )
70+ if (NOT DEFINED ITK_USE_PYTHON_LIMITED_API)
71+ if (
72+ (
73+ _SKBUILD_SABI_COMPONENT_REQUIRED
74+ GREATER
75+ -1
76+ )
77+ OR
78+ ITK_WRAP_PYTHON_VERSION
79+ VERSION_GREATER_EQUAL
80+ ${_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION}
81+ )
82+ set (
83+ ITK_USE_PYTHON_LIMITED_API
84+ 1
85+ CACHE BOOL
86+ "Configure Python's limited API for Python minor version compatibility."
87+ )
88+ else ()
89+ set (
90+ ITK_USE_PYTHON_LIMITED_API
91+ 0
92+ CACHE BOOL
93+ "Configure Python's limited API for Python minor version compatibility."
94+ )
95+ endif ()
96+ mark_as_advanced (ITK_USE_PYTHON_LIMITED_API)
97+ endif ()
98+ unset (_SKBUILD_SABI_COMPONENT_REQUIRED)
99+ if (ITK_USE_PYTHON_LIMITED_API)
100+ if (CMAKE_VERSION VERSION_LESS "3.26" )
101+ message (
102+ FATAL_ERROR
103+ "CMake version ${CMAKE_VERSION} is too old for Python limited API wrapping: "
104+ "the FindPython Development.SABIModule component requires CMake >= 3.26. "
105+ "Either upgrade CMake or disable ITK_USE_PYTHON_LIMITED_API."
106+ )
107+ endif ()
108+
109+ set (
110+ _python_find_components
111+ Interpreter
112+ Development.SABIModule
113+ )
114+ else ()
115+ set (
116+ _python_find_components
23117 Interpreter
24118 Development.Module
25- ${SKBUILD_SABI_COMPONENT}
119+ )
120+ endif ()
121+ if (BUILD_TESTING)
122+ # NumPy Required for testing ITK PythonTests, prefer to fail early if not installed
123+ list (APPEND _python_find_components NumPy)
124+ endif ()
125+ set (_missing_required_component FALSE )
126+ find_package (
127+ Python3
128+ ${ITK_WRAP_PYTHON_VERSION}
129+ COMPONENTS
130+ ${_python_find_components}
26131 )
132+ set (ITK_WRAP_PYTHON_VERSION "${Python3_VERSION} " )
133+ message (STATUS "Python3_FOUND=${Python3_FOUND} " )
134+ foreach (_required_component ${_python_find_components} )
135+ if (NOT Python3_${_required_component} _FOUND)
136+ message (
137+ STATUS
138+ " o Python3 Missing COMPONENT: Python3_${_required_component} _FOUND: ${Python3_${_required_component} _FOUND}"
139+ )
140+ set (_missing_required_component TRUE )
141+ else ()
142+ message (
143+ STATUS
144+ " o Python3 Found COMPONENT: Python3_${_required_component} _FOUND: ${Python3_${_required_component} _FOUND}"
145+ )
146+ endif ()
147+ endforeach ()
148+ unset (_required_component)
149+ if (_missing_required_component)
150+ message (
151+ FATAL_ERROR
152+ "At least 1 required Python3 COMPONENT could not be found from : ${_python_find_components}
153+ in range ${PYTHON_VERSION_MIN} ...${PYTHON_VERSION_MAX} :
154+ Python3_EXECUTABLE=:${Python3_EXECUTABLE} :
155+ ITK_WRAP_PYTHON_VERSION=:${ITK_WRAP_PYTHON_VERSION} :
156+ Python3_ROOT_DIR=:${Python3_ROOT_DIR} :
157+ ---
158+ Python3_FOUND=${Python3_FOUND}
159+ Python3_Interpreter_FOUND=${Python3_Interpreter_FOUND}
160+ Python3_Compiler_FOUND=${Python3_Compiler_FOUND}
161+ Python3_Development_FOUND=${Python3_Development_FOUND}
162+ Python3_Development.Module_FOUND=${Python3_Development.Module_FOUND}
163+ Python3_Development.SABIModule_FOUND=${Python3_Development.SABIModule_FOUND}
164+ Python3_Development.Embed_FOUND=${Python3_Development.Embed_FOUND}
165+ Python3_NumPy_FOUND=${Python3_NumPy_FOUND}
166+ "
167+ )
168+ else ()
169+ message (STATUS " o Python3_EXECUTABLE=${Python3_EXECUTABLE} " )
170+ message (STATUS " o Python3_ROOT_DIR=${Python3_ROOT_DIR} " )
171+ message (STATUS " o ITK_WRAP_PYTHON_VERSION=${ITK_WRAP_PYTHON_VERSION} " )
172+ endif ()
173+ unset (_missing_required_component)
174+ unset (_python_find_components)
175+ unset (_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION)
176+ # end section to define package components based on LIMITED_API support and choices
27177 if (DEFINED _specified_Python3_EXECUTABLE)
28178 set (
29179 Python3_EXECUTABLE
@@ -33,35 +183,81 @@ if(PYTHON_DEVELOPMENT_REQUIRED)
33183 FORCE
34184 )
35185 endif ()
36- else () # if not PYTHON_DEVELOPMENT_REQUIRED, just find some version of Python (don't need to be as specific)
37- find_package (
38- Python3
39- ${PYTHON_VERSION_MIN} ...${PYTHON_VERSION_MAX}
40- COMPONENTS
41- Interpreter
42- )
43- endif ()
44- if (ITK_WRAP_PYTHON)
45- set (ITK_WRAP_PYTHON_VERSION "${Python3_VERSION} " )
46- else ()
47- set (ITK_WRAP_PYTHON_VERSION "ITK_WRAP_PYTHON=OFF" )
48- endif ()
49- if (NOT Python3_EXECUTABLE AND _specified_Python3_EXECUTABLE) # workaround for cases where FindPython3 fails to set correctly
50- set (
51- Python3_EXECUTABLE
52- ${_specified_Python3_EXECUTABLE}
53- CACHE INTERNAL
54- "Path to the Python interpreter"
55- FORCE
186+
187+ if (NOT Python3_EXECUTABLE AND _specified_Python3_EXECUTABLE) # workaround for cases where FindPython3 fails to set correctly
188+ set (
189+ Python3_EXECUTABLE
190+ ${_specified_Python3_EXECUTABLE}
191+ CACHE INTERNAL
192+ "Path to the Python interpreter"
193+ FORCE
194+ )
195+ endif ()
196+
197+ # If a specific Python3_EXECUTABLE is provided by the user, try to infer
198+ # the corresponding Python3_ROOT_DIR for Unix/macOS/Linux so CMake's
199+ # FindPython3 locates the matching installation or virtual environment.
200+ # This is especially important for virtualenv/venv/conda environments.
201+ if (
202+ DEFINED
203+ Python3_EXECUTABLE
204+ AND
205+ NOT
206+ DEFINED
207+ Python3_ROOT_DIR
208+ AND
209+ (
210+ UNIX
211+ OR
212+ APPLE
213+ )
214+ AND
215+ NOT
216+ WIN32
56217 )
57- endif ()
218+ # First, try sys.prefix from the provided interpreter (works for venv/conda)
219+ execute_process (
220+ COMMAND
221+ "${Python3_EXECUTABLE} " -c "import sys; print(sys.prefix)"
222+ OUTPUT_VARIABLE _py_prefix
223+ ERROR_VARIABLE _py_prefix_err
224+ OUTPUT_STRIP_TRAILING_WHITESPACE
225+ )
226+ if (_py_prefix)
227+ file (TO_CMAKE_PATH "${_py_prefix} " _py_root_hint)
228+ endif ()
229+
230+ # Fallback: parent of the interpreter's bin directory, e.g., /path/to/env
231+ # from /path/to/env/bin/python3
232+ if (NOT _py_root_hint)
233+ get_filename_component (_py_exe_dir "${Python3_EXECUTABLE} " DIRECTORY )
234+ get_filename_component (_py_root_hint "${_py_exe_dir} /.." REALPATH)
235+ endif ()
58236
59- # Add user-visible cache entry
60- set (
61- Python3_ROOT_DIR
62- ${Python3_ROOT_DIR}
63- CACHE PATH
64- "Which installation or virtual environment of Python to use"
65- FORCE
66- )
67- mark_as_advanced (Python3_ROOT_DIR)
237+ if (_py_root_hint)
238+ set (
239+ Python3_ROOT_DIR
240+ "${_py_root_hint} "
241+ CACHE PATH
242+ "Which installation or virtual environment of Python to use"
243+ FORCE
244+ )
245+ mark_as_advanced (Python3_ROOT_DIR)
246+ endif ()
247+ unset (_py_prefix)
248+ unset (_py_prefix_err)
249+ unset (_py_exe_dir)
250+ unset (_py_root_hint)
251+ endif ()
252+ if (Python3_ROOT_DIR)
253+ # Add user-visible cache entry if Python3_ROOT_DIR value is set
254+ set (
255+ Python3_ROOT_DIR
256+ ${Python3_ROOT_DIR}
257+ CACHE PATH
258+ "Which installation or virtual environment of Python to use"
259+ FORCE
260+ )
261+ mark_as_advanced (Python3_ROOT_DIR)
262+ endif ()
263+ endif ()
0 commit comments