Crafter.Build/examples/tests
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jorijn van der Graaf 046609ff6f
feat(toolchain): resolve the compiler in one place
Every clang++, clang and llvm-ar the tool spawned was a bare name on
PATH, the std module source was /usr/share/libc++/v1/std.cppm, the WASI
sysroot /usr/share/wasi-sysroot and the mingw headers
/usr/x86_64-w64-mingw32, spelled out at eighteen call sites. A
toolchain installed anywhere else, which is what the Flatpak ships
under /app, had no way in.

Crafter.Build:Toolchain resolves all of it once per process:
CRAFTER_BUILD_LLVM_DIR, else a clang++ installed beside crafter-build
(<prefix>/bin, found through the same walk GetCrafterBuildHome does),
else PATH. What sits beside the compiler is used when present: its
libclang.so, its clang-c headers, its std module source, its WASI
sysroot. An explicit prefix without a clang++ is an error rather than a
fallback, for the same reason CRAFTER_BUILD_LIBCLANG was already
exclusive. CRAFTER_BUILD_LIBCXX_DIR points at a libc++ install other
than the compiler's own, and CRAFTER_MINGW_DIR now does what its help
line always claimed: it is passed as --sysroot for the mingw target.
Binaries linked by a toolchain outside /usr get an rpath to its lib
dir, since its libc++.so.1 is not on the loader's default path.

HostTarget() is the resolved clang's -print-target-triple. A clang built
without a vendor reports x86_64-unknown-linux-gnu where Arch's reports
x86_64-pc-linux-gnu, so target equality that decides whether a test runs
locally and which targets a bare `test` sweeps goes through SameTarget,
which ignores the vendor field. The strings themselves are never
rewritten: clang rejects a PCM whose triple differs from the importing
unit's. The examples and README stop hardcoding the host triple; the
default is already the host.

`crafter-build --version` prints the version baked in at build time
(CRAFTER_BUILD_VERSION, else `git describe`, else 0.0.0-dev), the
compiler and the host triple, and is answered before any argument is
forwarded to project.cpp. The version is a define on the library, so
each version is its own library variant and relinks the executable and
tests once per commit.

build.sh gains a shebang and `set -eu`, honours CRAFTER_BUILD_LLVM_DIR
and CRAFTER_BUILD_VERSION, skips the glslang clone when it is already
there, and writes a fresh archive instead of appending to one that may
hold members from an older layout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:26:31 +02:00
..
mylib linting 2026-07-23 01:24:42 +02:00
tests linting 2026-07-23 01:24:42 +02:00
project.cpp feat(toolchain): resolve the compiler in one place 2026-10-02 22:26:31 +02:00
README.md new tests 2026-05-27 19:45:05 +02:00

tests

Tests are declared in project.cpp via cfg.AddTest(name). One line per test; deps and other options chain off the returned builder.

cd examples/tests
crafter-build test

Layout:

mylib/MyMath.cppm                  # the library being tested
project.cpp                        # declares the library and its tests

tests/Smoke/main.cpp               # zero-config test
tests/UnitMyMath/main.cpp          # test that links MyMath

project.cpp does:

cfg.AddTest("Smoke");                                  // exit 0 = pass
cfg.AddTest("UnitMyMath").Dependencies({ &cfg });      // imports MyMath

AddTest(name) defaults the source to tests/<name>/main.cpp (resolved at the project root) and the target/march/mtune to the parent project's. The builder methods overlay overrides:

  • .Target(triple) / .March(...) / .Mtune(...) / .Sysroot(path)
  • .Define(name, value) / .Debug()
  • .Timeout(seconds)
  • .Args(vec) — runtime args
  • .Requires("tool:foo") / .Requires("file:/path") / .Requires("env:VAR")
  • .Dependencies({ &otherCfg, ... })
  • .Path(p) — rebase the test's source-resolution path
  • .LinkFlag(s) / .CompileFlag(s)

Test conventions

  • Exit code 0 = pass, anything nonzero = fail, 77 = skipped (autoconf convention). Use std::exit(77) for runtime skips like "tool not on PATH".
  • Each test runs in its own subprocess; a segfault doesn't take down the runner.
  • Default timeout is 60 s (crafter-build test --timeout=N overrides).
  • Filter by name: crafter-build test 'Unit*'. List without running: crafter-build test --list.

Linking the parent project

UnitMyMath depends on the library via .Dependencies({ &cfg }) — the test imports MyMath and the build engine links cfg's output into the test exe.

Cross-target test runs

AddTest inherits the parent project's target; for per-test cross-arch runs, override via the builder:

cfg.AddTest("CrossArchAarch64")
   .Target("aarch64-linux-gnu")
   .Sysroot("/opt/aarch64-rootfs")
   .Requires("tool:qemu-aarch64");

crafter-build test sweeps every distinct target declared across the project's tests plus the host triple, so cross-arch tests run by default. --target=<triple> restricts the run to that target.

--runner=<spec> overrides the per-target runner for one invocation. Useful specs: local, cmd:<command> (e.g. cmd:wine, cmd:qemu-aarch64). Persistent override via env: CRAFTER_BUILD_RUNNER_<normalized_target>=<spec>.

SIMD march fan-out

For libraries with per-march SIMD codegen (e.g. Crafter.Math), use AddMarchVariants to produce one Test per tier sharing the same source and interface set:

constexpr MarchTier tiers[] = {
    {"sapphirerapids", "native"},
    {"x86-64-v4",      "generic"},
    {"x86-64-v3",      "generic"},
};
cfg.AddMarchVariants("Vector", libInterfaces, tiers);

Each tier becomes a Test named Vector-<march>, with the library's interfaces rebuilt under that tier's -march/-mtune (so codegen actually varies). Per-arch gating is just an if (target.starts_with("x86_64")) guard around the call — no special toml syntax.