Builds n3xd-ocp end to end and publishes 7.9.3.1.dev1 to the Gitea registry, where it installs anonymously and passes its suite. - occt/Dockerfile: OCCT 7.9.3 compiled once into a manylinux_2_28 builder image (base digest + tarball sha256 pinned), Draw/VTK/Tk/Xlib/OpenGL off, FreeType on, -O2 without fast-math or march=native. A final layer asserts TKService/TKV3d exist with no libGL/libX11 DT_NEEDED, which is what lets the app image drop libgl1/libx11-6. Mounted into, never built FROM. - scikit-build-core + nanobind STABLE_ABI -> one cp312-abi3 extension that registers every OCP.* submodule via PyImport_AddModule, so `import OCP.TopoDS` needs no shim and cls.__module__ is right. Version <occt>.N is asserted against the OCCT found, keeping occt_version() truthful. - occt_handle.h: type caster for opencascade::handle<T> over OCCT's intrusive refcount. Wrappers are non-owning instances holding exactly one handle in their keep-alive list, reusing an existing wrapper so identity survives a round trip. Transient constructors go through ocp_new (never nb::init<>, which would let OCCT delete nanobind's storage); the caster refuses a refcount-0 object rather than corrupt the heap. Verified under ASAN with no memory-safety errors, plus an RSS bound over 50k create/destroy cycles. - Sub-shapes are returned by value everywhere, making the TShape lifetime class that segfaulted a process-global face memo unrepresentable. - Standard_Failure derives RuntimeError, with ~20 concrete types dispatched on the dynamic OCCT type (cad_pool marshals failures home by type name). - Inc 0 surface: gp subset, TopAbs, TopoDS (+ downcasts), TopExp, TopLoc, TopTools, BRep, BinTools, Poly, Standard. 34 of the app's 139 symbols. - n3xd_ocp: additive APIs kept out of the OCP namespace so parity testing stays meaningful. bintools (shape <-> bytes, GIL-free, byte-identical) and _debug. Two findings worth the record, both verified against the stock wheel rather than assumed: upstream binds __hash__ but leaves __eq__ at identity, which is exactly what geom_memo.py's hash-bucket + IsSame scan is built around, so we match it instead of "fixing" it; and BinTools can release the GIL after all, by slurping the file-like object instead of bridging a streambuf that would call back into Python. Gate: BREP round-trips are byte-identical to cadquery-ocp-novtk across six fixtures (the generator asserts stock idempotency first). That matters beyond IPC — derive.py content-addresses BREP payloads by sha256 and stores the ref.
111 lines
4.2 KiB
Markdown
111 lines
4.2 KiB
Markdown
# 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 7.9.3 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`.
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```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 (e.g. the OCCT 8.0 bump).
|
|
|
|
## Publishing
|
|
|
|
```bash
|
|
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:
|
|
|
|
```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.
|
|
|
|
## 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.
|
|
|
|
```bash
|
|
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.
|