// 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 mingw-w64 prefix that is a whole Linux system's /usr must not hand that // system's headers to a Windows build. Inside the Flatpak the prefix is the // host's /usr, and clang got it as the --sysroot. clang's mingw driver takes // a sysroot for a tree that holds mingw-w64 alone and searches its include/ // after mingw-w64's own headers, so every header mingw-w64 lacks came from // glibc: libstdc++ found and had shared_ptr read // __libc_single_threaded, which nothing on Windows defines, and every // program that used one failed to link (issue #43). CRAFTER_MINGW_DIR=/usr // did the same outside the Flatpak. // // The prefix staged here has the shape of such a /usr and needs no real // mingw-w64: glibc's features.h and in include/, // gcc's per-target directory, which clang derives libstdc++'s header // directory from, and a stand-in mingw-w64 tree whose libstdc++ has a std // module with nothing in it. The fixture is a static library, so nothing is // linked, and its C++ and C sources stop the compile when they see the // prefix's include/ or miss mingw-w64's or libstdc++'s headers. // // clang reaches such a prefix through a view of it (MakeMingwSysroot), which // has to be made where symbolic links can be, and a compile error in one of // its headers has to name the header where it is installed, not in the // view. 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; } } void 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; } BuildResult BuildLibrary(const fs::path& project, std::string_view name, const fs::path& cFile) { Configuration lib; lib.path = project; lib.name = std::string(name); lib.outputName = std::string(name); lib.target = "x86_64-w64-mingw32"; // Spelled out: the default, native, names the host's CPU, which is // no x86 CPU on an aarch64 host. lib.march = "x86-64"; lib.mtune = "x86-64"; lib.type = ConfigurationType::LibraryStatic; std::array ifaces = {}; std::array impls = { fs::path(name) }; lib.GetInterfacesAndImplementations(ifaces, impls); if (!cFile.empty()) lib.cFiles = { cFile }; std::unordered_map> depResults; std::mutex depMutex; return Build(lib, depResults, depMutex); } } int main() { const fs::path scratch = fs::temp_directory_path() / "crafter-build-mingw-sysroot"; std::error_code ec; fs::remove_all(scratch, ec); fs::create_directories(scratch); const fs::path project = scratch / "project"; fs::copy(fs::current_path() / "tests" / "MingwSysroot" / "fixture", project, fs::copy_options::recursive); const fs::path prefix = scratch / "usr"; Write(prefix / "include" / "features.h", "#define __GLIBC__ 2\n"); Write(prefix / "include" / "sys" / "single_threaded.h", "extern char __libc_single_threaded;\n"); fs::create_directories(prefix / "lib" / "gcc" / "x86_64-w64-mingw32" / "15.1.0"); const fs::path mingw = prefix / "x86_64-w64-mingw32"; Write(mingw / "include" / "_mingw.h", ""); Write(mingw / "include" / "crafter_probe_error.h", "#error stopped in a mingw-w64 header\n"); Write(mingw / "include" / "c++" / "15.1.0" / "memory", ""); // Through std::format because the build's import scanner reads string // literals as code: one holding "module std;" made this file a unit of a // module std, and the test did not build. Write(mingw / "include" / "c++" / "15.1.0" / "bits" / "std.cc", std::format("export module {};\n", "std")); Write(mingw / "lib" / "libstdc++.modules.json", R"({"version": 1, "modules": [{"logical-name": "std", "source-path": "../include/c++/15.1.0/bits/std.cc", "is-std-library": true}]})"); { // The view is made in the first place that can hold one. A cache on // a filesystem that takes no symbolic links (vfat, exFAT, some CIFS // and FUSE mounts) holds everything else crafter-build caches, so // its failure must not be every mingw build's: this one can make // nothing under mingw-sysroot, which is a file. const fs::path blocked = scratch / "blocked"; Write(blocked / "mingw-sysroot", ""); const fs::path runtime = scratch / "runtime"; const std::array parents = { blocked, runtime }; fs::path view; try { view = MakeMingwSysroot(prefix, parents); } catch (const std::exception& e) { Check(false, std::format("a view is made in the second place when the first can hold none: {}", e.what())); } if (!view.empty()) { Check(view.string().starts_with(runtime.string()), std::format("the view is in the place that could hold it: {}", view.string())); Check(fs::equivalent(view / "x86_64-w64-mingw32", mingw, ec), "the view shows the prefix's mingw-w64 tree"); Check(fs::equivalent(view / "lib" / "gcc", prefix / "lib" / "gcc", ec), "and gcc's directories"); Check(!fs::exists(view / "include") && !fs::exists(view / "lib" / "libstdc++.modules.json"), "and nothing else of the prefix's include/ and lib/"); fs::path again; try { again = MakeMingwSysroot(prefix, parents); } catch (const std::exception& e) { Check(false, std::format("a view already there is reused: {}", e.what())); } Check(again == view, "a view already there is reused"); } const std::array nowhere = { blocked }; std::string error; try { MakeMingwSysroot(prefix, nowhere); } catch (const std::exception& e) { error = e.what(); } Check(error.contains(prefix.string()) && error.contains((blocked / "mingw-sysroot").string()), std::format("with no place for a view, the error names the prefix and why each place failed: {}", error)); } // Before anything resolves the toolchain, which reads both once. The // view and the std module cache go to the scratch directory too. setenv("CRAFTER_MINGW_DIR", prefix.c_str(), 1); setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1); const BuildResult built = BuildLibrary(project, "probe", project / "cprobe"); Check(built.result.empty(), std::format("a Windows build sees mingw-w64's and libstdc++'s headers and not the prefix's own: {}", built.result)); Check(MingwSysroot() != prefix && MingwSysroot().string().starts_with((scratch / "cache").string()), std::format("a prefix holding a Linux system reaches clang through a view in the cache: {}", MingwSysroot().string())); const BuildResult failed = BuildLibrary(project, "failing", {}); const std::string header = (mingw / "include" / "crafter_probe_error.h").string(); Check(failed.result.contains("stopped in a mingw-w64 header"), std::format("an error in a mingw-w64 header fails the build: {}", failed.result)); Check(failed.result.contains(header) && !failed.result.contains(MingwSysroot().string()), std::format("and names the header in the prefix, not in the view: {}", failed.result)); fs::remove_all(scratch, ec); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }