fix(sysroot): take the std module source from the sysroot's libc++ release
A cross build against a sysroot whose libc++ differs from the toolchain's failed while precompiling the std module, with a page of "no member named 'runtime_format' in namespace 'std'". The module source is a list of using-declarations for what one libc++ release declares, so it only compiles against that release's headers. BuildStdPcm wants the sysroot's own <sysroot>/usr/share/libc++/v1/std.cppm, but Alpine packages the headers without it, and the fallback was the toolchain's copy whatever its release. That held while the compiler came from the same distro as the sysroot; with the Flatpak's libc++ 22.1.8 and Alpine edge on 23.1.2 it no longer does. The release is now read from _LIBCPP_VERSION in the sysroot's and the toolchain's <__config>, and SysrootStdModule picks in this order: the sysroot's own source, the toolchain's when both are the same major release (a release branch only takes fixes), the copy crafter-build ships for that release under share/crafter-build/libc++-modules/<major>. When none fits the build stops with one line naming both releases and the directory the matching source belongs in, before clang is run. If either release cannot be read the toolchain's source is tried as before, since there is nothing to compare. Matching sources live in crafter-build's own share directory rather than the LLVM prefix: they are data this tool ships for sysroots, not part of any one compiler install, and "sources ship" already describes that directory. `--version` prints the toolchain's libc++ release and the releases sources are shipped for, the first thing to look at when such a build fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
adc91ed9e2
commit
e15cd3c14d
4 changed files with 112 additions and 14 deletions
|
|
@ -1537,6 +1537,20 @@ static void PrintVersion() {
|
|||
return;
|
||||
}
|
||||
std::println("compiler {} ({})", tc.clangVersion, tc.clangxx.string());
|
||||
if (tc.libcxxVersion != 0) {
|
||||
// A sysroot on another libc++ release builds only when its std
|
||||
// module source is in the sysroot or among these; printed here
|
||||
// because that is the first question when such a build fails.
|
||||
std::vector<std::string> shipped;
|
||||
std::error_code ec;
|
||||
for (const fs::directory_entry& entry : fs::directory_iterator(tc.stdModulesDir, ec)) {
|
||||
if (fs::exists(entry.path() / "std.cppm")) shipped.push_back(entry.path().filename().string());
|
||||
}
|
||||
std::ranges::sort(shipped);
|
||||
std::string also;
|
||||
for (const std::string& release : shipped) also += std::format(", {}", release);
|
||||
std::println("libc++ {}{}", LibcxxVersionString(tc.libcxxVersion), also.empty() ? std::string() : std::format(" (std module sources for sysroots also for{})", also.substr(1)));
|
||||
}
|
||||
std::println("host {}", tc.hostTriple);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -663,14 +663,13 @@ std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
|||
} else if (config.sysroot.empty()) {
|
||||
stdCppm = GetToolchain().stdCppm.string();
|
||||
} else {
|
||||
stdCppm = std::format("{}/usr/share/libc++/v1/std.cppm", config.sysroot);
|
||||
// not every distro ships the std module sources (Alpine's
|
||||
// libc++-dev doesn't); the host copy is interchangeable as long
|
||||
// as the libc++ versions line up, which the compile of the PCM
|
||||
// against the sysroot's headers verifies anyway
|
||||
if (!fs::exists(stdCppm) && fs::exists(GetToolchain().stdCppm)) {
|
||||
stdCppm = GetToolchain().stdCppm.string();
|
||||
}
|
||||
// The source has to belong to the libc++ release whose headers
|
||||
// the sysroot carries. Handing clang the toolchain's own copy
|
||||
// regardless used to end in a page of "no member named ... in
|
||||
// namespace 'std'" once the two releases differed.
|
||||
StdModuleSource source = SysrootStdModule(GetToolchain(), config.sysroot);
|
||||
if (!source.error.empty()) return source.error;
|
||||
stdCppm = source.stdCppm.string();
|
||||
}
|
||||
std::string sysrootFlag = config.sysroot.empty()
|
||||
? std::string()
|
||||
|
|
|
|||
|
|
@ -112,15 +112,19 @@ Toolchain Crafter::ResolveToolchain(const ToolchainInputs& in) {
|
|||
|
||||
// 3. libc++ and its std module source. The compiler's own copy lives at
|
||||
// <prefix>/share/libc++/v1 when the prefix carries libc++; the system
|
||||
// copy is the last resort either way.
|
||||
const fs::path systemStdCppm = "/usr/share/libc++/v1/std.cppm";
|
||||
// copy is the last resort either way. The headers of the same install
|
||||
// say which release the source belongs to.
|
||||
fs::path libcxxRoot = "/usr";
|
||||
if (std::string_view dir = Env(in, "CRAFTER_BUILD_LIBCXX_DIR"); !dir.empty()) {
|
||||
tc.libcxxDir = fs::path(dir);
|
||||
tc.stdCppm = tc.libcxxDir / "share" / "libc++" / "v1" / "std.cppm";
|
||||
libcxxRoot = tc.libcxxDir;
|
||||
} else if (!tc.prefix.empty() && fs::exists(tc.prefix / "share" / "libc++" / "v1" / "std.cppm")) {
|
||||
tc.stdCppm = tc.prefix / "share" / "libc++" / "v1" / "std.cppm";
|
||||
} else {
|
||||
tc.stdCppm = systemStdCppm;
|
||||
libcxxRoot = tc.prefix;
|
||||
}
|
||||
tc.stdCppm = libcxxRoot / "share" / "libc++" / "v1" / "std.cppm";
|
||||
tc.libcxxVersion = LibcxxVersion(libcxxRoot / "include" / "c++" / "v1");
|
||||
if (!in.crafterPrefix.empty()) {
|
||||
tc.stdModulesDir = in.crafterPrefix / "share" / "crafter-build" / "libc++-modules";
|
||||
}
|
||||
|
||||
// 4. Sysroots for the cross targets that have a conventional location.
|
||||
|
|
@ -180,6 +184,56 @@ const Toolchain& Crafter::RequireToolchain() {
|
|||
return tc;
|
||||
}
|
||||
|
||||
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.
|
||||
constexpr std::string_view Marker = "define _LIBCPP_VERSION ";
|
||||
std::ifstream config(includeDir / "__config");
|
||||
std::string line;
|
||||
while (std::getline(config, line)) {
|
||||
const std::size_t at = line.find(Marker);
|
||||
if (at == std::string::npos) continue;
|
||||
const std::string_view digits = std::string_view(line).substr(at + Marker.size());
|
||||
std::int32_t version = 0;
|
||||
if (std::from_chars(digits.data(), digits.data() + digits.size(), version).ec == std::errc{}) return version;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string Crafter::LibcxxVersionString(std::int32_t version) {
|
||||
return std::format("{}.{}.{}", version / 10000, version / 100 % 100, version % 100);
|
||||
}
|
||||
|
||||
StdModuleSource Crafter::SysrootStdModule(const Toolchain& tc, const fs::path& sysroot) {
|
||||
const fs::path own = sysroot / "usr" / "share" / "libc++" / "v1" / "std.cppm";
|
||||
if (fs::exists(own)) return {own, {}};
|
||||
|
||||
const fs::path includeDir = sysroot / "usr" / "include" / "c++" / "v1";
|
||||
if (!fs::exists(includeDir / "__config")) {
|
||||
return {{}, std::format("sysroot {} has no libc++ headers at {}; install its libc++ development package", sysroot.string(), includeDir.string())};
|
||||
}
|
||||
const std::int32_t wanted = LibcxxVersion(includeDir);
|
||||
const bool comparable = wanted != 0 && tc.libcxxVersion != 0;
|
||||
// Releases are matched on the major version: a release branch only takes
|
||||
// fixes, so 22.1.3 headers and the 22.1.8 module source name the same
|
||||
// declarations.
|
||||
if (comparable && wanted / 10000 == tc.libcxxVersion / 10000 && fs::exists(tc.stdCppm)) return {tc.stdCppm, {}};
|
||||
if (wanted != 0 && !tc.stdModulesDir.empty()) {
|
||||
const fs::path shipped = tc.stdModulesDir / std::to_string(wanted / 10000) / "std.cppm";
|
||||
if (fs::exists(shipped)) return {shipped, {}};
|
||||
}
|
||||
// One of the two releases is unknown, so there is nothing to compare:
|
||||
// hand over the toolchain's source and let the compile decide, which is
|
||||
// what happened for every sysroot before releases were compared.
|
||||
if (!comparable && fs::exists(tc.stdCppm)) return {tc.stdCppm, {}};
|
||||
|
||||
const std::string sysrootRelease = wanted != 0 ? LibcxxVersionString(wanted) : std::string("of an unknown release");
|
||||
const std::string toolchainSource = !fs::exists(tc.stdCppm)
|
||||
? std::string("the toolchain has no std module source at all")
|
||||
: std::format("the toolchain's is for libc++ {}", LibcxxVersionString(tc.libcxxVersion));
|
||||
return {{}, std::format("sysroot {} carries libc++ {} without its std module source, and crafter-build has none for that release ({}); install the share/libc++/v1 directory of that libc++ release (std.cppm and std/) as {}", sysroot.string(), sysrootRelease, toolchainSource, own.parent_path().string())};
|
||||
}
|
||||
|
||||
std::string_view Crafter::CrafterBuildVersion() {
|
||||
return CRAFTER_BUILD_VERSION;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,14 @@ export namespace Crafter {
|
|||
fs::path libcxxDir;
|
||||
// libc++'s std module source for host builds.
|
||||
fs::path stdCppm;
|
||||
// The _LIBCPP_VERSION of the libc++ stdCppm belongs to (220108 for
|
||||
// 22.1.8); 0 when its <__config> could not be read.
|
||||
std::int32_t libcxxVersion = 0;
|
||||
// Std module sources for libc++ releases other than the toolchain's
|
||||
// own, one <major>/std.cppm each, for sysroots that carry libc++
|
||||
// without them: <crafter prefix>/share/crafter-build/libc++-modules.
|
||||
// Empty when the crafter prefix is unknown; need not exist.
|
||||
fs::path stdModulesDir;
|
||||
fs::path wasiSysroot;
|
||||
// Holds include/c++/<ver>/bits/std.cc for the mingw target. Passed to
|
||||
// clang as --sysroot only when it was set explicitly: clang's own
|
||||
|
|
@ -74,6 +82,29 @@ export namespace Crafter {
|
|||
const Toolchain& GetToolchain();
|
||||
// GetToolchain(), or a std::runtime_error carrying its error.
|
||||
const Toolchain& RequireToolchain();
|
||||
// _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);
|
||||
// "23.1.2" for 230102.
|
||||
std::string LibcxxVersionString(std::int32_t version);
|
||||
|
||||
struct StdModuleSource {
|
||||
fs::path stdCppm;
|
||||
// One line saying why no source fits; empty when stdCppm is set.
|
||||
std::string error;
|
||||
};
|
||||
// The std module source to precompile for a native (non-wasm) build
|
||||
// against `sysroot`. The module source is a list of using-declarations
|
||||
// for names the headers of the same libc++ release declare, so one from
|
||||
// another release fails on every name that release added or dropped. In
|
||||
// order: the sysroot's own <sysroot>/usr/share/libc++/v1/std.cppm, the
|
||||
// toolchain's when both are the same major release, the copy for the
|
||||
// sysroot's release under toolchain.stdModulesDir. Not every distro
|
||||
// packages the module sources with its libc++ (Alpine does not), which
|
||||
// is why the sysroot's own copy cannot be the only answer. When the
|
||||
// sysroot's release cannot be read, the toolchain's source is returned
|
||||
// and the compile is the check.
|
||||
StdModuleSource SysrootStdModule(const Toolchain& toolchain, const fs::path& sysroot);
|
||||
// The version baked in when crafter-build was built; "0.0.0-dev" for a
|
||||
// build that was given none.
|
||||
std::string_view CrafterBuildVersion();
|
||||
|
|
|
|||
Loading…
Reference in a new issue