Skip to content

Commit c385b1b

Browse files
committed
COMP: Enhance Python wrapping logic and configuration
- Refactored Python LIMITED_API setup for improved modularity and reusability. - Adjusted `itk_end_wrap_module.cmake` to properly handle LIMITED_API settings. - Updated `ITKSetPython3Vars.cmake` to streamline Python version selection and configuration. - Introduced logic to infer `Python3_ROOT_DIR` when `Python3_EXECUTABLE` is specified. - Improved maintainability by centralizing Python-related configuration and ensuring consistency across CMake files. - Fix linking of ITKCommon when including python support
1 parent 2d235a5 commit c385b1b

File tree

3 files changed

+169
-79
lines changed

3 files changed

+169
-79
lines changed

CMake/ITKSetPython3Vars.cmake

Lines changed: 152 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,93 +6,120 @@
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-
109
cmake_policy(SET CMP0094 NEW) # makes FindPython3 prefer activated virtualenv Python to latest version
1110
set(PYTHON_VERSION_MIN 3.9)
1211
set(PYTHON_VERSION_MAX 3.999)
13-
if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug")
14-
set(
15-
Python3_FIND_ABI
16-
"OFF" # pydebug - use non-debug python even for debug builds
17-
"ANY" # pymalloc
18-
"ANY" # unicode
19-
"ANY" # gil_disabled
12+
13+
if(NOT PYTHON_DEVELOPMENT_REQUIRED)
14+
# if not PYTHON_DEVELOPMENT_REQUIRED, just find some version of
15+
# Python (don't need to be as specific)
16+
find_package(
17+
Python3
18+
${PYTHON_VERSION_MIN}...${PYTHON_VERSION_MAX}
19+
COMPONENTS
20+
Interpreter
2021
)
2122
else()
2223
set(
2324
Python3_FIND_ABI
24-
"OFF" # pydebug
25+
"OFF" # pydebug - use non-debug python even for debug builds
2526
"ANY" # pymalloc
2627
"ANY" # unicode
27-
"ANY" # gil_disabled
2828
)
29-
endif()
29+
# For CMake >= 3.30, append an additional ANY to Python3_FIND_ABI to
30+
# accommodate newer FindPython3 ABI fields (e.g., free-threaded builds).
31+
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30")
32+
list(APPEND Python3_FIND_ABI "ANY") # gil_disabled
33+
endif()
3034

31-
if(PYTHON_DEVELOPMENT_REQUIRED)
3235
if(DEFINED Python3_EXECUTABLE) # if already specified
3336
set(_specified_Python3_EXECUTABLE ${Python3_EXECUTABLE})
3437
endif()
3538
# set(Python3_FIND_REGISTRY LAST) # default is FIRST. Do we need/want this?
3639
find_package(
40+
# Used to find the exact version for setting ITK_WRAP_PYTHON_VERSION
41+
# NOTE: no REQUIRED FLAG during this call to find_package, just search
42+
# for a best fit version to support wrapping
3743
Python3
3844
${PYTHON_VERSION_MIN}...${PYTHON_VERSION_MAX}
3945
COMPONENTS
4046
Interpreter
47+
Development
4148
Development.Module
42-
${SKBUILD_SABI_COMPONENT}
43-
NumPy # Required for ITK, prefer to fail early
49+
Development.SABIModule
50+
NumPy
4451
)
52+
if(ITK_WRAP_PYTHON)
53+
set(ITK_WRAP_PYTHON_VERSION "${Python3_VERSION}")
54+
else()
55+
set(ITK_WRAP_PYTHON_VERSION "ITK_WRAP_PYTHON=OFF")
56+
endif()
57+
4558
# start section to define package components based on LIMITED_API support and choices
4659
set(_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION 3.11)
47-
if(
48-
ITK_WRAP_PYTHON_VERSION
49-
VERSION_GREATER_EQUAL
50-
${_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION}
60+
61+
# Force ITK_WRAP_PYTHON_VERSION if SKBUILD_SABI_COMPONENT requests it
62+
string(
63+
FIND
64+
"${SKBUILD_SABI_COMPONENT}"
65+
"${SABIModule}"
66+
_SKBUILD_SABI_COMPONENT_REQUIRED
5167
)
68+
if(NOT DEFINED ITK_USE_PYTHON_LIMITED_API)
69+
if(
70+
(
71+
_SKBUILD_SABI_COMPONENT_REQUIRED
72+
GREATER
73+
-1
74+
)
75+
OR
76+
ITK_WRAP_PYTHON_VERSION
77+
VERSION_GREATER_EQUAL
78+
${_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION}
79+
)
80+
set(
81+
ITK_USE_PYTHON_LIMITED_API
82+
1
83+
CACHE BOOL
84+
"Configure Python's limited API for Python minor version compatibility."
85+
)
86+
else()
87+
set(
88+
ITK_USE_PYTHON_LIMITED_API
89+
0
90+
CACHE BOOL
91+
"Configure Python's limited API for Python minor version compatibility."
92+
)
93+
endif()
94+
mark_as_advanced(ITK_USE_PYTHON_LIMITED_API)
95+
endif()
96+
unset(_SKBUILD_SABI_COMPONENT_REQUIRED)
97+
if(ITK_USE_PYTHON_LIMITED_API)
5298
set(
53-
ITK_USE_PYTHON_LIMITED_API
54-
1
55-
CACHE BOOL
56-
"Configure Python's limited API for Python minor version compatibility."
99+
_python_find_components
100+
Interpreter
101+
Development
102+
Development.SABIModule
103+
NumPy # NumPy Required for ITK, prefer to fail early
57104
)
58105
else()
59106
set(
60-
ITK_USE_PYTHON_LIMITED_API
61-
0
62-
CACHE BOOL
63-
"Configure Python's limited API for Python minor version compatibility."
107+
_python_find_components
108+
Interpreter
109+
Development
110+
Development.Module
111+
NumPy # NumPy Required for ITK, prefer to fail early
64112
)
65113
endif()
66-
mark_as_advanced(ITK_USE_PYTHON_LIMITED_API)
67114

68-
set(_development_component "Development.Module")
69-
set(_Python3_ABI_SETTINGS)
70-
if(ITK_USE_PYTHON_LIMITED_API)
71-
set(_development_component "Development.SABIModule")
72-
set(
73-
_Python3_ABI_SETTINGS
74-
USE_SABI
75-
${_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION}
76-
WITH_SOABI
77-
)
78-
endif()
79-
set(
80-
_py_components
81-
Interpreter
82-
${_development_component}
83-
NumPy # Required for ITK, prefer to fail early
84-
${SKBUILD_SABI_COMPONENT}
85-
)
86-
list(REMOVE_DUPLICATES _py_components)
87115
find_package(
88116
Python3
89117
${ITK_WRAP_PYTHON_VERSION} # Force python version to match previously found
90118
REQUIRED
91119
COMPONENTS
92-
${_py_components}
120+
${_python_find_components}
93121
)
94-
unset(_py_components)
95-
unset(_development_component)
122+
unset(_python_find_components)
96123
unset(_ITK_MINIMUM_SUPPORTED_LIMITED_API_VERSION)
97124
# end section to define package components based on LIMITED_API support and choices
98125
if(DEFINED _specified_Python3_EXECUTABLE)
@@ -104,35 +131,81 @@ if(PYTHON_DEVELOPMENT_REQUIRED)
104131
FORCE
105132
)
106133
endif()
107-
else() # if not PYTHON_DEVELOPMENT_REQUIRED, just find some version of Python (don't need to be as specific)
108-
find_package(
109-
Python3
110-
${PYTHON_VERSION_MIN}...${PYTHON_VERSION_MAX}
111-
COMPONENTS
112-
Interpreter
113-
)
114-
endif()
115-
if(ITK_WRAP_PYTHON)
116-
set(ITK_WRAP_PYTHON_VERSION "${Python3_VERSION}")
117-
else()
118-
set(ITK_WRAP_PYTHON_VERSION "ITK_WRAP_PYTHON=OFF")
119-
endif()
120-
if(NOT Python3_EXECUTABLE AND _specified_Python3_EXECUTABLE) # workaround for cases where FindPython3 fails to set correctly
121-
set(
122-
Python3_EXECUTABLE
123-
${_specified_Python3_EXECUTABLE}
124-
CACHE INTERNAL
125-
"Path to the Python interpreter"
126-
FORCE
134+
135+
if(NOT Python3_EXECUTABLE AND _specified_Python3_EXECUTABLE) # workaround for cases where FindPython3 fails to set correctly
136+
set(
137+
Python3_EXECUTABLE
138+
${_specified_Python3_EXECUTABLE}
139+
CACHE INTERNAL
140+
"Path to the Python interpreter"
141+
FORCE
142+
)
143+
endif()
144+
145+
# If a specific Python3_EXECUTABLE is provided by the user, try to infer
146+
# the corresponding Python3_ROOT_DIR for Unix/macOS/Linux so CMake's
147+
# FindPython3 locates the matching installation or virtual environment.
148+
# This is especially important for virtualenv/venv/conda environments.
149+
if(
150+
DEFINED
151+
Python3_EXECUTABLE
152+
AND
153+
NOT
154+
DEFINED
155+
Python3_ROOT_DIR
156+
AND
157+
(
158+
UNIX
159+
OR
160+
APPLE
161+
)
162+
AND
163+
NOT
164+
WIN32
127165
)
128-
endif()
166+
# First, try sys.prefix from the provided interpreter (works for venv/conda)
167+
execute_process(
168+
COMMAND
169+
"${Python3_EXECUTABLE}" -c "import sys; print(sys.prefix)"
170+
OUTPUT_VARIABLE _py_prefix
171+
ERROR_VARIABLE _py_prefix_err
172+
OUTPUT_STRIP_TRAILING_WHITESPACE
173+
)
174+
if(_py_prefix)
175+
file(TO_CMAKE_PATH "${_py_prefix}" _py_root_hint)
176+
endif()
129177

130-
# Add user-visible cache entry
131-
set(
132-
Python3_ROOT_DIR
133-
${Python3_ROOT_DIR}
134-
CACHE PATH
135-
"Which installation or virtual environment of Python to use"
136-
FORCE
137-
)
138-
mark_as_advanced(Python3_ROOT_DIR)
178+
# Fallback: parent of the interpreter's bin directory, e.g., /path/to/env
179+
# from /path/to/env/bin/python3
180+
if(NOT _py_root_hint)
181+
get_filename_component(_py_exe_dir "${Python3_EXECUTABLE}" DIRECTORY)
182+
get_filename_component(_py_root_hint "${_py_exe_dir}/.." REALPATH)
183+
endif()
184+
185+
if(_py_root_hint)
186+
set(
187+
Python3_ROOT_DIR
188+
"${_py_root_hint}"
189+
CACHE PATH
190+
"Which installation or virtual environment of Python to use"
191+
FORCE
192+
)
193+
mark_as_advanced(Python3_ROOT_DIR)
194+
endif()
195+
unset(_py_prefix)
196+
unset(_py_prefix_err)
197+
unset(_py_exe_dir)
198+
unset(_py_root_hint)
199+
endif()
200+
if(Python3_ROOT_DIR)
201+
# Add user-visible cache entry if Python3_ROOT_DIR value is set
202+
set(
203+
Python3_ROOT_DIR
204+
${Python3_ROOT_DIR}
205+
CACHE PATH
206+
"Which installation or virtual environment of Python to use"
207+
FORCE
208+
)
209+
mark_as_advanced(Python3_ROOT_DIR)
210+
endif()
211+
endif()

Modules/Core/Common/src/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ if(UNIX)
254254
target_link_libraries(ITKCommon LINK_PUBLIC ${CMAKE_DL_LIBS})
255255
endif()
256256
endif()
257+
if(ITK_WRAP_PYTHON)
258+
if(ITK_USE_PYTHON_LIMITED_API)
259+
target_link_libraries(ITKCommon PRIVATE Python3::SABIModule)
260+
else()
261+
target_link_libraries(ITKCommon PRIVATE Python3::Module)
262+
endif()
263+
endif()
257264

258265
if(ITK_USE_TBB)
259266
# https://software.intel.com/en-us/node/506139

Wrapping/macro_files/itk_end_wrap_module.cmake

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,16 @@ ${DO_NOT_WAIT_FOR_THREADS_CALLS}
483483
# build all the c++ files from this module in a common lib
484484
if(NOT TARGET ${lib})
485485
# -- START PYTHON MODULE CREATION
486+
if(ITK_USE_PYTHON_LIMITED_API)
487+
set(
488+
_Python3_ABI_SETTINGS
489+
USE_SABI
490+
${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}
491+
WITH_SOABI
492+
)
493+
else()
494+
unset(_Python3_ABI_SETTINGS)
495+
endif()
486496

487497
#Python3_add_library sets PREFIX "" and the correct extension suffix automatically.
488498
# No manual SUFFIX editing is needed.

0 commit comments

Comments
 (0)