feat(toolchain): resolve the compiler in one place
Every clang++, clang and llvm-ar the tool spawned was a bare name on PATH, the std module source was /usr/share/libc++/v1/std.cppm, the WASI sysroot /usr/share/wasi-sysroot and the mingw headers /usr/x86_64-w64-mingw32, spelled out at eighteen call sites. A toolchain installed anywhere else, which is what the Flatpak ships under /app, had no way in. Crafter.Build:Toolchain resolves all of it once per process: CRAFTER_BUILD_LLVM_DIR, else a clang++ installed beside crafter-build (<prefix>/bin, found through the same walk GetCrafterBuildHome does), else PATH. What sits beside the compiler is used when present: its libclang.so, its clang-c headers, its std module source, its WASI sysroot. An explicit prefix without a clang++ is an error rather than a fallback, for the same reason CRAFTER_BUILD_LIBCLANG was already exclusive. CRAFTER_BUILD_LIBCXX_DIR points at a libc++ install other than the compiler's own, and CRAFTER_MINGW_DIR now does what its help line always claimed: it is passed as --sysroot for the mingw target. Binaries linked by a toolchain outside /usr get an rpath to its lib dir, since its libc++.so.1 is not on the loader's default path. HostTarget() is the resolved clang's -print-target-triple. A clang built without a vendor reports x86_64-unknown-linux-gnu where Arch's reports x86_64-pc-linux-gnu, so target equality that decides whether a test runs locally and which targets a bare `test` sweeps goes through SameTarget, which ignores the vendor field. The strings themselves are never rewritten: clang rejects a PCM whose triple differs from the importing unit's. The examples and README stop hardcoding the host triple; the default is already the host. `crafter-build --version` prints the version baked in at build time (CRAFTER_BUILD_VERSION, else `git describe`, else 0.0.0-dev), the compiler and the host triple, and is answered before any argument is forwarded to project.cpp. The version is a define on the library, so each version is its own library variant and relinks the executable and tests once per commit. build.sh gains a shebang and `set -eu`, honours CRAFTER_BUILD_LLVM_DIR and CRAFTER_BUILD_VERSION, skips the glslang clone when it is already there, and writes a fresh archive instead of appending to one that may hold members from an older layout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
68e9b6bfa5
commit
046609ff6f
18 changed files with 767 additions and 190 deletions
|
|
@ -9,6 +9,7 @@ export module Crafter.Build:Clang_impl;
|
|||
import std;
|
||||
import :Clang;
|
||||
import :Platform;
|
||||
import :Toolchain;
|
||||
import :Test;
|
||||
import :Lint;
|
||||
import :Progress;
|
||||
|
|
@ -404,6 +405,11 @@ CompileCommand Crafter::GetCompileCommand(const Configuration& config) {
|
|||
}
|
||||
|
||||
BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, std::shared_future<BuildResult>>& depResults, std::mutex& depMutex) {
|
||||
// Before anything is spawned: one message naming the missing compiler
|
||||
// beats a clang++-not-found per translation unit.
|
||||
if (const Toolchain& tc = GetToolchain(); !tc.error.empty()) {
|
||||
return {tc.error, false, {}};
|
||||
}
|
||||
// Reset per-build cached state on every Module/ModulePartition so that
|
||||
// successive Build() calls on the same Configuration re-evaluate mtimes
|
||||
// (incremental-rebuild test scenarios).
|
||||
|
|
@ -449,7 +455,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// triples (wasi, wasip1, wasip2, ...); the sysroot's per-triple subdirs
|
||||
// handle the differences.
|
||||
if (config.sysroot.empty() && config.target.starts_with("wasm32")) {
|
||||
config.sysroot = "/usr/share/wasi-sysroot";
|
||||
config.sysroot = GetToolchain().wasiSysroot.string();
|
||||
}
|
||||
|
||||
fs::path buildDir = config.BuildDir();
|
||||
|
|
@ -780,7 +786,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
std::string result = RunBuildCommand(std::format("clang {0}.c --target={1}{2} -O3{3} -c{4}{5}{6} -MD -MF {7}_source.o.d -o {7}_source.o", cFile.string(), config.target, cArchFlags, ltoCompileFlags, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string()));
|
||||
std::string result = RunBuildCommand(std::format("{8} {0}.c --target={1}{2} -O3{3} -c{4}{5}{6} -MD -MF {7}_source.o.d -o {7}_source.o", cFile.string(), config.target, cArchFlags, ltoCompileFlags, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string(), GetToolchain().clang.string()));
|
||||
if (result.empty()) return;
|
||||
|
||||
bool expected = false;
|
||||
|
|
@ -1005,6 +1011,13 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// link below ends with linkExtras, so this reaches them all; the static-lib
|
||||
// archive path uses ar instead and is handled separately.
|
||||
linkExtras += ltoLinkFlags;
|
||||
// Host links use the toolchain's own libc++. When the toolchain lives
|
||||
// outside /usr (a bundled one) the binary needs an rpath to find
|
||||
// libc++.so.1 at run time. mingw links libstdc++ and every sysroot build
|
||||
// (wasm included) brings its own libc++.
|
||||
if (config.target != "x86_64-w64-mingw32" && config.sysroot.empty()) {
|
||||
linkExtras += GetToolchain().LibcxxLinkFlags();
|
||||
}
|
||||
// mingw uses libstdc++; C++26 std::print/format extras live in libstdc++exp.
|
||||
// libstdc++ on mingw uses winpthreads for std::atomic_wait /
|
||||
// counting_semaphore / stop_token, so -lpthread is required as soon as
|
||||
|
|
@ -1142,7 +1155,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// pull the needed members. llvm-ar writes a bitcode-aware index
|
||||
// and handles plain ELF and COFF objects just as well, so every
|
||||
// build uses it.
|
||||
buildResult.result = RunBuildCommand(std::format("llvm-ar rcs {}.a {}", (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files));
|
||||
buildResult.result = RunBuildCommand(std::format("{} rcs {}.a {}", GetToolchain().llvmAr.string(), (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files));
|
||||
} else {
|
||||
// LibraryDynamic. Output names follow each target's convention so
|
||||
// consumers can link via the standard linker search:
|
||||
|
|
@ -1420,14 +1433,32 @@ void Crafter::EnableWasiRelaxedSimdVariant(Configuration& cfg) {
|
|||
}
|
||||
|
||||
std::string Crafter::HostTarget() {
|
||||
static const std::string Cached = []() -> std::string {
|
||||
CommandResult r = RunCommandChecked("clang++ -print-target-triple");
|
||||
if (r.exitCode != 0) return {};
|
||||
std::string out = std::move(r.output);
|
||||
while (!out.empty() && (out.back() == '\n' || out.back() == '\r')) out.pop_back();
|
||||
return out;
|
||||
}();
|
||||
return Cached;
|
||||
return GetToolchain().hostTriple;
|
||||
}
|
||||
|
||||
std::string Crafter::NormalizeTarget(std::string_view triple) {
|
||||
// <arch>-<vendor>-<os>-<env>: drop the vendor. A three-part triple only
|
||||
// carries one when its middle part is a vendor name rather than an OS
|
||||
// (x86_64-unknown-wasip1 yes, x86_64-linux-gnu no).
|
||||
std::vector<std::string_view> parts;
|
||||
for (std::size_t begin = 0;;) {
|
||||
std::size_t dash = triple.find('-', begin);
|
||||
parts.push_back(dash == std::string_view::npos ? triple.substr(begin) : triple.substr(begin, dash - begin));
|
||||
if (dash == std::string_view::npos) break;
|
||||
begin = dash + 1;
|
||||
}
|
||||
const bool hasVendor = parts.size() == 4 || (parts.size() == 3 && (parts[1] == "unknown" || parts[1] == "pc"));
|
||||
if (hasVendor) parts.erase(parts.begin() + 1);
|
||||
std::string out;
|
||||
for (std::string_view part : parts) {
|
||||
if (!out.empty()) out += '-';
|
||||
out += part;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Crafter::SameTarget(std::string_view a, std::string_view b) {
|
||||
return a == b || NormalizeTarget(a) == NormalizeTarget(b);
|
||||
}
|
||||
|
||||
bool Crafter::ArgQuery::Has(std::string_view flag) const {
|
||||
|
|
@ -1475,7 +1506,7 @@ ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::str
|
|||
// ApplyStandardArgs, but doing it here makes dep PcmDirs consistent
|
||||
// between the consumer's command-construction and the dep's own build.)
|
||||
if (cfg.sysroot.empty() && cfg.target.starts_with("wasm32")) {
|
||||
cfg.sysroot = "/usr/share/wasi-sysroot";
|
||||
cfg.sysroot = GetToolchain().wasiSysroot.string();
|
||||
}
|
||||
return ArgQuery{args};
|
||||
}
|
||||
|
|
@ -1495,6 +1526,17 @@ std::vector<fs::path> Crafter::CleanProject(const fs::path& projectFile) {
|
|||
return removed;
|
||||
}
|
||||
|
||||
static void PrintVersion() {
|
||||
std::println("crafter-build {}", CrafterBuildVersion());
|
||||
const Toolchain& tc = GetToolchain();
|
||||
if (!tc.error.empty()) {
|
||||
std::println("compiler: {}", tc.error);
|
||||
return;
|
||||
}
|
||||
std::println("compiler {} ({})", tc.clangVersion, tc.clangxx.string());
|
||||
std::println("host {}", tc.hostTriple);
|
||||
}
|
||||
|
||||
static void PrintHelp(std::string_view argv0) {
|
||||
std::println(
|
||||
R"(Usage:
|
||||
|
|
@ -1504,6 +1546,7 @@ R"(Usage:
|
|||
{0} format [format-options] [globs...] Apply the project's transform rules (rewrites files)
|
||||
{0} clean Delete the project's bin/ and build/ trees
|
||||
{0} help | -h | --help Show this help
|
||||
{0} --version Print the version and the compiler in use
|
||||
|
||||
Loads ./project.cpp (override with --project=<path>), compiles it to a shared
|
||||
object, and invokes its CrafterBuildProject() to obtain a Configuration that
|
||||
|
|
@ -1571,7 +1614,14 @@ Environment:
|
|||
CRAFTER_BUILD_RUNNER_<TARGET> Default test runner for a target triple.
|
||||
Replace '-' and '.' with '_' in the
|
||||
triple. CLI --runner= overrides this.
|
||||
CRAFTER_MINGW_DIR Override mingw-w64 sysroot auto-detect.
|
||||
CRAFTER_BUILD_LLVM_DIR LLVM prefix to use instead of a toolchain
|
||||
installed beside crafter-build or PATH.
|
||||
CRAFTER_BUILD_LIBCXX_DIR libc++ install to compile and link against
|
||||
instead of the compiler's own.
|
||||
CRAFTER_BUILD_LIBCLANG Exact libclang to load for lint.
|
||||
CRAFTER_BUILD_WASI_SYSROOT WASI sysroot for wasm32 targets.
|
||||
CRAFTER_MINGW_DIR mingw-w64 sysroot, passed as --sysroot.
|
||||
CRAFTER_BUILD_HOME Directory holding the module sources.
|
||||
|
||||
Exit status:
|
||||
0 success / all non-skipped tests passed / lint clean / format applied
|
||||
|
|
@ -1599,7 +1649,13 @@ std::int32_t Crafter::Run(std::int32_t argc, char** argv) {
|
|||
|
||||
for (std::int32_t i = 1; i < argc; ++i) {
|
||||
std::string_view arg = argv[i];
|
||||
if (arg == "-h" || arg == "--help" || (!runTests && !runLint && !runFormat && arg == "help")) {
|
||||
if (arg == "--version") {
|
||||
// Answered here, before the project file is looked for, so
|
||||
// it works in any directory and is never forwarded to
|
||||
// project.cpp as an unknown argument.
|
||||
PrintVersion();
|
||||
return 0;
|
||||
} else if (arg == "-h" || arg == "--help" || (!runTests && !runLint && !runFormat && arg == "help")) {
|
||||
PrintHelp(argv0);
|
||||
return 0;
|
||||
} else if (!runLint && !runFormat && !runClean && arg == "test") {
|
||||
|
|
|
|||
Loading…
Reference in a new issue