// 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; // Building for x86_64-w64-mingw32 without a usable mingw-w64 libstdc++ has to // fail the build with a message that says what is missing. It used to abort // the whole process with a bare "libc++abi: terminating": the std module's // source was found by listing /include/c++ with a throwing // directory_iterator, the exception unwound through Build() while its worker // threads were still joinable, and ~thread called std::terminate. That is // what every mingw build inside the Flatpak did, as the sandbox has no // /usr/x86_64-w64-mingw32. // // The std module's source is now found through libstdc++.modules.json in the // mingw toolchain's library directories. CRAFTER_MINGW_DIR points the // toolchain at a scratch prefix, which the test takes through four states: // - missing; // - with only a manifest in the prefix's generic lib/, where the host's // Linux libstdc++ keeps one: not the mingw library, so still missing; // - with a mingw manifest that names no std module; // - with one whose std module source isn't there. 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 BuildOnce(const fs::path& project) { Configuration app; app.path = project; app.name = "hello"; app.outputName = "hello"; app.target = "x86_64-w64-mingw32"; 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() { const fs::path scratch = fs::temp_directory_path() / "crafter-build-mingw-missing"; fs::remove_all(scratch); fs::create_directories(scratch); const fs::path project = scratch / "project"; fs::copy(fs::current_path() / "tests" / "MingwMissing" / "fixture", project, fs::copy_options::recursive); const fs::path sysroot = scratch / "mingw"; // Before anything resolves the toolchain, which reads both once. The std // module cache goes to the scratch directory too. setenv("CRAFTER_MINGW_DIR", sysroot.c_str(), 1); setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1); const BuildResult missing = BuildOnce(project); Check(!missing.result.empty(), "a missing mingw prefix fails the build"); Check(missing.result.contains("mingw") && missing.result.contains(sysroot.string()) && missing.result.contains("CRAFTER_MINGW_DIR"), std::format("and says where it looked and how to point elsewhere: {}", missing.result)); auto write = [](const fs::path& file, std::string_view text) { fs::create_directories(file.parent_path()); std::ofstream(file, std::ios::binary | std::ios::trunc) << text; }; write(sysroot / "lib" / "libstdc++.modules.json", R"({"version": 1, "modules": [{"logical-name": "std", "source-path": "../include/c++/16/bits/std.cc", "is-std-library": true}]})"); write(sysroot / "include" / "c++" / "16" / "bits" / "std.cc", "// the host's Linux std module\n"); const BuildResult linux = BuildOnce(project); Check(linux.result.contains("no mingw-w64 libstdc++"), std::format("a manifest outside the mingw toolchain's own directories isn't taken for it: {}", linux.result)); const fs::path manifest = sysroot / "x86_64-w64-mingw32" / "lib" / "libstdc++.modules.json"; write(manifest, R"({"version": 1, "modules": [{"logical-name": "std.compat", "source-path": "../include/c++/15.1.0/bits/std.compat.cc"}]})"); const BuildResult noStd = BuildOnce(project); Check(noStd.result.contains(manifest.string()) && noStd.result.contains("no std module"), std::format("a manifest without the std module fails the build, naming the manifest: {}", noStd.result)); write(manifest, R"({"version": 1, "modules": [{"source-path": "../include/c++/15.1.0/bits/std.cc", "logical-name": "std"}]})"); const BuildResult noSource = BuildOnce(project); Check(noSource.result.contains((sysroot / "x86_64-w64-mingw32" / "include" / "c++" / "15.1.0" / "bits" / "std.cc").string()) && noSource.result.contains("missing"), std::format("a std module source that isn't there fails the build, naming it: {}", noSource.result)); std::error_code ec; fs::remove_all(scratch, ec); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }