Skip to content

Commit 47ec96d

Browse files
committed
python: add experimental nanobind bindings (ImageSpec, ParamValue, ROI, TypeDesc)
Build as optional oiio_python_nanobind target; share tests with the pybind11 module. Documents migration status in src/python-nanobind/MIGRATION_STATUS.md. Assisted-by: Cursor/Composer-2 Signed-off-by: Aleksandr Motsjonov <soswow@gmail.com>
1 parent 3602148 commit 47ec96d

27 files changed

Lines changed: 1902 additions & 88 deletions

CMakeLists.txt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,6 @@ if (TEX_BATCH_SIZE)
142142
add_compile_definitions (OIIO_TEXTURE_SIMD_BATCH_WIDTH=${TEX_BATCH_SIZE})
143143
endif ()
144144

145-
# If we're building this just for CI, define a symbol so the code can
146-
# tell (and reduce complexity of some tests)
147-
set_option (OIIO_CI "Set if this build is for our GHA CI" OFF)
148-
if (OIIO_CI)
149-
add_compile_definitions (OIIO_CI=1)
150-
endif ()
151145

152146
# Namespace settings
153147
#
@@ -314,7 +308,12 @@ else ()
314308
set (_py_dev_found Python3_Development.Module_FOUND)
315309
endif ()
316310
if (USE_PYTHON AND ${_py_dev_found} AND NOT BUILD_OIIOUTIL_ONLY)
317-
add_subdirectory (src/python)
311+
if (OIIO_BUILD_PYTHON_PYBIND11)
312+
add_subdirectory (src/python)
313+
endif ()
314+
if (OIIO_BUILD_PYTHON_NANOBIND)
315+
add_subdirectory (src/python-nanobind)
316+
endif ()
318317
else ()
319318
message (STATUS "Not building Python bindings: USE_PYTHON=${USE_PYTHON}, Python3_Development.Module_FOUND=${Python3_Development.Module_FOUND}")
320319
endif ()

INSTALL.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**.
4242
* Python >= 3.9 (tested through 3.13).
4343
* pybind11 >= 2.7 (tested through 3.0)
4444
* NumPy (tested through 2.4.4)
45+
* If you enable the optional nanobind (WIP) backend for source/CMake
46+
builds (`OIIO_PYTHON_BINDINGS_BACKEND` is `nanobind` or `both`):
47+
* nanobind discoverable by CMake, or installed in the active Python
48+
environment so `python -m nanobind --cmake_dir` works
4549
* If you want support for PNG files:
4650
* libPNG >= 1.6.0 (tested though 1.6.56)
4751
* If you want support for camera "RAW" formats:
@@ -157,6 +161,12 @@ Make wrapper (`make PkgName_ROOT=...`).
157161

158162
`USE_PYTHON=0` : Omits building the Python bindings.
159163

164+
`OIIO_PYTHON_BINDINGS_BACKEND=pybind11|nanobind|both` : Select which Python
165+
binding backend(s) to configure for source/CMake builds. `both` keeps the
166+
existing pybind11 module and also builds the nanobind (WIP) module. The
167+
Python packaging path driven by `pyproject.toml` still targets the production
168+
pybind11 bindings today.
169+
160170
`OIIO_BUILD_TESTS=0` : Omits building tests (you probably don't need them
161171
unless you are a developer of OIIO or want to verify that your build
162172
passes all tests).
@@ -247,6 +257,7 @@ Additionally, a few helpful modifiers alter some build-time options:
247257
| make USE_QT=0 ... | Skip anything that needs Qt |
248258
| make MYCC=xx MYCXX=yy ... | Use custom compilers |
249259
| make USE_PYTHON=0 ... | Don't build the Python binding |
260+
| make OIIO_PYTHON_BINDINGS_BACKEND=both ... | For source/CMake builds, build the existing pybind11 bindings and the nanobind (WIP) module |
250261
| make BUILD_SHARED_LIBS=0 | Build static library instead of shared |
251262
| make IGNORE_HOMEBREWED_DEPS=1 | Ignore homebrew-managed dependencies |
252263
| make LINKSTATIC=1 ... | Link with static external libraries when possible |

src/build-scripts/ci-startup.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages:$P
3636
export COMPILER=${COMPILER:=gcc}
3737
export CC=${CC:=gcc}
3838
export CXX=${CXX:=g++}
39-
export OpenImageIO_CI=1
39+
export OpenImageIO_CI=true
4040
export USE_NINJA=${USE_NINJA:=1}
4141
export CMAKE_GENERATOR=${CMAKE_GENERATOR:=Ninja}
4242
export CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:=Release}

src/cmake/externalpackages.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,13 @@ endif()
118118
if (USE_PYTHON)
119119
find_python()
120120
endif ()
121-
if (USE_PYTHON)
121+
if (USE_PYTHON AND OIIO_BUILD_PYTHON_PYBIND11)
122122
checked_find_package (pybind11 REQUIRED VERSION_MIN 2.7)
123123
endif ()
124+
if (USE_PYTHON AND OIIO_BUILD_PYTHON_NANOBIND)
125+
discover_nanobind_cmake_dir()
126+
checked_find_package (nanobind CONFIG REQUIRED)
127+
endif ()
124128

125129

126130
###########################################################################

src/cmake/pythonutils.cmake

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@ set (PYTHON_VERSION "" CACHE STRING "Target version of python to find")
88
option (PYLIB_INCLUDE_SONAME "If ON, soname/soversion will be set for Python module library" OFF)
99
option (PYLIB_LIB_PREFIX "If ON, prefix the Python module with 'lib'" OFF)
1010
set (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 ()
1136
if (WIN32)
1237
set (PYLIB_LIB_TYPE SHARED CACHE STRING "Type of library to build for python module (MODULE or SHARED)")
1338
else ()
@@ -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}")
72111
endmacro()
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

164227
endmacro ()
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 ()

src/cmake/testing.cmake

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ set(OIIO_TESTSUITE_IMAGEDIR "${PROJECT_BINARY_DIR}/testsuite" CACHE PATH
3333
# [ DISABLEVAR variable_name ... ]
3434
# [ SUFFIX suffix ]
3535
# [ ENVIRONMENT "VAR=value" ... ]
36+
# [ ENVIRONMENT_MODIFICATION "VAR=op:value" ... ]
3637
# )
3738
#
3839
# The optional argument IMAGEDIR is used to check whether external test images
@@ -55,8 +56,12 @@ set(OIIO_TESTSUITE_IMAGEDIR "${PROJECT_BINARY_DIR}/testsuite" CACHE PATH
5556
# The optional ENVIRONMENT is a list of environment variables to set for the
5657
# test.
5758
#
59+
# The optional ENVIRONMENT_MODIFICATION is a list of environment variable
60+
# modifications in the format accepted by the CTest ENVIRONMENT_MODIFICATION
61+
# property.
62+
#
5863
macro (oiio_add_tests)
59-
cmake_parse_arguments (_ats "" "SUFFIX;TESTNAME" "URL;IMAGEDIR;LABEL;FOUNDVAR;ENABLEVAR;DISABLEVAR;ENVIRONMENT" ${ARGN})
64+
cmake_parse_arguments (_ats "" "SUFFIX;TESTNAME" "URL;IMAGEDIR;LABEL;FOUNDVAR;ENABLEVAR;DISABLEVAR;ENVIRONMENT;ENVIRONMENT_MODIFICATION" ${ARGN})
6065
# Arguments: <prefix> <options> <one_value_keywords> <multi_value_keywords> args...
6166
set (_ats_testdir "${OIIO_TESTSUITE_IMAGEDIR}/${_ats_IMAGEDIR}")
6267
# If there was a FOUNDVAR param specified and that variable name is
@@ -131,6 +136,11 @@ macro (oiio_add_tests)
131136
"OIIO_TESTSUITE_CUR=${_testdir}"
132137
"Python_EXECUTABLE=${Python3_EXECUTABLE}"
133138
${_ats_ENVIRONMENT})
139+
if (_ats_ENVIRONMENT_MODIFICATION)
140+
set_property(TEST ${_testname} APPEND PROPERTY
141+
ENVIRONMENT_MODIFICATION
142+
${_ats_ENVIRONMENT_MODIFICATION})
143+
endif ()
134144
if (NOT ${_ats_testdir} STREQUAL "")
135145
set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT
136146
"OIIO_TESTSUITE_IMAGEDIR=${_ats_testdir}")
@@ -228,25 +238,48 @@ macro (oiio_add_all_tests)
228238
# Python interpreter itself won't be linked with the right asan
229239
# libraries to run correctly.
230240
if (USE_PYTHON AND NOT BUILD_OIIOUTIL_ONLY AND NOT SANITIZE)
231-
oiio_add_tests (
232-
docs-examples-python
233-
python-colorconfig
234-
python-deep
235-
python-imagebuf
236-
python-imagecache
237-
python-imageoutput
238-
python-imagespec
239-
python-paramlist
240-
python-roi
241-
python-texturesys
242-
python-typedesc
243-
filters
244-
)
245-
# These Python tests also need access to oiio-images
246-
oiio_add_tests (
247-
python-imageinput python-imagebufalgo
248-
IMAGEDIR oiio-images
249-
)
241+
set (pybind11_python_path_mod
242+
"PYTHONPATH=path_list_prepend:${CMAKE_BINARY_DIR}/lib/python/site-packages")
243+
set (nanobind_python_tests
244+
python-imagespec
245+
python-paramlist
246+
python-roi
247+
python-typedesc)
248+
set (nanobind_python_test_suffix ".nanobind")
249+
if (OIIO_BUILD_PYTHON_PYBIND11)
250+
oiio_add_tests (
251+
docs-examples-python
252+
python-colorconfig
253+
python-deep
254+
python-imagebuf
255+
python-imagecache
256+
python-imageoutput
257+
python-imagespec
258+
python-paramlist
259+
python-roi
260+
python-texturesys
261+
python-typedesc
262+
filters
263+
ENVIRONMENT_MODIFICATION ${pybind11_python_path_mod}
264+
)
265+
# These Python tests also need access to oiio-images
266+
oiio_add_tests (
267+
python-imageinput python-imagebufalgo
268+
IMAGEDIR oiio-images
269+
ENVIRONMENT_MODIFICATION ${pybind11_python_path_mod}
270+
)
271+
else ()
272+
set (nanobind_python_test_suffix "")
273+
endif ()
274+
275+
if (OIIO_BUILD_PYTHON_NANOBIND)
276+
oiio_add_tests (
277+
${nanobind_python_tests}
278+
SUFFIX ${nanobind_python_test_suffix}
279+
ENVIRONMENT_MODIFICATION
280+
"PYTHONPATH=path_list_prepend:${CMAKE_BINARY_DIR}/lib/python/nanobind"
281+
)
282+
endif ()
250283
endif ()
251284

252285
oiio_add_tests (oiiotool-color

src/libOpenImageIO/CMakeLists.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,11 @@ if (OIIO_BUILD_TESTS AND BUILD_TESTING)
263263
LINK_LIBRARIES OpenImageIO Imath::Imath
264264
FOLDER "Unit Tests" NO_INSTALL)
265265
add_test (unit_image_span ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/image_span_test)
266-
set_tests_properties (unit_image_span PROPERTIES COST 10)
267266

268267
fancy_add_executable (NAME imagebuf_test SRC imagebuf_test.cpp
269268
LINK_LIBRARIES OpenImageIO
270269
FOLDER "Unit Tests" NO_INSTALL)
271270
add_test (unit_imagebuf ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/imagebuf_test)
272-
set_tests_properties (unit_imagebuf PROPERTIES COST 30)
273271

274272
fancy_add_executable (NAME imagecache_test SRC imagecache_test.cpp
275273
LINK_LIBRARIES OpenImageIO
@@ -286,7 +284,6 @@ if (OIIO_BUILD_TESTS AND BUILD_TESTING)
286284
FOLDER "Unit Tests" NO_INSTALL)
287285
target_compile_definitions (imagebufalgo_test PRIVATE OIIO_USE_HWY=${_oiio_use_hwy})
288286
add_test (unit_imagebufalgo ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/imagebufalgo_test)
289-
set_tests_properties (unit_imagebufalgo PROPERTIES PROCESSORS 2 COST 60)
290287

291288
fancy_add_executable (NAME imagespec_test SRC imagespec_test.cpp
292289
LINK_LIBRARIES OpenImageIO
@@ -297,7 +294,6 @@ if (OIIO_BUILD_TESTS AND BUILD_TESTING)
297294
LINK_LIBRARIES OpenImageIO
298295
FOLDER "Unit Tests" NO_INSTALL)
299296
add_test (unit_imageinout ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/imageinout_test)
300-
set_tests_properties (unit_imageinout PROPERTIES PROCESSORS 2 COST 30)
301297

302298
if (NOT DEFINED ENV{OpenImageIO_CI})
303299
fancy_add_executable (NAME imagespeed_test SRC imagespeed_test.cpp

0 commit comments

Comments
 (0)