ADR 0039: Platform wheels carrying the native library¶
Status: Accepted
Date: 2026-09-28
Context¶
ADR 0008 made the Python package a platform-neutral wheel that loads a native
library the caller names, by argument or through POSTPROJECT_LIBRARY. A
Python application installs a native archive beside the wheel and points the
binding at it.
An application that embeds Python and loads plug-ins cannot work that way. The Blender pilot showed why:
A Blender extension bundles its Python dependencies as wheels. Blender installs the wheels of every extension into one shared
site-packagesand, when two extensions bundle wheels of the same name, keeps only the newest (wheel_manager.pyin Blender 5.2). The pilot shipped the neutral wheel and its own copy of the native library. A second extension bundling a newer PostProject would replace the binding. The newer binding requires its own C ABI version exactly, so it would reject the first extension’s library, and the first extension would stop working.An environment variable is process-wide. One plug-in setting
POSTPROJECT_LIBRARYdecides for every other plug-in in the same process.The Linux native library was built on the newest Ubuntu runner and required glibc 2.34. The Linux builds of Blender 5.2.2 and 5.3 alpha require only glibc 2.28 (the newest
GLIBC_symbol version their executables import), so the library did not load on every system its host runs on.
Other embedding hosts share the first two constraints: DCC applications with Python plug-in systems (Nuke, Houdini, Maya, Krita), pipeline launchers that compose environments, and OpenAssetIO hosts loading a Manager plug-in.
Decision¶
Platform wheels. Each release publishes, beside the native archives, a wheel per platform that contains the binding and the native library of the same build:
manylinux_2_28_x86_64,macosx_11_0_arm64, andwin_amd64. Whichever of these wheels a host keeps, its binding and library match.Loading order. The binding loads an explicit
library_path, elsePOSTPROJECT_LIBRARY, else the library installed inside the package. It still never searches the working directory or changes the platform loader path. A platform wheel therefore works with no configuration, and an explicit path or the variable still overrides it for development.The neutral wheel remains for platforms without a platform wheel and for applications that install the native package themselves. Without an explicit path or the variable it fails as before.
Linux builds for glibc 2.28. The Linux native archive and wheel are built in the
manylinux_2_28container, and CI checks that the library requires no newer glibc symbol version.Hosts with shared plug-in environments bundle the platform wheel and pass no library path. The integrator guide explains the newest-wins behavior and why a plug-in must not set the environment variable.
Alternatives considered¶
Vendoring the binding inside each plug-in. Each plug-in would carry its own binding and library under a private module name and never share it. That isolates plug-ins but asks every host integration to repackage PostProject, and two copies of different versions would then write one production with different schema expectations. The platform wheel keeps one binding per process, which is what the production file’s single schema needs anyway.
Accepting any library with an equal or newer ABI version. The C ABI is not backward compatible before 1.0 (ADR 0020), so a newer library cannot serve an older binding.
A compiled extension module. It would also need per-platform wheels and add a build toolchain; ADR 0008’s reasons for
ctypesstill hold.
Standards impact¶
None. Wheel file names and platform tags follow the Python packaging
specifications (binary distribution format and platform compatibility tags,
including PEP 600 manylinux_x_y tags).
Consequences¶
pip install postproject on a supported platform gives a working binding
without a native archive, and embedding hosts ship one file. The Linux library
loads on glibc 2.28 and newer. A plug-in may receive a newer binding than it
was built with when another plug-in bundles one; before 1.0 its Python API may
differ, which the guide states. ADR 0008’s explicit-path rule gains the
package’s own library as the last choice.