This commit is contained in:
parent
bc9ceb8f24
commit
0ab30a1d81
5 changed files with 249 additions and 18 deletions
|
|
@ -62,7 +62,9 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
|
||||||
|
|
||||||
std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>> tempModulePaths = std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>>(interfaces.size());
|
std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>> tempModulePaths = std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>>(interfaces.size());
|
||||||
for(std::uint16_t i = 0; i < interfaces.size(); i++){
|
for(std::uint16_t i = 0; i < interfaces.size(); i++){
|
||||||
fs::path file = path / interfaces[i];
|
// Resolve to absolute now so the stored path survives cwd changes
|
||||||
|
// (matters for GitProject deps loaded from a different working dir).
|
||||||
|
fs::path file = fs::absolute(path / interfaces[i]).lexically_normal();
|
||||||
file += ".cppm";
|
file += ".cppm";
|
||||||
std::ifstream t(file);
|
std::ifstream t(file);
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
|
|
@ -136,7 +138,7 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const fs::path& tempFile : implementations) {
|
for(const fs::path& tempFile : implementations) {
|
||||||
fs::path file = path / tempFile;
|
fs::path file = fs::absolute(path / tempFile).lexically_normal();
|
||||||
file += ".cpp";
|
file += ".cpp";
|
||||||
std::ifstream t(file);
|
std::ifstream t(file);
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
|
|
@ -232,8 +234,8 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fs::path buildDir = config.path/"build"/std::format("{}-{}-{}", config.name, config.target, config.march);
|
fs::path buildDir = config.BuildDir();
|
||||||
fs::path outputDir = config.path/"bin"/std::format("{}-{}-{}", config.name, config.target, config.march);
|
fs::path outputDir = config.BinDir();
|
||||||
|
|
||||||
if (!fs::exists(buildDir)) {
|
if (!fs::exists(buildDir)) {
|
||||||
fs::create_directories(buildDir);
|
fs::create_directories(buildDir);
|
||||||
|
|
@ -520,7 +522,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
||||||
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
||||||
const std::string srcPath = cFile.string() + ".c";
|
const std::string srcPath = cFile.string() + ".c";
|
||||||
if (!fs::exists(objPath) || fs::last_write_time(srcPath) > fs::last_write_time(objPath)) {
|
if (!fs::exists(objPath) || (fs::exists(srcPath) && fs::last_write_time(srcPath) > fs::last_write_time(objPath))) {
|
||||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config]() {
|
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config]() {
|
||||||
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
|
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
|
||||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||||
|
|
@ -540,7 +542,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
||||||
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
||||||
const std::string srcPath = cFile.string() + ".cu";
|
const std::string srcPath = cFile.string() + ".cu";
|
||||||
if (!fs::exists(objPath) || fs::last_write_time(srcPath) > fs::last_write_time(objPath)) {
|
if (!fs::exists(objPath) || (fs::exists(srcPath) && fs::last_write_time(srcPath) > fs::last_write_time(objPath))) {
|
||||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled]() {
|
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled]() {
|
||||||
Progress::Task task(std::format("Compiling {}.cu", cFile.filename().string()));
|
Progress::Task task(std::format("Compiling {}.cu", cFile.filename().string()));
|
||||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||||
|
|
@ -669,7 +671,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
// but the consumer was never relinked against the new dep.
|
// but the consumer was never relinked against the new dep.
|
||||||
{
|
{
|
||||||
auto expectedOutputFor = [](const Configuration& c) -> fs::path {
|
auto expectedOutputFor = [](const Configuration& c) -> fs::path {
|
||||||
fs::path dir = c.path/"bin"/std::format("{}-{}-{}", c.name, c.target, c.march);
|
fs::path dir = c.BinDir();
|
||||||
if (c.type == ConfigurationType::Executable) {
|
if (c.type == ConfigurationType::Executable) {
|
||||||
if (c.target.starts_with("wasm32"))
|
if (c.target.starts_with("wasm32"))
|
||||||
return dir / (c.outputName + ".wasm");
|
return dir / (c.outputName + ".wasm");
|
||||||
|
|
@ -719,7 +721,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
std::function<void(Configuration*)> copyDepDlls = [&](Configuration* dep) {
|
std::function<void(Configuration*)> copyDepDlls = [&](Configuration* dep) {
|
||||||
if (!dllSeen.insert(dep).second) return;
|
if (!dllSeen.insert(dep).second) return;
|
||||||
if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") {
|
if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") {
|
||||||
fs::path depDir = dep->path / "bin" / std::format("{}-{}-{}", dep->name, dep->target, dep->march);
|
fs::path depDir = dep->BinDir();
|
||||||
// The DLL itself (Windows resolves it from the
|
// The DLL itself (Windows resolves it from the
|
||||||
// exe's directory at load time) and the mingw
|
// exe's directory at load time) and the mingw
|
||||||
// import lib (so a downstream `crafter-build.exe`
|
// import lib (so a downstream `crafter-build.exe`
|
||||||
|
|
@ -761,7 +763,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
||||||
std::function<void(Configuration*)> copyDepDlls = [&](Configuration* dep) {
|
std::function<void(Configuration*)> copyDepDlls = [&](Configuration* dep) {
|
||||||
if (!dllSeen.insert(dep).second) return;
|
if (!dllSeen.insert(dep).second) return;
|
||||||
if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") {
|
if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") {
|
||||||
fs::path depDir = dep->path / "bin" / std::format("{}-{}-{}", dep->name, dep->target, dep->march);
|
fs::path depDir = dep->BinDir();
|
||||||
for (auto fname : {std::format("{}.dll", dep->outputName),
|
for (auto fname : {std::format("{}.dll", dep->outputName),
|
||||||
std::format("lib{}.dll.a", dep->outputName)}) {
|
std::format("lib{}.dll.a", dep->outputName)}) {
|
||||||
fs::path src = depDir / fname;
|
fs::path src = depDir / fname;
|
||||||
|
|
@ -863,19 +865,25 @@ std::string Crafter::HostTarget() {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::string_view> args) {
|
ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::string_view> args) {
|
||||||
if (const char* envMarch = std::getenv("CRAFTER_BUILD_MARCH"); envMarch && *envMarch) {
|
if (const char* envMarch = std::getenv("CRAFTER_BUILD_MARCH"); envMarch && *envMarch) {
|
||||||
cfg.march = envMarch;
|
cfg.march = envMarch;
|
||||||
}
|
}
|
||||||
if (const char* envMtune = std::getenv("CRAFTER_BUILD_MTUNE"); envMtune && *envMtune) {
|
if (const char* envMtune = std::getenv("CRAFTER_BUILD_MTUNE"); envMtune && *envMtune) {
|
||||||
cfg.mtune = envMtune;
|
cfg.mtune = envMtune;
|
||||||
}
|
}
|
||||||
|
bool sawLib = false, sawShared = false;
|
||||||
for (std::string_view a : args) {
|
for (std::string_view a : args) {
|
||||||
if (a == "--debug") cfg.debug = true;
|
if (a == "--debug") cfg.debug = true;
|
||||||
|
else if (a == "--lib") sawLib = true;
|
||||||
|
else if (a == "--shared") sawShared = true;
|
||||||
else if (a.starts_with("--target=")) cfg.target = std::string(a.substr(std::string_view("--target=").size()));
|
else if (a.starts_with("--target=")) cfg.target = std::string(a.substr(std::string_view("--target=").size()));
|
||||||
else if (a.starts_with("--march=")) cfg.march = std::string(a.substr(std::string_view("--march=").size()));
|
else if (a.starts_with("--march=")) cfg.march = std::string(a.substr(std::string_view("--march=").size()));
|
||||||
else if (a.starts_with("--mtune=")) cfg.mtune = std::string(a.substr(std::string_view("--mtune=").size()));
|
else if (a.starts_with("--mtune=")) cfg.mtune = std::string(a.substr(std::string_view("--mtune=").size()));
|
||||||
}
|
}
|
||||||
|
if (sawLib && cfg.type == ConfigurationType::Executable) cfg.type = ConfigurationType::LibraryStatic;
|
||||||
|
if (sawShared && cfg.type == ConfigurationType::LibraryStatic) cfg.type = ConfigurationType::LibraryDynamic;
|
||||||
|
return ArgQuery{args};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintHelp(std::string_view argv0) {
|
static void PrintHelp(std::string_view argv0) {
|
||||||
|
|
@ -1015,7 +1023,7 @@ int Crafter::Run(int argc, char** argv) {
|
||||||
std::println(std::cerr, "-r: cannot run a library");
|
std::println(std::cerr, "-r: cannot run a library");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
fs::path dir = config.path / "bin" / std::format("{}-{}-{}", config.name, config.target, config.march);
|
fs::path dir = config.BinDir();
|
||||||
fs::path artifact = dir / config.outputName;
|
fs::path artifact = dir / config.outputName;
|
||||||
if (config.target.starts_with("wasm32")) {
|
if (config.target.starts_with("wasm32")) {
|
||||||
artifact += ".wasm";
|
artifact += ".wasm";
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ module Crafter.Build:External_impl;
|
||||||
import std;
|
import std;
|
||||||
import :External;
|
import :External;
|
||||||
import :Platform;
|
import :Platform;
|
||||||
|
import :Progress;
|
||||||
|
import :Clang;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using namespace Crafter;
|
using namespace Crafter;
|
||||||
|
|
||||||
|
|
@ -76,6 +78,14 @@ std::string FetchGit(const GitSource& source, const fs::path& cloneDir) {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
|
// No commit pinned — pull latest. The user opted into "track latest"
|
||||||
|
// by not pinning; the cost is one network round-trip per build.
|
||||||
|
// Pin source.commit to a SHA for reproducible, offline-capable builds.
|
||||||
|
if (!source.branch.empty()) {
|
||||||
|
if (auto err = runGit(std::format("git -C {} pull origin {}", ShellQuote(cloneDir.string()), ShellQuote(source.branch)))) return *err;
|
||||||
|
} else {
|
||||||
|
if (auto err = runGit(std::format("git -C {} pull", ShellQuote(cloneDir.string())))) return *err;
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,3 +275,131 @@ ExternalBuildResult Crafter::BuildExternal(
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::mutex projectCacheMutex;
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Configuration>> projectCache;
|
||||||
|
|
||||||
|
// Run the dep's project.cpp from its own directory so its relative paths
|
||||||
|
// (cfg.path = "./", interfaces/, lib/, ...) resolve against the dep
|
||||||
|
// root, then rebase cfg.path and all other relative path collections to
|
||||||
|
// absolute so Build() can run from any cwd without misresolving them.
|
||||||
|
// The mutex is NOT held across LoadProject — the dep may itself call
|
||||||
|
// GitProject/LocalProject, and a non-recursive lock would deadlock.
|
||||||
|
Configuration LoadProjectFromRoot(const fs::path& projectPath, const fs::path& depRoot, std::span<const std::string_view> args) {
|
||||||
|
fs::path savedCwd = fs::current_path();
|
||||||
|
Configuration cfg;
|
||||||
|
fs::current_path(depRoot);
|
||||||
|
try {
|
||||||
|
cfg = LoadProject(projectPath, args);
|
||||||
|
} catch (...) {
|
||||||
|
fs::current_path(savedCwd);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
fs::current_path(savedCwd);
|
||||||
|
cfg.path = fs::absolute(depRoot / cfg.path).lexically_normal();
|
||||||
|
auto abs = [&](const fs::path& p) -> fs::path {
|
||||||
|
return p.is_absolute() ? p : fs::absolute(depRoot / p).lexically_normal();
|
||||||
|
};
|
||||||
|
for (fs::path& p : cfg.cFiles) p = abs(p);
|
||||||
|
for (fs::path& p : cfg.cuda) p = abs(p);
|
||||||
|
for (fs::path& p : cfg.files) p = abs(p);
|
||||||
|
for (Shader& s : cfg.shaders) s.path = abs(s.path);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration* Crafter::GitProject(const GitProjectSpec& spec) {
|
||||||
|
// Two hashes: the clone is shared across specs that point at the same
|
||||||
|
// source revision (forwarded args don't change what gets cloned, just
|
||||||
|
// how the dep's project.cpp interprets them at runtime), while the
|
||||||
|
// in-memory Configuration is keyed by the full spec so each (args)
|
||||||
|
// permutation gets its own Configuration object.
|
||||||
|
std::string srcKey = std::format("git|{}|{}|{}|{}",
|
||||||
|
spec.source.url, spec.source.branch, spec.source.commit, spec.projectFile.string());
|
||||||
|
std::string srcHash = std::format("{:016x}", std::hash<std::string>{}(srcKey));
|
||||||
|
|
||||||
|
std::string fullKey = srcKey;
|
||||||
|
for (const std::string& a : spec.args) {
|
||||||
|
fullKey += '|';
|
||||||
|
fullKey += a;
|
||||||
|
}
|
||||||
|
std::string fullHash = std::format("{:016x}", std::hash<std::string>{}(fullKey));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(projectCacheMutex);
|
||||||
|
if (auto it = projectCache.find(fullHash); it != projectCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name = DeriveName(spec.source);
|
||||||
|
if (name.empty()) {
|
||||||
|
throw std::runtime_error(std::format("GitProject: could not derive name from URL '{}'", spec.source.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path externalRoot = GetCacheDir() / "external";
|
||||||
|
fs::create_directories(externalRoot);
|
||||||
|
fs::path cloneDir = externalRoot / std::format("{}-{}", name, srcHash);
|
||||||
|
|
||||||
|
{
|
||||||
|
bool exists = fs::exists(cloneDir);
|
||||||
|
Progress::Task task(std::format("{} {}", exists ? "Updating" : "Cloning", name));
|
||||||
|
if (std::string err = FetchGit(spec.source, cloneDir); !err.empty()) {
|
||||||
|
throw std::runtime_error(std::format("GitProject({}): {}", spec.source.url, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path projectPath = cloneDir / spec.projectFile;
|
||||||
|
if (!fs::exists(projectPath)) {
|
||||||
|
throw std::runtime_error(std::format(
|
||||||
|
"GitProject({}): {} not found in clone", spec.source.url, spec.projectFile.string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
|
||||||
|
Configuration cfg = LoadProjectFromRoot(projectPath, cloneDir, argViews);
|
||||||
|
|
||||||
|
std::lock_guard lock(projectCacheMutex);
|
||||||
|
if (auto it = projectCache.find(fullHash); it != projectCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
auto stored = std::make_unique<Configuration>(std::move(cfg));
|
||||||
|
Configuration* ptr = stored.get();
|
||||||
|
projectCache.emplace(std::move(fullHash), std::move(stored));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration* Crafter::LocalProject(const LocalProjectSpec& spec) {
|
||||||
|
fs::path projectPath = fs::absolute(spec.projectFile).lexically_normal();
|
||||||
|
if (!fs::exists(projectPath)) {
|
||||||
|
throw std::runtime_error(std::format("LocalProject: {} not found", projectPath.string()));
|
||||||
|
}
|
||||||
|
fs::path canonical = fs::canonical(projectPath);
|
||||||
|
|
||||||
|
std::string fullKey = std::format("local|{}", canonical.string());
|
||||||
|
for (const std::string& a : spec.args) {
|
||||||
|
fullKey += '|';
|
||||||
|
fullKey += a;
|
||||||
|
}
|
||||||
|
std::string fullHash = std::format("{:016x}", std::hash<std::string>{}(fullKey));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(projectCacheMutex);
|
||||||
|
if (auto it = projectCache.find(fullHash); it != projectCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path depRoot = canonical.parent_path();
|
||||||
|
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
|
||||||
|
Configuration cfg = LoadProjectFromRoot(canonical, depRoot, argViews);
|
||||||
|
|
||||||
|
std::lock_guard lock(projectCacheMutex);
|
||||||
|
if (auto it = projectCache.find(fullHash); it != projectCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
auto stored = std::make_unique<Configuration>(std::move(cfg));
|
||||||
|
Configuration* ptr = stored.get();
|
||||||
|
projectCache.emplace(std::move(fullHash), std::move(stored));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path TestBinaryPath(const Configuration& cfg) {
|
fs::path TestBinaryPath(const Configuration& cfg) {
|
||||||
fs::path outputDir = cfg.path/"bin"/std::format("{}-{}-{}", cfg.name, cfg.target, cfg.march);
|
fs::path outputDir = cfg.BinDir();
|
||||||
return outputDir / (TargetIsWindows(cfg.target) ? cfg.outputName + ".exe" : cfg.outputName);
|
return outputDir / (TargetIsWindows(cfg.target) ? cfg.outputName + ".exe" : cfg.outputName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,35 @@ export namespace Crafter {
|
||||||
std::vector<std::string> linkFlags;
|
std::vector<std::string> linkFlags;
|
||||||
std::vector<Test> tests;
|
std::vector<Test> tests;
|
||||||
CRAFTER_API void GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> implementations);
|
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 {
|
fs::path PcmDir() const {
|
||||||
return path
|
return type == ConfigurationType::Executable ? BuildDir() : BinDir();
|
||||||
/ (type == ConfigurationType::Executable ? "build" : "bin")
|
|
||||||
/ std::format("{}-{}-{}", name, target, march);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -143,14 +168,39 @@ export namespace Crafter {
|
||||||
// outputName so renaming the binary later requires another call.
|
// outputName so renaming the binary later requires another call.
|
||||||
CRAFTER_API void EnableWasiBrowserRuntime(Configuration& cfg);
|
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:
|
// Apply the framework's standard CLI args + env vars onto cfg:
|
||||||
// --debug cfg.debug = true
|
// --debug cfg.debug = true
|
||||||
// --target=<triple> cfg.target = <triple>
|
// --target=<triple> cfg.target = <triple>
|
||||||
// --march=<value> cfg.march = <value>
|
// --march=<value> cfg.march = <value>
|
||||||
// --mtune=<value> cfg.mtune = <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.
|
// $CRAFTER_BUILD_MARCH / $CRAFTER_BUILD_MTUNE seed march/mtune.
|
||||||
// Env applies first, then args, so CLI wins over env wins over caller's
|
// Env applies first, then args, so CLI wins over env wins over caller's
|
||||||
// pre-set defaults. Project-specific flags (e.g. --shared, --timing) are
|
// pre-set defaults. Returns an ArgQuery over the same span so projects
|
||||||
// not handled here — parse them in your own loop alongside this call.
|
// can query their own flags (`--timing`, ...) without re-rolling the
|
||||||
CRAFTER_API void ApplyStandardArgs(Configuration& cfg, std::span<const std::string_view> args);
|
// for-arg-in-args loop.
|
||||||
|
CRAFTER_API ArgQuery ApplyStandardArgs(Configuration& cfg, std::span<const std::string_view> args);
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +24,8 @@ import std;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
export namespace Crafter {
|
export namespace Crafter {
|
||||||
|
struct Configuration;
|
||||||
|
|
||||||
struct GitSource {
|
struct GitSource {
|
||||||
std::string url;
|
std::string url;
|
||||||
std::string branch;
|
std::string branch;
|
||||||
|
|
@ -55,4 +57,37 @@ export namespace Crafter {
|
||||||
const ExternalDependency& dep,
|
const ExternalDependency& dep,
|
||||||
std::string_view target,
|
std::string_view target,
|
||||||
std::atomic<bool>& cancelled);
|
std::atomic<bool>& cancelled);
|
||||||
|
|
||||||
|
// Specification for a sibling crafter-build project to fetch and depend on.
|
||||||
|
// GitSource picks the revision: leave branch + commit empty for the
|
||||||
|
// remote's default branch HEAD (sloppy but convenient), set branch to
|
||||||
|
// track a branch tip, or set commit to pin to an immutable SHA. args are
|
||||||
|
// forwarded verbatim to the dep's CrafterBuildProject — typically you
|
||||||
|
// pass through the parent's args so target/march/debug propagate.
|
||||||
|
struct GitProjectSpec {
|
||||||
|
GitSource source;
|
||||||
|
fs::path projectFile = "project.cpp";
|
||||||
|
std::vector<std::string> args;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clones the spec's git URL into the per-project external cache (keyed by
|
||||||
|
// url+branch+commit+args+projectFile), recursively LoadProject's the
|
||||||
|
// remote's project.cpp, and returns a stable Configuration* you can drop
|
||||||
|
// into cfg.dependencies. The Configuration is owned by an internal cache
|
||||||
|
// for the lifetime of the build; identical specs share one Configuration.
|
||||||
|
CRAFTER_API Configuration* GitProject(const GitProjectSpec& spec);
|
||||||
|
|
||||||
|
// Specification for a local sibling crafter-build project (e.g. an
|
||||||
|
// example folder depending on its parent project, or a workspace where
|
||||||
|
// multiple projects live side-by-side without going through git).
|
||||||
|
struct LocalProjectSpec {
|
||||||
|
fs::path projectFile; // relative to cwd or absolute
|
||||||
|
std::vector<std::string> args; // forwarded to dep's CrafterBuildProject
|
||||||
|
};
|
||||||
|
|
||||||
|
// Same as GitProject but for a local on-disk project — no fetch, no
|
||||||
|
// cache copy. Resolves projectFile to its canonical absolute path and
|
||||||
|
// recursively LoadProject's it. Returns a stable Configuration* owned
|
||||||
|
// by the same internal cache as GitProject.
|
||||||
|
CRAFTER_API Configuration* LocalProject(const LocalProjectSpec& spec);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue