/* Crafter® Build Copyright (C) 2026 Catcrafts® Catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; #include "Crafter.Build-Api.h" export module Crafter.Build:Clang; import std; import :Shader; import :Interface; import :Implementation; import :External; namespace fs = std::filesystem; export namespace Crafter { struct BuildResult { std::string result; bool repack; std::unordered_set libs; // Compile flags (typically -I include paths) the dep wants its // consumers to see — sourced from its external dependencies' include // dirs so headers a dep exposes in its public modules are reachable // when a consumer #includes them directly. Propagates transitively. std::unordered_set publicCompileFlags; }; struct Define { std::string name; std::string value; }; enum class ConfigurationType { Executable, LibraryStatic, LibraryDynamic, }; struct Test; struct TestRunner { // Quoting style for {args} when this runner is used. Args reach // whichever shell the runner ultimately delivers them to: Host for // Local + Cmd-prefix (host shell parses), Sh for Ssh (remote bash), // Cmd for SshWin (remote cmd.exe). enum class Shell { Host, Sh, Cmd }; std::string copy; std::string exec; std::string cleanup; std::string remoteDir; std::string name; // Runs once per RunTests invocation (cached by `name`). Exit 0 = runner // is available; non-zero = skip every Test using this runner with a // "runner unavailable" message. Empty = always available (e.g., Local). std::string probe; Shell argsShell = Shell::Host; bool IsLocal() const { return exec.empty(); } static CRAFTER_API TestRunner Local(); static CRAFTER_API TestRunner Ssh(std::string host, std::string remoteDir = "/tmp/crafter-tests"); static CRAFTER_API TestRunner SshWin(std::string host, std::string remoteDir = "C:/temp/crafter-tests"); static CRAFTER_API TestRunner Wsl(std::string remoteDir = "/tmp/crafter-tests-wsl"); static CRAFTER_API TestRunner Cmd(std::string command); static CRAFTER_API std::optional FromSpec(std::string_view spec); static CRAFTER_API TestRunner FromEnv(std::string_view target, TestRunner fallback = Local()); }; enum class TestOutcome { Pass, Fail, Crash, Timeout, Skipped }; struct TestResult { std::string name; TestOutcome outcome = TestOutcome::Pass; int exitCode = 0; int signal = 0; std::chrono::milliseconds duration{0}; std::string output; }; // The host target triple, detected once per process by running // `clang++ -print-target-triple` and cached. Used as the default for // Configuration::target so projects don't have to hardcode it for the // no-cross-compile case. Returns "" if clang isn't on PATH or doesn't // print a triple — those projects still need an explicit cfg.target. CRAFTER_API std::string HostTarget(); struct Configuration { fs::path path; std::string outputName; std::string name; std::string march = "native"; std::string mtune = "native"; std::string target = HostTarget(); std::string sysroot; bool debug = false; ConfigurationType type = ConfigurationType::Executable; std::vector> interfaces; std::vector implementations; std::vector cFiles; std::vector cuda; std::vector dependencies; std::vector files; std::vector defines; std::vector shaders; std::vector externalDependencies; std::vector compileFlags; std::vector linkFlags; std::vector tests; CRAFTER_API void GetInterfacesAndImplementations(std::span interfaces, std::span implementations); // Suffix that uniquely identifies this Configuration's compile state. // target+march+mtune are spelled out for readability; the rest // (type, debug, sysroot, defines, compileFlags) collapse into a short // hash so two Configurations sharing a path but with different // compile state can't clobber each other's outputs. std::string VariantId() const { std::string compileKey; compileKey += std::to_string(static_cast(type)); compileKey += '|'; compileKey += debug ? '1' : '0'; compileKey += '|'; compileKey += sysroot; for (const Define& d : defines) { compileKey += "|D:"; compileKey += d.name; compileKey += '='; compileKey += d.value; } for (const std::string& f : compileFlags) { compileKey += "|F:"; compileKey += f; } std::size_t configHash = std::hash{}(compileKey); return std::format("{}-{}-{}-{}-{:08x}", name, target, march, mtune, configHash); } fs::path BuildDir() const { return path / "build" / VariantId(); } fs::path BinDir() const { return path / "bin" / VariantId(); } fs::path PcmDir() const { return type == ConfigurationType::Executable ? BuildDir() : BinDir(); } }; struct Test { Configuration config; TestRunner runner; std::chrono::seconds timeout{60}; std::vector args; }; CRAFTER_API BuildResult Build(Configuration& config, std::unordered_map>& depResults, std::mutex& depMutex); CRAFTER_API int Run(int argc, char** argv); // Add a small index.html + runtime.js pair next to the .wasm output so the // build can be loaded directly in a browser (just `serve` the bin dir and // open it). Opt-in: WASI builds destined for wasmtime/wasmer don't need // this and shouldn't carry the extra files. Call from project.cpp after // outputName is set; index.html is generated against the current // outputName so renaming the binary later requires another call. CRAFTER_API void EnableWasiBrowserRuntime(Configuration& cfg); // View over the project's args with simple query helpers. Has(flag) for // boolean switches; Get(prefix) for valued options (e.g. Get("--prefix=") // returns the substring after the equals). Definitions are out-of-line // and CRAFTER_API so they cross the Windows DLL boundary cleanly — clang // does not emit module-attached in-class inline bodies into consumers. struct ArgQuery { std::span args; CRAFTER_API bool Has(std::string_view flag) const; CRAFTER_API std::optional Get(std::string_view prefix) const; }; // Apply the framework's standard CLI args + env vars onto cfg: // --debug cfg.debug = true // --target= cfg.target = // --march= cfg.march = // --mtune= cfg.mtune = // --lib cfg.type promoted Executable → LibraryStatic // --shared cfg.type promoted LibraryStatic → LibraryDynamic // Promotions chain in priority order so `--lib --shared` lands on // LibraryDynamic regardless of arg order; each is a no-op when the // baseline doesn't match (e.g. --shared on an Executable, or --lib on a // pre-set library). // $CRAFTER_BUILD_MARCH / $CRAFTER_BUILD_MTUNE seed march/mtune. // Env applies first, then args, so CLI wins over env wins over caller's // pre-set defaults. Returns an ArgQuery over the same span so projects // can query their own flags (`--timing`, ...) without re-rolling the // for-arg-in-args loop. CRAFTER_API ArgQuery ApplyStandardArgs(Configuration& cfg, std::span args); }