This commit is contained in:
parent
a25b0a1ded
commit
8892154b28
70 changed files with 2780 additions and 596 deletions
|
|
@ -1,5 +1,5 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
module;
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
|
|
@ -18,6 +18,7 @@ import std;
|
|||
import :Clang;
|
||||
import :Platform;
|
||||
import :Test;
|
||||
import :Lint;
|
||||
import :Progress;
|
||||
import :Asset;
|
||||
namespace fs = std::filesystem;
|
||||
|
|
@ -25,9 +26,7 @@ using namespace Crafter;
|
|||
|
||||
|
||||
void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> implementations) {
|
||||
auto resolveImport = [this](const std::string& importName,
|
||||
std::vector<Module*>& localDeps,
|
||||
std::vector<std::pair<Module*, fs::path>>& externalDeps) -> bool {
|
||||
auto resolveImport = [this](const std::string& importName, std::vector<Module*>& localDeps, std::vector<std::pair<Module*, fs::path>>& externalDeps) -> bool {
|
||||
for(const std::unique_ptr<Module>& interface : this->interfaces) {
|
||||
if(interface->name == importName) {
|
||||
localDeps.push_back(interface.get());
|
||||
|
|
@ -40,7 +39,7 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
|
|||
if (!seen.insert(depCfg).second) return false;
|
||||
for(const std::unique_ptr<Module>& depInterface : depCfg->interfaces) {
|
||||
if(depInterface->name == importName) {
|
||||
fs::path depPcmPath = (depCfg->PcmDir() / depInterface->path.filename()).string() + ".pcm";
|
||||
fs::path depPcmPath = std::format("{}.pcm", (depCfg->PcmDir() / depInterface->path.filename()).string());
|
||||
externalDeps.emplace_back(depInterface.get(), std::move(depPcmPath));
|
||||
return true;
|
||||
}
|
||||
|
|
@ -98,9 +97,9 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
|
|||
goto next;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error(std::format("Module {} not found, referenced in {}", match[1].str(), std::get<0>(tempModulePaths[i]).string()));
|
||||
throw std::runtime_error(std::format("Module {} not found, referenced in {}", match[1].str(), std::get<0>(tempModulePaths[i]).string()));
|
||||
} else {
|
||||
throw std::runtime_error(std::format("No module declaration found in {}", std::get<0>(tempModulePaths[i]).string()));
|
||||
throw std::runtime_error(std::format("No module declaration found in {}", std::get<0>(tempModulePaths[i]).string()));
|
||||
}
|
||||
next:;
|
||||
}
|
||||
|
|
@ -305,7 +304,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// a single thread to match the cfg.files pattern.
|
||||
auto compressedName = [](const fs::path& src) -> std::optional<fs::path> {
|
||||
std::string ext = src.extension().string();
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<std::uint8_t>(c)));
|
||||
// stb_image (used by CompressAsset → TextureAsset::LoadPNG) handles
|
||||
// png/tga/jpg/bmp; all map to .ctex.
|
||||
if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {
|
||||
|
|
@ -435,7 +434,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// it with extra codegen flags (e.g. -mrelaxed-simd) — its BMI must not be
|
||||
// shared with the baseline's, or the consuming TUs see a target-feature
|
||||
// mismatch. Suffix the cache dir with the variant flags when present.
|
||||
std::string stdPcmKey = config.target + "-" + config.march;
|
||||
std::string stdPcmKey = std::format("{}-{}", config.target, config.march);
|
||||
for (const std::string& f : config.wasmVariantFlags) {
|
||||
stdPcmKey += "+";
|
||||
for (char c : f) stdPcmKey += (c == '/' || c == '\\') ? '_' : c;
|
||||
|
|
@ -492,7 +491,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// for the baseline build. Part of VariantId, so these objects/PCMs
|
||||
// land in their own dir.
|
||||
for (const std::string& f : config.wasmVariantFlags) {
|
||||
command += " " + f;
|
||||
command += std::format(" {}", f);
|
||||
}
|
||||
}
|
||||
if (config.target == "x86_64-w64-mingw32") {
|
||||
|
|
@ -542,7 +541,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
if (!seen.insert(dep).second) return;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dep->path)) {
|
||||
if (entry.is_directory() && entry.path().filename() == "include") {
|
||||
includeFlags += " -I" + entry.path().string();
|
||||
includeFlags += std::format(" -I{}", entry.path().string());
|
||||
}
|
||||
}
|
||||
includeFlags += std::format(" -I{}", dep->path.string());
|
||||
|
|
@ -629,7 +628,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// pick them up too (vendored C deps usually need -I from this set).
|
||||
std::string userFlags;
|
||||
for(const std::string& flag : config.compileFlags) {
|
||||
userFlags += " " + flag;
|
||||
userFlags += std::format(" {}", flag);
|
||||
}
|
||||
command += userFlags;
|
||||
|
||||
|
|
@ -676,8 +675,8 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
}
|
||||
for (const fs::path& cFile : config.cFiles) {
|
||||
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
||||
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
||||
const std::string srcPath = cFile.string() + ".c";
|
||||
const std::string objPath = std::format("{}_source.o", (buildDir / cFile.filename()).string());
|
||||
const std::string srcPath = std::format("{}.c", cFile.string());
|
||||
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, &includeFlags, &defineFlags, &userFlags, &cArchFlags, <oCompileFlags]() {
|
||||
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
|
||||
|
|
@ -696,8 +695,8 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
|
||||
for (const fs::path& cFile : config.cuda) {
|
||||
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
||||
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
||||
const std::string srcPath = cFile.string() + ".cu";
|
||||
const std::string objPath = std::format("{}_source.o", (buildDir / cFile.filename()).string());
|
||||
const std::string srcPath = std::format("{}.cu", cFile.string());
|
||||
if (!fs::exists(objPath) || (fs::exists(srcPath) && fs::last_write_time(srcPath) > fs::last_write_time(objPath))) {
|
||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled]() {
|
||||
Progress::Task task(std::format("Compiling {}.cu", cFile.filename().string()));
|
||||
|
|
@ -789,7 +788,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
continue;
|
||||
}
|
||||
std::string ext = asset.extension().string();
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<std::uint8_t>(c)));
|
||||
fs::path srcName = asset.filename();
|
||||
if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {
|
||||
srcName.replace_extension(".ctex");
|
||||
|
|
@ -821,12 +820,12 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// Public compile flags propagated from sub-deps. Add them to this build's
|
||||
// command so config sees the headers its deps expose, and re-publish them
|
||||
// so config's own consumers see them transitively.
|
||||
for (const std::string& flag : publicFlagSet) command += " " + flag;
|
||||
for (const std::string& flag : publicFlagSet) command += std::format(" {}", flag);
|
||||
buildResult.publicCompileFlags = std::move(publicFlagSet);
|
||||
fs::file_time_type externalFloor = fs::file_time_type::min();
|
||||
for(const ExternalBuildResult& ext : externalResults) {
|
||||
for(const std::string& flag : ext.compileFlags) {
|
||||
command += " " + flag;
|
||||
command += std::format(" {}", flag);
|
||||
// Headers a dep links via ExternalDependency are part of its
|
||||
// public surface (its modules can include them in declarations
|
||||
// visible to consumers), so propagate the -I to consumers.
|
||||
|
|
@ -889,7 +888,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
|
||||
std::string linkExtras;
|
||||
for(const std::string& flag : buildResult.libs) {
|
||||
linkExtras += " " + flag;
|
||||
linkExtras += std::format(" {}", flag);
|
||||
}
|
||||
// Link-only LTO/section flags (empty in Debug and for wasm). Every exe/lib
|
||||
// link below ends with linkExtras, so this reaches them all; the static-lib
|
||||
|
|
@ -927,10 +926,9 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
auto expectedOutputFor = [](const Configuration& c) -> fs::path {
|
||||
fs::path dir = c.BinDir();
|
||||
if (c.type == ConfigurationType::Executable) {
|
||||
if (c.target.starts_with("wasm32"))
|
||||
return dir / (c.outputName + ".wasm");
|
||||
if (c.target.starts_with("wasm32")) return dir / (std::format("{}.wasm", c.outputName));
|
||||
return c.target == "x86_64-w64-mingw32"
|
||||
? dir / (c.outputName + ".exe")
|
||||
? dir / (std::format("{}.exe", c.outputName))
|
||||
: dir / c.outputName;
|
||||
}
|
||||
if (c.type == ConfigurationType::LibraryStatic) {
|
||||
|
|
@ -971,13 +969,13 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
return !ec && t > consumerMtime;
|
||||
};
|
||||
for (const std::unique_ptr<Module>& iface : config.interfaces) {
|
||||
if (objNewer(buildDir / (iface->path.filename().string() + ".o"))) {
|
||||
if (objNewer(buildDir / std::format("{}.o", iface->path.filename().string()))) {
|
||||
buildResult.repack = true;
|
||||
break;
|
||||
}
|
||||
bool partHit = false;
|
||||
for (const std::unique_ptr<ModulePartition>& part : iface->partitions) {
|
||||
if (objNewer(buildDir / (part->path.filename().string() + ".o"))) {
|
||||
if (objNewer(buildDir / std::format("{}.o", part->path.filename().string()))) {
|
||||
partHit = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -986,7 +984,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
}
|
||||
if (!buildResult.repack) {
|
||||
for (const Implementation& impl : config.implementations) {
|
||||
if (objNewer(buildDir / (impl.path.filename().string() + "_impl.o"))) {
|
||||
if (objNewer(buildDir / std::format("{}_impl.o", impl.path.filename().string()))) {
|
||||
buildResult.repack = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1016,8 +1014,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// import lib (so a downstream `crafter-build.exe`
|
||||
// can link a fresh project.dll against it without
|
||||
// hunting through sibling output dirs).
|
||||
for (auto fname : {std::format("{}.dll", dep->outputName),
|
||||
std::format("lib{}.dll.a", dep->outputName)}) {
|
||||
for (auto fname : {std::format("{}.dll", dep->outputName), std::format("lib{}.dll.a", dep->outputName)}) {
|
||||
fs::path src = depDir / fname;
|
||||
if (!fs::exists(src)) continue;
|
||||
fs::path dest = outputDir / src.filename();
|
||||
|
|
@ -1053,8 +1050,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
if (!dllSeen.insert(dep).second) return;
|
||||
if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") {
|
||||
fs::path depDir = dep->BinDir();
|
||||
for (auto fname : {std::format("{}.dll", dep->outputName),
|
||||
std::format("lib{}.dll.a", dep->outputName)}) {
|
||||
for (auto fname : {std::format("{}.dll", dep->outputName), std::format("lib{}.dll.a", dep->outputName)}) {
|
||||
fs::path src = depDir / fname;
|
||||
if (!fs::exists(src)) continue;
|
||||
fs::path dest = outputDir / src.filename();
|
||||
|
|
@ -1067,9 +1063,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
};
|
||||
for (Configuration* dep : config.dependencies) copyDepDlls(dep);
|
||||
|
||||
buildResult.result = RunCommand(std::format(
|
||||
"{}{} -o {} -fuse-ld=lld{}",
|
||||
command, files, (outputDir/config.outputName).string(), linkExtras));
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {} -fuse-ld=lld{}", command, files, (outputDir/config.outputName).string(), linkExtras));
|
||||
} else {
|
||||
std::system(std::format("copy \"%LIBCXX_DIR%\\lib\\c++.dll\" \"{}\\c++.dll\"", outputDir.string()).c_str());
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {}.exe -fuse-ld=lld -L %LIBCXX_DIR%\\lib -lc++ -nostdinc++ -nostdlib++{}", command, files, (outputDir/config.outputName).string(), linkExtras));
|
||||
|
|
@ -1095,19 +1089,13 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
if (config.target == "x86_64-w64-mingw32") {
|
||||
fs::path dll = outputDir / std::format("{}.dll", config.outputName);
|
||||
fs::path implib = outputDir / std::format("lib{}.dll.a", config.outputName);
|
||||
buildResult.result = RunCommand(std::format(
|
||||
"{}{} -shared -o {} -Wl,--out-implib,{} -fuse-ld=lld{}",
|
||||
command, files, dll.string(), implib.string(), linkExtras));
|
||||
buildResult.result = RunCommand(std::format("{}{} -shared -o {} -Wl,--out-implib,{} -fuse-ld=lld{}", command, files, dll.string(), implib.string(), linkExtras));
|
||||
} else if (config.target == "x86_64-pc-windows-msvc") {
|
||||
fs::path dll = outputDir / std::format("{}.dll", config.outputName);
|
||||
fs::path implib = outputDir / std::format("{}.lib", config.outputName);
|
||||
buildResult.result = RunCommand(std::format(
|
||||
"{}{} -shared -o {} -Wl,/IMPLIB:{} -fuse-ld=lld{}",
|
||||
command, files, dll.string(), implib.string(), linkExtras));
|
||||
buildResult.result = RunCommand(std::format("{}{} -shared -o {} -Wl,/IMPLIB:{} -fuse-ld=lld{}", command, files, dll.string(), implib.string(), linkExtras));
|
||||
} else {
|
||||
buildResult.result = RunCommand(std::format(
|
||||
"{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld{}",
|
||||
command, files, (outputDir/(std::string("lib")+config.outputName)).string(), linkExtras));
|
||||
buildResult.result = RunCommand(std::format("{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld{}", command, files, (outputDir/(std::string("lib")+config.outputName)).string(), linkExtras));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1127,11 +1115,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// Guard: only fires on the top-level config that *declared* wasmVariants
|
||||
// (deps leave it empty) and never on a variant pass itself (those carry
|
||||
// wasmVariantFlags), so there's no recursion. Skipped on build error.
|
||||
if (buildResult.result.empty()
|
||||
&& config.target.starts_with("wasm32")
|
||||
&& config.type == ConfigurationType::Executable
|
||||
&& !config.wasmVariants.empty()
|
||||
&& config.wasmVariantFlags.empty()) {
|
||||
if (buildResult.result.empty() && config.target.starts_with("wasm32") && config.type == ConfigurationType::Executable && !config.wasmVariants.empty() && config.wasmVariantFlags.empty()) {
|
||||
fs::path baselineBin = config.BinDir();
|
||||
|
||||
// The whole transitive graph, so the variant flags reach every TU.
|
||||
|
|
@ -1151,8 +1135,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// another name — nothing extra to compile.
|
||||
if (variant.label.empty() || variant.flags.empty()) continue;
|
||||
|
||||
Progress::Task task(std::format("Building wasm variant {} ({})",
|
||||
variant.label, config.outputName));
|
||||
Progress::Task task(std::format("Building wasm variant {} ({})", variant.label, config.outputName));
|
||||
|
||||
for (Configuration* c : graph) c->wasmVariantFlags = variant.flags;
|
||||
fs::path variantBin = config.BinDir();
|
||||
|
|
@ -1164,19 +1147,16 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
for (Configuration* c : graph) c->wasmVariantFlags.clear();
|
||||
|
||||
if (!vr.result.empty()) {
|
||||
buildResult.result = std::format(
|
||||
"wasm variant '{}' build failed: {}", variant.label, vr.result);
|
||||
buildResult.result = std::format("wasm variant '{}' build failed: {}", variant.label, vr.result);
|
||||
break;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
fs::path src = variantBin / (config.outputName + ".wasm");
|
||||
fs::path src = variantBin / (std::format("{}.wasm", config.outputName));
|
||||
fs::path dst = baselineBin / std::format("{}.{}.wasm", config.outputName, variant.label);
|
||||
fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
buildResult.result = std::format(
|
||||
"copy wasm variant '{}' {} -> {}: {}",
|
||||
variant.label, src.string(), dst.string(), ec.message());
|
||||
buildResult.result = std::format("copy wasm variant '{}' {} -> {}: {}", variant.label, src.string(), dst.string(), ec.message());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1190,9 +1170,7 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
fs::path runtimeJs = runtimeDir / "runtime.js";
|
||||
fs::path htmlTemplate = runtimeDir / "index.html.in";
|
||||
if (!fs::exists(runtimeJs) || !fs::exists(htmlTemplate)) {
|
||||
throw std::runtime_error(std::format(
|
||||
"wasi-runtime assets missing under {} (set CRAFTER_BUILD_HOME or reinstall)",
|
||||
runtimeDir.string()));
|
||||
throw std::runtime_error(std::format("wasi-runtime assets missing under {} (set CRAFTER_BUILD_HOME or reinstall)", runtimeDir.string()));
|
||||
}
|
||||
|
||||
fs::path htmlOutDir = cfg.path / "build" / "wasi-runtime" / cfg.name;
|
||||
|
|
@ -1225,15 +1203,11 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
// the dev server (python -m http.server) sends no Cache-Control
|
||||
// headers. Using ms-since-epoch is enough to be unique per build
|
||||
// without invoking any version-control machinery.
|
||||
const std::string buildId = std::to_string(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
const std::string buildId = std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
|
||||
std::string envScriptTags;
|
||||
for (const std::string& name : envScripts) {
|
||||
envScriptTags += std::format(
|
||||
" <script src=\"{}?v={}\" type=\"module\"></script>\n",
|
||||
name, buildId);
|
||||
envScriptTags += std::format(" <script src=\"{}?v={}\" type=\"module\"></script>\n", name, buildId);
|
||||
}
|
||||
|
||||
// Walk the dep graph again for non-JS assets — these get pre-loaded by
|
||||
|
|
@ -1250,7 +1224,7 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
// one preloaded wins. Avoid collisions in the source tree.
|
||||
auto compressedExt = [](const fs::path& src) -> std::optional<std::string> {
|
||||
std::string ext = src.extension().string();
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<std::uint8_t>(c)));
|
||||
if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {
|
||||
return std::string(".ctex");
|
||||
}
|
||||
|
|
@ -1340,7 +1314,7 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
if (!first) v << ",";
|
||||
first = false;
|
||||
std::string url = label.empty()
|
||||
? cfg.outputName + ".wasm"
|
||||
? std::format("{}.wasm", cfg.outputName)
|
||||
: std::format("{}.{}.wasm", cfg.outputName, label);
|
||||
v << "{\"label\":\"" << jsonEscape(label) << "\""
|
||||
<< ",\"url\":\"" << jsonEscape(url) << "\""
|
||||
|
|
@ -1363,7 +1337,7 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
std::stringstream buf;
|
||||
buf << in.rdbuf();
|
||||
std::string html = buf.str();
|
||||
html = std::regex_replace(html, std::regex(R"(\{\{WASM\}\})"), cfg.outputName + ".wasm");
|
||||
html = std::regex_replace(html, std::regex(R"(\{\{WASM\}\})"), std::format("{}.wasm", cfg.outputName));
|
||||
html = std::regex_replace(html, std::regex(R"(\{\{ENV_SCRIPTS\}\})"), envScriptTags);
|
||||
html = std::regex_replace(html, std::regex(R"(\{\{BUILDID\}\})"), buildId);
|
||||
std::ofstream out(htmlPath);
|
||||
|
|
@ -1388,14 +1362,14 @@ void Crafter::EnableWasiRelaxedSimdVariant(Configuration& cfg) {
|
|||
}
|
||||
|
||||
std::string Crafter::HostTarget() {
|
||||
static const std::string cached = []() -> std::string {
|
||||
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 Cached;
|
||||
}
|
||||
|
||||
bool Crafter::ArgQuery::Has(std::string_view flag) const {
|
||||
|
|
@ -1417,7 +1391,8 @@ ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::str
|
|||
if (const char* envMtune = std::getenv("CRAFTER_BUILD_MTUNE"); envMtune && *envMtune) {
|
||||
cfg.mtune = envMtune;
|
||||
}
|
||||
bool sawLib = false, sawShared = false;
|
||||
bool sawLib = false;
|
||||
bool sawShared = false;
|
||||
for (std::string_view a : args) {
|
||||
if (a == "--debug") cfg.debug = true;
|
||||
else if (a == "--lib") sawLib = true;
|
||||
|
|
@ -1446,6 +1421,8 @@ static void PrintHelp(std::string_view argv0) {
|
|||
R"(Usage:
|
||||
{0} [options] [-- project-args...] Build the project in the current directory
|
||||
{0} test [test-options] [globs...] Build and run the project's tests
|
||||
{0} lint [lint-options] [globs...] Run the project's lint rules over its sources
|
||||
{0} format [format-options] [globs...] Apply the project's transform rules (rewrites files)
|
||||
{0} help | -h | --help Show this help
|
||||
|
||||
Loads ./project.cpp (override with --project=<path>), compiles it to a shared
|
||||
|
|
@ -1472,6 +1449,22 @@ Test options (after the `test` subcommand):
|
|||
project's tests plus the host triple.
|
||||
<glob> One or more name globs to filter tests (e.g. 'Unit*').
|
||||
|
||||
Lint options (after the `lint` subcommand):
|
||||
--list Enumerate matching lint rules without running them.
|
||||
<glob> One or more name globs to filter rules (e.g. 'spdx*').
|
||||
|
||||
Lint rules are defined in project.cpp via cfg.AddLintRule(name, callback) —
|
||||
C++ callbacks run once per source file. No rules ship by default.
|
||||
|
||||
Format options (after the `format` subcommand):
|
||||
--check Dry run: list files that would change, exit 1 if any.
|
||||
--list Enumerate matching rules without running them.
|
||||
<glob> One or more name globs to filter rules.
|
||||
|
||||
Rules are shared with lint — a rule that calls ctx.SetContent is a
|
||||
transform. `format` rewrites changed files in place; `lint` reports the
|
||||
same transforms as would-reformat findings without writing.
|
||||
|
||||
Project args:
|
||||
Any flag not consumed above is forwarded verbatim to CrafterBuildProject as
|
||||
part of its `args` span. Project-specific flags (e.g. --target=, custom
|
||||
|
|
@ -1487,8 +1480,9 @@ Environment:
|
|||
LIBCXX_DIR Windows libc++ install (MSVC ABI builds).
|
||||
|
||||
Exit status:
|
||||
0 success / all non-skipped tests passed
|
||||
1 build failure or one or more tests failed
|
||||
0 success / all non-skipped tests passed / lint clean / format applied
|
||||
1 build failure, one or more tests failed, lint findings, no rules defined,
|
||||
format write errors, or `format --check` found files that would change
|
||||
)", argv0);
|
||||
}
|
||||
|
||||
|
|
@ -1500,17 +1494,27 @@ int Crafter::Run(int argc, char** argv) {
|
|||
projectArgs.reserve(argc);
|
||||
|
||||
bool runTests = false;
|
||||
bool runLint = false;
|
||||
bool runFormat = false;
|
||||
bool runAfterBuild = false;
|
||||
RunTestsOptions testOpts;
|
||||
RunLintOptions lintOpts;
|
||||
Progress::Verbosity verbosity = Progress::Verbosity::Default;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string_view arg = argv[i];
|
||||
if (arg == "-h" || arg == "--help" || (!runTests && arg == "help")) {
|
||||
if (arg == "-h" || arg == "--help" || (!runTests && !runLint && !runFormat && arg == "help")) {
|
||||
PrintHelp(argv0);
|
||||
return 0;
|
||||
} else if (arg == "test") {
|
||||
} else if (!runLint && !runFormat && arg == "test") {
|
||||
runTests = true;
|
||||
} else if (!runTests && !runFormat && arg == "lint") {
|
||||
runLint = true;
|
||||
} else if (!runTests && !runLint && arg == "format") {
|
||||
runFormat = true;
|
||||
lintOpts.mode = LintMode::Apply;
|
||||
} else if (runFormat && arg == "--check") {
|
||||
lintOpts.mode = LintMode::Check;
|
||||
} else if (arg == "-r") {
|
||||
runAfterBuild = true;
|
||||
} else if (arg == "-v" || arg == "--verbose") {
|
||||
|
|
@ -1529,6 +1533,10 @@ int Crafter::Run(int argc, char** argv) {
|
|||
testOpts.runnerOverride = std::string(arg.substr(std::string_view("--runner=").size()));
|
||||
} else if (runTests && !arg.starts_with("-")) {
|
||||
testOpts.globs.emplace_back(arg);
|
||||
} else if ((runLint || runFormat) && arg == "--list") {
|
||||
lintOpts.listOnly = true;
|
||||
} else if ((runLint || runFormat) && !arg.starts_with("-")) {
|
||||
lintOpts.globs.emplace_back(arg);
|
||||
} else {
|
||||
projectArgs.push_back(arg);
|
||||
}
|
||||
|
|
@ -1553,6 +1561,18 @@ int Crafter::Run(int argc, char** argv) {
|
|||
Configuration config = LoadProject(projectFile, projectArgs);
|
||||
SetParentProject(&config);
|
||||
|
||||
if (runLint || runFormat) {
|
||||
// lexically_normal: fs::absolute keeps a "./" component, which
|
||||
// would leak into displayed paths and the project-root derivation.
|
||||
lintOpts.projectFile = fs::absolute(projectFile).lexically_normal();
|
||||
LintSummary lintSummary = RunLint(config, lintOpts);
|
||||
if (!runFormat) return lintSummary.Clean() ? 0 : 1;
|
||||
if (lintSummary.noRulesDefined || lintSummary.errors > 0) return 1;
|
||||
// Apply: formatting files is success (gofmt convention).
|
||||
// Check: a would-change file is the CI failure signal.
|
||||
return lintOpts.mode == LintMode::Check && !lintSummary.changedFiles.empty() ? 1 : 0;
|
||||
}
|
||||
|
||||
if (runTests) {
|
||||
TestSummary summary = RunTests(config, testOpts, projectArgs);
|
||||
return summary.AllPassed() ? 0 : 1;
|
||||
|
|
@ -1602,20 +1622,20 @@ int Crafter::Run(int argc, char** argv) {
|
|||
// Probe-bind to find a free port starting at 8080 — if the
|
||||
// user has another dev server running we shift up rather
|
||||
// than letting caddy/python exit with EADDRINUSE.
|
||||
auto findFreePort = [](int basePort, int span) -> int {
|
||||
auto findFreePort = [](std::int32_t basePort, std::int32_t span) -> std::int32_t {
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
static const bool wsaInit = []{ WSADATA d; return WSAStartup(MAKEWORD(2, 2), &d) == 0; }();
|
||||
if (!wsaInit) return basePort;
|
||||
using sock_t = SOCKET;
|
||||
const sock_t invalid = INVALID_SOCKET;
|
||||
auto closesock = [](sock_t s){ closesocket(s); };
|
||||
static const bool WsaInit = []{ WSADATA d; return WSAStartup(MAKEWORD(2, 2), &d) == 0; }();
|
||||
if (!WsaInit) return basePort;
|
||||
using Sock = SOCKET;
|
||||
const Sock invalid = INVALID_SOCKET;
|
||||
auto closesock = [](Sock s){ closesocket(s); };
|
||||
#else
|
||||
using sock_t = int;
|
||||
const sock_t invalid = -1;
|
||||
auto closesock = [](sock_t s){ ::close(s); };
|
||||
using Sock = std::int32_t;
|
||||
const Sock invalid = -1;
|
||||
auto closesock = [](Sock s){ ::close(s); };
|
||||
#endif
|
||||
for (int p = basePort; p < basePort + span; ++p) {
|
||||
sock_t s = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
for (std::int32_t p = basePort; p < basePort + span; ++p) {
|
||||
Sock s = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (s == invalid) return basePort;
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
|
|
@ -1627,7 +1647,7 @@ int Crafter::Run(int argc, char** argv) {
|
|||
}
|
||||
return basePort;
|
||||
};
|
||||
const int port = findFreePort(8080, 16);
|
||||
const std::int32_t port = findFreePort(8080, 16);
|
||||
// Cross-origin isolation: the browser coarsens
|
||||
// performance.now() (and chrono::steady_clock under wasi)
|
||||
// to ~0.1ms unless the response carries COOP/COEP, which
|
||||
|
|
@ -1661,8 +1681,7 @@ int Crafter::Run(int argc, char** argv) {
|
|||
" file_server\n"
|
||||
"}}\n",
|
||||
port, absDir.string()));
|
||||
cmd = std::format("caddy run --config {} --adapter caddyfile",
|
||||
cf.string());
|
||||
cmd = std::format("caddy run --config {} --adapter caddyfile", cf.string());
|
||||
} else if (have("python3") || have("python")) {
|
||||
std::string_view py = have("python3") ? "python3" : "python";
|
||||
picked = py;
|
||||
|
|
@ -1701,8 +1720,7 @@ int Crafter::Run(int argc, char** argv) {
|
|||
"header('Cache-Control: no-store');\n"
|
||||
"return false;\n");
|
||||
isolated = true;
|
||||
cmd = std::format("php -S 0.0.0.0:{} -t {} {}",
|
||||
port, absDir.string(), rp.string());
|
||||
cmd = std::format("php -S 0.0.0.0:{} -t {} {}", port, absDir.string(), rp.string());
|
||||
} else if (have("ruby")) {
|
||||
picked = "ruby";
|
||||
cmd = std::format("ruby -run -e httpd {} -p{}", absDir.string(), port);
|
||||
|
|
@ -1713,17 +1731,12 @@ int Crafter::Run(int argc, char** argv) {
|
|||
picked = "npx http-server";
|
||||
cmd = std::format("npx --yes http-server {} -p {} --silent", absDir.string(), port);
|
||||
} else {
|
||||
std::println(std::cerr,
|
||||
"-r wasm: no HTTP server found in PATH. Install one of: "
|
||||
"caddy, python3, python, php, ruby, busybox, npx (Node.js).");
|
||||
std::println(std::cerr, "-r wasm: no HTTP server found in PATH. Install one of: " "caddy, python3, python, php, ruby, busybox, npx (Node.js).");
|
||||
return 1;
|
||||
}
|
||||
std::println("listening on port :{}", port);
|
||||
if (!isolated) {
|
||||
std::println(std::cerr,
|
||||
"warning: {} does not emit COOP/COEP — performance.now() will be coarse "
|
||||
"(~0.1ms). Install caddy, python3, or php for cross-origin isolation.",
|
||||
picked);
|
||||
std::println(std::cerr, "warning: {} does not emit COOP/COEP — performance.now() will be coarse " "(~0.1ms). Install caddy, python3, or php for cross-origin isolation.", picked);
|
||||
}
|
||||
// Silence the backend's own banner/request logs so the
|
||||
// experience is identical regardless of which server is
|
||||
|
|
@ -1742,9 +1755,7 @@ int Crafter::Run(int argc, char** argv) {
|
|||
if (have("wasmer")) {
|
||||
return std::system(std::format("wasmer run {}", artifact.string()).c_str()) == 0 ? 0 : 1;
|
||||
}
|
||||
std::println(std::cerr,
|
||||
"-r wasm: no wasm runtime found in PATH. Install wasmtime or wasmer, "
|
||||
"or call EnableWasiBrowserRuntime(cfg) in project.cpp for a browser build.");
|
||||
std::println(std::cerr, "-r wasm: no wasm runtime found in PATH. Install wasmtime or wasmer, " "or call EnableWasiBrowserRuntime(cfg) in project.cpp for a browser build.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue