Crafter.Build/interfaces/Crafter.Build-Clang.cppm
Jorijn van der Graaf 0ab30a1d81
Some checks failed
CI / build-test-release (push) Failing after 8m31s
fixes
2026-04-30 02:20:19 +02:00

206 lines
No EOL
8.5 KiB
C++

/*
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<std::string> libs;
};
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<TestRunner> 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<std::unique_ptr<Module>> interfaces;
std::vector<Implementation> implementations;
std::vector<fs::path> cFiles;
std::vector<fs::path> cuda;
std::vector<Configuration*> dependencies;
std::vector<fs::path> files;
std::vector<Define> defines;
std::vector<Shader> shaders;
std::vector<ExternalDependency> externalDependencies;
std::vector<std::string> compileFlags;
std::vector<std::string> linkFlags;
std::vector<Test> tests;
CRAFTER_API void GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> 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<int>(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<std::string>{}(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<std::string> args;
};
CRAFTER_API BuildResult Build(Configuration& config, std::unordered_map<fs::path, std::shared_future<BuildResult>>& 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 — kept inline
// so it works across the DLL boundary without an extra ABI hop. Use
// Has(flag) for boolean switches and Get(prefix) for valued options
// (e.g. Get("--prefix=") returns the substring after the equals).
struct ArgQuery {
std::span<const std::string_view> args;
bool Has(std::string_view flag) const {
for (std::string_view a : args) if (a == flag) return true;
return false;
}
std::optional<std::string> Get(std::string_view prefix) const {
for (std::string_view a : args) {
if (a.starts_with(prefix)) return std::string(a.substr(prefix.size()));
}
return std::nullopt;
}
};
// Apply the framework's standard CLI args + env vars onto cfg:
// --debug cfg.debug = true
// --target=<triple> cfg.target = <triple>
// --march=<value> cfg.march = <value>
// --mtune=<value> cfg.mtune = <value>
// --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<const std::string_view> args);
}