WasmExceptions builds a fixture for wasm32-wasip1 and runs it under
wasmtime. It throws through four frames whose destructors have to run,
catches the exception, catches one that vector::at throws, and with an
argument lets one escape main, which has to fail the run. With the
previous flags the fixture does not compile ("cannot use 'throw' with
exceptions disabled"), and each part of turning exceptions on fails it
in its own way, from a BMI built without them to the legacy encoding
wasmtime refuses.
WasiSysrootWithoutExceptions takes a scratch sysroot through three
states, no libunwind, libunwind under eh/ and libunwind in the
triple's own directory, and checks that only the first stops the
build, with the message naming the library it looked for. It needs no
working wasm toolchain, so it also runs where the others skip.
WasmHelloRuns and WasmExceptions treat a sysroot without the
exception-enabled libraries as missing and skip. The manifest tests
expect the exception-handling probe on every entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
4 KiB
C++
95 lines
4 KiB
C++
// 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/<triple>/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<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
app.GetInterfacesAndImplementations(ifaces, impls);
|
|
std::unordered_map<fs::path, std::shared_future<BuildResult>> 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;
|
|
}
|