How do I build the system?
Table of Contents
Configure first if you haven't (see How do I configure the project?). For preset / output / parallelism details, see CMake setup.
1. Question
How do I build ORE Studio?
2. Answer
2.1. Day-to-day with compass
compass build resolves the checkout's preset from ORES_PRESET in
.env and configures the build directory automatically on first use:
./compass.sh build # everything ./compass.sh build ores.iam.core # specific target(s) ./compass.sh build site # friendly alias (deploy_site) ./compass.sh build --preset linux-clang-release-make # preset override ./compass.sh build -j 4 --dry-run # jobs override / print-only ./compass.sh build -k # keep going past a failed target
-k (--keep-going) passes the native tool's own option after cmake's --:
-k 0 for ninja and -k for make. One build then reports every failing
target, not only the first one. -k N stops ninja after N failures, as ninja's
own -k does; make takes no count.
With ninja 1.13, a build first runs ninja -t recompact on the build
directory. Ninja 1.13 cannot remove a damaged record from its deps log
(ninja#2703): each
load stops at that record and drops all later records, so every build is a
full one. The recompact writes the log again without the damaged record, in
less than a second. Other ninja versions build directly.
Friendly aliases: site → deploy_site, manual → deploy_manual,
org-roam-db-sync → org_roam_db_sync; any raw cmake target is also
accepted.
For documentation/agile targets, add --direct to skip cmake entirely
and run the Emacs build script directly (no cmake/vcpkg — works in light
environments): ./compass.sh build --direct site skills settings manual.
See How do I deploy the site?.
Always build via compass build (not raw cmake --build) — never
bypass it, even to "just quickly build one target". See Build
locking below for why.
2.2. Build locking
This only matters on a host running several ORE Studio worktrees at once (each their own environment) — the common case for this repo's LLM-driven workflow, where multiple environments can be building in parallel. On a single-checkout machine with nothing else building, the lock is a no-op (you always get a free slot immediately).
Where it applies: concurrent, uncoordinated cmake --build
invocations from different worktrees on the same host contend for
the same CPUs and have been observed to corrupt shared library
outputs when one build is killed mid-link while another links
against the same target. A non-direct compass build automatically
waits for one of two host-wide lock slots before invoking cmake, so
at most two environments on the host build at once, each capped to
that slot's -j:
./compass.sh build --status
reports each slot as held (with the holding environment's identity,
pid, and start time) or free, plus a tail of the holder's build log,
read from the holder's own checkout. ./compass.sh build status is the
same command, and both answer while a build of this worktree runs. A
free slot also reports the last holder recorded in this checkout, from
tmp/ores-build-last-holder.<slot>, or says that no build has taken it
yet. The abstract socket itself leaves nothing behind, so this sidecar
is the only way to see a previous holder.
Every build's combined stdout/stderr is also streamed live to a
well-known path per slot, tmp/ores-build.log.<slot> under the
checkout (overwritten each run) — tail -f it directly if you want
to watch a build you (or another environment) kicked off rather than
polling --status. The log lives in the checkout rather than in the
host's /tmp so that a sandboxed reader, which mounts its own /tmp,
can still read it.
If both slots are busy, compass build prints a waiting message and
blocks until one frees — this is expected, not a hang; let it wait
rather than killing it or falling back to a raw cmake --build.
--jobs overrides the slot's job count if you deliberately need a
different value for one invocation. --dry-run does not take a lock
or write a log.
The lock only works because the sandbox shares the host's network
namespace: an abstract socket name is exclusive within that namespace,
not across filesystems. A launcher can publish the host namespace in
ORES_HOST_NETNS (the inode of the host's /proc/self/ns/net);
compass build then refuses a sandbox whose namespace differs. Without
that reference nothing inside the sandbox can prove the namespace is
shared, so the build header prints the namespace it holds the slot in
and marks the sharing unverified. Two builds that both report slot
='a' with different namespace inodes are the symptom of a private
network namespace.
The actual cmake --build step (not the configure step) also runs
inside its own memory-capped app-build-<env>.slice when a user
systemd manager is available — deployed on demand from a checked-in
dash-truncated template drop-in (same mechanism as How do I launch
Claude Code inside a systemd scope?'s app-claude-.slice.d/), so an
overrunning build is killed by its own cgroup OOM instead of pushing
the whole machine into swap. Inspect with:
systemd-cgls --user-unit=app-build-<env>.slice systemctl --user show app-build-<env>.slice -p MemoryMax -p CPUWeight
2.3. Direct CMake
Only for local single-environment work where you know no other
worktree on the host is building concurrently — it bypasses the lock
entirely, so simultaneous invocations from other environments can
corrupt shared build outputs (see Build locking above). Prefer
compass build otherwise.
cmake --build --preset <preset> [--target <target_name>]
3. Script
compass build (projects/ores.compass/src/compass.py) wrapping
cmake --build --preset. Locking is _acquire_build_lock=/
=BUILD_LOCK_SLOTS in the same file — two slots (each -j3), held by
binding the abstract AF_UNIX socket names @ores-build-slot-a and
@ores-build-slot-b. Abstract names live in the network namespace, not
on a filesystem, so every worktree and every bubblewrap sandbox on the
host contends for the same two slots; a lock file in /tmp would be
private to each sandbox. Two environments build at once and six cores are
in flight at most. The memory cap comes from
_ensure_build_slice_deployed=/=_build_slice_name in the same file,
syncing
projects/ores.compass/src/systemd/app-build-.slice.d/50-limits.conf.
4. Tested by
CI builds every preset on every push.
5. See also
- CMake setup — preset naming, output layout, parallelism.
- How do I configure the project?
- How do I run the tests? — once the build is green.