Files
IfcOpenShell/docker/SKILL.md
T
Stephen Boddy fa98aad469 First docker build environment
First functional version, but it needs some improvements and fixes
identified as I've used it personally on one thing, and when an AI
(Claude) used it to work through the CI test errors.

I had the AI make a SKILL.md file. If the AI indicates it needs to
build the ifcopenshell binary, use this and let it rip.
2026-07-12 18:43:27 +01:00

6.8 KiB

name, description
name description
ifcopenshell-docker-build 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

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:

./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:

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:

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 installs 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.