This document is the source of truth for the canonical build commands. The canonical-commands table in roadmap/README.md §1 mirrors it; later roadmap steps MUST use these exact forms.
-
CMake ≥ 3.22 (NFR-8.8): single configure, no autotools, no network access needed to build.
-
A C++20 compiler (NFR-8.10) for one of the P0 platforms (PRD §6):
P0 platform Toolchain Linux (x64/arm64) GCC or Clang Windows (x64) MSVC 2022 (clang-cl secondary) macOS (arm64/Intel) AppleClang -
All dependencies are vendored in-tree (NFR-8.8): no network access is needed to build. The only vendored dependency so far is GoogleTest (dev-only, PRD §11), used by
tests/only and integrity-checked againstdeps.lockon every configure (M0-DEP-01).
| Purpose | Command |
|---|---|
| Configure | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug |
| Build | cmake --build build -j |
| Test | ctest --test-dir build --output-on-failure |
| ASan/UBSan build | cmake -S . -B build-asan -DCMAKE_BUILD_TYPE=Debug -DLAIGE_ASAN=ON |
| Test (ASan/UBSan tree) | ctest --test-dir build-asan --output-on-failure |
| TSan build | cmake -S . -B build-tsan -DCMAKE_BUILD_TYPE=Debug -DLAIGE_TSAN=ON |
| Test (TSan tree) | ctest --test-dir build-tsan --output-on-failure |
| Headless run | ./build/bin/laige-run --headless CONFIG.json [--ticks N] |
| Fuzz (bounded) | ./build/bin/laige-fuzz <target> --runs=1000 |
| Fuzz (long, nightly form) | ./build/bin/laige-fuzz <target> --runs=1000000 |
| Benchmarks | ./build/bin/laige-bench --suite=<name> |
| Determinism check | ./build/bin/laige-detcheck --scenario=<name> |
| Replay runner | ./build/bin/laige-replay --log LOG --config CONFIG.json [--expect BASELINE] |
| hello template (run) | ./samples/hello/bin/hello [--replay LOG] [--expect BASELINE] |
| hello template (replay) | ./samples/hello/bin/hello --log LOG [--expect BASELINE] |
| hello template (fp32 backend) | ./samples/hello/bin/hello-fp32 [--replay LOG] [--expect BASELINE] |
| API manifest | cmake --build build --target laige-api |
| Include-graph lint + dependency count | python3 tools/laige-include-lint |
| Determinism source scan (sim module) | python3 tools/laige-determinism-lint |
Notes:
Debugis the canonicalCMAKE_BUILD_TYPE;Releaseis supported.- The tool rows above the lint row are live targets now:
laige-run(M1-HEAD-01:--headless CONFIG.json [--ticks N] [--replay LOG], exit codes 0/1/2, thelaige_run_smokeCTest entry is its CI form — contract in docs/api/engine.md),laige-fuzz(M0-CORE-07: thejson_parsetarget and deterministic bounded runs; M0-TEST-01 documents the CI lane semantics — bounded--runs=1000in every P0 job'sctest, the nightly long-run form above — and the seed-handling rules),laige-bench(M0-CORE-08),laige-detcheck(M0-TOOL-02),laige-replay(M1-DET-03: prints the replayed per-tick state-hash stream on stdout;--expect BASELINEcompares and exits 1 at the first diverging tick — contract in docs/api/replay.md), and targetlaige-api(M0-TOOL-01). Their command forms were fixed here when they were reserved, so no step can drift them. Thehellorows are the M1-SAMPLE-01 template game (samples/hello): it builds into the source-tree pathsamples/hello/bin/hello(its own CMake target property — the CI detcheck job invokes it from the repository root with no arguments), prints the 301-line per-tick state-hash stream on stdout (the detcheck scenario contract — docs/api/detcheck.md), and exits 0/1/2.--expect BASELINEcompares the stream against a committed per-tick hash baseline with thelaige-replay --expectcontract (0 match, 1 first-divergence report, 2 baseline read/contract error) — M1-DET-04's baseline check; the committed streams live insamples/hello/baselines/(one per backend; the reference build is canonical Debug g++).hello-fp32is the same game on thefloat_pinned_32backend (ADR 0002 — same source, the backend swapped by the build). Thehello_*CTest entries (tests/sample) are its CI form — including the baseline tests every P0 OS job runs — and--replayis a debug-build feature. Contract: samples/hello/README.md. Fuzz and randomized-test seeds: fixed default0x1F055EED, overridable (laige-fuzz --seed=…; tests via theLAIGE_TEST_SEEDenvironment variable) — see docs/testing.md. - Include-graph lint (M0-CI-03): platform-independent (Python 3 stdlib
only, no setup). It parses the
#includeedges ofsrc/**and enforces the PRD §10.1 rules (laige-core includes nothing internal; arrows only downward in the module stack; vendored deps only from theirdeps.lockowner), then prints the vendored-dependency list and fails above the PRD §11 budget of 10. CI runs it on every PR and merge (jobinclude-lintinci-pull.yml/ci.yml), and the CTest suite runs it against the real tree in every build job (tests/tools). On Windows usepython tools\laige-include-lint. - Determinism source scan (M1-DET-01): platform-independent (Python 3
stdlib only, no setup). It scans
src/laige-sim/**for rawfloat/double(type tokens and float/double literals) andunordered_*containers — the textual half of the G-R8 guarantee (the other half is the compile-time trait inWorld::registerSystem, checked by thetrait_compile_*CTest fixtures). Same-line// LAIGE-DETERM-EXCEPTION: G-R8 <reason>markers are the documented false-positive policy (every suppressed line is counted and printed). CI runs it on every PR and merge (jobdeterminism-lintinci-pull.yml/ci.yml), and the CTest suite runs it against fixture trees and the real tree in every build job (tests/tools,determinism-lint-*). Scope, rules, and the exception policy: docs/concepts/determinism.md. - Test (TSan tree): registered tests automatically run with
TSAN_OPTIONS=halt_on_error=1(wired intests/<module>/CMakeLists.txtwhenLAIGE_TSAN=ON), so a data race makesctestfail with a non-zero exit — no extra environment setup needed. - G-R8 trait compile checks (
trait_compile_*,tests/laige-sim): four CTest fixtures that compile (not link, not run) one translation unit each with the engine policy flags and assert both the exit code and — for the negative fixtures — the actionable G-R8 message in the compiler output. On Windows they invokecl.exedirectly from a generatedcmake -Pscript, outside the VS generator's toolchain setup, so run them from a VS Developer shell (or any shell with the MSVC environment loaded:INCLUDE/LIB/PATHset); CI's Windows Test step imports it viaVsDevCmd.bat(see.github/workflows/ci.yml).
| Tree | Configure flags | Contents |
|---|---|---|
build/ |
(default) | Engine libraries (static), tests, tools |
build-shared/ |
-DLAIGE_BUILD_SHARED=ON |
Same, engine libraries shared (NFR-8.9) |
build-asan/ |
-DLAIGE_ASAN=ON |
Same, whole tree instrumented with ASan+UBSan |
build-tsan/ |
-DLAIGE_TSAN=ON |
Same, whole tree instrumented with TSan |
Runtime artifacts (test binaries, future tools) land in <tree>/bin/,
libraries in <tree>/lib/ — the canonical commands above reference
build/bin/ accordingly.
| Option | Default | Effect |
|---|---|---|
CMAKE_BUILD_TYPE |
Debug |
Build type (canonical: Debug). |
LAIGE_BUILD_SHARED |
OFF |
OFF: engine libraries are static; ON: shared (NFR-8.9). Both variants are built with position-independent code so they link identically. |
LAIGE_BUILD_TESTS |
ON |
Build tests/ and register it with CTest. |
LAIGE_ASAN |
OFF |
Instrument the whole tree with AddressSanitizer + UBSan (NFR-8.2). UBSan reports are fatal: any UB aborts the process. |
LAIGE_TSAN |
OFF |
Instrument the whole tree with ThreadSanitizer (NFR-8.2); registered tests run with TSAN_OPTIONS=halt_on_error=1. Mutually exclusive with LAIGE_ASAN — configuring both fails loudly. |
LAIGE_SCRIPT |
OFF |
Reserved for the scripting module (M4); no effect in M0. |
- ASan/UBSan (
LAIGE_ASAN=ON): every target — engine libraries, tests, future tools — compiles and links with-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer(GCC/Clang/AppleClang). MSVC 2022 uses the documented equivalent/fsanitize=address(plus the MSVC-supported UBSan subset where available). - TSan (
LAIGE_TSAN=ON):-fsanitize=thread(GCC/Clang/AppleClang),/fsanitize=thread(MSVC 2022). - Any sanitizer report therefore fails the build/test loudly: ASan aborts
on the first error, UBSan is made fatal by
-fno-sanitize-recover=all, and TSan failures are fatal to the test process viaTSAN_OPTIONS=halt_on_error=1. CI (M0-CI-02) archives the reports as artifacts. - Sanitizer builds are separate build trees (
build-asan,build-tsan); the options are mutually exclusive and configuring both is rejected. - MSVC sanitizer flags are wired but verified on the Windows CI job (M0-CI-01); the Linux flag set is the reference implementation.
Every engine target is passed through laige_apply_engine_policy()
(root CMakeLists.txt):
- GCC/Clang/AppleClang:
-Wall -Werror -fno-exceptions -fno-rtti - MSVC 2022:
/W4 /WX /EHs- /EHc- /GR-plus-D_HAS_EXCEPTIONS=0(the documented equivalent; the define switches the MS STL to its no-exception code paths, because the STL gates its owntry/catchon_HAS_EXCEPTIONS, not on/EHs-) - C++20 required. The requirement propagates to consumers; the warning flags do not (CPP-010 — a game linking the engine keeps its own compiler policy).
Every target that carries deterministic sim math is passed through
laige_apply_simmath_policy() (root CMakeLists.txt) — in M0 that is
laige-core and the laige-core_tests executable; from M1 on, every
sim module joins the list (e.g. laige-sim). The flags pin the
IEEE float semantics of the fp32_pinned backend
(src/laige-core/include/laige/sim_math.h is the source of truth for the
pinned set and the NaN/Inf policy):
- GCC/Clang/AppleClang:
-ffp-contract=off -fno-associative-math(no FMA contraction ofa*b+c, no reassociation — the pinned set is visible on every compile line). - MSVC 2022:
/fp:precise(MSVC does not FMA-contract C expressions and never reassociates at this setting). - Banned in sim translation units:
-ffast-math/-funsafe-math-optimizations//fp:fast, floating-point intrinsics, rounding-mode changes, FP exception modes (re-audited at every toolchain upgrade, ADR 0002).
laige-corebuilds as a static library (default) or a shared library (-DLAIGE_BUILD_SHARED=ON). It carries the version/build identifier (include/laige/core/version.h,version.cpp) plus the first functional engine code from M0-CORE-01:laige::Result<T,E>/laige::Statusand the error-code registry (include/laige/result.h,include/laige/errors.h,errors.cpp; error text follows the NFR-13.3 5-field grammar — see docs/api/errors.md), the structured logging facade from M0-CORE-02 (include/laige/logging.h,logging.cpp; API contract in docs/api/logging.md), and the SimMath deterministic-math interface with both backends —fp32_pinnedfrom M0-CORE-03 and the defaultfpx16_16from M0-CORE-04 (include/laige/sim_math.h,include/laige/fpx16_16.h,sim_math.cpp,sim_math_fixed.cpp; API contract, NaN/Inf policy, and the fpx16_16 rounding/saturation policy in docs/api/sim_math.md, pinned flags vialaige_apply_simmath_policy()), and the memory pools from M0-CORE-05 (include/laige/pools.h:laige::ArenaPool<T>andlaige::Pool<T>withlaige::PoolStatsaccounting; API contract in docs/api/pools.md), and the bounded JSON parser + serializer from M0-CORE-07 (include/laige/json.h,json.cpp:laige::JsonValue,parseJson,serializeJson,JsonOptions; API contract in docs/api/json.md).tests/laige-core/laige-core_testsis a CTest link smoke test (a GoogleTest suite since M0-DEP-01) that runs in every build tree above: it verifies the static/shared link and checks the NFR-8.10 policy flags withstatic_assert(a policy violation fails the build).result_statusis the M0-CORE-01 CTest entry: a filtered view of the samelaige-core_testsexecutable covering theResultStatus,Status, andErrorCodeRegistrysuites — the step's Verify command isctest -R result_status.loggingis the M0-CORE-02 CTest entry: a filtered view of the same executable covering theLogGate,LogRecord,LogSinks,LogRateLimit,LogFatal,LogCrash,LogConcurrency, andLogPerformancesuites — the step's Verify command isctest -R logging.math_floatis the M0-CORE-03 CTest entry: a filtered view of the same executable covering theSimMathBasics,SimMathNanInf,SimMathProperties, andSimMathDispatchsuites — the step's Verify command isctest -R math_float.math_fixedis the M0-CORE-04 CTest entry: a filtered view of the same executable covering theFixedPointBasics,FixedPointRounding,FixedPointSaturation,FixedPointConversions,FixedPointSimMath,FixedPointDispatch, andFixedPointDeterminismsuites — the step's Verify command isctest -R math_fixed(verified under ASan+UBSan).poolsis the M0-CORE-05 CTest entry: a filtered view of the same executable covering theArenaPoolBasics,ArenaPoolBudget,PoolBasics,PoolBudget,PoolStale,PoolDestruction,PoolStats, andPoolMovesuites — the step's Verify command isctest -R pools(budget exhaustion, reset semantics, and generation-checked stale handles; the stale-handle assert runs in a forked child on the POSIX jobs).config_jsonis the M0-CORE-07 CTest entry: a filtered view of the samelaige-core_testsexecutable covering theConfigJsonValid,ConfigJsonInvalid,ConfigJsonRoundTrip,ConfigJsonValue, andConfigJsonOptionssuites — the step's Verify command isctest -R config_json.fuzz_json_parseis the M0-CORE-07 bounded-fuzz CTest entry (laige-fuzz json_parse --runs=1000, registered intools/fuzz): it runs in every build tree — in the ASan tree it is instrumented and is the step's sanitizer gate (NFR-8.7; PRD §14: fuzz "every commit (bounded), nightly (long)").api-fixture-scan,api-check-fresh,api-check-stale,api-unsupported-construct,api-root-error, andapi-real-treeare the M0-TOOL-01 CTest entries (tests/api): the public API manifest scanner (tools/api/laige-api-scanner) runs against a synthetic fixture tree (the exact manifest bytes are asserted) and against the real repository tree (--check laige-api.json), so a public-header change that misses the manifest fails in every P0 job — and in the dedicatedapi-manifestCI job, which regenerates the manifest and fails on any drift (PRD §9.4, NFR-13.1). The manifest contract (symbol kinds, doc association, exit codes, unsupported constructs) is the header comment oftools/api/laige-api.cpp.detcheck-synthetic,detcheck-synthetic-perturbed,detcheck-bin-identical,detcheck-bin-identical-args,detcheck-bin-diverged,detcheck-bin-malformed,detcheck-scenario-failure,detcheck-stream-mismatch, anddetcheck-unknown-scenarioare the M0-TOOL-02 CTest entries (tests/detcheck): the determinism checker (tools/detcheck/laige-detcheck) runs the synthetic two-run scenario — the built-insyntheticself-check, the built-in perturbation fixture, and the cross-binary mode against fixture scenario binaries (one source, five compiled variants) — asserting both the exit code and the required output fragments per test (ctest -R detcheck). The scenario contract (<tick> <hash>lines, 16 lowercase hex hash digits) and the tool's report/exit-code grammar are in docs/api/detcheck.md; the CIdetcheckjob runs the self-check on every PR and merge, and — with M1-DET-04 — the real-scenario determinism matrix: every P0 OS job's ctest asserts the hello scenario's per-tick hash stream against the committed baselines on both SimMath backends (hello --expect— thehello_baseline_*entries oftests/sample), and the mergedetcheckjob adds the two- configuration pairs (g++ vs clang++ Debug, Debug+ASan vs Release, both backends, via--run-a/--run-b+--compare-combined). Scope and results: docs/benchmarks/determinism-matrix.md.test_infrais the M0-TEST-01 CTest entry (tests/testing): theSeededRandomsuite of thetest_infra_testsexecutable pins known-answer hashes for the seed-handling convention (the default seed0x1F055EED, theLAIGE_TEST_SEEDoverride, the loud-failure path of an invalid seed, and substream isolation) and prints the machine-greppabletest-seed-checklines that let two CI runs of the same commit be compared byte-for-byte (docs/testing.md §4).- Every configure verifies the vendored dependency lock
(
cmake/laige-deps-lock.cmakeagainstdeps.lock); a tampered or unlisted file underdeps/fails the configure loudly. GoogleTest is the only vendored dependency and is linked into tests only (M0-DEP-01, ADR 0004).