From a56c10a710b718428f1676f2a54fc220d957ef76 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 27 Jul 2026 16:36:44 -0700 Subject: [PATCH] python: nanobind tidying, auto-build, CI testing A collection of changes related to our switch from pybind11 to nanobind. None of these change the binding code itself! Mostly related to build and testing odds and ends. * CI: Build and test both python back ends! Except for Windows, only do nanobind there, since it's really slow to build the pybind11 bindings. * auto-builder for nanobind if not found - src/cmake/build_nanobind.cmake, mirroring build_pybind11.cmake - With auto-build of nanobind, no need to use pip to install nanobind. - Take care to share robin-map with OIIO core (both OIIO and nanobind need robin-map). * CMakeLists.txt : Change OIIO_PYTHON_BINDINGS_BACKEND initialization to use set_cache() to allow env variables to easily control it. * Testing : - Fixes that make the tests run properly when only nanobind is built (it previously worked for "both" and of course for "pybind11"). - Add tests to the nanobind set that were overlooked: filters, docs-examples-python, and the hwy-enabled python-imagebufalgo variant. - Add missing PYTHONPATH to python-imagebufalgo.hwy and openexr-copy tests, which relied on the ambient shell already having it set (true in CI via ci-startup.bash, not for a plain local ctest run). - Semi-related cleanup: Don't have docs-examples-python depend on docs-examples-cpp having already run to create its ref symlink; create it ourselves if missing. Fixes failures when running testing subset that includes docs-examples-python but not docs-examples-cpp. * pythonutils.cmake: comment out a now-redundant find_package call for nanobind, since externalpackages.cmake already resolves it before this macro runs. * Fix discover_nanobind_cmake_dir(): it was a macro, so its return could abort the entire including file (externalpackages.cmake) rather than just itself, whenever a stale cached nanobind_DIR was present -- silently skipping every dependency check after the Python section. Convert to function. * Robin-map: - Raise robinmap minimum to 1.3, as needed by nanobind. - Remove our bespoke FindRobinmap.cmake module in favor of using modern robinmap's exported cmake configs (including cleanup of the changed target names). * Suppress nanobind reference leak warnings for python 3.9, allow for 3.10+. We were only getting these for 3.9, not newer Python, so discussion with Wenzel concluded that it's probably just an old python cleanup problem and not worth tracking down unless we see it surface with a newer Python release. Assisted-by: Claude Code / Sonnet 5 Signed-off-by: Larry Gritz --- .github/workflows/ci.yml | 14 ++-- src/build-scripts/ci-startup.bash | 3 + src/build-scripts/gh-installdeps.bash | 11 --- src/build-scripts/gh-win-installdeps.bash | 5 -- src/cmake/build_Robinmap.cmake | 15 ++-- src/cmake/build_nanobind.cmake | 68 +++++++++++++++++++ src/cmake/externalpackages.cmake | 16 +++-- src/cmake/modules/FindRobinmap.cmake | 25 ------- src/cmake/pythonutils.cmake | 27 +++++--- src/cmake/testing.cmake | 36 ++++++++-- src/libOpenImageIO/CMakeLists.txt | 7 +- src/libutil/CMakeLists.txt | 3 +- src/oiiotool/CMakeLists.txt | 2 +- src/python/py_oiio.cpp | 9 +++ src/testtex/CMakeLists.txt | 3 +- .../docs-examples-python/ref/out-arm.txt | 8 +++ .../docs-examples-python/ref/out-linuxarm.txt | 8 +++ testsuite/docs-examples-python/ref/out.txt | 8 +++ testsuite/docs-examples-python/run.py | 15 +++- .../src/docs-examples-imagebuf.py | 1 + .../src/docs-examples-imagebufalgo.py | 1 + .../src/docs-examples-imagecache.py | 1 + .../src/docs-examples-imageinput.py | 1 + .../src/docs-examples-imageioapi.py | 1 + .../src/docs-examples-imageoutput.py | 1 + .../src/docs-examples-texturesys.py | 1 + .../src/docs-examples-writingplugins.py | 1 + 27 files changed, 209 insertions(+), 82 deletions(-) create mode 100644 src/cmake/build_nanobind.cmake delete mode 100644 src/cmake/modules/FindRobinmap.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b6191bd80..5db9c35c1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -489,8 +489,8 @@ jobs: BUILD_PNG_VERSION=1.6.0 PIP_SUFFIX=.9 PIP_INSTALLS=numpy - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + Robinmap_BUILD_VERSION=1.3.0 + Robinmap_GIT_COMMIT=188c45569cc2a5dd768077c193830b51d33a5020 TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 # OpenJPEG_BUILD_VERSION=2.2.0 @@ -519,8 +519,8 @@ jobs: PIP_INSTALLS=numpy OIIO_CC=clang OIIO_CXX=clang++ - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + Robinmap_BUILD_VERSION=1.3.0 + Robinmap_GIT_COMMIT=188c45569cc2a5dd768077c193830b51d33a5020 TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 openjph_CMAKE_C_COMPILER=gcc @@ -560,8 +560,8 @@ jobs: BUILD_PNG_VERSION=1.6.0 PIP_SUFFIX=.9 PIP_INSTALLS=numpy - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + Robinmap_BUILD_VERSION=1.3.0 + Robinmap_GIT_COMMIT=188c45569cc2a5dd768077c193830b51d33a5020 TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 required_deps: none @@ -706,6 +706,7 @@ jobs: python_ver: "3.12" ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + OIIO_PYTHON_BINDINGS_BACKEND=nanobind - desc: Windows-2025 VS2026 runner: windows-2025-vs2026 nametag: windows-2025-vs2026 @@ -713,5 +714,6 @@ jobs: python_ver: "3.12" ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + OIIO_PYTHON_BINDINGS_BACKEND=nanobind benchmark: 1 oiio_python_bindings_backend: both diff --git a/src/build-scripts/ci-startup.bash b/src/build-scripts/ci-startup.bash index 276f1c7199..50c433c3a9 100755 --- a/src/build-scripts/ci-startup.bash +++ b/src/build-scripts/ci-startup.bash @@ -53,6 +53,9 @@ export TESTSUITE_CLEANUP_ON_SUCCESS=${TESTSUITE_CLEANUP_ON_SUCCESS:=1} # For CI, default to building missing dependencies automatically export OpenImageIO_BUILD_MISSING_DEPS=${OpenImageIO_BUILD_MISSING_DEPS:=all} +# Build both set of python bindings in main/3.2 for now +export OIIO_PYTHON_BINDINGS_BACKEND=both + # Sonar export BUILD_WRAPPER_OUT_DIR="${PWD}/bw_output" export BW_OUTPUT_DIR="${PWD}/bw_output" diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index a4b6fa1b57..75b17d2f0b 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -234,16 +234,5 @@ fi df -h . df -h /host/root || true -# nanobind's CMake config is discovered via `python -m nanobind --cmake_dir`. -# Version + wheel hash: src/build-scripts/ci-requirements-nanobind.txt (CodeQL / supply chain). -if [[ "${OIIO_PYTHON_BINDINGS_BACKEND:-}" == "both" || "${OIIO_PYTHON_BINDINGS_BACKEND:-}" == "nanobind" ]] ; then - _oiio_nanobind_requirements_file="$PWD/src/build-scripts/ci-requirements-nanobind.txt" - if [[ "$ASWF_ORG" != "" ]] ; then - time pip3 install -r "$_oiio_nanobind_requirements_file" --require-hashes || true - else - time pip3${PIP_SUFFIX} install -r "$_oiio_nanobind_requirements_file" --require-hashes - fi -fi - # Save the env for use by other stages src/build-scripts/save-env.bash diff --git a/src/build-scripts/gh-win-installdeps.bash b/src/build-scripts/gh-win-installdeps.bash index 73500f42b6..4e4f5bb29c 100755 --- a/src/build-scripts/gh-win-installdeps.bash +++ b/src/build-scripts/gh-win-installdeps.bash @@ -39,11 +39,6 @@ elif [[ "$PYTHON_VERSION" == "3.14" ]] ; then fi pip install numpy -if [[ "${OIIO_PYTHON_BINDINGS_BACKEND:-}" == "both" || "${OIIO_PYTHON_BINDINGS_BACKEND:-}" == "nanobind" ]] ; then - _oiio_nanobind_requirements_file="$PWD/src/build-scripts/ci-requirements-nanobind.txt" - "${Python_EXECUTABLE:-python}" -m pip install -r "$_oiio_nanobind_requirements_file" --require-hashes -fi - # In case we need vcpkg, example: # echo "All pre-installed VCPkg installs:" diff --git a/src/cmake/build_Robinmap.cmake b/src/cmake/build_Robinmap.cmake index a106e91b15..7e26ee8b6c 100644 --- a/src/cmake/build_Robinmap.cmake +++ b/src/cmake/build_Robinmap.cmake @@ -6,10 +6,10 @@ # Robinmap by hand! ###################################################################### -set_cache (Robinmap_BUILD_VERSION 1.4.0 "Robinmap version for local builds") +set_cache (Robinmap_BUILD_VERSION 1.4.1 "Robinmap version for local builds") set (Robinmap_GIT_REPOSITORY "https://github.com/Tessil/robin-map") set (Robinmap_GIT_TAG "v${Robinmap_BUILD_VERSION}") -set_cache (Robinmap_GIT_COMMIT "4ec1bf19c6a96125ea22062f38c2cf5b958e448e" +set_cache (Robinmap_GIT_COMMIT "bd14e6830a1474fed9d2d03f5c3b0683d818d540" "commit hash to verify tag against") build_dependency_with_cmake(Robinmap @@ -23,9 +23,10 @@ build_dependency_with_cmake(Robinmap -D CMAKE_POLICY_VERSION_MINIMUM=3.5 ) -# Set some things up that we'll need for a subsequent find_package to work -set (Robinmap_ROOT ${Robinmap_INSTALL_DIR}) - -# Signal to caller that we need to find again at the installed location +# Signal to caller that we need to find again at the installed location, +# this time via the real upstream package name (see the NAMES tsl-robin-map +# on the checked_find_package call in externalpackages.cmake) so that we +# get the genuine tsl::robin_map target -- the same one nanobind looks for. set (Robinmap_REFIND TRUE) -set (Robinmap_VERSION ${Robinmap_BUILD_VERSION}) +set (Robinmap_REFIND_ARGS CONFIG NAMES tsl-robin-map) +set (Robinmap_REFIND_VERSION ${Robinmap_BUILD_VERSION}) diff --git a/src/cmake/build_nanobind.cmake b/src/cmake/build_nanobind.cmake new file mode 100644 index 0000000000..9564e9ec04 --- /dev/null +++ b/src/cmake/build_nanobind.cmake @@ -0,0 +1,68 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +###################################################################### +# nanobind by hand! +# +# Unlike most of our other bundled dependencies, nanobind isn't a library +# to compile and install -- it's a source tree plus a set of CMake helper +# functions (nanobind_add_module(), etc.) that get pulled into whichever +# project actually builds Python extension modules. Its own top-level +# CMakeLists.txt, configured standalone with NB_TEST=OFF, does nothing +# but copy its headers/sources/cmake helpers to an install prefix and +# generate a version file -- there's nothing to actually compile. That's +# the (unusually light) local build performed below. +###################################################################### + +set_cache (nanobind_BUILD_VERSION 2.13.0 "nanobind version for local builds") +set (nanobind_GIT_REPOSITORY "https://github.com/wjakob/nanobind") +set_cache (nanobind_GIT_TAG "v${nanobind_BUILD_VERSION}" + "nanobind git tag to checkout") +set_cache (nanobind_GIT_COMMIT "e2dc00f7a34f935c6cf91948776d59c4709e9fe6" + "nanobind commit hash to verify tag against") + +set (nanobind_LOCAL_SOURCE_DIR "${${PROJECT_NAME}_LOCAL_DEPS_ROOT}/nanobind") + +# nanobind vendors tsl::robin_map as a git submodule (ext/robin_map), and +# its CMakeLists.txt hard-errors if that submodule isn't checked out. +# build_dependency_with_cmake()'s plain `git clone` doesn't init submodules, +# so fetch the source and its submodule ourselves before handing off to the +# shared helper below (which will find the directory already present and +# just checkout/verify the pinned tag against it). +if (NOT IS_DIRECTORY "${nanobind_LOCAL_SOURCE_DIR}") + find_package (Git REQUIRED) + message (STATUS "Cloning ${nanobind_GIT_REPOSITORY} @ ${nanobind_GIT_TAG}") + execute_process (COMMAND ${GIT_EXECUTABLE} clone -q + -b ${nanobind_GIT_TAG} --depth 1 + ${nanobind_GIT_REPOSITORY} ${nanobind_LOCAL_SOURCE_DIR}) + execute_process (COMMAND ${GIT_EXECUTABLE} submodule update --init --depth 1 + -- ext/robin_map + WORKING_DIRECTORY ${nanobind_LOCAL_SOURCE_DIR}) +endif () + +build_dependency_with_cmake(nanobind + VERSION ${nanobind_BUILD_VERSION} + GIT_REPOSITORY ${nanobind_GIT_REPOSITORY} + GIT_TAG ${nanobind_GIT_TAG} + GIT_COMMIT ${nanobind_GIT_COMMIT} + CMAKE_ARGS + # Skip nanobind's own test suite -- we only want its install + # rules (headers, sources, and CMake helper functions), not a + # build of its tests, which would otherwise need Python at + # configure time and a lot of unnecessary compilation. + -D NB_TEST=OFF + ) + +# nanobind installs its CMake package config to /nanobind/cmake, a +# layout that generic find_package() prefix search doesn't check, so point +# straight at it. (This is the same trick externalpackages.cmake's +# discover_nanobind_cmake_dir() uses for a pip install, via +# `python -m nanobind --cmake_dir`.) +set (nanobind_DIR "${nanobind_LOCAL_INSTALL_DIR}/nanobind/cmake" CACHE PATH + "Path to the nanobind CMake package" FORCE) + +# Signal to caller that we need to find again at the installed location +set (nanobind_REFIND TRUE) +set (nanobind_REFIND_ARGS CONFIG) +set (nanobind_REFIND_VERSION ${nanobind_BUILD_VERSION}) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 588606cd19..c09675fba1 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -124,7 +124,9 @@ if (USE_PYTHON AND OIIO_BUILD_PYTHON_PYBIND11) endif () if (USE_PYTHON AND OIIO_BUILD_PYTHON_NANOBIND) discover_nanobind_cmake_dir() - checked_find_package (nanobind CONFIG REQUIRED) + checked_find_package (nanobind CONFIG REQUIRED + VERSION_MIN 2.8.0 + BUILD_LOCAL missing) endif () @@ -237,9 +239,15 @@ if (OIIO_USE_HWY) checked_find_package (hwy) endif () -# Tessil/robin-map -checked_find_package (Robinmap REQUIRED - VERSION_MIN 1.2.0 +# Tessil/robin-map. Use its own exported CMake config (target tsl::robin_map) +# rather than a bespoke Find module. This also means that when the nanobind +# Python backend is enabled, nanobind's own CMake code will detect and reuse +# this exact target/version instead of compiling against its private vendored +# copy (see nanobind_build_library()'s `NOT TARGET tsl::robin_map` check). +checked_find_package (Robinmap CONFIG REQUIRED + NAMES tsl-robin-map + VERSION_MIN 1.3.0 + NO_FP_RANGE_CHECK BUILD_LOCAL missing ) diff --git a/src/cmake/modules/FindRobinmap.cmake b/src/cmake/modules/FindRobinmap.cmake deleted file mode 100644 index 2aaa86f468..0000000000 --- a/src/cmake/modules/FindRobinmap.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# Find Robinmap -# -# Copyright Contributors to the OpenImageIO project. -# SPDX-License-Identifier: Apache-2.0 -# https://github.com/AcademySoftwareFoundation/OpenImageIO -# -# Sets the usual variables expected for find_package scripts: -# -# ROBINMAP_INCLUDES - header location -# ROBINMAP_FOUND - true if robin-map was found. - -find_path (ROBINMAP_INCLUDE_DIR tsl/robin_map.h - HINTS "${PROJECT_SOURCE_DIR}/ext/robin-map" - ) - -# Support the REQUIRED and QUIET arguments, and set ROBINMAP_FOUND if found. -include (FindPackageHandleStandardArgs) -find_package_handle_standard_args (Robinmap - REQUIRED_VARS ROBINMAP_INCLUDE_DIR) - -if (ROBINMAP_FOUND) - set (ROBINMAP_INCLUDES ${ROBINMAP_INCLUDE_DIR}) -endif () - -mark_as_advanced (ROBINMAP_INCLUDE_DIR) diff --git a/src/cmake/pythonutils.cmake b/src/cmake/pythonutils.cmake index 913021d001..099f7c7d90 100644 --- a/src/cmake/pythonutils.cmake +++ b/src/cmake/pythonutils.cmake @@ -8,8 +8,8 @@ set (PYTHON_VERSION "" CACHE STRING "Target version of python to find") option (PYLIB_INCLUDE_SONAME "If ON, soname/soversion will be set for Python module library" OFF) option (PYLIB_LIB_PREFIX "If ON, prefix the Python module with 'lib'" OFF) set (PYMODULE_SUFFIX "" CACHE STRING "Suffix to add to Python module init namespace") -set (OIIO_PYTHON_BINDINGS_BACKEND "pybind11" CACHE STRING - "Which Python binding backend(s) to build: pybind11, nanobind, or both") +set_cache (OIIO_PYTHON_BINDINGS_BACKEND "pybind11" + "Which Python binding backend(s) to build: pybind11, nanobind, or both" VERBOSE) set_property (CACHE OIIO_PYTHON_BINDINGS_BACKEND PROPERTY STRINGS pybind11 nanobind both) @@ -112,8 +112,18 @@ endmacro() # Help CMake locate nanobind when it was installed as a Python package. -macro (discover_nanobind_cmake_dir) - if (nanobind_DIR OR nanobind_ROOT OR "$ENV{nanobind_DIR}" OR "$ENV{nanobind_ROOT}") +# This is a function (not a macro) deliberately: its early return must +# not escape into whatever file happens to include pythonutils.cmake and +# call this at file scope (a macro's return() would abort that entire +# caller file, silently skipping everything after it). +function (discover_nanobind_cmake_dir) + # Cached from a previous configure. Trust it only if it still points to + # a real nanobind install -- e.g. it may be stale if nanobind was + # uninstalled/upgraded since the cache was written. + if (nanobind_DIR AND EXISTS "${nanobind_DIR}/nanobind-config.cmake") + return() + endif () + if (nanobind_ROOT OR "$ENV{nanobind_DIR}" OR "$ENV{nanobind_ROOT}") return() endif () @@ -132,7 +142,7 @@ macro (discover_nanobind_cmake_dir) set (nanobind_DIR "${_oiio_nanobind_cmake_dir}" CACHE PATH "Path to the nanobind CMake package" FORCE) endif () -endmacro() +endfunction() ########################################################################### @@ -239,12 +249,13 @@ macro (setup_python_module_nanobind) if (NOT COMMAND nanobind_add_module) discover_nanobind_cmake_dir() - find_package (nanobind CONFIG REQUIRED) endif () nanobind_add_module(${target_name} ${lib_SOURCES}) - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND TARGET nanobind-static) - target_compile_options (nanobind-static PRIVATE -Wno-error=format-nonliteral) + if (TARGET nanobind-static AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang" + OR CMAKE_CXX_COMPILER_ID MATCHES "Apple" + OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM")) + target_compile_options (nanobind-static PUBLIC -Wno-format-nonliteral) endif () target_include_directories (${target_name} diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index e1be9b89f8..6f29ea7e9a 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -259,11 +259,23 @@ macro (oiio_add_all_tests) oiio_tests_pythonpath_env_entry (_pybind_tests_pythonpath "${CMAKE_BINARY_DIR}/lib/python/site-packages") endif () - oiio_tests_pythonpath_env_entry (_nanobind_tests_pythonpath - "${CMAKE_BINARY_DIR}/lib/python/nanobind") + if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "both") + # In "both" mode, the nanobind module is kept isolated under its + # own build-tree location so it doesn't clobber the pybind11 + # module that also lives in lib/python/site-packages. + oiio_tests_pythonpath_env_entry (_nanobind_tests_pythonpath + "${CMAKE_BINARY_DIR}/lib/python/nanobind") + else () + # nanobind-only builds install the module to the same location + # pybind11 would have used (see setup_python_module_nanobind() + # in pythonutils.cmake). + set (_nanobind_tests_pythonpath "${_pybind_tests_pythonpath}") + endif () # Keep in sync with the pybind11 python-* tests below as dual-backend # coverage expands. imageinput/imagebufalgo also need oiio-images. set (nanobind_python_tests + docs-examples-python + filters python-colorconfig python-deep python-imagebuf @@ -341,7 +353,18 @@ macro (oiio_add_all_tests) ENABLEVAR OIIO_USE_HWY USE_PYTHON OIIO_BUILD_PYTHON_PYBIND11 DISABLEVAR BUILD_OIIOUTIL_ONLY SANITIZE SUFFIX ".hwy" - ENVIRONMENT "OPENIMAGEIO_ENABLE_HWY=1" + ENVIRONMENT "OPENIMAGEIO_ENABLE_HWY=1" "${_pybind_tests_pythonpath}" + IMAGEDIR oiio-images + ) + + # Same test, run against the nanobind bindings (mirrors the pybind11 + # variant above so hwy coverage isn't pybind11-only). + oiio_add_tests ( python-imagebufalgo + FOUNDVAR hwy_FOUND + ENABLEVAR OIIO_USE_HWY USE_PYTHON OIIO_BUILD_PYTHON_NANOBIND + DISABLEVAR BUILD_OIIOUTIL_ONLY SANITIZE + SUFFIX "${nanobind_python_test_suffix}.hwy" + ENVIRONMENT "OPENIMAGEIO_ENABLE_HWY=1" "${_nanobind_tests_pythonpath}" IMAGEDIR oiio-images ) @@ -418,15 +441,18 @@ macro (oiio_add_all_tests) list (APPEND all_openexr_tests openexr-idmanifest) endif () # Run all OpenEXR tests without core library + # (openexr-copy is Python-based, so pass along the site-packages + # PYTHONPATH -- it must not depend on the ambient shell environment + # already having it set, the way CI's ci-startup.bash does.) oiio_add_tests (${all_openexr_tests} openexr-luminance-chroma - ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=0 + ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=0 "${_pybind_tests_pythonpath}" IMAGEDIR openexr-images URL http://github.com/AcademySoftwareFoundation/openexr-images) # For OpenEXR >= 3.1, be sure to test with the core option on if (OpenEXR_VERSION VERSION_GREATER_EQUAL 3.1) oiio_add_tests (${all_openexr_tests} SUFFIX ".core" - ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=1 + ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=1 "${_pybind_tests_pythonpath}" IMAGEDIR openexr-images URL http://github.com/AcademySoftwareFoundation/openexr-images) endif () diff --git a/src/libOpenImageIO/CMakeLists.txt b/src/libOpenImageIO/CMakeLists.txt index 2fa1c457c3..7baf1682a4 100644 --- a/src/libOpenImageIO/CMakeLists.txt +++ b/src/libOpenImageIO/CMakeLists.txt @@ -127,8 +127,6 @@ target_include_directories (OpenImageIO $ ${OPENIMAGEIO_IMATH_DEPENDENCY_VISIBILITY} ${OPENEXR_INCLUDES} - PRIVATE - ${ROBINMAP_INCLUDES} ) if (NOT BUILD_SHARED_LIBS) @@ -147,6 +145,7 @@ target_link_libraries (OpenImageIO ${OPENIMAGEIO_IMATH_DEPENDENCY_VISIBILITY} ${OPENIMAGEIO_IMATH_TARGETS} PRIVATE + tsl::robin_map ${OPENIMAGEIO_OPENEXR_TARGETS} ${format_plugin_libs} # Add all the target link libraries from the plugins OpenColorIO::OpenColorIO @@ -269,9 +268,9 @@ if (OIIO_BUILD_TESTS AND BUILD_TESTING) fancy_add_executable (NAME imagebufalgo_test SRC imagebufalgo_test.cpp SYSTEM_INCLUDE_DIRS - ${ROBINMAP_INCLUDES} - ${OpenCV_INCLUDES} + ${OpenCV_INCLUDES} LINK_LIBRARIES OpenImageIO + tsl::robin_map ${OpenCV_LIBRARIES} ${OPENIMAGEIO_IMATH_TARGETS} FOLDER "Unit Tests" NO_INSTALL) diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt index 877e5b7b6f..d52c4ea975 100644 --- a/src/libutil/CMakeLists.txt +++ b/src/libutil/CMakeLists.txt @@ -58,8 +58,6 @@ function (setup_oiio_util_library targetname) PUBLIC $ $ - PRIVATE - ${ROBINMAP_INCLUDES} ) target_link_libraries (${targetname} PUBLIC @@ -68,6 +66,7 @@ function (setup_oiio_util_library targetname) ${OPENIMAGEIO_IMATH_DEPENDENCY_VISIBILITY} ${OPENIMAGEIO_IMATH_TARGETS} PRIVATE + tsl::robin_map $ ${CMAKE_DL_LIBS} ) diff --git a/src/oiiotool/CMakeLists.txt b/src/oiiotool/CMakeLists.txt index 87b6a3e787..8392073093 100644 --- a/src/oiiotool/CMakeLists.txt +++ b/src/oiiotool/CMakeLists.txt @@ -3,10 +3,10 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO fancy_add_executable (SYSTEM_INCLUDE_DIRS - ${ROBINMAP_INCLUDES} ${OpenCV_INCLUDES} LINK_LIBRARIES OpenImageIO + tsl::robin_map $ ${OpenCV_LIBRARIES} ) diff --git a/src/python/py_oiio.cpp b/src/python/py_oiio.cpp index 35e029c7ae..b617b176d1 100644 --- a/src/python/py_oiio.cpp +++ b/src/python/py_oiio.cpp @@ -591,6 +591,15 @@ OIIO_DECLARE_NB_MODULE(OpenImageIO) { m.doc() = "OpenImageIO nanobind bindings."; +# if PY_VERSION_HEX < 0x030a0000 /* less then 3.10 */ + // Python 3.9's interpreter shutdown/refcounting order triggers bogus + // nanobind leak warnings that don't occur on 3.10+. Silence them only for + // the affected old version. + // https://github.com/wjakob/nanobind/discussions/1405 + // https://nanobind.readthedocs.io/en/latest/refleaks.html#disabling-leak-warnings + py::set_leak_warnings(false); +# endif + PyOpenImageIO::declare_global_bindings(m); PyOpenImageIO::declare_global_attribute_functions(m); PyOpenImageIO::declare_module_attributes(m); diff --git a/src/testtex/CMakeLists.txt b/src/testtex/CMakeLists.txt index 5cecb6ce79..6a04846017 100644 --- a/src/testtex/CMakeLists.txt +++ b/src/testtex/CMakeLists.txt @@ -9,5 +9,4 @@ set (ENABLE_INSTALL_testtex OFF PARENT_SCOPE) fancy_add_executable (NAME testtex - INCLUDE_DIRS ${ROBINMAP_INCLUDES} - LINK_LIBRARIES OpenImageIO ) + LINK_LIBRARIES OpenImageIO tsl::robin_map ) diff --git a/testsuite/docs-examples-python/ref/out-arm.txt b/testsuite/docs-examples-python/ref/out-arm.txt index 90a3f94f2b..c3db95ea8a 100644 --- a/testsuite/docs-examples-python/ref/out-arm.txt +++ b/testsuite/docs-examples-python/ref/out-arm.txt @@ -1,4 +1,12 @@ +docs-examples-imageioapi.py +docs-examples-imageoutput.py +docs-examples-imageinput.py pixels holds unassociated alpha +docs-examples-writingplugins.py +docs-examples-imagecache.py +docs-examples-texturesys.py +docs-examples-imagebuf.py +docs-examples-imagebufalgo.py example1 example_output_error1 error: Uninitialized input image diff --git a/testsuite/docs-examples-python/ref/out-linuxarm.txt b/testsuite/docs-examples-python/ref/out-linuxarm.txt index 94c92a7c54..4af07dee1a 100644 --- a/testsuite/docs-examples-python/ref/out-linuxarm.txt +++ b/testsuite/docs-examples-python/ref/out-linuxarm.txt @@ -1,4 +1,12 @@ +docs-examples-imageioapi.py +docs-examples-imageoutput.py +docs-examples-imageinput.py pixels holds unassociated alpha +docs-examples-writingplugins.py +docs-examples-imagecache.py +docs-examples-texturesys.py +docs-examples-imagebuf.py +docs-examples-imagebufalgo.py example1 example_output_error1 error: Uninitialized input image diff --git a/testsuite/docs-examples-python/ref/out.txt b/testsuite/docs-examples-python/ref/out.txt index 6c6c628cb6..4f12d91132 100644 --- a/testsuite/docs-examples-python/ref/out.txt +++ b/testsuite/docs-examples-python/ref/out.txt @@ -1,4 +1,12 @@ +docs-examples-imageioapi.py +docs-examples-imageoutput.py +docs-examples-imageinput.py pixels holds unassociated alpha +docs-examples-writingplugins.py +docs-examples-imagecache.py +docs-examples-texturesys.py +docs-examples-imagebuf.py +docs-examples-imagebufalgo.py example1 example_output_error1 error: Uninitialized input image diff --git a/testsuite/docs-examples-python/run.py b/testsuite/docs-examples-python/run.py index 3d6d84d236..68667551f6 100755 --- a/testsuite/docs-examples-python/run.py +++ b/testsuite/docs-examples-python/run.py @@ -7,8 +7,19 @@ redirect = " >> out.txt 2>&1 " # To avoid duplicating example images between the C++ and Python tests, -# they all live with the C++ ones. -refdirlist += [ "../docs-examples-cpp/ref" ] +# they all live with the C++ ones. Normally docs-examples-cpp creates this +# symlink itself (pointing at its own source ref/ dir) the first time it +# runs, but if this ctest invocation filters to a subset that excludes it +# (e.g. -R/TEST=py), the symlink may not exist yet. Create it ourselves so +# this test doesn't depend on run order/selection. +_cpp_ref_link = "../docs-examples-cpp/ref" +if not os.path.lexists (_cpp_ref_link) : + try : + os.symlink (os.path.join (test_source_dir, "..", "docs-examples-cpp", "ref"), + _cpp_ref_link) + except FileExistsError : + pass # another concurrently-run test variant made it first +refdirlist += [ _cpp_ref_link ] # Prep: command += run_app("cmake -E copy " + test_source_dir + "/../common/grid-small.exr grid.exr") diff --git a/testsuite/docs-examples-python/src/docs-examples-imagebuf.py b/testsuite/docs-examples-python/src/docs-examples-imagebuf.py index 5604536f39..bd76227d0d 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagebuf.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagebuf.py @@ -39,6 +39,7 @@ def example1() -> None: if __name__ == '__main__': + print("docs-examples-imagebuf.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1() diff --git a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py index 0bd717c9ac..d9a07a9bd6 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py @@ -593,6 +593,7 @@ def example_make_texture() -> None: if __name__ == '__main__': + print("docs-examples-imagebufalgo.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1() diff --git a/testsuite/docs-examples-python/src/docs-examples-imagecache.py b/testsuite/docs-examples-python/src/docs-examples-imagecache.py index 868b019d43..ef5bbc9487 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagecache.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagecache.py @@ -39,6 +39,7 @@ def example1() -> None: if __name__ == '__main__': + print("docs-examples-imagecache.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1() diff --git a/testsuite/docs-examples-python/src/docs-examples-imageinput.py b/testsuite/docs-examples-python/src/docs-examples-imageinput.py index 05d8f015a0..a76096e410 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageinput.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageinput.py @@ -132,6 +132,7 @@ def error_checking() -> None: if __name__ == '__main__': + print("docs-examples-imageinput.py") # Each example function needs to get called here, or it won't execute # as part of the test. simple_read() diff --git a/testsuite/docs-examples-python/src/docs-examples-imageioapi.py b/testsuite/docs-examples-python/src/docs-examples-imageioapi.py index 45525728ea..67a9741087 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageioapi.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageioapi.py @@ -39,6 +39,7 @@ def example1() -> None: if __name__ == '__main__': + print("docs-examples-imageioapi.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1() diff --git a/testsuite/docs-examples-python/src/docs-examples-imageoutput.py b/testsuite/docs-examples-python/src/docs-examples-imageoutput.py index 0b9b8ada0b..623776a736 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageoutput.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageoutput.py @@ -125,6 +125,7 @@ def copy_write() -> None: if __name__ == '__main__': + print("docs-examples-imageoutput.py") # Each example function needs to get called here, or it won't execute # as part of the test. simple_write() diff --git a/testsuite/docs-examples-python/src/docs-examples-texturesys.py b/testsuite/docs-examples-python/src/docs-examples-texturesys.py index f94b5060f7..4c59ca809d 100644 --- a/testsuite/docs-examples-python/src/docs-examples-texturesys.py +++ b/testsuite/docs-examples-python/src/docs-examples-texturesys.py @@ -39,6 +39,7 @@ def example1() -> None: if __name__ == '__main__': + print("docs-examples-texturesys.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1() diff --git a/testsuite/docs-examples-python/src/docs-examples-writingplugins.py b/testsuite/docs-examples-python/src/docs-examples-writingplugins.py index b37d989a97..5b2a262033 100644 --- a/testsuite/docs-examples-python/src/docs-examples-writingplugins.py +++ b/testsuite/docs-examples-python/src/docs-examples-writingplugins.py @@ -39,6 +39,7 @@ def example1() -> None: if __name__ == '__main__': + print("docs-examples-writingplugins.py") # Each example function needs to get called here, or it won't execute # as part of the test. example1()