diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index c37c362..f5943ca 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -265,6 +265,13 @@ fs::path Crafter::StdPcmDir(const Configuration& config) { if (config.target.starts_with("wasm32")) { stdPcmKey += std::format("-{:08x}", static_cast(std::hash{}(WasmBaselineFlags))); } + // The same goes for the mingw-w64 prefix the mingw std module is + // compiled against: releases up to the fix for issue #43 gave clang the + // prefix itself as the sysroot and the ones after give it a view of it, + // and two prefixes are two sets of headers. + if (config.target == "x86_64-w64-mingw32" && !GetToolchain().mingwPrefix.empty()) { + stdPcmKey += std::format("-{:08x}", static_cast(std::hash{}(GetToolchain().mingwPrefix.string()))); + } for (const std::string& f : config.wasmVariantFlags) { stdPcmKey += "+"; for (char c : f) stdPcmKey += (c == '/' || c == '\\') ? '_' : c; @@ -928,9 +935,9 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map source = StdModuleSourcePath(FileBytes(manifestPath)); if (!source) return std::format("{} names no std module", manifestPath.string()); const fs::path stdCc = (manifestPath.parent_path() / *source).lexically_normal(); @@ -862,9 +869,9 @@ std::string Crafter::GetBaseCommand(const Configuration& config) { if (config.target == "x86_64-w64-mingw32") { // libstdc++ from the mingw toolchain, said outright: a clang built to // default to libc++ (the Flatpak's) would otherwise reach for that. - // Found by clang's own mingw discovery unless there is a sysroot. + // Found by clang's own mingw discovery unless there is a prefix. command += " -stdlib=libstdc++"; - if (!tc.mingwSysroot.empty()) command += std::format(" --sysroot={}", tc.mingwSysroot.string()); + if (const fs::path& sysroot = MingwSysroot(); !sysroot.empty()) command += std::format(" --sysroot={}", sysroot.string()); return command; } command += " -stdlib=libc++"; diff --git a/implementations/Crafter.Build-Toolchain.cpp b/implementations/Crafter.Build-Toolchain.cpp index 68d9f8f..c75f627 100644 --- a/implementations/Crafter.Build-Toolchain.cpp +++ b/implementations/Crafter.Build-Toolchain.cpp @@ -43,6 +43,20 @@ namespace { while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' ')) s.pop_back(); return s; } + + // `link` made a symbolic link to `target`, or found one already. Another + // crafter-build may make the same link at the same moment, which is as + // good as making it; a link to anything else is an error. + void EnsureSymlink(const fs::path& target, const fs::path& link) { + std::error_code ec; + if (fs::read_symlink(link, ec) == target) return; + fs::create_directories(link.parent_path()); + fs::create_directory_symlink(target, link, ec); + std::error_code ignored; + if (ec && fs::read_symlink(link, ignored) != target) { + throw fs::filesystem_error("cannot make the mingw-w64 sysroot view", target, link, ec); + } + } } std::string Toolchain::LibcxxCompileFlags() const { @@ -136,11 +150,14 @@ Toolchain Crafter::ResolveToolchain(const ToolchainInputs& in) { tc.wasiSysroot = "/usr/share/wasi-sysroot"; } if (std::string_view dir = Env(in, "CRAFTER_MINGW_DIR"); !dir.empty()) { - tc.mingwSysroot = fs::path(dir); + // Made absolute here, once: the prefix becomes the target of links + // in the cache and part of directory names, and a relative path read + // later from another working directory would name something else. + tc.mingwPrefix = fs::absolute(fs::path(dir)); } else if (!in.hostRoot.empty() && fs::exists(in.hostRoot / "usr" / "x86_64-w64-mingw32")) { // The sandbox has no mingw-w64 of its own, and no mingw gcc on PATH // for clang to find one by. - tc.mingwSysroot = in.hostRoot / "usr"; + tc.mingwPrefix = in.hostRoot / "usr"; } return tc; } @@ -186,6 +203,31 @@ const Toolchain& Crafter::RequireToolchain() { return tc; } +const fs::path& Crafter::MingwSysroot() { + // Made on first use rather than by GetToolchain(), so only a mingw build + // writes to the cache for it, and a failure surfaces as that build's + // error instead of on every command. + static const fs::path Sysroot = []() -> fs::path { + const fs::path& prefix = GetToolchain().mingwPrefix; + if (prefix.empty()) return {}; + // A view per prefix keeps two shells with different + // CRAFTER_MINGW_DIRs, which share one cache, from rewriting each + // other's. + const fs::path sysroot = GetCacheDir() / "mingw-sysroot" / std::format("{:016x}", std::hash{}(prefix.string())); + // All clang's mingw driver takes from a sysroot other than its + // include/ and the rest of its lib/: the mingw-w64 tree (headers, + // libraries, and libstdc++ where a distro's cross gcc installs it) + // and gcc's per-target directory (crtbegin.o, libgcc, and the version + // that names libstdc++'s header directory), which openSUSE keeps + // under lib64. A link to nothing is skipped like a missing directory. + for (std::string_view entry : {"x86_64-w64-mingw32", "lib/gcc", "lib64/gcc"}) { + EnsureSymlink(prefix / entry, sysroot / entry); + } + return sysroot; + }(); + return Sysroot; +} + std::int32_t Crafter::LibcxxVersion(const fs::path& includeDir) { // "# define _LIBCPP_VERSION 230102", the one place libc++ states its // release in a form the preprocessor does not have to be run for. diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index b4bbc68..89583cc 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -628,6 +628,17 @@ export namespace Crafter { compileKey += "|B:"; compileKey += WasmBaselineFlags; } + // The mingw-w64 prefix decides what the mingw target compiles + // against, and until issue #43 clang got it as the sysroot + // itself, which put the host's own headers on the include path. + // Unkeyed, the release that fixed it kept the objects compiled + // against glibc's headers: the build after the update only + // relinked them, and failed to link as before. No prefix keeps + // the id as it was, since clang's own discovery has not changed. + if (target == "x86_64-w64-mingw32" && !toolchain.mingwPrefix.empty()) { + compileKey += "|M:"; + compileKey += toolchain.mingwPrefix.string(); + } // Sorted by ApplyStandardArgs, so flag order on the command line // doesn't split the cache. for (const std::string& a : projectArgs) { diff --git a/interfaces/Crafter.Build-Toolchain.cppm b/interfaces/Crafter.Build-Toolchain.cppm index 797fd94..d6b360a 100644 --- a/interfaces/Crafter.Build-Toolchain.cppm +++ b/interfaces/Crafter.Build-Toolchain.cppm @@ -39,11 +39,14 @@ export namespace Crafter { fs::path wasiSysroot; // The mingw-w64 install prefix, the directory that holds // x86_64-w64-mingw32/ and lib/gcc/x86_64-w64-mingw32/ (/usr for a - // distro package), passed to clang as --sysroot for the mingw target. - // Empty means clang's own discovery, which follows the mingw gcc on - // PATH. Set by CRAFTER_MINGW_DIR, else the host's /usr when running - // sandboxed and it has mingw-w64 (the sandbox's own /usr never does). - fs::path mingwSysroot; + // distro package), as an absolute path. Empty means clang's own + // discovery, which follows the mingw gcc on PATH. Set by + // CRAFTER_MINGW_DIR, else the host's /usr when running sandboxed and + // it has mingw-w64 (the sandbox's own /usr never does). clang never + // gets the prefix itself: the mingw target's --sysroot is + // MingwSysroot(), a view of it. Keyed into the mingw target's + // VariantId and std PCM directory. + fs::path mingwPrefix; // `clang++ -print-target-triple` and the first line of // `clang++ --version`; both empty when clang++ could not be run. std::string hostTriple; @@ -86,6 +89,18 @@ export namespace Crafter { const Toolchain& GetToolchain(); // GetToolchain(), or a std::runtime_error carrying its error. const Toolchain& RequireToolchain(); + // The --sysroot of every compile, link and CMake configure for the mingw + // target: a directory in crafter-build's cache holding only links to the + // x86_64-w64-mingw32/, lib/gcc/ and lib64/gcc/ of GetToolchain()'s + // mingwPrefix, made on the first call of a process. Empty without a + // prefix. The prefix cannot be the sysroot: clang takes a mingw sysroot + // for a tree that holds mingw-w64 alone and searches its include/ and + // lib/ as well, which for /usr (the host's, in the Flatpak) are the + // Linux system's own. A Windows build then saw glibc's headers, and + // libstdc++, finding , had shared_ptr read + // __libc_single_threaded, which nothing on Windows defines (issue #43). + // Throws when the view cannot be made. + const fs::path& MingwSysroot(); // _LIBCPP_VERSION as the <__config> in `includeDir` (an include/c++/v1) // defines it, XXYYZZ; 0 when the file is missing or has no such line. std::int32_t LibcxxVersion(const fs::path& includeDir); diff --git a/tests/ToolchainResolve/main.cpp b/tests/ToolchainResolve/main.cpp index 81969eb..1912418 100644 --- a/tests/ToolchainResolve/main.cpp +++ b/tests/ToolchainResolve/main.cpp @@ -61,7 +61,7 @@ int main() { Check(tc.libcxxVersion == LibcxxVersion("/usr/include/c++/v1"), "the system libc++ headers state the release of the system module source"); Check(tc.stdModulesDir.empty(), "no crafter-build prefix, no shipped module sources"); Check(tc.wasiSysroot == "/usr/share/wasi-sysroot", "the system wasi sysroot is the default"); - Check(tc.mingwSysroot.empty(), "outside a sandbox, clang finds mingw-w64 itself: no --sysroot"); + Check(tc.mingwPrefix.empty(), "outside a sandbox, clang finds mingw-w64 itself: no prefix"); Check(tc.LibcxxCompileFlags().empty() && tc.LibcxxLinkFlags().empty(), "the PATH fallback adds no libc++ flags"); } { @@ -144,7 +144,9 @@ int main() { in.env["CRAFTER_MINGW_DIR"] = "/opt/mingw"; Toolchain tc = ResolveToolchain(in); Check(tc.wasiSysroot == "/opt/wasi", "CRAFTER_BUILD_WASI_SYSROOT overrides the wasi sysroot"); - Check(tc.mingwSysroot == "/opt/mingw", "CRAFTER_MINGW_DIR names the mingw prefix passed as --sysroot"); + Check(tc.mingwPrefix == "/opt/mingw", "CRAFTER_MINGW_DIR names the mingw prefix"); + in.env["CRAFTER_MINGW_DIR"] = "mingw"; + Check(ResolveToolchain(in).mingwPrefix == fs::absolute("mingw"), "a relative CRAFTER_MINGW_DIR is resolved against the working directory once, when the toolchain is"); } { // Sandboxed, the host's /usr stands in for the missing mingw-w64, @@ -153,11 +155,11 @@ int main() { fs::remove_all(host); ToolchainInputs in; in.hostRoot = host; - Check(ResolveToolchain(in).mingwSysroot.empty(), "a host without mingw-w64 gives no --sysroot"); + Check(ResolveToolchain(in).mingwPrefix.empty(), "a host without mingw-w64 gives no mingw prefix"); fs::create_directories(host / "usr" / "x86_64-w64-mingw32"); - Check(ResolveToolchain(in).mingwSysroot == host / "usr", "a host with mingw-w64 gives its /usr as the mingw prefix"); + Check(ResolveToolchain(in).mingwPrefix == host / "usr", "a host with mingw-w64 gives its /usr as the mingw prefix"); in.env["CRAFTER_MINGW_DIR"] = "/opt/mingw"; - Check(ResolveToolchain(in).mingwSysroot == "/opt/mingw", "CRAFTER_MINGW_DIR wins over the host's"); + Check(ResolveToolchain(in).mingwPrefix == "/opt/mingw", "CRAFTER_MINGW_DIR wins over the host's"); fs::remove_all(host); } {