97 lines
4.9 KiB
C++
97 lines
4.9 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;
|
||
|
|
|
||
|
|
// 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 <mingw sysroot>/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<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() {
|
||
|
|
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;
|
||
|
|
}
|