@@ -8,6 +8,31 @@ set (PYTHON_VERSION "" CACHE STRING "Target version of python to find")
88option (PYLIB_INCLUDE_SONAME "If ON, soname/soversion will be set for Python module library" OFF )
99option (PYLIB_LIB_PREFIX "If ON, prefix the Python module with 'lib'" OFF )
1010set (PYMODULE_SUFFIX "" CACHE STRING "Suffix to add to Python module init namespace" )
11+ set (OIIO_PYTHON_BINDINGS_BACKEND "pybind11" CACHE STRING
12+ "Which Python binding backend(s) to build: pybind11, nanobind, or both" )
13+ set_property (CACHE OIIO_PYTHON_BINDINGS_BACKEND PROPERTY STRINGS
14+ pybind11 nanobind both )
15+
16+ # Normalize and validate the user-facing backend selector early so the rest
17+ # of the file can make simple boolean decisions.
18+ string (TOLOWER "${OIIO_PYTHON_BINDINGS_BACKEND} " OIIO_PYTHON_BINDINGS_BACKEND)
19+ if (NOT OIIO_PYTHON_BINDINGS_BACKEND MATCHES "^(pybind11|nanobind|both)$" )
20+ message (FATAL_ERROR
21+ "OIIO_PYTHON_BINDINGS_BACKEND must be one of: pybind11, nanobind, both" )
22+ endif ()
23+
24+ # Derive internal switches used by the top-level CMakeLists and the Python
25+ # helper macros below.
26+ set (OIIO_BUILD_PYTHON_PYBIND11 OFF )
27+ set (OIIO_BUILD_PYTHON_NANOBIND OFF )
28+ if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "pybind11"
29+ OR OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "both" )
30+ set (OIIO_BUILD_PYTHON_PYBIND11 ON )
31+ endif ()
32+ if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind"
33+ OR OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "both" )
34+ set (OIIO_BUILD_PYTHON_NANOBIND ON )
35+ endif ()
1136if (WIN32 )
1237 set (PYLIB_LIB_TYPE SHARED CACHE STRING "Type of library to build for python module (MODULE or SHARED)" )
1338else ()
@@ -54,6 +79,15 @@ macro (find_python)
5479 Python3_Development.Module_FOUND
5580 Python3_Interpreter_FOUND )
5681
82+ if (OIIO_BUILD_PYTHON_NANOBIND)
83+ # nanobind's CMake package expects the generic FindPython targets and
84+ # variables (Python::Module, Python_EXECUTABLE, etc.), not the
85+ # versioned Python3::* targets that the rest of OIIO uses today.
86+ find_package (Python ${Python3_VERSION_MAJOR} .${Python3_VERSION_MINOR}
87+ EXACT REQUIRED
88+ COMPONENTS ${_py_components} )
89+ endif ()
90+
5791 # The version that was found may not be the default or user
5892 # defined one.
5993 set (PYTHON_VERSION_FOUND ${Python3_VERSION_MAJOR} .${Python3_VERSION_MINOR} )
@@ -63,15 +97,44 @@ macro (find_python)
6397 set (PythonInterp3_FIND_VERSION PYTHON_VERSION_FOUND)
6498 set (PythonInterp3_FIND_VERSION_MAJOR ${Python3_VERSION_MAJOR} )
6599
100+ if (NOT DEFINED PYTHON_SITE_ROOT_DIR)
101+ set (PYTHON_SITE_ROOT_DIR
102+ "${CMAKE_INSTALL_LIBDIR} /python${PYTHON_VERSION_FOUND} /site-packages" )
103+ endif ()
66104 if (NOT DEFINED PYTHON_SITE_DIR)
67- set (PYTHON_SITE_DIR "${CMAKE_INSTALL_LIBDIR} /python ${PYTHON_VERSION_FOUND} /site-packages /OpenImageIO" )
105+ set (PYTHON_SITE_DIR "${PYTHON_SITE_ROOT_DIR} /OpenImageIO" )
68106 endif ()
69107 message (VERBOSE " Python site packages dir ${PYTHON_SITE_DIR} " )
108+ message (VERBOSE " Python site packages root ${PYTHON_SITE_ROOT_DIR} " )
70109 message (VERBOSE " Python to include 'lib' prefix: ${PYLIB_LIB_PREFIX} " )
71110 message (VERBOSE " Python to include SO version: ${PYLIB_INCLUDE_SONAME} " )
72111endmacro ()
73112
74113
114+ # Help CMake locate nanobind when it was installed as a Python package.
115+ macro (discover_nanobind_cmake_dir )
116+ if (nanobind_DIR OR nanobind_ROOT OR "$ENV{nanobind_DIR} " OR "$ENV{nanobind_ROOT} " )
117+ return ()
118+ endif ()
119+
120+ if (NOT Python3_Interpreter_FOUND)
121+ return ()
122+ endif ()
123+
124+ execute_process (
125+ COMMAND ${Python3_EXECUTABLE} -m nanobind --cmake_dir
126+ RESULT_VARIABLE _oiio_nanobind_result
127+ OUTPUT_VARIABLE _oiio_nanobind_cmake_dir
128+ OUTPUT_STRIP_TRAILING_WHITESPACE
129+ ERROR_QUIET )
130+ if (_oiio_nanobind_result EQUAL 0
131+ AND EXISTS "${_oiio_nanobind_cmake_dir} /nanobind-config.cmake" )
132+ set (nanobind_DIR "${_oiio_nanobind_cmake_dir} " CACHE PATH
133+ "Path to the nanobind CMake package" FORCE )
134+ endif ()
135+ endmacro ()
136+
137+
75138###########################################################################
76139# pybind11
77140
@@ -163,3 +226,62 @@ macro (setup_python_module)
163226
164227endmacro ()
165228
229+
230+ ###########################################################################
231+ # nanobind
232+
233+ macro (setup_python_module_nanobind )
234+ cmake_parse_arguments (lib "" "TARGET;MODULE"
235+ "SOURCES;LIBS;INCLUDES;SYSTEM_INCLUDE_DIRS;PACKAGE_FILES"
236+ ${ARGN} )
237+
238+ set (target_name ${lib_TARGET} )
239+
240+ if (NOT COMMAND nanobind_add_module)
241+ discover_nanobind_cmake_dir ()
242+ find_package (nanobind CONFIG REQUIRED )
243+ endif ()
244+
245+ nanobind_add_module (${target_name} ${lib_SOURCES} )
246+ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND TARGET nanobind-static)
247+ target_compile_options (nanobind-static PRIVATE -Wno-error=format-nonliteral )
248+ endif ()
249+
250+ target_include_directories (${target_name}
251+ PRIVATE ${lib_INCLUDES} )
252+ target_include_directories (${target_name}
253+ SYSTEM PRIVATE ${lib_SYSTEM_INCLUDE_DIRS} )
254+ target_link_libraries (${target_name}
255+ PRIVATE ${lib_LIBS} )
256+
257+ set (_module_LINK_FLAGS "${VISIBILITY_MAP_COMMAND} ${EXTRA_DSO_LINK_ARGS} " )
258+ if (UNIX AND NOT APPLE )
259+ set (_module_LINK_FLAGS "${_module_LINK_FLAGS} -Wl,--exclude-libs,ALL" )
260+ endif ()
261+ set_target_properties (${target_name} PROPERTIES
262+ LINK_FLAGS ${_module_LINK_FLAGS}
263+ OUTPUT_NAME ${lib_MODULE}
264+ DEBUG_POSTFIX "" )
265+
266+ if (SKBUILD)
267+ set (_nanobind_install_dir .)
268+ else ()
269+ set (_nanobind_install_dir ${PYTHON_SITE_DIR} )
270+ endif ()
271+
272+ # Keep nanobind modules isolated in the build tree so they don't alter
273+ # how the existing top-level OpenImageIO module is imported during tests.
274+ set_target_properties (${target_name} PROPERTIES
275+ LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} /lib/python/nanobind/OpenImageIO
276+ ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} /lib/python/nanobind/OpenImageIO
277+ )
278+
279+ install (TARGETS ${target_name}
280+ RUNTIME DESTINATION ${_nanobind_install_dir} COMPONENT user
281+ LIBRARY DESTINATION ${_nanobind_install_dir} COMPONENT user)
282+
283+ if (lib_PACKAGE_FILES)
284+ install (FILES ${lib_PACKAGE_FILES}
285+ DESTINATION ${_nanobind_install_dir} COMPONENT user)
286+ endif ()
287+ endmacro ()
0 commit comments