fix: give the mingw target a sysroot that holds mingw-w64 alone
Inside the Flatpak the mingw-w64 prefix is the host's /usr, and it was clang's --sysroot as is. clang's mingw driver takes a sysroot for a tree that holds mingw-w64 alone, and adds its include/ to the include path and its lib/ to the library path. For /usr those are the Linux system's own: every header mingw-w64 lacks came from glibc, and libstdc++'s <ext/atomicity.h>, finding <sys/single_threaded.h>, had shared_ptr read __libc_single_threaded, which nothing on Windows defines. Every program that used one failed to link, among them every Crafter.Graphics Win32 executable, through Crafter.Asset's GLB loader. CRAFTER_MINGW_DIR=/usr did the same outside the Flatpak (#43). The sysroot is now MingwSysroot(): a directory in the cache, one per prefix, holding only links to the prefix's x86_64-w64-mingw32/, lib/gcc/ and lib64/gcc/ (where openSUSE keeps gcc). Through it clang finds mingw-w64, gcc's version and libstdc++ exactly as it does through the mingw gcc on PATH, and nothing else. It is made by the first mingw compile, link or CMake configure of a process, so no other command writes to the cache for it and a failure to make it is that build's error. The Toolchain field is now mingwPrefix, since clang never gets the prefix itself, and it is made absolute when the toolchain is resolved, as it ends up in link targets and directory names. Messages keep naming paths under the prefix rather than under the view. The prefix is also part of the mingw target's VariantId and of its std PCM directory. Units do not track the std PCM they import: when the fixed release built in a directory a broken build had left, the std module was rebuilt and the objects compiled against glibc's headers were only relinked, failing as before. As with a compiler update, a new directory is what gets them rebuilt. It also keeps two releases that share a cache, as the Flatpak's branches do, from rebuilding one std PCM under each other's PCMs, and two prefixes apart. Without a prefix both stay as they were: clang's own discovery has not changed. Two shorter ways do not work: --sysroot=<prefix>/x86_64-w64-mingw32 loses the directory gcc's version names, so <string> is not found, and clang ignores --gcc-toolchain and --gcc-install-dir for this target. Fixes #43 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
909e027698
commit
ab998081e1
7 changed files with 106 additions and 22 deletions
|
|
@ -265,6 +265,13 @@ fs::path Crafter::StdPcmDir(const Configuration& config) {
|
|||
if (config.target.starts_with("wasm32")) {
|
||||
stdPcmKey += std::format("-{:08x}", static_cast<std::uint32_t>(std::hash<std::string_view>{}(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::uint32_t>(std::hash<std::string>{}(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<fs::path, s
|
|||
: std::format(" -march={} -mtune={}", config.march, config.mtune);
|
||||
if (!config.sysroot.empty()) {
|
||||
cArchFlags += std::format(" --sysroot={}", config.sysroot);
|
||||
} else if (config.target == "x86_64-w64-mingw32" && !GetToolchain().mingwSysroot.empty()) {
|
||||
} else if (config.target == "x86_64-w64-mingw32" && !MingwSysroot().empty()) {
|
||||
// The C++ side gets it through GetBaseCommand.
|
||||
cArchFlags += std::format(" --sysroot={}", GetToolchain().mingwSysroot.string());
|
||||
cArchFlags += std::format(" --sysroot={}", MingwSysroot().string());
|
||||
}
|
||||
if (isWasm) {
|
||||
// The C++ path's wasm flags, all of them, a variant pass's included.
|
||||
|
|
@ -1868,9 +1875,9 @@ Environment:
|
|||
CRAFTER_BUILD_LIBCLANG Exact libclang to load for lint.
|
||||
CRAFTER_BUILD_WASI_SYSROOT WASI sysroot for wasm32 targets.
|
||||
CRAFTER_MINGW_DIR mingw-w64 prefix (holding x86_64-w64-mingw32/
|
||||
and lib/gcc/), passed as --sysroot. Default:
|
||||
the mingw gcc on PATH; in the Flatpak, the
|
||||
host's /usr when it has mingw-w64.
|
||||
and lib/gcc/). Default: the mingw gcc on
|
||||
PATH; in the Flatpak, the host's /usr when
|
||||
it has mingw-w64.
|
||||
CRAFTER_BUILD_HOME Directory holding the module sources.
|
||||
|
||||
Exit status:
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ std::string BuildInjectedCMakeFlags(const fs::path& cmakeBuildDir, std::string_v
|
|||
// The libstdc++ and the mingw-w64 install the project's own compiles
|
||||
// use (GetBaseCommand).
|
||||
out += " -DCMAKE_CXX_FLAGS=-stdlib=libstdc++";
|
||||
if (!tc.mingwSysroot.empty()) out += std::format(" -DCMAKE_SYSROOT={}", ShellQuote(tc.mingwSysroot.string()));
|
||||
if (const fs::path& sysroot = MingwSysroot(); !sysroot.empty()) out += std::format(" -DCMAKE_SYSROOT={}", ShellQuote(sysroot.string()));
|
||||
}
|
||||
fs::path absBuildDir = fs::absolute(cmakeBuildDir);
|
||||
out += std::format(" -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}", ShellQuote(absBuildDir.string()));
|
||||
|
|
|
|||
|
|
@ -742,9 +742,9 @@ std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
|||
// its manifest. A toolchain that isn't there is a build error, not
|
||||
// an exception: Build() runs this with its worker threads started.
|
||||
const Toolchain& tc = GetToolchain();
|
||||
const std::string where = tc.mingwSysroot.empty()
|
||||
const std::string where = tc.mingwPrefix.empty()
|
||||
? std::string("where clang finds it by the x86_64-w64-mingw32 gcc on PATH")
|
||||
: std::format("under {}", tc.mingwSysroot.string());
|
||||
: std::format("under {}", tc.mingwPrefix.string());
|
||||
const std::string missing = std::format("no mingw-w64 libstdc++ with the std module (libstdc++ 15 or newer) {}. Install mingw-w64, or point CRAFTER_MINGW_DIR at its prefix, the directory holding x86_64-w64-mingw32/ and lib/gcc/", where);
|
||||
// Asked once a run for each compiler and target: every mingw
|
||||
// configuration comes through here, up to date or not, and the answer
|
||||
|
|
@ -775,6 +775,13 @@ std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
|||
}
|
||||
}
|
||||
if (manifestPath.empty()) return missing;
|
||||
// Found through the view MingwSysroot() makes of the prefix, and
|
||||
// named from here on by its place in the prefix, which is the
|
||||
// directory the user knows and can go and look at.
|
||||
if (!tc.mingwPrefix.empty()) {
|
||||
const fs::path inPrefix = manifestPath.lexically_relative(MingwSysroot());
|
||||
if (!inPrefix.empty() && *inPrefix.begin() != "..") manifestPath = tc.mingwPrefix / inPrefix;
|
||||
}
|
||||
const std::optional<std::string> 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++";
|
||||
|
|
|
|||
|
|
@ -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<std::string>{}(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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue