109
docs/building.md
109
docs/building.md
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user