Signed-off-by: stroblme <stroblme@posteo.de>
This commit is contained in:
2026-08-11 15:07:47 +02:00
parent ab32514df1
commit 8175b8aff3
5 changed files with 261 additions and 380 deletions

View File

@@ -1,8 +1,8 @@
# 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`.
Everything runs through `make` `make help` lists the targets. All
compilation happens inside the OCCT builder image, so the only things you
need on the host are Docker and, for publishing, `uv`.
## The builder image
@@ -11,102 +11,95 @@ and (for publishing) `uv`.
tarball's sha256 are pinned) and installs it to `/opt/occt`.
```bash
make image # ~40 min on 16 cores
make image-push # needs: docker login git.stroblme.de
make image
make image-push
```
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.
It's a **compiler appliance**: wheel builds mount the repo into it rather
than building `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 OCCT doesn't recompile.
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.
The configuration turns Draw, VTK, Tk, Xlib, OpenGL and GLES off and
FreeType on, and the last build layer checks the result: TKService and TKV3d
exist (needed for text emboss via `Font_BRepFont`), neither links
`libGL`/`libX11`, and FreeType is linked. Flags are `-O2`, no `-ffast-math`,
no `-march=native` — the build must produce the same floating-point results
as any other build of the same kernel version, since downstream code
compares geometry output across builds.
## 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`.
Object files, ccache, the pip cache and the build venv live under
`$(CACHE)`, default `/mnt/cache/n3xd/ocp` kept off the root filesystem,
which is tight on the dev box. Override it per invocation with
`make CACHE=/somewhere wheel`, or reset it with `make clean-cache`.
## The loop
```bash
make dev # incremental compile + pytest — the inner loop, seconds
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 # 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.
`make wheel` compiles twice on purpose: stubs come from importing the
freshly built extension, so they can't exist before the first compile, and
the wheel is packed from the source tree afterwards. 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
The smoke test installs the repaired wheel into a bare venv and imports it
with `LD_LIBRARY_PATH` unset — the only real 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:
`tests/data/*.brep` are byte-identity references, generated under the
**stock** wheel from a checkout of the app that consumes this binding:
```bash
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.
The generator asserts each fixture is idempotent under stock before
recording its digest, so the gate compares against a fixed target rather
than a moving one. Regenerate only when deliberately re-blessing — a kernel
bump doesn't automatically mean the fixtures need it, so run the gate first
and let it tell you before touching `tests/data/`. The generator also needs
a wheel carrying more of OCCT than this binding exposes, so it can't
currently run from this project's own venv.
## Publishing
```bash
make version # confirm what you are about to publish
make version # confirm what you're 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`.
`UV_PUBLISH_PASSWORD` a real Gitea username rather than PyPI's `__token__`
convention, with a token scoped to `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
A version can never be republished. Iteration builds 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:
Consumers install anonymously, since the registry is public:
```bash
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.
Gitea serves no root `/simple/` listing (404), only the per-package path,
which is all pip and uv ever 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` builds a side environment where the app runs against
this wheel instead of the stock one, without touching the app's own
manifests — both distributions own the `OCP/` import path, so swapping is
per-environment and reversible by re-syncing.
```bash
tools/parity_venv.sh # from the registry
@@ -114,5 +107,5 @@ 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.
Coverage is expected to be partial until everything is bound — `--check`
prints what's still missing, grouped by module.