// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® export module Crafter.Build:Toolchain; import std; namespace fs = std::filesystem; export namespace Crafter { // The compiler crafter-build drives and what sits next to it, resolved // once per process by GetToolchain(). Every clang++, clang and llvm-ar // the tool spawns, the libclang the linter dlopens, the std module // source and the default sysroots come from here, so a toolchain // installed anywhere other than /usr (the Flatpak carries its own under // /app) needs no per-call-site knowledge. struct Toolchain { // LLVM install prefix. Empty when the tools are bare names on PATH. fs::path prefix; fs::path clangxx; fs::path clang; fs::path llvmAr; // Where clang-c/Index.h lives when it is not on the default include // path; empty otherwise. fs::path llvmIncludeDir; // The exact libclang to dlopen; nullopt means search the usual names. std::optional libclang; // A libc++ install other than the compiler's own // (CRAFTER_BUILD_LIBCXX_DIR); empty means clang finds its own. 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 /std.cppm each, for sysroots that carry libc++ // without them: /share/crafter-build/libc++-modules. // Empty when the crafter prefix is unknown; need not exist. fs::path stdModulesDir; fs::path wasiSysroot; // The mingw-w64 install prefix, the directory that holds // x86_64-w64-mingw32/ and lib/gcc/x86_64-w64-mingw32/ (/usr for a // distro package), absolute and lexically normal. Empty means clang's // own discovery, which follows the mingw gcc on PATH. Set by // CRAFTER_MINGW_DIR, else the host's /usr when running sandboxed and // it has mingw-w64 (the sandbox's own /usr never does). What the // mingw target passes as --sysroot is MingwSysroot(), not this: // see mingwPrefixIsSystem. 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++ --version`; both empty when clang++ could not be run. std::string hostTriple; std::string clangVersion; // Why clang++ could not be resolved or run; empty when it could. std::string error; // Flags for compiling against libcxxDir; empty without one. std::string LibcxxCompileFlags() const; // Flags for linking against the libc++ the compiler will use: a -L // and an rpath for /lib when the prefix is not the system one // (clang searches only its per-target runtime directory and the // system paths, and a bundled toolchain's libc++.so.1 is on neither // the linker's nor the loader's default path), or for an explicit // libcxxDir. std::string LibcxxLinkFlags() const; // Names this toolchain for cache directory names: the clang version // followed by a hash of what decides which headers and libraries a // PCM is built against. Two toolchains with different keys never // share a PCM. std::string Key() const; }; // What ResolveToolchain reads, so a test can feed it a staged layout. struct ToolchainInputs { // The CRAFTER_* variables, present only when set and non-empty. std::map> env; // The of /share/crafter-build; empty when unknown. fs::path crafterPrefix; // Where the host's root shows inside a sandbox (/run/host in a // Flatpak); empty outside one. fs::path hostRoot; }; // Pure path resolution: an env override wins, then a toolchain installed // beside crafter-build (/bin/clang++), then bare names on // PATH. Never runs a process, so hostTriple and clangVersion stay empty. Toolchain ResolveToolchain(const ToolchainInputs& inputs); // ResolveToolchain over the real environment plus the two clang probes, // computed once and cached. Never throws. const Toolchain& GetToolchain(); // GetToolchain(), or a std::runtime_error carrying its error. 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 /mingw-sysroot/, 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 parents); // The --sysroot of every compile, link and CMake configure for the mingw // target, settled on the first call of a process: empty without a // mingwPrefix, the prefix itself for one that holds mingw-w64 alone, and // for a system prefix the view MakeMingwSysroot makes in crafter-build's // cache, or in $XDG_RUNTIME_DIR/crafter.build when the cache's // filesystem takes no symbolic links. A system prefix cannot be the // sysroot: clang takes a mingw sysroot for a tree that holds mingw-w64 // 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 , had shared_ptr read // __libc_single_threaded, which nothing on Windows defines (issue #43). // Throws when the view cannot be made. 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) // 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 /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(); }