Skip to content

Commit d7b6699

Browse files
committed
int(python): unify pybind11 and nanobind into single-source bindings
Remove the parallel src/python-nanobind/ tree and compile one set of binding sources per build, selected by OIIO_PYTHON_BINDINGS_BACKEND (pybind11 or nanobind; drop the "both" mode). Add py_backend.h with namespace py = pybind11|nanobind and small oiio_py:: helpers/macros only where the APIs differ. Migrate roi, typedesc, imagespec, and paramvalue to dual-backend declare_* sources; fold the former py_oiio_nb helpers and partial module init into py_oiio.h / py_oiio.cpp behind #if OIIO_PY_BACKEND_NANOBIND. Install nanobind modules into the same site-packages/OpenImageIO tree as pybind11 (no separate nanobind package or duplicate tests). Update CMake, CI, and INSTALL.md accordingly; move MIGRATION_STATUS.md into src/python/. Materialize string_view returns as std::string in py_imagespec.cpp so nanobind can convert them to Python str. Assisted-by: Cursor / Auto Signed-off-by: Aleksandr Motsjonov <soswow@gmail.com>
1 parent 83fb484 commit d7b6699

23 files changed

Lines changed: 1055 additions & 1938 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ jobs:
300300

301301
- desc: latest releases gcc15 C++23 py3.12 avx2 exr3.4 ocio2.4
302302
nametag: linux-latest-releases
303-
oiio_python_bindings_backend: both
303+
oiio_python_bindings_backend: nanobind
304304
runner: ubuntu-24.04
305305
# cc_compiler: gcc-13
306306
# cxx_compiler: g++-13
@@ -601,7 +601,7 @@ jobs:
601601
- desc: MacOS-14-ARM aclang15/C++20/py3.13
602602
runner: macos-14
603603
nametag: macos14-arm-py313
604-
oiio_python_bindings_backend: both
604+
oiio_python_bindings_backend: nanobind
605605
cc_compiler: /usr/bin/clang
606606
cxx_compiler: /usr/bin/clang++
607607
cxx_std: 20
@@ -685,4 +685,4 @@ jobs:
685685
ctest_test_timeout: "240"
686686
setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1
687687
benchmark: 1
688-
oiio_python_bindings_backend: both
688+
oiio_python_bindings_backend: nanobind

CMakeLists.txt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,7 @@ else ()
314314
set (_py_dev_found Python3_Development.Module_FOUND)
315315
endif ()
316316
if (USE_PYTHON AND ${_py_dev_found} AND NOT BUILD_OIIOUTIL_ONLY)
317-
if (OIIO_BUILD_PYTHON_PYBIND11)
318-
add_subdirectory (src/python)
319-
endif ()
320-
if (OIIO_BUILD_PYTHON_NANOBIND)
321-
add_subdirectory (src/python-nanobind)
322-
endif ()
317+
add_subdirectory (src/python)
323318
else ()
324319
message (STATUS "Not building Python bindings: USE_PYTHON=${USE_PYTHON}, Python3_Development.Module_FOUND=${Python3_Development.Module_FOUND}")
325320
endif ()

INSTALL.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**.
4444
* Python >= 3.9 (tested through 3.13).
4545
* pybind11 >= 2.7 (tested through 3.0)
4646
* NumPy (tested through 2.4.4)
47-
* If you enable the optional nanobind (WIP) backend for source/CMake
48-
builds (`OIIO_PYTHON_BINDINGS_BACKEND` is `nanobind` or `both`):
47+
* If you enable the nanobind (WIP) backend for source/CMake builds
48+
(`OIIO_PYTHON_BINDINGS_BACKEND=nanobind`):
4949
* nanobind discoverable by CMake, or installed in the active Python
5050
environment so `python -m nanobind --cmake_dir` works
5151
* If you want support for PNG files:
@@ -163,11 +163,10 @@ Make wrapper (`make PkgName_ROOT=...`).
163163

164164
`USE_PYTHON=0` : Omits building the Python bindings.
165165

166-
`OIIO_PYTHON_BINDINGS_BACKEND=pybind11|nanobind|both` : Select which Python
167-
binding backend(s) to configure for source/CMake builds. `both` keeps the
168-
existing pybind11 module and also builds the nanobind (WIP) module. The
169-
Python packaging path driven by `pyproject.toml` still targets the production
170-
pybind11 bindings today.
166+
`OIIO_PYTHON_BINDINGS_BACKEND=pybind11|nanobind` : Select the Python binding
167+
backend for source/CMake builds (`pybind11` is the default production
168+
backend; `nanobind` is the in-progress migration backend). The Python
169+
packaging path driven by `pyproject.toml` still targets pybind11 today.
171170

172171
`OIIO_BUILD_TESTS=0` : Omits building tests (you probably don't need them
173172
unless you are a developer of OIIO or want to verify that your build
@@ -259,7 +258,7 @@ Additionally, a few helpful modifiers alter some build-time options:
259258
| make USE_QT=0 ... | Skip anything that needs Qt |
260259
| make MYCC=xx MYCXX=yy ... | Use custom compilers |
261260
| make USE_PYTHON=0 ... | Don't build the Python binding |
262-
| make OIIO_PYTHON_BINDINGS_BACKEND=both ... | For source/CMake builds, build the existing pybind11 bindings and the nanobind (WIP) module |
261+
| make OIIO_PYTHON_BINDINGS_BACKEND=nanobind ... | For source/CMake builds, use the nanobind (WIP) Python bindings instead of pybind11 |
263262
| make BUILD_SHARED_LIBS=0 | Build static library instead of shared |
264263
| make IGNORE_HOMEBREWED_DEPS=1 | Ignore homebrew-managed dependencies |
265264
| make LINKSTATIC=1 ... | Link with static external libraries when possible |

src/cmake/externalpackages.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ endif()
118118
if (USE_PYTHON)
119119
find_python()
120120
endif ()
121-
if (USE_PYTHON AND OIIO_BUILD_PYTHON_PYBIND11)
121+
if (USE_PYTHON AND OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "pybind11")
122122
checked_find_package (pybind11 REQUIRED VERSION_MIN 2.7)
123123
endif ()
124-
if (USE_PYTHON AND OIIO_BUILD_PYTHON_NANOBIND)
124+
if (USE_PYTHON AND OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind")
125125
discover_nanobind_cmake_dir()
126126
checked_find_package (nanobind CONFIG REQUIRED)
127127
endif ()

src/cmake/pythonutils.cmake

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,16 @@ option (PYLIB_INCLUDE_SONAME "If ON, soname/soversion will be set for Python mod
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")
1111
set (OIIO_PYTHON_BINDINGS_BACKEND "pybind11" CACHE STRING
12-
"Which Python binding backend(s) to build: pybind11, nanobind, or both")
12+
"Which Python binding backend to build: pybind11 or nanobind")
1313
set_property (CACHE OIIO_PYTHON_BINDINGS_BACKEND PROPERTY STRINGS
14-
pybind11 nanobind both)
14+
pybind11 nanobind)
1515

16-
# Normalize and validate the user-facing backend selector early so the rest
17-
# of the file can make simple boolean decisions.
1816
string (TOLOWER "${OIIO_PYTHON_BINDINGS_BACKEND}" OIIO_PYTHON_BINDINGS_BACKEND)
19-
if (NOT OIIO_PYTHON_BINDINGS_BACKEND MATCHES "^(pybind11|nanobind|both)$")
17+
if (NOT OIIO_PYTHON_BINDINGS_BACKEND MATCHES "^(pybind11|nanobind)$")
2018
message (FATAL_ERROR
21-
"OIIO_PYTHON_BINDINGS_BACKEND must be one of: pybind11, nanobind, both")
19+
"OIIO_PYTHON_BINDINGS_BACKEND must be pybind11 or nanobind")
2220
endif ()
2321

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 ()
3622
if (WIN32)
3723
set (PYLIB_LIB_TYPE SHARED CACHE STRING "Type of library to build for python module (MODULE or SHARED)")
3824
else ()
@@ -79,7 +65,7 @@ macro (find_python)
7965
Python3_Development.Module_FOUND
8066
Python3_Interpreter_FOUND )
8167

82-
if (OIIO_BUILD_PYTHON_NANOBIND)
68+
if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind")
8369
# nanobind's CMake package expects the generic FindPython targets and
8470
# variables (Python::Module, Python_EXECUTABLE, etc.), not the
8571
# versioned Python3::* targets that the rest of OIIO uses today.
@@ -232,7 +218,7 @@ endmacro ()
232218

233219
macro (setup_python_module_nanobind)
234220
cmake_parse_arguments (lib "" "TARGET;MODULE"
235-
"SOURCES;LIBS;INCLUDES;SYSTEM_INCLUDE_DIRS;PACKAGE_FILES"
221+
"SOURCES;LIBS;INCLUDES;SYSTEM_INCLUDE_DIRS"
236222
${ARGN})
237223

238224
set (target_name ${lib_TARGET})
@@ -264,24 +250,19 @@ macro (setup_python_module_nanobind)
264250
DEBUG_POSTFIX "")
265251

266252
if (SKBUILD)
267-
set (_nanobind_install_dir .)
268-
else ()
269-
set (_nanobind_install_dir ${PYTHON_SITE_DIR})
253+
set (PYTHON_SITE_DIR .)
270254
endif ()
271255

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.
274256
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
257+
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/site-packages
258+
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/site-packages
277259
)
278260

279261
install (TARGETS ${target_name}
280-
RUNTIME DESTINATION ${_nanobind_install_dir} COMPONENT user
281-
LIBRARY DESTINATION ${_nanobind_install_dir} COMPONENT user)
262+
RUNTIME DESTINATION ${PYTHON_SITE_DIR} COMPONENT user
263+
LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user)
264+
265+
install (FILES __init__.py stubs/OpenImageIO/__init__.pyi stubs/OpenImageIO/py.typed
266+
DESTINATION ${PYTHON_SITE_DIR} COMPONENT user)
282267

283-
if (lib_PACKAGE_FILES)
284-
install (FILES ${lib_PACKAGE_FILES}
285-
DESTINATION ${_nanobind_install_dir} COMPONENT user)
286-
endif ()
287268
endmacro ()

src/cmake/testing.cmake

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -247,25 +247,15 @@ macro (oiio_add_all_tests)
247247
# libraries to run correctly.
248248
if (USE_PYTHON AND NOT BUILD_OIIOUTIL_ONLY AND NOT SANITIZE)
249249
if (WIN32)
250-
# On Windows CI we run the install target before tests. Use the
251-
# installed package path to avoid multi-config output layout quirks.
252250
set (_installed_python_site_packages
253251
"${CMAKE_INSTALL_PREFIX}/${PYTHON_SITE_ROOT_DIR}")
254-
oiio_tests_pythonpath_env_entry (_pybind_tests_pythonpath
252+
oiio_tests_pythonpath_env_entry (_python_tests_pythonpath
255253
"${_installed_python_site_packages}")
256254
else ()
257-
oiio_tests_pythonpath_env_entry (_pybind_tests_pythonpath
255+
oiio_tests_pythonpath_env_entry (_python_tests_pythonpath
258256
"${CMAKE_BINARY_DIR}/lib/python/site-packages")
259257
endif ()
260-
oiio_tests_pythonpath_env_entry (_nanobind_tests_pythonpath
261-
"${CMAKE_BINARY_DIR}/lib/python/nanobind")
262-
set (nanobind_python_tests
263-
python-imagespec
264-
python-paramlist
265-
python-roi
266-
python-typedesc)
267-
set (nanobind_python_test_suffix ".nanobind")
268-
if (OIIO_BUILD_PYTHON_PYBIND11)
258+
if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "pybind11")
269259
oiio_add_tests (
270260
docs-examples-python
271261
python-colorconfig
@@ -279,23 +269,20 @@ macro (oiio_add_all_tests)
279269
python-texturesys
280270
python-typedesc
281271
filters
282-
ENVIRONMENT "${_pybind_tests_pythonpath}"
272+
ENVIRONMENT "${_python_tests_pythonpath}"
283273
)
284-
# These Python tests also need access to oiio-images
285274
oiio_add_tests (
286275
python-imageinput python-imagebufalgo
287276
IMAGEDIR oiio-images
288-
ENVIRONMENT "${_pybind_tests_pythonpath}"
277+
ENVIRONMENT "${_python_tests_pythonpath}"
289278
)
290-
else ()
291-
set (nanobind_python_test_suffix "")
292-
endif ()
293-
294-
if (OIIO_BUILD_PYTHON_NANOBIND)
279+
elseif (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind")
295280
oiio_add_tests (
296-
${nanobind_python_tests}
297-
SUFFIX ${nanobind_python_test_suffix}
298-
ENVIRONMENT "${_nanobind_tests_pythonpath}"
281+
python-imagespec
282+
python-paramlist
283+
python-roi
284+
python-typedesc
285+
ENVIRONMENT "${_python_tests_pythonpath}"
299286
)
300287
endif ()
301288
endif ()
@@ -317,7 +304,7 @@ macro (oiio_add_all_tests)
317304

318305
oiio_add_tests ( python-imagebufalgo
319306
FOUNDVAR hwy_FOUND
320-
ENABLEVAR OIIO_USE_HWY USE_PYTHON OIIO_BUILD_PYTHON_PYBIND11
307+
ENABLEVAR OIIO_USE_HWY USE_PYTHON
321308
DISABLEVAR BUILD_OIIOUTIL_ONLY SANITIZE
322309
SUFFIX ".hwy"
323310
ENVIRONMENT "OPENIMAGEIO_ENABLE_HWY=1"

src/python-nanobind/CMakeLists.txt

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/python-nanobind/__init__.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)