diff --git a/project.cpp b/project.cpp index 0395bbf..43584c8 100644 --- a/project.cpp +++ b/project.cpp @@ -131,6 +131,7 @@ extern "C" Configuration CrafterBuildProject(std::span a cfg.AddTest("VariantId").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("ToolchainResolve").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("SysrootStdModule").Dependencies({ CrafterBuildLib.get() }); + cfg.AddTest("SysrootCompileFlags").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("VersionFlag").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("WasiBrowserRuntime").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("WasmVariants").Dependencies({ CrafterBuildLib.get() }); diff --git a/tests/SysrootCompileFlags/main.cpp b/tests/SysrootCompileFlags/main.cpp new file mode 100644 index 0000000..bfaf95b --- /dev/null +++ b/tests/SysrootCompileFlags/main.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +import std; +import Crafter.Build; +namespace fs = std::filesystem; +using namespace Crafter; + +// clang's driver ranks the host toolchain's own libc++ headers +// (/../include/c++/v1) above --sysroot. A unit that only does +// `import std;` never notices, because the std module is precompiled with the +// sysroot's headers forced. A unit that includes a standard header textually +// (vendored C++ in a dependency, say) got the host's libc++ configured for the +// host's libc: against a musl sysroot every such unit died with "unknown rune +// table for this platform", and against a glibc sysroot it compiled against +// the wrong release without a word. +// +// GetCompileCommand therefore has to point every sysroot compile at the +// sysroot's copy. It must not do so where there is no sysroot, for wasm (which +// has its own sysroot handling) or for mingw (libstdc++, no libc++ at all). +// The command is checked as text: no compiler runs. +namespace { + std::int32_t Failures = 0; + + void Check(bool cond, std::string_view msg) { + if (!cond) { + std::println(std::cerr, "FAIL: {}", msg); + ++Failures; + } + } + + std::string CommandFor(std::string_view target, std::string_view sysroot) { + Configuration cfg; + cfg.path = "/tmp/crafter-build-sysroot-compile-flags"; + cfg.name = "flags"; + cfg.outputName = "flags"; + cfg.target = std::string(target); + cfg.march = "generic"; + cfg.mtune = "generic"; + cfg.type = ConfigurationType::Executable; + cfg.sysroot = std::string(sysroot); + return GetCompileCommand(cfg).command; + } +} + +int main() { + const std::string sysroot = "/opt/crafter-build-test-sysroot"; + const std::string forced = std::format("-nostdinc++ -isystem {}/usr/include/c++/v1", sysroot); + + for (std::string_view target : {"aarch64-alpine-linux-musl", "aarch64-unknown-linux-gnu", "x86_64-pc-linux-gnu"}) { + const std::string command = CommandFor(target, sysroot); + Check(command.contains(std::format("--sysroot={}", sysroot)), std::format("{}: the sysroot is passed", target)); + Check(command.contains(forced), std::format("{}: the sysroot's libc++ headers are forced", target)); + } + + Check(!CommandFor("x86_64-pc-linux-gnu", "").contains("/usr/include/c++/v1 -Wno-unused-command-line-argument"), "no sysroot: nothing is forced"); + Check(!CommandFor("wasm32-wasip1", sysroot).contains(forced), "wasm: left to its own sysroot handling"); + Check(!CommandFor("x86_64-w64-mingw32", sysroot).contains(forced), "mingw: libstdc++ target, libc++ headers are not forced"); + + if (Failures > 0) { + std::println(std::cerr, "{} assertions failed", Failures); + return 1; + } + return 0; +}