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);
|
: std::format(" -march={} -mtune={}", config.march, config.mtune);
|
||||||
if (!config.sysroot.empty()) {
|
if (!config.sysroot.empty()) {
|
||||||
cArchFlags += std::format(" --sysroot={}", config.sysroot);
|
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.
|
// 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) {
|
if (isWasm) {
|
||||||
// The C++ path's wasm flags, all of them, a variant pass's included.
|
// 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/
|
CRAFTER_MINGW_DIR mingw-w64 prefix (holding x86_64-w64-mingw32/
|
||||||
and lib/gcc/). Default: the mingw gcc on
|
and lib/gcc/). Default: the mingw gcc on
|
||||||
PATH; in the Flatpak, the host's /usr when
|
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.
|
CRAFTER_BUILD_HOME Directory holding the module sources.
|
||||||
|
|
||||||
Exit status:
|
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) {
|
std::string Crafter::RunBuildCommand(std::string_view cmd) {
|
||||||
Progress::EchoCommand(cmd);
|
Progress::EchoCommand(cmd);
|
||||||
CommandResult r = RunCommandChecked(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) {
|
if (r.exitCode == 0) {
|
||||||
// Warnings from a step that succeeded. Show them — until now they were
|
// 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
|
// library, naming the std module's source relative to itself. It is
|
||||||
// looked for in the library directories the compiler searches for
|
// looked for in the library directories the compiler searches for
|
||||||
// this target, as it would look for libstdc++ itself, but only in
|
// this target, as it would look for libstdc++ itself, but only in
|
||||||
// those that belong to the mingw toolchain: the list also has the
|
// those that belong to the mingw toolchain. The list also holds
|
||||||
// generic <prefix>/lib, where the host's own (Linux) libstdc++ keeps
|
// clang's resource directory, the sysroot's generic lib/, where a
|
||||||
// its manifest. A toolchain that isn't there is a build error, not
|
// prefix given whole can keep a libstdc++ built for another target,
|
||||||
// an exception: Build() runs this with its worker threads started.
|
// 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 Toolchain& tc = GetToolchain();
|
||||||
const std::string where = tc.mingwPrefix.empty()
|
const std::string where = tc.mingwPrefix.empty()
|
||||||
? std::string("where clang finds it by the x86_64-w64-mingw32 gcc on PATH")
|
? 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;
|
if (manifestPath.empty()) return missing;
|
||||||
// Found through the view MingwSysroot() makes of the prefix, and
|
// Found through the view MingwSysroot() may have made of the prefix,
|
||||||
// named from here on by its place in the prefix, which is the
|
// and named from here on by its place in the prefix, the directory
|
||||||
// directory the user knows and can go and look at.
|
// the user knows and can go and look at.
|
||||||
if (!tc.mingwPrefix.empty()) {
|
manifestPath = fs::path(MingwViewToPrefix(manifestPath.string()));
|
||||||
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));
|
const std::optional<std::string> source = StdModuleSourcePath(FileBytes(manifestPath));
|
||||||
if (!source) return std::format("{} names no std module", manifestPath.string());
|
if (!source) return std::format("{} names no std module", manifestPath.string());
|
||||||
const fs::path stdCc = (manifestPath.parent_path() / *source).lexically_normal();
|
const fs::path stdCc = (manifestPath.parent_path() / *source).lexically_normal();
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,26 @@ namespace {
|
||||||
fs::create_directory_symlink(target, link, ec);
|
fs::create_directory_symlink(target, link, ec);
|
||||||
std::error_code ignored;
|
std::error_code ignored;
|
||||||
if (ec && fs::read_symlink(link, ignored) != target) {
|
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 {
|
std::string Toolchain::LibcxxCompileFlags() const {
|
||||||
|
|
@ -150,14 +167,19 @@ Toolchain Crafter::ResolveToolchain(const ToolchainInputs& in) {
|
||||||
tc.wasiSysroot = "/usr/share/wasi-sysroot";
|
tc.wasiSysroot = "/usr/share/wasi-sysroot";
|
||||||
}
|
}
|
||||||
if (std::string_view dir = Env(in, "CRAFTER_MINGW_DIR"); !dir.empty()) {
|
if (std::string_view dir = Env(in, "CRAFTER_MINGW_DIR"); !dir.empty()) {
|
||||||
// Made absolute here, once: the prefix becomes the target of links
|
tc.mingwPrefix = PrefixPath(fs::path(dir));
|
||||||
// in the cache and part of directory names, and a relative path read
|
// A prefix that holds a Linux system as well as mingw-w64, /usr
|
||||||
// later from another working directory would name something else.
|
// itself, has that system's C library in include/: features.h, which
|
||||||
tc.mingwPrefix = fs::absolute(fs::path(dir));
|
// 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")) {
|
} 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
|
// The sandbox has no mingw-w64 of its own, and no mingw gcc on PATH
|
||||||
// for clang to find one by.
|
// for clang to find one by. The host's /usr is the host's system,
|
||||||
tc.mingwPrefix = in.hostRoot / "usr";
|
// whether or not its C library's headers are installed.
|
||||||
|
tc.mingwPrefix = PrefixPath(in.hostRoot / "usr");
|
||||||
|
tc.mingwPrefixIsSystem = true;
|
||||||
}
|
}
|
||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
|
|
@ -203,31 +225,69 @@ const Toolchain& Crafter::RequireToolchain() {
|
||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fs::path& Crafter::MingwSysroot() {
|
fs::path Crafter::MakeMingwSysroot(const fs::path& prefix, std::span<const fs::path> parents) {
|
||||||
// Made on first use rather than by GetToolchain(), so only a mingw build
|
std::string reasons;
|
||||||
// writes to the cache for it, and a failure surfaces as that build's
|
for (const fs::path& parent : parents) {
|
||||||
// error instead of on every command.
|
// One view per prefix: two shells with different CRAFTER_MINGW_DIRs
|
||||||
static const fs::path Sysroot = []() -> fs::path {
|
// share one cache and would otherwise rewrite each other's.
|
||||||
const fs::path& prefix = GetToolchain().mingwPrefix;
|
const fs::path sysroot = parent / "mingw-sysroot" / std::format("{:016x}", std::hash<std::string>{}(prefix.string()));
|
||||||
if (prefix.empty()) return {};
|
try {
|
||||||
// A view per prefix keeps two shells with different
|
// All clang's mingw driver takes from a sysroot other than its
|
||||||
// CRAFTER_MINGW_DIRs, which share one cache, from rewriting each
|
// include/ and the rest of its lib/: the mingw-w64 tree (headers,
|
||||||
// other's.
|
// libraries, and libstdc++ where a distro's cross gcc installs
|
||||||
const fs::path sysroot = GetCacheDir() / "mingw-sysroot" / std::format("{:016x}", std::hash<std::string>{}(prefix.string()));
|
// it) and gcc's per-target directory (crtbegin.o, libgcc, and the
|
||||||
// All clang's mingw driver takes from a sysroot other than its
|
// version that names libstdc++'s header directory), which
|
||||||
// include/ and the rest of its lib/: the mingw-w64 tree (headers,
|
// openSUSE keeps under lib64. A link to nothing is skipped like a
|
||||||
// libraries, and libstdc++ where a distro's cross gcc installs it)
|
// missing directory.
|
||||||
// and gcc's per-target directory (crtbegin.o, libgcc, and the version
|
for (std::string_view entry : {"x86_64-w64-mingw32", "lib/gcc", "lib64/gcc"}) {
|
||||||
// that names libstdc++'s header directory), which openSUSE keeps
|
EnsureSymlink(prefix / entry, sysroot / entry);
|
||||||
// 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"}) {
|
return sysroot;
|
||||||
EnsureSymlink(prefix / entry, sysroot / entry);
|
} 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;
|
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) {
|
std::int32_t Crafter::LibcxxVersion(const fs::path& includeDir) {
|
||||||
// "# define _LIBCPP_VERSION 230102", the one place libc++ states its
|
// "# define _LIBCPP_VERSION 230102", the one place libc++ states its
|
||||||
// release in a form the preprocessor does not have to be run for.
|
// release in a form the preprocessor does not have to be run for.
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,20 @@ export namespace Crafter {
|
||||||
fs::path wasiSysroot;
|
fs::path wasiSysroot;
|
||||||
// The mingw-w64 install prefix, the directory that holds
|
// The mingw-w64 install prefix, the directory that holds
|
||||||
// x86_64-w64-mingw32/ and lib/gcc/x86_64-w64-mingw32/ (/usr for a
|
// x86_64-w64-mingw32/ and lib/gcc/x86_64-w64-mingw32/ (/usr for a
|
||||||
// distro package), as an absolute path. Empty means clang's own
|
// distro package), absolute and lexically normal. Empty means clang's
|
||||||
// discovery, which follows the mingw gcc on PATH. Set by
|
// own discovery, which follows the mingw gcc on PATH. Set by
|
||||||
// CRAFTER_MINGW_DIR, else the host's /usr when running sandboxed and
|
// 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
|
// it has mingw-w64 (the sandbox's own /usr never does). What the
|
||||||
// gets the prefix itself: the mingw target's --sysroot is
|
// mingw target passes as --sysroot is MingwSysroot(), not this:
|
||||||
// MingwSysroot(), a view of it. Keyed into the mingw target's
|
// see mingwPrefixIsSystem.
|
||||||
// VariantId and std PCM directory.
|
|
||||||
fs::path mingwPrefix;
|
fs::path mingwPrefix;
|
||||||
|
// The prefix holds a Linux system as well as mingw-w64: the host's
|
||||||
|
// /usr in the sandbox, or a prefix whose include/ has a C library's
|
||||||
|
// features.h, as /usr has. clang searches a mingw sysroot's include/
|
||||||
|
// and lib/, so such a prefix is shown to it only through a view of
|
||||||
|
// its mingw-w64 parts; any other prefix is the sysroot itself, and
|
||||||
|
// Windows libraries installed into its include/ and lib/ are found.
|
||||||
|
bool mingwPrefixIsSystem = false;
|
||||||
// `clang++ -print-target-triple` and the first line of
|
// `clang++ -print-target-triple` and the first line of
|
||||||
// `clang++ --version`; both empty when clang++ could not be run.
|
// `clang++ --version`; both empty when clang++ could not be run.
|
||||||
std::string hostTriple;
|
std::string hostTriple;
|
||||||
|
|
@ -89,18 +95,32 @@ export namespace Crafter {
|
||||||
const Toolchain& GetToolchain();
|
const Toolchain& GetToolchain();
|
||||||
// GetToolchain(), or a std::runtime_error carrying its error.
|
// GetToolchain(), or a std::runtime_error carrying its error.
|
||||||
const Toolchain& RequireToolchain();
|
const Toolchain& RequireToolchain();
|
||||||
|
// Makes, in the first of `parents` that can hold one, a directory that
|
||||||
|
// shows clang only the mingw-w64 parts of `prefix`: links named
|
||||||
|
// x86_64-w64-mingw32, lib/gcc and lib64/gcc to the same paths under
|
||||||
|
// `prefix`, at <parent>/mingw-sysroot/<hash of prefix>, and returns it.
|
||||||
|
// A view already there is reused; one another process makes at the same
|
||||||
|
// moment is as good as one made here. Throws when no parent can hold
|
||||||
|
// one, with the reason for each.
|
||||||
|
fs::path MakeMingwSysroot(const fs::path& prefix, std::span<const fs::path> parents);
|
||||||
// The --sysroot of every compile, link and CMake configure for the mingw
|
// 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
|
// target, settled on the first call of a process: empty without a
|
||||||
// x86_64-w64-mingw32/, lib/gcc/ and lib64/gcc/ of GetToolchain()'s
|
// mingwPrefix, the prefix itself for one that holds mingw-w64 alone, and
|
||||||
// mingwPrefix, made on the first call of a process. Empty without a
|
// for a system prefix the view MakeMingwSysroot makes in crafter-build's
|
||||||
// prefix. The prefix cannot be the sysroot: clang takes a mingw sysroot
|
// cache, or in $XDG_RUNTIME_DIR/crafter.build when the cache's
|
||||||
// for a tree that holds mingw-w64 alone and searches its include/ and
|
// filesystem takes no symbolic links. A system prefix cannot be the
|
||||||
// lib/ as well, which for /usr (the host's, in the Flatpak) are the
|
// sysroot: clang takes a mingw sysroot for a tree that holds mingw-w64
|
||||||
// Linux system's own. A Windows build then saw glibc's headers, and
|
// alone and searches its include/ and lib/ as well, which for /usr are
|
||||||
|
// the Linux system's own. A Windows build then saw glibc's headers, and
|
||||||
// libstdc++, finding <sys/single_threaded.h>, had shared_ptr read
|
// libstdc++, finding <sys/single_threaded.h>, had shared_ptr read
|
||||||
// __libc_single_threaded, which nothing on Windows defines (issue #43).
|
// __libc_single_threaded, which nothing on Windows defines (issue #43).
|
||||||
// Throws when the view cannot be made.
|
// Throws when the view cannot be made.
|
||||||
const fs::path& MingwSysroot();
|
const fs::path& MingwSysroot();
|
||||||
|
// `text` with every path into the view MingwSysroot() made written as
|
||||||
|
// the same path into the prefix it shows, so that compiler messages name
|
||||||
|
// a header where the user installed it rather than in a hashed cache
|
||||||
|
// directory; `text` unchanged when no view was made.
|
||||||
|
std::string MingwViewToPrefix(std::string text);
|
||||||
// _LIBCPP_VERSION as the <__config> in `includeDir` (an include/c++/v1)
|
// _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.
|
// defines it, XXYYZZ; 0 when the file is missing or has no such line.
|
||||||
std::int32_t LibcxxVersion(const fs::path& includeDir);
|
std::int32_t LibcxxVersion(const fs::path& includeDir);
|
||||||
|
|
|
||||||
6
tests/MingwSysroot/fixture/failing.cpp
Normal file
6
tests/MingwSysroot/fixture/failing.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
|
||||||
|
// Stops in a header of the stand-in mingw-w64, so that the error clang
|
||||||
|
// reports is located in one.
|
||||||
|
#include <crafter_probe_error.h>
|
||||||
|
|
@ -17,17 +17,17 @@ using namespace Crafter;
|
||||||
// did the same outside the Flatpak.
|
// did the same outside the Flatpak.
|
||||||
//
|
//
|
||||||
// The prefix staged here has the shape of such a /usr and needs no real
|
// 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
|
// mingw-w64: glibc's features.h and <sys/single_threaded.h> in include/,
|
||||||
// directory, which clang derives libstdc++'s header directory from, and a
|
// gcc's per-target directory, which clang derives libstdc++'s header
|
||||||
// stand-in mingw-w64 tree whose libstdc++ has a std module with nothing in
|
// directory from, and a stand-in mingw-w64 tree whose libstdc++ has a std
|
||||||
// it. The fixture is a static library, so nothing is linked, and its C++ and
|
// module with nothing in it. The fixture is a static library, so nothing is
|
||||||
// C sources stop the compile when they see the prefix's include/ or miss
|
// linked, and its C++ and C sources stop the compile when they see the
|
||||||
// mingw-w64's or libstdc++'s headers.
|
// 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
|
// clang reaches such a prefix through a view of it (MakeMingwSysroot), which
|
||||||
// fix compiled against the prefix itself, so it has to live in a directory
|
// has to be made where symbolic links can be, and a compile error in one of
|
||||||
// of its own: the Flatpak's branches share one cache, and two releases
|
// its headers has to name the header where it is installed, not in the
|
||||||
// taking turns rebuilding one PCM do it under the PCMs built against it.
|
// view.
|
||||||
|
|
||||||
extern "C" int setenv(const char* name, const char* value, int overwrite);
|
extern "C" int setenv(const char* name, const char* value, int overwrite);
|
||||||
|
|
||||||
|
|
@ -45,6 +45,26 @@ namespace {
|
||||||
fs::create_directories(file.parent_path());
|
fs::create_directories(file.parent_path());
|
||||||
std::ofstream(file, std::ios::binary | std::ios::trunc) << text;
|
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<fs::path, 0> ifaces = {};
|
||||||
|
std::array<fs::path, 1> impls = { fs::path(name) };
|
||||||
|
lib.GetInterfacesAndImplementations(ifaces, impls);
|
||||||
|
if (!cFile.empty()) lib.cFiles = { cFile };
|
||||||
|
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
||||||
|
std::mutex depMutex;
|
||||||
|
return Build(lib, depResults, depMutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|
@ -56,37 +76,71 @@ int main() {
|
||||||
fs::copy(fs::current_path() / "tests" / "MingwSysroot" / "fixture", project, fs::copy_options::recursive);
|
fs::copy(fs::current_path() / "tests" / "MingwSysroot" / "fixture", project, fs::copy_options::recursive);
|
||||||
|
|
||||||
const fs::path prefix = scratch / "usr";
|
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");
|
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");
|
fs::create_directories(prefix / "lib" / "gcc" / "x86_64-w64-mingw32" / "15.1.0");
|
||||||
const fs::path mingw = prefix / "x86_64-w64-mingw32";
|
const fs::path mingw = prefix / "x86_64-w64-mingw32";
|
||||||
Write(mingw / "include" / "_mingw.h", "");
|
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", "");
|
Write(mingw / "include" / "c++" / "15.1.0" / "memory", "");
|
||||||
// Through std::format because the build's import scanner reads string
|
// 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
|
// literals as code: one holding "module std;" made this file a unit of a
|
||||||
// module std, and the test did not build.
|
// 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 / "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}]})");
|
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<fs::path, 2> 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<fs::path, 1> 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
|
// Before anything resolves the toolchain, which reads both once. The
|
||||||
// std module cache goes to the scratch directory too.
|
// view and the std module cache go to the scratch directory too.
|
||||||
setenv("CRAFTER_MINGW_DIR", prefix.c_str(), 1);
|
setenv("CRAFTER_MINGW_DIR", prefix.c_str(), 1);
|
||||||
setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1);
|
setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1);
|
||||||
|
|
||||||
Configuration lib;
|
const BuildResult built = BuildLibrary(project, "probe", project / "cprobe");
|
||||||
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));
|
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(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()));
|
||||||
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()));
|
|
||||||
|
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);
|
fs::remove_all(scratch, ec);
|
||||||
if (failures > 0) {
|
if (failures > 0) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ using namespace Crafter;
|
||||||
// sysroot's copy. It must not do so where there is no sysroot, for wasm (which
|
// sysroot's copy. It must not do so where there is no sysroot, for wasm (which
|
||||||
// has its own sysroot handling) or for mingw (libstdc++, no libc++ at all).
|
// has its own sysroot handling) or for mingw (libstdc++, no libc++ at all).
|
||||||
// The command is checked as text: no compiler runs.
|
// The command is checked as text: no compiler runs.
|
||||||
|
|
||||||
|
extern "C" int setenv(const char* name, const char* value, int overwrite);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::int32_t Failures = 0;
|
std::int32_t Failures = 0;
|
||||||
|
|
||||||
|
|
@ -44,6 +47,15 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
// A compile command names the std module's directory in the cache, and
|
||||||
|
// the mingw one can make the view of a system mingw prefix there
|
||||||
|
// (MingwSysroot), as with CRAFTER_MINGW_DIR=/usr or inside the Flatpak
|
||||||
|
// on a host with mingw-w64. Set before anything resolves the toolchain.
|
||||||
|
const fs::path scratch = fs::temp_directory_path() / "crafter-build-sysroot-compile-flags-cache";
|
||||||
|
std::error_code ec;
|
||||||
|
fs::remove_all(scratch, ec);
|
||||||
|
setenv("XDG_CACHE_HOME", scratch.c_str(), 1);
|
||||||
|
|
||||||
const std::string sysroot = "/opt/crafter-build-test-sysroot";
|
const std::string sysroot = "/opt/crafter-build-test-sysroot";
|
||||||
const std::string forced = std::format("-nostdinc++ -isystem {}/usr/include/c++/v1", sysroot);
|
const std::string forced = std::format("-nostdinc++ -isystem {}/usr/include/c++/v1", sysroot);
|
||||||
|
|
||||||
|
|
@ -57,6 +69,7 @@ int main() {
|
||||||
Check(!CommandFor("wasm32-wasip1", sysroot).contains(forced), "wasm: left to its own sysroot handling");
|
Check(!CommandFor("wasm32-wasip1", sysroot).contains(forced), "wasm: left to its own sysroot handling");
|
||||||
Check(!CommandFor("x86_64-w64-mingw32", sysroot).contains(forced), "mingw: libstdc++ target, libc++ headers are not forced");
|
Check(!CommandFor("x86_64-w64-mingw32", sysroot).contains(forced), "mingw: libstdc++ target, libc++ headers are not forced");
|
||||||
|
|
||||||
|
fs::remove_all(scratch, ec);
|
||||||
if (Failures > 0) {
|
if (Failures > 0) {
|
||||||
std::println(std::cerr, "{} assertions failed", Failures);
|
std::println(std::cerr, "{} assertions failed", Failures);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,31 @@ int main() {
|
||||||
Check(tc.mingwPrefix == "/opt/mingw", "CRAFTER_MINGW_DIR names the mingw prefix");
|
Check(tc.mingwPrefix == "/opt/mingw", "CRAFTER_MINGW_DIR names the mingw prefix");
|
||||||
in.env["CRAFTER_MINGW_DIR"] = "mingw";
|
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");
|
Check(ResolveToolchain(in).mingwPrefix == fs::absolute("mingw"), "a relative CRAFTER_MINGW_DIR is resolved against the working directory once, when the toolchain is");
|
||||||
|
// The prefix names directories, so one directory has one spelling:
|
||||||
|
// otherwise a trailing slash rebuilds every mingw build for nothing.
|
||||||
|
for (const char* spelling : {"/opt/mingw/", "/opt//mingw", "/opt/./mingw", "/opt/x/../mingw"}) {
|
||||||
|
in.env["CRAFTER_MINGW_DIR"] = spelling;
|
||||||
|
Check(ResolveToolchain(in).mingwPrefix == "/opt/mingw", std::format("{} is spelled /opt/mingw (got '{}')", spelling, ResolveToolchain(in).mingwPrefix.string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// A prefix that holds a Linux system's C library, as /usr does, is
|
||||||
|
// shown to clang through a view; one that holds mingw-w64 alone is
|
||||||
|
// the sysroot itself, Windows libraries in its include/ and lib/
|
||||||
|
// included.
|
||||||
|
const fs::path staged = fs::temp_directory_path() / "crafter-build-toolchain-mingw-prefixes";
|
||||||
|
fs::remove_all(staged);
|
||||||
|
Touch(staged / "system" / "include" / "features.h");
|
||||||
|
Touch(staged / "system" / "x86_64-w64-mingw32" / "include" / "_mingw.h");
|
||||||
|
Touch(staged / "dedicated" / "include" / "SDL2" / "SDL.h");
|
||||||
|
Touch(staged / "dedicated" / "x86_64-w64-mingw32" / "include" / "_mingw.h");
|
||||||
|
ToolchainInputs in;
|
||||||
|
in.env["CRAFTER_MINGW_DIR"] = (staged / "system").string();
|
||||||
|
Check(ResolveToolchain(in).mingwPrefixIsSystem, "a prefix with a C library's features.h holds a Linux system");
|
||||||
|
in.env["CRAFTER_MINGW_DIR"] = (staged / "dedicated").string();
|
||||||
|
Check(!ResolveToolchain(in).mingwPrefixIsSystem, "a prefix of mingw-w64 and Windows libraries does not");
|
||||||
|
Check(!ResolveToolchain({}).mingwPrefixIsSystem, "no prefix, nothing to show");
|
||||||
|
fs::remove_all(staged);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Sandboxed, the host's /usr stands in for the missing mingw-w64,
|
// Sandboxed, the host's /usr stands in for the missing mingw-w64,
|
||||||
|
|
@ -158,6 +183,7 @@ int main() {
|
||||||
Check(ResolveToolchain(in).mingwPrefix.empty(), "a host without mingw-w64 gives no mingw prefix");
|
Check(ResolveToolchain(in).mingwPrefix.empty(), "a host without mingw-w64 gives no mingw prefix");
|
||||||
fs::create_directories(host / "usr" / "x86_64-w64-mingw32");
|
fs::create_directories(host / "usr" / "x86_64-w64-mingw32");
|
||||||
Check(ResolveToolchain(in).mingwPrefix == 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");
|
||||||
|
Check(ResolveToolchain(in).mingwPrefixIsSystem, "the host's /usr is the host's system, with or without its C library's headers");
|
||||||
in.env["CRAFTER_MINGW_DIR"] = "/opt/mingw";
|
in.env["CRAFTER_MINGW_DIR"] = "/opt/mingw";
|
||||||
Check(ResolveToolchain(in).mingwPrefix == "/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);
|
fs::remove_all(host);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue