14 KiB
Wheel Backend Architecture Plan
Status: proposed aggressive refactor, implementation-ready after local owner approval.
This plan describes the direct path to the preferred long-term Datoviz wheel architecture. It is
not the minimal RC workaround. The goal is to remove the current split between wheel staging,
pip wheel, post-build retagging, platform repair, and installed-smoke scripts, and replace it
with a first-class Datoviz wheel backend that produces the intended platform wheel directly.
The implementation should promote the existing tools/release_wheels/ package into the backend
rather than start from a blank design. The current scripts already encode useful payload, matrix,
inspection, and installed-smoke knowledge; the refactor should make that knowledge the release
wheel authority.
Motivation
Datoviz v0.4 wheels are Python-ABI independent but platform dependent:
py3-none-<platform>
The Python package uses generated ctypes bindings and does not contain a CPython extension module.
The wheel still carries native runtime payloads: libdatoviz, public C headers, CMake package
files, and platform runtime libraries such as shaderc, Vulkan loader, or MoltenVK when policy
requires them.
Generic cibuildwheel expects the Python build backend to emit a platform wheel by itself. The
current Datoviz path instead creates a py3-none-any wheel, then retags and repairs it. That is a
valid transitional implementation, but it is not the best long-term architecture and causes CI to
fight the packaging model.
Target Architecture
Create a dedicated Datoviz wheel backend under tools/:
tools/datoviz_build_backend/
__init__.py
backend.py
config.py
metadata.py
wheel.py
native_payload.py
manifest.py
repair.py
tags.py
validate.py
The backend should be usable through PEP 517 with backend-path, for example:
[build-system]
requires = ["packaging", "setuptools", "tomli; python_version < '3.11'", "wheel"]
build-backend = "datoviz_build_backend.backend"
backend-path = ["tools"]
Normal Python source installs, editable installs, and source distributions must remain ordinary
Python builds. The backend should delegate those paths to setuptools.build_meta unless release
wheel mode is explicitly requested. Release wheel mode is entered only when the frontend passes a
namespaced config setting such as:
python -m build --wheel \
--config-setting=datoviz.release-wheel=true \
--config-setting=datoviz.platform-tag=macosx_15_0_arm64
In release wheel mode, build_wheel() should directly emit the final artifact:
datoviz-<version>-py3-none-macosx_15_0_arm64.whl
datoviz-<version>-py3-none-manylinux_2_34_x86_64.whl
datoviz-<version>-py3-none-win_amd64.whl
It should not depend on first producing py3-none-any and retagging as an external post-process.
Any platform repair that rewrites wheel contents or filenames is part of build_wheel() and the
returned artifact path must point to the repaired, final wheel.
The backend may call setuptools.build_meta internally to generate metadata, but the external
release-wheel contract is owned by Datoviz.
Backend Responsibilities
The backend owns the complete release-wheel contract:
- discover or accept the target platform tag;
- verify that the native build tree exists and was configured with required release options such
as
DVZ_ENABLE_SHADERC=ON; - collect Python package files;
- collect public C headers and CMake package files;
- collect
libdatovizand any split runtime libraries required by the platform build; - collect
dlopen()runtime dependencies from explicit runtime directories, especially shaderc; - write or delegate wheel metadata with
Root-Is-Purelib: trueand apy3-none-<platform>tag; - write a payload manifest that records every included native/header/CMake/runtime file;
- build the wheel archive with the final platform tag from the start;
- run platform repair as part of wheel construction when required;
- validate wheel contents and run installed smoke checks.
The backend must preserve the important distinction that source installs are ordinary Python source installs, while release-wheel builds require an existing native Datoviz build tree and platform runtime payload policy.
Source Builds And Metadata
The backend must not make ordinary pip install ., editable installs, or source distributions
depend on a configured native build tree. Required behavior:
build_sdist()delegates to setuptools and produces the normal source distribution.build_editable()delegates to setuptools when available.build_wheel()withoutdatoviz.release-wheel=truedelegates to setuptools and keeps the existing source-install behavior.build_wheel()withdatoviz.release-wheel=trueuses the Datoviz release-wheel path and requires a native build tree.
Project metadata remains sourced from [project] in pyproject.toml: name, version,
dependencies, optional dependencies, entry points, classifiers, URLs, license, and readme. The
backend must either delegate metadata generation to setuptools or have tests that compare generated
METADATA, entry points, and dependency metadata against the setuptools output.
The wheel writer must also produce valid WHEEL and RECORD files. Validation must check
RECORD hashes, Root-Is-Purelib, and the exact py3-none-<platform> tag.
Configuration Model
Move wheel policy into explicit project configuration, not shell fragments spread across CI.
Example shape:
[tool.datoviz.wheel]
platform-specific = true
native-build-dir = "build"
include-headers = true
include-cmake-package = true
runtime-dirs-env = "DVZ_WHEEL_RUNTIME_DIRS"
require-shaderc = true
payload-manifest = "datoviz/_wheel_payload.json"
[tool.datoviz.wheel.macos]
platform-tags = ["macosx_15_0_x86_64", "macosx_15_0_arm64"]
repair-tool = "delocate"
runtime-patterns = [
"libshaderc*.dylib",
"libvulkan*.dylib",
"libMoltenVK.dylib",
"MoltenVK_icd.json",
]
[tool.datoviz.wheel.linux]
platform-tags = ["manylinux_2_34_x86_64", "manylinux_2_34_aarch64"]
repair-tool = "auditwheel"
runtime-patterns = ["libshaderc*.so*"]
[tool.datoviz.wheel.windows]
platform-tags = ["win_amd64", "win_arm64"]
repair-tool = "delvewheel"
runtime-patterns = ["*.dll"]
The exact TOML format can evolve, but the policy should be declarative enough that local release commands and GitHub Actions consume the same source of truth.
Release wheel config settings should be namespaced:
datoviz.release-wheel=true
datoviz.platform-tag=<platform-tag>
datoviz.native-build-dir=<path>
datoviz.dist-dir=<path>
datoviz.include-qtbridge=true|false
datoviz.skip-repair=true|false
Unknown datoviz.* config settings should fail with a clear error. Non-Datoviz settings should be
passed through to the delegated setuptools backend on non-release builds.
Payload Manifest
The release path should create a manifest before archiving the wheel. Each entry should record:
- source path;
- wheel archive path;
- kind:
python,header,cmake,libdatoviz,runtime,qtbridge, ormetadata; - required or optional status;
- policy reason, such as
core-runtime,shaderc-runtime,vulkan-loader,moltenvk, orcmake-consumer; - platform repair status when repair changes the file.
The manifest gives reviewers and future automation a stable way to detect missing payloads,
unexpected runtime libraries, accidental generated files, or unrelated assets. Validation should
reject __pycache__, .DS_Store, source-tree build artifacts outside the declared policy, and
unapproved generated/runtime binary payloads.
Platform Validation
The backend should fail early when a wheel tag would be dishonest.
macOS:
- require
MACOSX_DEPLOYMENT_TARGETfor release-tag builds; - inspect bundled dylibs for minimum macOS version;
- reject a macOS wheel tag if any bundled dylib requires a newer target;
- run
delocate-wheelor equivalent repair insidebuild_wheel(); - revalidate the repaired wheel filename, contents, and native dependencies.
Linux:
- build inside the intended manylinux image or an equivalent controlled environment;
- run
auditwheel show; - run
auditwheel repairinsidebuild_wheel()when required; - reject unresolved native dependencies that are not allowed by policy;
- revalidate the repaired wheel filename, contents, and native dependencies.
Windows:
- collect Datoviz and required runtime DLLs explicitly;
- run
delvewheel show; - run
delvewheel repairinsidebuild_wheel()when required; - reject missing runtime dependencies before upload;
- revalidate the repaired wheel filename, contents, and native dependencies.
CI Shape
GitHub Actions should stop using cibuildwheel as the wheel builder for Datoviz. CI should build
one wheel per platform/architecture and then smoke-test the resulting py3-none-<platform> wheel
across the supported Python versions.
The build job should look conceptually like:
just build
python -m build --wheel \
--config-setting=datoviz.release-wheel=true \
--config-setting=datoviz.platform-tag=<platform-tag>
python -m tools.datoviz_build_backend.validate \
--wheel dist/datoviz-*.whl \
--shaderc \
--cmake-consumer \
--qt-probe optional
The install-smoke jobs should download the artifact and run the same installed checks on Python 3.10, 3.11, 3.12, 3.13, and 3.14. Python 3.15 remains a prerelease, non-blocking lane until it is appropriate for ordinary users.
The migration must also remove or quarantine the existing [tool.cibuildwheel] configuration from
pyproject.toml once .github/workflows/wheels.yml no longer uses cibuildwheel. Keeping stale
cibuildwheel policy beside the new backend would create two apparent sources of truth.
Migration Plan
This is an aggressive refactor, but it should still land in controlled commits:
- Add
tools/datoviz_build_backend/and move reusable code fromtools/release_wheels/into backend modules without changing command behavior. - Add config parsing, namespaced config settings, and explicit release-wheel mode.
- Add setuptools delegation for normal wheel, editable, and sdist builds.
- Add direct wheel writing with final
py3-none-<platform>tags, valid metadata, validRECORD, and a payload manifest. - Move platform-tag selection, payload collection, repair, and validation from the current scripts into backend modules.
- Make
just wheel-stage,just wheel-build,just wheel-validate, andjust wheel-checkdelegate to the backend so local workflows stay stable while internals change. - Add focused tests for delegated source builds, metadata parity, payload manifests, tag validation, missing-runtime diagnostics, repair invocation, and macOS deployment-target rejection.
- Switch
.github/workflows/wheels.ymlfromcibuildwheelto the backend. - Remove or quarantine
[tool.cibuildwheel]frompyproject.toml. - Remove duplicated legacy wheel code after local and CI parity is proven.
- Update maintainer docs to describe the backend as the sole wheel authority.
Non-Goals
- Do not add a CPython extension module only to satisfy generic wheel tooling.
- Do not change the wheel tag to
cp<version>-...; the Python layer remains ABI-independent. - Do not claim an older macOS, manylinux, or Windows support tag than the bundled native payloads actually support.
- Do not vendor SDK/runtime payloads in git.
- Do not make source installs depend on a local native build tree unless the user is explicitly building a release wheel.
- Do not silently synthesize missing runtime dependencies; fail with the searched paths and the exact policy that required the dependency.
Acceptance Criteria
The refactor is complete when:
- the namespaced release-wheel build command shown above produces
datoviz-<version>-py3-none-<tag>.whldirectly; - local
just wheel-*commands are thin wrappers around the backend; - GitHub Actions no longer invokes
cibuildwheelfor Datoviz wheel construction; - Linux, macOS, and Windows wheels build, inspect, and install-smoke on the declared release matrix;
- shaderc runtime GLSL compilation passes from the installed wheel;
- CMake consumer smoke passes from the installed wheel;
- optional Qt probing remains optional and reports clear diagnostics when PyQt/Qt support is absent;
- deployment-target mismatches fail before upload with a clear, platform-specific error;
- ordinary source, editable, and non-release wheel builds do not require a native build tree;
- wheel
METADATA, entry points, dependencies,WHEEL, andRECORDare valid and passtwine check; - wheel validation can run from outside the source checkout and does not depend on repo-local files;
- the payload manifest is present and contains no undeclared runtime libraries,
__pycache__,.DS_Store, or unrelated generated artifacts; [tool.cibuildwheel]is removed or explicitly marked obsolete after the CI workflow switches to the backend.
Implementation Handoff
A future agent can implement this end to end from this document plus the current
tools/release_wheels/ code. The intended pickup order is:
- preserve the current
just wheel-*user interface; - move code into
tools/datoviz_build_backend/behind compatibility wrappers; - add release-wheel mode and setuptools delegation before changing CI;
- prove one host-native wheel locally;
- switch GitHub Actions only after local parity passes;
- delete or quarantine obsolete cibuildwheel configuration last.
The highest-risk details are metadata parity, valid RECORD generation, repair tools that rewrite
wheel contents, and keeping normal Python installs independent of native build artifacts.