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