Files
ocp/docs/building.md
stroblme 153f2e4cca Bump to OCCT 8.0.1
Four mechanical binding edits; the bound Python surface is unchanged
(sigdiff compared the 7.9.3.1 dump against 8.0.1.1's in both directions
and found no class, member or constructor arity moved), so the app needs
no change.

- NCollection_Utf8String is gone; NCollection_String is the same UTF-8
  type. Python name kept, since the app imports it.
- Standard_Failure lost its Standard_Transient RTTI when it moved to
  deriving std::exception; ExceptionType() replaced DynamicType()->Name()
  and returns the same class names the exception dispatch keys on.
- FindKey gained a size_t overload beside the int one, so the plain
  member pointer is ambiguous; overload_cast picks the int form, which
  keeps the negative-index guard.
- StdPrs moved from TKService to TKV3d, so CMakeLists links both.

The byte-identity fixtures did NOT retire, contrary to the watchlist:
all six round-trip to the same digests under the new kernel, as do the
measurement, history and mesh-count blocks, so tests/data is untouched
and the app's content-addressed BREP payloads stay valid.

Gates: binding suite 83 passed, 138/138 symbols, sigdiff clean both
directions, ASAN clean, app suite 1798 passed / 1 skipped (unchanged),
sweep over 4486 parts with anchors_digest and every status unchanged,
-m perf 16% faster on the 32-feature chain.
2026-08-11 12:43:54 +02:00

4.7 KiB

Building

Everything runs through make; make help lists the targets. All compilation happens inside the OCCT builder image, so the only host requirements are Docker and (for publishing) uv.

The builder image

occt/Dockerfile compiles OCCT 8.0.1 once inside quay.io/pypa/manylinux_2_28_x86_64 (both the base digest and the source tarball's sha256 are pinned) and installs it to /opt/occt.

make image        # ~40 min on 16 cores
make image-push   # needs: docker login git.stroblme.de

It is a compiler appliance: wheel builds mount the repo into it rather than FROM it, so iterating on the binding never re-layers the kernel. Add new packages at the end of the Dockerfile — earlier layers stay cached and the kernel is not recompiled.

The configuration turns Draw, VTK, Tk, Xlib, OpenGL and GLES off and FreeType on, and the last layer asserts the result: TKService and TKV3d exist (text emboss reaches Font_BRepFont through them), neither carries a libGL/libX11 DT_NEEDED, and freetype is linked. That is what lets the app image eventually drop libgl1 and libx11-6. Flags are -O2, no -ffast-math, no -march=native: OCCT's version is a determinism input for assay's goldens, so the binding must not introduce a different FP contract than the kernel it wraps.

Production never compiles OCCT. The host has 4 cores and make update is a git pull plus a compose build; the kernel arrives prebuilt inside the wheel, which is the whole reason this image exists.

Build cache

Object files, ccache, the pip cache and the build venv live under $(CACHE), default /mnt/cache/n3xd/ocp — off the root filesystem, which is tight on the dev box. Change it per invocation with make CACHE=/somewhere wheel, or reset it with make clean-cache.

The loop

make dev      # incremental compile + pytest — the inner loop, seconds
make test-asan # handle-model memory-safety check
make wheel    # full build: compile, stubs, repack, auditwheel, smoke test

make wheel compiles twice on purpose: stubs are produced by importing the freshly built extension, so they cannot exist before the first compile, and the wheel is packed from the source tree. The second pass is incremental.

The smoke test installs the repaired wheel into a bare venv and imports it with LD_LIBRARY_PATH unset — the only honest proof that auditwheel made it self-contained.

Fixtures

tests/data/*.brep are the byte-identity references and are generated under the stock wheel, from the app checkout:

cd ../app && .venv/bin/python ../ocp/tools/gen_fixtures.py

The generator asserts stock is idempotent for each fixture before recording its digest — otherwise the gate would compare against a moving target. Regenerate only when deliberately re-blessing.

The OCCT 8.0.1 bump was expected to be such an occasion and turned out not to be: every fixture round-tripped to the same digest under the new kernel, as did the measurement, history and mesh-count blocks, so tests/data/ was left untouched. Do not assume the next bump re-blesses either — run the gate first and let it say. Note also that the generator needs a wheel carrying more of OCCT than this binding exposes (it is written against the stock wheel), so regenerating is not currently possible from the app's own venv.

Publishing

make version   # confirm what you are about to publish
make publish   # uv publish -> https://git.stroblme.de/api/packages/N3XD/pypi

Credentials come from .secrets (gitignored) as UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD; the username is a real Gitea username, not the PyPI __token__ convention, and the token needs package: Read and Write.

A version can never be republished. Iteration builds therefore carry a .devN suffix and are the only ones the registry's cleanup rule collects; bump N in pyproject.toml for each upload.

Consumers read anonymously — the N3XD org is public:

uv pip install --index-url https://git.stroblme.de/api/packages/N3XD/pypi/simple/ \
    --prerelease=allow n3xd-ocp

Gitea serves no root /simple/ listing (404), only the per-package path, which is all pip and uv ask for.

Parity

tools/parity_venv.sh builds a side environment where the app runs against this wheel instead of the stock one. The app's manifests are never edited: both distributions own the OCP/ import path and a process can hold only one OCCT build, so a swap is per-environment and reversible by re-syncing.

tools/parity_venv.sh           # from the registry
tools/parity_venv.sh --local   # from wheelhouse/
python tools/inventory.py --check

Coverage is expected to be partial until the increments land — --check prints what is still missing, grouped by module, which is the work queue.