// 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; // A WASI sysroot without exception support has to stop a wasm build with one // line that says so. wasm builds compile with -fwasm-exceptions and link the // libc++ and libunwind that wasi-sdk 33 and later ship under lib//eh; // a sysroot that lacks them (an older wasi-sdk, one built without its // exceptions option) otherwise compiles and then fails the first link with // "unable to find library -lunwind", which does not say why it was asked // for. The check runs before the std module is built, so the first case // needs no working wasm toolchain. // // A scratch sysroot is taken through three states: no libunwind; libunwind // under eh/; libunwind in the triple's own directory, which the driver // searches after eh/. The last two pass the check and fail later on the // sysroot's empty std module source, which is not what is under test. The // cache is redirected into the scratch directory as well: the std PCM cache // is not keyed on the sysroot, and a PCM built from the fake source must not // land where the real wasm tests read theirs. extern "C" int setenv(const char* name, const char* value, int overwrite); namespace { std::int32_t failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++failures; } } BuildResult BuildAgainst(const fs::path& project, const fs::path& sysroot) { Configuration app; app.path = project; app.name = "hello"; app.outputName = "hello"; app.target = "wasm32-wasip1"; app.sysroot = sysroot.string(); app.type = ConfigurationType::Executable; std::array ifaces = {}; std::array impls = { "main" }; app.GetInterfacesAndImplementations(ifaces, impls); std::unordered_map> depResults; std::mutex depMutex; return Build(app, depResults, depMutex); } } int main() { // Build() reports a missing compiler before it gets to the sysroot. if (const Toolchain& tc = GetToolchain(); !tc.error.empty()) { std::println(std::cerr, "SKIP: {}", tc.error); return 77; } const fs::path scratch = fs::temp_directory_path() / "crafter-build-wasi-sysroot-without-exceptions"; fs::remove_all(scratch); fs::create_directories(scratch); setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1); const fs::path project = scratch / "project"; fs::copy(fs::current_path() / "tests" / "WasiSysrootWithoutExceptions" / "fixture", project, fs::copy_options::recursive); const fs::path sysroot = scratch / "sysroot"; const fs::path libDir = sysroot / "lib" / "wasm32-wasip1"; fs::create_directories(libDir / "eh"); fs::create_directories(sysroot / "share" / "libc++" / "v1"); std::ofstream(sysroot / "share" / "libc++" / "v1" / "std.cppm"); constexpr std::string_view missing = "has no C++ exception support"; { const BuildResult r = BuildAgainst(project, sysroot); Check(r.result.contains(missing), std::format("without libunwind the build stops on the sysroot: {}", r.result)); Check(r.result.contains((libDir / "eh" / "libunwind.a").string()), std::format("the message names the library it looked for: {}", r.result)); } std::ofstream(libDir / "eh" / "libunwind.a"); Check(!BuildAgainst(project, sysroot).result.contains(missing), "libunwind under eh/ passes the check"); fs::remove(libDir / "eh" / "libunwind.a"); std::ofstream(libDir / "libunwind.a"); Check(!BuildAgainst(project, sysroot).result.contains(missing), "libunwind in the triple's directory passes the check"); fs::remove_all(scratch); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }