Files
IfcOpenShell/docker/SKILL.md
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

138 lines
6.8 KiB
Markdown
Raw Normal View History

2026-07-12 18:43:27 +01:00
---
name: ifcopenshell-docker-build
description: >-
Build a real ifcopenshell_wrapper (.so + .py) and IfcConvert locally via
the docker/ifcos_env toolchain, then wire them into a checkout for
running C++-dependent parts of the test suite (geometry, the SWIG
wrapper stub, the C++ parser). Use whenever a task needs to compile
IfcOpenShell's C++ core rather than just read/patch source - e.g.
reproducing or fixing a bug in src/ifcgeom, src/ifcparse, src/ifcwrap,
or validating util/scripts/validate_stub.py against the actual
generated wrapper.
---
# Building IfcOpenShell locally with docker/ifcos_env
`docker/` is a small toolchain (see `docker/README.md` for the original
author's own description and design rationale - read that first for the
*why*; this file is the practical *how*, distilled from actually driving it
end-to-end) that mirrors the project's GitHub Actions build environment
locally, with a persistent container and ccache so repeat builds are fast.
Pure-Python changes don't need any of this - only reach for it when you need
a real compiled `_ifcopenshell_wrapper*.so` or `IfcConvert` binary.
## Placement
This `docker/` folder must live as a direct child of the repo root you want
to build (sibling of `src/`, `cmake/`, etc.) - `compose.yaml` and
`ifcos_env` resolve the repo via `../` relative to wherever `docker/`
itself sits, and bind-mount it into the container. If you're setting this
up in a fresh clone, copy the whole `docker/` directory there first.
## First-time setup
```bash
cd docker
./ifcos_env create # build the base image (shared across all your clones/checkouts by name, so usually instant after the first time anywhere)
./ifcos_env up # start the container, clone+unpack the third-party dependency cache (~10GB, one-time per container)
./ifcos_env build # full build: all deps + IfcParse + IfcGeom + IfcConvert + the Python wrapper, for one Python version
```
`PY_TGT` and `UNIQUE_ID` live in `docker/.env` - `PY_TGT` (e.g. `py-311`)
restricts the build to one Python version instead of building five;
`UNIQUE_ID` is a hash of the folder path, recalculated on every `up`, so
each checkout gets its own container/volumes automatically.
A full first build takes ~1.5 hours (mostly compiling IfcOpenShell's own
C++, not the cached third-party deps). After that, ccache makes incremental
rebuilds of a couple of touched `.cpp` files **under a minute**.
## Fast iteration
Pass a target to `build` to skip the parts you don't need:
```bash
./ifcos_env build IfcConvert # only the executables (IfcConvert, IfcGeomServer) - skips the Python wrapper entirely
./ifcos_env build IfcOpenShell-Python # only the SWIG Python wrapper - skips executables entirely
./ifcos_env build # no target = everything (needed the first time, or after touching shared headers)
```
Use this to keep the edit -> rebuild -> test loop fast when debugging: if
you're only touching `src/ifcgeom/`, build `IfcConvert`; if you're only
exercising the Python API, build `IfcOpenShell-Python`.
## Where the artifacts land
Build output goes to `<repo_root>/build/Linux/x86_64/install/` on the host
(bind-mounted, not just inside the container):
- `ifcopenshell/bin/IfcConvert` - the CLI binary
- `python-<version>/lib/python<X.Y>/site-packages/ifcopenshell/_ifcopenshell_wrapper*.so`
and `ifcopenshell_wrapper.py` - the compiled wrapper + its generated
Python glue
## Wiring the build into a checkout for testing
`_ifcopenshell_wrapper*.so` and `ifcopenshell_wrapper.py` are already
gitignored under `src/ifcopenshell-python/ifcopenshell/`, which is exactly
where a normal in-tree build would put them - copy the two files there:
```bash
SRC=build/Linux/x86_64/install/python-3.11.8/lib/python3.11/site-packages/ifcopenshell
cp "$SRC/_ifcopenshell_wrapper.cpython-311-x86_64-linux-gnu.so" src/ifcopenshell-python/ifcopenshell/
cp "$SRC/ifcopenshell_wrapper.py" src/ifcopenshell-python/ifcopenshell/
```
Then, to run the test suite against it:
```bash
export PATH="$PWD/build/Linux/x86_64/install/ifcopenshell/bin:$PATH" # for IfcConvert-dependent tests
cd src/ifcopenshell-python/test
PYTHONPATH="$PWD/.." python3.11 -m pytest -p no:pytest-blender .
```
(`-p no:pytest-blender` avoids the pytest-blender plugin trying to find a
`blender` executable and failing collection entirely, even for non-Blender
tests.) You'll need the matching Python version's `pip install`s too
(numpy, shapely, isodate, lark, tabulate, pytest, ... - whatever the
modules under test import) since this is a bare interpreter, not the
project's pixi env.
## Known gotchas (some fixed in this copy, watch for them if you're on an
## older/different copy of this script)
- **`try` is an unimplemented stub** - it prints a message and does
nothing. If you want the wrapper pushed straight into a Blender
extensions folder for manual testing, do the copy yourself (see the
README's example path) rather than relying on `try`.
- **`stop`/`down` removes the container**, it does not pause it (it's
literally `docker compose down`). Named volumes (ccache) and the
bind-mounted `build/` survive, so nothing is really lost - `up` just has
to recreate the container - but don't expect `docker ps -a` to still
show it afterwards.
- **The final "Package .zip archives" step of `build()` has a pre-existing
bash syntax error** unrelated to compilation - the actual build already
succeeded by that point (look for `Built IfcOpenShell...` in the output),
so this is safe to ignore if you only need the raw artifacts under
`build/.../install/`, not packaged release zips.
- **`ready_repo` originally cloned the third-party dependency cache one
directory level too shallow** (`../build` instead of `build`, relative to
the repo root), so `nix/build-all.py` would never find it and silently
rebuild every dependency (boost, OCCT, CGAL, ...) from source - "did the
build finish in ~1 minute, or is it grinding for 40+ minutes reconfiguring
OCCT" is the tell. Fixed in this copy; if `up` seems to be building
dependencies that should already be cached, check `ready_repo`'s `cd`
targets first.
- **On a brand-new `UNIQUE_ID`/folder, `up` used to fail on the very first
run** because `ready_repo` tried to `docker exec` into the container
before `docker compose up -d` had created it. Also fixed in this copy
(container creation now happens first); if you see
`Error response from daemon: No such container` right after "Getting the
repo ready to build...", just run `up` again.
- **Root-partition disk space**: only the bind-mounted `<repo>/build` lives
on the host filesystem your repo is checked out on. Anything the
container writes *outside* that mount (stray files, apt/dnf state, etc.)
lives in the container's own writable layer under Docker's data root
(commonly `/var/lib/docker`, i.e. usually your root partition) - keep an
eye on `df -h /` if you're running several of these containers at once.