97 lines
4.7 KiB
C++
97 lines
4.7 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 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 <sys/single_threaded.h> 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 <sys/single_threaded.h> 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.
|
||
|
|
//
|
||
|
|
// The std module such a build compiles is not the one releases before the
|
||
|
|
// fix compiled against the prefix itself, so it has to live in a directory
|
||
|
|
// of its own: the Flatpak's branches share one cache, and two releases
|
||
|
|
// taking turns rebuilding one PCM do it under the PCMs built against it.
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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" / "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" / "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}]})");
|
||
|
|
// Before anything resolves the toolchain, which reads both once. The
|
||
|
|
// std module cache goes to the scratch directory too.
|
||
|
|
setenv("CRAFTER_MINGW_DIR", prefix.c_str(), 1);
|
||
|
|
setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1);
|
||
|
|
|
||
|
|
Configuration lib;
|
||
|
|
lib.path = project;
|
||
|
|
lib.name = "probe";
|
||
|
|
lib.outputName = "probe";
|
||
|
|
lib.target = "x86_64-w64-mingw32";
|
||
|
|
lib.type = ConfigurationType::LibraryStatic;
|
||
|
|
std::array<fs::path, 0> ifaces = {};
|
||
|
|
std::array<fs::path, 1> impls = { "probe" };
|
||
|
|
lib.GetInterfacesAndImplementations(ifaces, impls);
|
||
|
|
lib.cFiles = { project / "cprobe" };
|
||
|
|
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
||
|
|
std::mutex depMutex;
|
||
|
|
const BuildResult built = Build(lib, depResults, depMutex);
|
||
|
|
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));
|
||
|
|
const std::string unkeyed = std::format("{}-{}-{}", lib.target, lib.march, GetToolchain().Key());
|
||
|
|
Check(StdPcmDir(lib).filename() != unkeyed, std::format("the std module of a mingw build with a prefix is not cached where older releases built it against the prefix itself: {}", StdPcmDir(lib).string()));
|
||
|
|
|
||
|
|
fs::remove_all(scratch, ec);
|
||
|
|
if (failures > 0) {
|
||
|
|
std::println(std::cerr, "{} assertions failed", failures);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|