Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -706,12 +706,14 @@ 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
generator: "Visual Studio 18 2026"
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
3 changes: 3 additions & 0 deletions src/build-scripts/ci-startup.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 0 additions & 11 deletions src/build-scripts/gh-installdeps.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 0 additions & 5 deletions src/build-scripts/gh-win-installdeps.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand Down
15 changes: 8 additions & 7 deletions src/cmake/build_Robinmap.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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})
68 changes: 68 additions & 0 deletions src/cmake/build_nanobind.cmake
Original file line number Diff line number Diff line change
@@ -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 <prefix>/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})
16 changes: 12 additions & 4 deletions src/cmake/externalpackages.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()


Expand Down Expand Up @@ -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
)

Expand Down
25 changes: 0 additions & 25 deletions src/cmake/modules/FindRobinmap.cmake

This file was deleted.

27 changes: 19 additions & 8 deletions src/cmake/pythonutils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 ()

Expand All @@ -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()


###########################################################################
Expand Down Expand Up @@ -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}
Expand Down
36 changes: 31 additions & 5 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)

Expand Down Expand Up @@ -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 ()
Expand Down
7 changes: 3 additions & 4 deletions src/libOpenImageIO/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ target_include_directories (OpenImageIO
$<INSTALL_INTERFACE:include>
${OPENIMAGEIO_IMATH_DEPENDENCY_VISIBILITY}
${OPENEXR_INCLUDES}
PRIVATE
${ROBINMAP_INCLUDES}
)

if (NOT BUILD_SHARED_LIBS)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading