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
3 changes: 3 additions & 0 deletions .github/workflows/build-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ on:
type: string
pybind11_ver:
type: string
python_bindings_backend:
type: string
python_action_ver:
type: string
python_ver:
Expand Down Expand Up @@ -133,6 +135,7 @@ jobs:
OPENEXR_VERSION: ${{inputs.openexr_ver}}
OPENIMAGEIO_VERSION: ${{inputs.openimageio_ver}}
PYBIND11_VERSION: ${{inputs.pybind11_ver}}
OSL_PYTHON_BINDINGS_BACKEND: ${{inputs.python_bindings_backend}}
PYTHON_VERSION: ${{inputs.python_ver}}
USE_BATCHED: ${{inputs.batched}}
ABI_CHECK: ${{inputs.abi_check}}
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ jobs:
openexr_ver: ${{ matrix.openexr_ver }}
openimageio_ver: ${{ matrix.openimageio_ver }}
pybind11_ver: ${{ matrix.pybind11_ver }}
python_bindings_backend: ${{ matrix.python_bindings_backend }}
python_ver: ${{ matrix.python_ver }}
setenvs: ${{ matrix.setenvs }}
simd: ${{ matrix.simd }}
Expand Down Expand Up @@ -148,6 +149,7 @@ jobs:
openexr_ver: ${{ matrix.openexr_ver }}
openimageio_ver: ${{ matrix.openimageio_ver }}
pybind11_ver: ${{ matrix.pybind11_ver }}
python_bindings_backend: ${{ matrix.python_bindings_backend }}
python_ver: ${{ matrix.python_ver }}
setenvs: ${{ matrix.setenvs }}
simd: ${{ matrix.simd }}
Expand Down Expand Up @@ -196,9 +198,7 @@ jobs:
python_ver: "3.11"
simd: avx2,f16c
batched: b8_AVX2
setenvs: export CTEST_EXCLUSIONS="broken|python-oslquery"
# ^^ exclude python-oslquery test until the ASWF container properly
# includes OIIO's python bindings, then we can remove that.
setenvs: export CTEST_EXCLUSIONS="broken"
- desc: VP2026 gcc14/C++20 llvm20 py3.13 oiio-3.1 avx2
nametag: linux-vfx2026
runner: ubuntu-latest
Expand All @@ -209,9 +209,7 @@ jobs:
batched: b8_AVX2
# OSL_TEST_CPP_BACKEND=1 also exercises the C++ source-gen backend
# (debug_output_cpp=3) on this variant, validating the Linux .so path.
setenvs: export CTEST_EXCLUSIONS="broken|python-oslquery" OSL_TEST_CPP_BACKEND=1
# ^^ exclude python-oslquery test until the ASWF container properly
# includes OIIO's python bindings, then we can remove that.
setenvs: export CTEST_EXCLUSIONS="broken" OSL_TEST_CPP_BACKEND=1
# Address and leak sanitizers (debug build)
- desc: sanitizers
nametag: sanitizer
Expand Down Expand Up @@ -432,6 +430,7 @@ jobs:
openexr_ver: ${{ matrix.openexr_ver }}
openimageio_ver: ${{ matrix.openimageio_ver }}
pybind11_ver: ${{ matrix.pybind11_ver }}
python_bindings_backend: ${{ matrix.python_bindings_backend }}
python_ver: ${{ matrix.python_ver }}
setenvs: ${{ matrix.setenvs }}
simd: ${{ matrix.simd }}
Expand Down Expand Up @@ -512,6 +511,7 @@ jobs:
openexr_ver: ${{ matrix.openexr_ver }}
openimageio_ver: ${{ matrix.openimageio_ver }}
pybind11_ver: ${{ matrix.pybind11_ver }}
python_bindings_backend: ${{ matrix.python_bindings_backend }}
python_ver: ${{ matrix.python_ver }}
setenvs: ${{ matrix.setenvs }}
simd: ${{ matrix.simd }}
Expand Down Expand Up @@ -582,6 +582,7 @@ jobs:
openexr_ver: ${{ matrix.openexr_ver }}
openimageio_ver: ${{ matrix.openimageio_ver }}
pybind11_ver: ${{ matrix.pybind11_ver }}
python_bindings_backend: ${{ matrix.python_bindings_backend }}
python_ver: ${{ matrix.python_ver }}
setenvs: ${{ matrix.setenvs }}
simd: ${{ matrix.simd }}
Expand Down
64 changes: 63 additions & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,20 @@ NEW or CHANGED minimum dependencies since the last major release are **bold**.
* (optional) Python: If you are building the Python bindings or running the
testsuite:
* **Python >= 3.9** (tested through 3.14)
* pybind11 >= 2.7 (tested through 3.0)
* NumPy (tested through 2.4)
* A binding framework, depending on `OSL_PYTHON_BINDINGS_BACKEND` (see
[Python binding backends](#python-binding-backends) below):
* pybind11 >= 2.7 (tested through 3.0) -- needed for the `pybind11`
backend and for `both`. It is the auto-selected default when
OpenImageIO is older than 3.2 or Python is older than 3.10.
* nanobind >= 2.8.0 (tested through 3.0), with Python >= 3.10 -- needed
for the `nanobind` backend and for `both`. It is the auto-selected
default when OpenImageIO is 3.2 or newer and Python is 3.10 or newer.
Usually installed as a Python package (`pip install nanobind`, or
`brew install nanobind`), which is enough: the build locates it by
asking the interpreter. If it is not installed, the build fetches and
builds it locally (it is a small header/CMake package, not a
compiled library).
* (optional) Qt5 >= 5.6 or Qt6 (tested Qt5 through 5.15 and Qt6 through 6.10).
If not found at build time, the `osltoy` application will be disabled.

Expand Down Expand Up @@ -125,6 +137,56 @@ Here are the steps to check out, build, and test the OSL distribution:

make test

Python binding backends
-----------------------

OSL's Python bindings (the `oslquery` module, wrapping `OSLQuery`) can be
built with either [pybind11](https://github.com/pybind/pybind11) or
[nanobind](https://github.com/wjakob/nanobind). Both are generated from one
set of sources and expose exactly the same Python API; which one you get is a
build-time choice:

cmake -B build -S . # auto (see below)
cmake -B build -S . -DOSL_PYTHON_BINDINGS_BACKEND=nanobind
cmake -B build -S . -DOSL_PYTHON_BINDINGS_BACKEND=pybind11
cmake -B build -S . -DOSL_PYTHON_BINDINGS_BACKEND=both

or equivalently by setting an environment variable of the same name.

When `OSL_PYTHON_BINDINGS_BACKEND` is left unset, OSL auto-selects `nanobind`
when both of these hold, and `pybind11` otherwise:

* OpenImageIO is 3.2 or newer. Reading `OSLQuery.Parameter.type` (see below)
needs OSL's and OpenImageIO's Python modules to have been built with the
same binding framework, and OpenImageIO switched its own default to nanobind
in 3.2; matching it keeps `type` working out of the box.
* Python is 3.10 or newer (nanobind's minimum).

nanobind does not have to be installed for this -- if it is missing the build
fetches and builds it locally. If that local build is not possible in your
environment, configure with `-DOSL_PYTHON_BINDINGS_BACKEND=pybind11`.

With `pybind11` or `nanobind`, you get a single `oslquery` module installed in
the usual place, and it makes no difference to Python code which one it is.
With `both`, the pybind11 module keeps the ordinary location and the nanobind
one is installed alongside it under a `nanobind/` subdirectory of the
site-packages directory; put that subdirectory on `PYTHONPATH` to import it
instead. `both` exists so that the testsuite can run against each backend and
confirm they agree; it is not intended for deployment.

Why this is a choice at all: `OSLQuery.Parameter.type` returns an OpenImageIO
`TypeDesc`, and reading that attribute only works if OpenImageIO's own Python
module has been imported *and* was built with the same binding framework as
OSL's. (Each framework keeps its own registry of bound C++ types, and they
cannot see each other's.) So if you use that attribute, build OSL's bindings
to match whatever OpenImageIO you are pairing them with. Otherwise the
attribute raises `TypeError`.

Everything else in the module is free of that constraint, and
`Parameter.type_name` -- a plain string such as `"color"` or `"float[4]"` --
gives you the same information with no coupling to OpenImageIO at all. Prefer
it. `type` is retained for backward compatibility.

Conda Environment
-----------------

Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ test: build
PYTHONPATH=${working_dir}/${build_dir}/lib/python/site-packages:${PYTHONPATH} \
ctest -E broken ${TEST_FLAGS} \
)
# PYTHONPATH here is a convenience for interactive use; the python` tests
# set their own via a CTest ENVIRONMENT property, which wins,` and which
# is how a backend-specific variant finds its module.`
@ ( if [[ "${CODECOV}" == "1" ]] ; then \
cd ${build_dir} ; \
lcov -b . -d . -c -o cov.info ; \
Expand Down
1 change: 1 addition & 0 deletions docs/dev/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ specs/*
# we add specs that we intend to commit and make available to all project
# developers, they must be individually added here.
!specs/002-backend-cpp
!specs/003-nanobind-python-bindings
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Specification Quality Checklist: nanobind Python bindings (dual-backend)

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-08-02
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- **On "no implementation details"**: this feature is itself a build-and-packaging
change, so its stakeholders are OSL builders, packagers, and maintainers rather than
end users. The names pybind11 and nanobind appear in the spec because they are the
subject matter - the thing being selected between - not because they are an
implementation choice made while writing the spec. Everything else is stated in terms
of observable outcomes: module identity, public surface equality, install layout,
which tests run, and what fails at configure time. Specific CMake variable names,
macro names, file paths, and header names are deliberately confined to plan.md.
- Two clarifications were resolved with the requester before the spec was written and
are recorded as Assumptions rather than as open questions: the default backend stays
pybind11 (FR-002), and `Parameter.type` is retained as-is in both backends with the
interoperability constraint handled by documentation (FR-015, FR-026).
- All items pass on the first validation iteration.
Loading
Loading