fix(mingw): view only a prefix that holds a Linux system
The first fix showed clang every mingw prefix through a view of its mingw-w64 parts. That broke a prefix that holds mingw-w64 alone, where Windows libraries installed with --prefix land in include/ and lib/: with CRAFTER_MINGW_DIR=/opt/mingw, <SDL2/SDL.h> and -lSDL2 were no longer found. Such a prefix is clang's sysroot whole again. Only a prefix that also holds a Linux system gets the view (Toolchain::mingwPrefixIsSystem): the host's /usr inside the Flatpak, whatever its include/ holds, and a prefix whose include/ has a C library's features.h, which every glibc and musl header includes and mingw-w64 has no use for, as CRAFTER_MINGW_DIR=/usr has. MakeMingwSysroot makes the view in the cache, or in $XDG_RUNTIME_DIR/crafter.build when the cache's filesystem takes no symbolic links (vfat, exFAT, CIFS without unix extensions): the XDG spec promises them there, and nothing else in the cache needs them. Its path is absolute, because CMake runs the compiler from directories of its own and a relative cache did not resolve from there. The prefix is resolved once, absolute and lexically normal, so /opt/mingw/ and /opt/mingw are one prefix and not two builds. clang names a header by the path it found it at, so every diagnostic from a viewed mingw-w64 header named a hashed cache directory. RunBuildCommand now writes paths into the view as paths into the prefix, as BuildStdPcm did for its manifest messages already. Also from the review of PR #44: SysrootCompileFlags builds a mingw command, which can make a view, so it redirects the cache like the other tests; the manifest filter's comment names what it still keeps out; the C path asks MingwSysroot() once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5fdbf2baee
commit
06b61e84f9
8 changed files with 262 additions and 78 deletions
|
|
@ -935,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" && !MingwSysroot().empty()) {
|
||||
} else if (config.target == "x86_64-w64-mingw32") {
|
||||
// The C++ side gets it through GetBaseCommand.
|
||||
cArchFlags += std::format(" --sysroot={}", MingwSysroot().string());
|
||||
if (const fs::path& sysroot = MingwSysroot(); !sysroot.empty()) cArchFlags += std::format(" --sysroot={}", sysroot.string());
|
||||
}
|
||||
if (isWasm) {
|
||||
// The C++ path's wasm flags, all of them, a variant pass's included.
|
||||
|
|
@ -1877,7 +1877,9 @@ Environment:
|
|||
CRAFTER_MINGW_DIR mingw-w64 prefix (holding x86_64-w64-mingw32/
|
||||
and lib/gcc/). Default: the mingw gcc on
|
||||
PATH; in the Flatpak, the host's /usr when
|
||||
it has mingw-w64.
|
||||
it has mingw-w64. Of a prefix that holds a
|
||||
Linux system too, such as /usr, clang sees
|
||||
only those directories.
|
||||
CRAFTER_BUILD_HOME Directory holding the module sources.
|
||||
|
||||
Exit status:
|
||||
|
|
|
|||
|
|
@ -281,6 +281,9 @@ bool Crafter::MatchAny(std::span<const std::string> globs, std::string_view name
|
|||
std::string Crafter::RunBuildCommand(std::string_view cmd) {
|
||||
Progress::EchoCommand(cmd);
|
||||
CommandResult r = RunCommandChecked(cmd);
|
||||
// A mingw build against a system prefix sees mingw-w64 through a view in
|
||||
// the cache, and clang names every header by the path it found it at.
|
||||
r.output = MingwViewToPrefix(std::move(r.output));
|
||||
|
||||
if (r.exitCode == 0) {
|
||||
// Warnings from a step that succeeded. Show them — until now they were
|
||||
|
|
@ -737,10 +740,13 @@ std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
|||
// library, naming the std module's source relative to itself. It is
|
||||
// looked for in the library directories the compiler searches for
|
||||
// this target, as it would look for libstdc++ itself, but only in
|
||||
// those that belong to the mingw toolchain: the list also has the
|
||||
// generic <prefix>/lib, where the host's own (Linux) libstdc++ keeps
|
||||
// its manifest. A toolchain that isn't there is a build error, not
|
||||
// an exception: Build() runs this with its worker threads started.
|
||||
// those that belong to the mingw toolchain. The list also holds
|
||||
// clang's resource directory, the sysroot's generic lib/, where a
|
||||
// prefix given whole can keep a libstdc++ built for another target,
|
||||
// and, when clang finds no gcc directory, an empty entry that would
|
||||
// name a manifest in the working directory. 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.mingwPrefix.empty()
|
||||
? std::string("where clang finds it by the x86_64-w64-mingw32 gcc on PATH")
|
||||
|
|
@ -775,13 +781,10 @@ 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;
|
||||
}
|
||||
// Found through the view MingwSysroot() may have made of the prefix,
|
||||
// and named from here on by its place in the prefix, the directory
|
||||
// the user knows and can go and look at.
|
||||
manifestPath = fs::path(MingwViewToPrefix(manifestPath.string()));
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -54,9 +54,26 @@ namespace {
|
|||
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);
|
||||
throw fs::filesystem_error("cannot make a link", target, link, ec);
|
||||
}
|
||||
}
|
||||
|
||||
// `dir` made absolute and spelled one way, once, when the toolchain is
|
||||
// resolved. The mingw prefix ends up as the target of links and in the
|
||||
// names of build and cache directories: a relative path read later from
|
||||
// another working directory would name something else, and /opt/mingw/
|
||||
// next to /opt/mingw would rebuild everything for the same directory.
|
||||
fs::path PrefixPath(const fs::path& dir) {
|
||||
fs::path normal = fs::absolute(dir).lexically_normal();
|
||||
if (!normal.has_filename() && normal.has_relative_path()) normal = normal.parent_path();
|
||||
return normal;
|
||||
}
|
||||
|
||||
// The view MingwSysroot() made and the prefix it shows, for
|
||||
// MingwViewToPrefix. Written once, before ViewMade is set.
|
||||
fs::path MadeView;
|
||||
fs::path ViewedPrefix;
|
||||
std::atomic<bool> ViewMade = false;
|
||||
}
|
||||
|
||||
std::string Toolchain::LibcxxCompileFlags() const {
|
||||
|
|
@ -150,14 +167,19 @@ Toolchain Crafter::ResolveToolchain(const ToolchainInputs& in) {
|
|||
tc.wasiSysroot = "/usr/share/wasi-sysroot";
|
||||
}
|
||||
if (std::string_view dir = Env(in, "CRAFTER_MINGW_DIR"); !dir.empty()) {
|
||||
// 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));
|
||||
tc.mingwPrefix = PrefixPath(fs::path(dir));
|
||||
// A prefix that holds a Linux system as well as mingw-w64, /usr
|
||||
// itself, has that system's C library in include/: features.h, which
|
||||
// every glibc and musl header includes and mingw-w64 has no use for.
|
||||
// One that holds mingw-w64 alone stays the sysroot, so Windows
|
||||
// libraries installed into its include/ and lib/ stay visible.
|
||||
tc.mingwPrefixIsSystem = fs::exists(tc.mingwPrefix / "include" / "features.h");
|
||||
} 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.mingwPrefix = in.hostRoot / "usr";
|
||||
// for clang to find one by. The host's /usr is the host's system,
|
||||
// whether or not its C library's headers are installed.
|
||||
tc.mingwPrefix = PrefixPath(in.hostRoot / "usr");
|
||||
tc.mingwPrefixIsSystem = true;
|
||||
}
|
||||
return tc;
|
||||
}
|
||||
|
|
@ -203,31 +225,69 @@ 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);
|
||||
fs::path Crafter::MakeMingwSysroot(const fs::path& prefix, std::span<const fs::path> parents) {
|
||||
std::string reasons;
|
||||
for (const fs::path& parent : parents) {
|
||||
// One view per prefix: two shells with different CRAFTER_MINGW_DIRs
|
||||
// share one cache and would otherwise rewrite each other's.
|
||||
const fs::path sysroot = parent / "mingw-sysroot" / std::format("{:016x}", std::hash<std::string>{}(prefix.string()));
|
||||
try {
|
||||
// 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;
|
||||
} catch (const std::exception& e) {
|
||||
reasons += std::format("; {}", e.what());
|
||||
}
|
||||
return sysroot;
|
||||
}
|
||||
throw std::runtime_error(std::format("cannot make a sysroot that shows clang only the mingw-w64 parts of {}{}", prefix.string(), reasons));
|
||||
}
|
||||
|
||||
const fs::path& Crafter::MingwSysroot() {
|
||||
// Made on first use rather than by GetToolchain(), so that only what
|
||||
// compiles for the mingw target, a build or an AST lint of one, writes
|
||||
// anything for it, and a failure surfaces as that command's error
|
||||
// instead of on every command.
|
||||
static const fs::path Sysroot = []() -> fs::path {
|
||||
const Toolchain& tc = GetToolchain();
|
||||
if (!tc.mingwPrefixIsSystem) return tc.mingwPrefix;
|
||||
// In the cache, or where the XDG spec promises symbolic links when
|
||||
// the cache's filesystem takes none (vfat, exFAT, CIFS without unix
|
||||
// extensions), which costs nothing else crafter-build caches.
|
||||
// Absolute, because CMake runs the compiler from directories of its
|
||||
// own.
|
||||
std::vector<fs::path> parents = { fs::absolute(GetCacheDir()) };
|
||||
if (const char* runtime = std::getenv("XDG_RUNTIME_DIR"); runtime && *runtime) {
|
||||
parents.push_back(fs::absolute(fs::path(runtime) / "crafter.build"));
|
||||
}
|
||||
fs::path view = MakeMingwSysroot(tc.mingwPrefix, parents);
|
||||
MadeView = view;
|
||||
ViewedPrefix = tc.mingwPrefix;
|
||||
ViewMade.store(true, std::memory_order_release);
|
||||
return view;
|
||||
}();
|
||||
return Sysroot;
|
||||
}
|
||||
|
||||
std::string Crafter::MingwViewToPrefix(std::string text) {
|
||||
if (!ViewMade.load(std::memory_order_acquire)) return text;
|
||||
// With the separator, so a --sysroot= naming the view itself, in a
|
||||
// command line the output quotes, stays what was run.
|
||||
const std::string view = (MadeView / "").string();
|
||||
const std::string prefix = (ViewedPrefix / "").string();
|
||||
for (std::size_t at = text.find(view); at != std::string::npos; at = text.find(view, at + prefix.size())) {
|
||||
text.replace(at, view.size(), prefix);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
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