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>
This commit is contained in:
Jorijn van der Graaf 2026-10-02 22:26:31 +02:00
commit 046609ff6f
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
18 changed files with 767 additions and 190 deletions

View file

@ -92,6 +92,32 @@ int main() {
Check(r.name == "wine", "ForTarget(mingw) uses wine");
}
// SameTarget: the vendor field is spelling, not identity. A clang built
// without a vendor reports x86_64-unknown-linux-gnu where Arch's reports
// x86_64-pc-linux-gnu; both are the host, and a project that wrote the
// other spelling must still run its tests locally.
{
Check(SameTarget("x86_64-pc-linux-gnu", "x86_64-unknown-linux-gnu"), "pc and unknown vendors compare equal");
Check(SameTarget("x86_64-pc-linux-gnu", "x86_64-linux-gnu"), "a vendorless triple compares equal");
Check(SameTarget("x86_64-alpine-linux-musl", "x86_64-linux-musl"), "a distro vendor compares equal");
Check(SameTarget("aarch64-unknown-linux-gnu", "aarch64-linux-gnu"), "the vendor is dropped on other arches too");
Check(SameTarget("wasm32-unknown-wasip1", "wasm32-wasip1"), "a three-part triple with a vendor compares equal");
Check(!SameTarget("x86_64-pc-linux-gnu", "x86_64-linux-musl"), "a different environment stays different");
Check(!SameTarget("x86_64-pc-linux-gnu", "aarch64-unknown-linux-gnu"), "a different arch stays different");
Check(!SameTarget("x86_64-w64-mingw32", "x86_64-linux-gnu"), "mingw's w64 is not a vendor to drop");
Check(NormalizeTarget("x86_64-w64-mingw32") == "x86_64-w64-mingw32", "a mingw triple is left alone");
Check(NormalizeTarget("wasm32-wasip1") == "wasm32-wasip1", "a two-part triple is left alone");
Configuration pc;
pc.target = "x86_64-pc-linux-gnu";
Configuration unknown;
unknown.target = "x86_64-unknown-linux-gnu";
if (SameTarget(HostTarget(), "x86_64-linux-gnu")) {
Check(TestRunner::ForTarget(pc).IsLocal(), "ForTarget runs the pc spelling locally on an x86_64 glibc host");
Check(TestRunner::ForTarget(unknown).IsLocal(), "ForTarget runs the unknown spelling locally on an x86_64 glibc host");
}
}
// FromEnv: when the env var is set, it wins over the fallback.
{
// Use a unique fake triple so we don't stomp on a real env var the