This commit is contained in:
parent
dea67ae5aa
commit
c466d90eec
4 changed files with 386 additions and 24 deletions
|
|
@ -471,7 +471,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// -mllvm is consumed by codegen but not the link driver, which is the
|
||||
// same command line; quiet the unused-flag warning rather than split
|
||||
// compile and link commands.
|
||||
command += " -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL -Wno-unused-command-line-argument";
|
||||
command += " -fno-exceptions -msimd128 -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL -Wno-unused-command-line-argument";
|
||||
}
|
||||
if (config.target == "x86_64-w64-mingw32") {
|
||||
// mingw libstdc++ defines TLS via __emutls_v.* (emulated TLS); without
|
||||
|
|
@ -621,16 +621,32 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
command += " -O3";
|
||||
}
|
||||
|
||||
// Same target-aware setup as the C++ compile path (line 459-): wasm32
|
||||
// rejects -march, silently ignores -mtune, and needs --sysroot to find
|
||||
// wasi-libc headers. Build the prefix once so all C compiles share it.
|
||||
const bool cIsWasm = config.target.starts_with("wasm32");
|
||||
std::string cArchFlags = cIsWasm
|
||||
? std::string()
|
||||
: std::format(" -march={} -mtune={}", config.march, config.mtune);
|
||||
if (!config.sysroot.empty()) {
|
||||
cArchFlags += std::format(" --sysroot={}", config.sysroot);
|
||||
}
|
||||
if (cIsWasm) {
|
||||
// Matches the C++ path's wasi flag set so any libc shim defines
|
||||
// (e.g. _WASI_EMULATED_SIGNAL → signal.h shims) are visible to
|
||||
// C dependencies that drag in signal.h transitively.
|
||||
cArchFlags += " -D_WASI_EMULATED_SIGNAL";
|
||||
}
|
||||
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";
|
||||
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]() {
|
||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config, &includeFlags, &defineFlags, &userFlags, &cArchFlags]() {
|
||||
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
std::string result = RunCommand(std::format("clang {}.c --target={} -march={} -mtune={} -O3 -c{}{}{} -o {}_source.o", cFile.string(), config.target, config.march, config.mtune, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string()));
|
||||
std::string result = RunCommand(std::format("clang {}.c --target={}{} -O3 -c{}{}{} -o {}_source.o", cFile.string(), config.target, cArchFlags, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string()));
|
||||
if (result.empty()) return;
|
||||
|
||||
bool expected = false;
|
||||
|
|
@ -1034,16 +1050,79 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
|
|||
fs::create_directories(htmlOutDir);
|
||||
fs::path htmlPath = htmlOutDir / "index.html";
|
||||
|
||||
// Walk the dep graph for env-style JS bridges that need to load BEFORE
|
||||
// runtime.js so they can populate `window.crafter_webbuild_env`. Any
|
||||
// `*.js` entry in a (transitive) dep's `cfg.files` qualifies — the
|
||||
// file is already going to be copied into the consumer's bin dir, we
|
||||
// just need its basename for a `<script src=...>` tag.
|
||||
std::vector<std::string> envScripts;
|
||||
std::unordered_set<Configuration*> seen;
|
||||
std::function<void(Configuration*)> walk = [&](Configuration* c) {
|
||||
if (!c || !seen.insert(c).second) return;
|
||||
for (const fs::path& f : c->files) {
|
||||
if (f.extension() == ".js" && f.filename() != "runtime.js") {
|
||||
std::string name = f.filename().string();
|
||||
if (std::find(envScripts.begin(), envScripts.end(), name) == envScripts.end()) {
|
||||
envScripts.push_back(std::move(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Configuration* dep : c->dependencies) walk(dep);
|
||||
};
|
||||
walk(&cfg);
|
||||
|
||||
std::string envScriptTags;
|
||||
for (const std::string& name : envScripts) {
|
||||
envScriptTags += std::format(" <script src=\"{}\" type=\"module\"></script>\n", name);
|
||||
}
|
||||
|
||||
// Walk the dep graph again for non-JS assets — these get pre-loaded by
|
||||
// runtime.js into an in-memory VFS so the wasm's std::ifstream et al.
|
||||
// can actually read them (the wasi-runtime in this repo otherwise
|
||||
// stubs every fd syscall to zero). The manifest lists basenames only;
|
||||
// each file lives next to the .wasm in the bin dir.
|
||||
std::vector<std::string> assetFiles;
|
||||
seen.clear();
|
||||
std::function<void(Configuration*)> walkAssets = [&](Configuration* c) {
|
||||
if (!c || !seen.insert(c).second) return;
|
||||
for (const fs::path& f : c->files) {
|
||||
std::string ext = f.extension().string();
|
||||
if (ext == ".js" || ext == ".html") continue;
|
||||
if (f.filename() == "runtime.js") continue;
|
||||
std::string name = f.filename().string();
|
||||
if (name.empty()) continue;
|
||||
if (std::find(assetFiles.begin(), assetFiles.end(), name) == assetFiles.end()) {
|
||||
assetFiles.push_back(std::move(name));
|
||||
}
|
||||
}
|
||||
for (Configuration* dep : c->dependencies) walkAssets(dep);
|
||||
};
|
||||
walkAssets(&cfg);
|
||||
|
||||
fs::path manifestPath = htmlOutDir / "files.json";
|
||||
{
|
||||
std::ofstream m(manifestPath);
|
||||
m << "[";
|
||||
for (std::size_t i = 0; i < assetFiles.size(); ++i) {
|
||||
if (i) m << ",";
|
||||
m << "\"" << assetFiles[i] << "\"";
|
||||
}
|
||||
m << "]";
|
||||
}
|
||||
|
||||
std::ifstream in(htmlTemplate);
|
||||
std::stringstream buf;
|
||||
buf << in.rdbuf();
|
||||
std::string html = std::regex_replace(buf.str(), std::regex(R"(\{\{WASM\}\})"), cfg.outputName + ".wasm");
|
||||
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"(\{\{ENV_SCRIPTS\}\})"), envScriptTags);
|
||||
std::ofstream out(htmlPath);
|
||||
out << html;
|
||||
out.close();
|
||||
|
||||
cfg.files.push_back(runtimeJs);
|
||||
cfg.files.push_back(htmlPath);
|
||||
cfg.files.push_back(manifestPath);
|
||||
}
|
||||
|
||||
std::string Crafter::HostTarget() {
|
||||
|
|
@ -1087,6 +1166,15 @@ ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::str
|
|||
}
|
||||
if (sawLib && cfg.type == ConfigurationType::Executable) cfg.type = ConfigurationType::LibraryStatic;
|
||||
if (sawShared && cfg.type == ConfigurationType::LibraryStatic) cfg.type = ConfigurationType::LibraryDynamic;
|
||||
// WASI sysroot autodetect, applied at config-load time so the VariantId
|
||||
// includes it. (Build() also runs this once more for callers that bypassed
|
||||
// ApplyStandardArgs, but doing it here makes dep PcmDirs consistent
|
||||
// between the consumer's command-construction and the dep's own build.)
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
if (cfg.sysroot.empty() && cfg.target.starts_with("wasm32")) {
|
||||
cfg.sysroot = "/usr/share/wasi-sysroot";
|
||||
}
|
||||
#endif
|
||||
return ArgQuery{args};
|
||||
}
|
||||
|
||||
|
|
@ -1234,11 +1322,76 @@ int Crafter::Run(int argc, char** argv) {
|
|||
} else if (config.target == "x86_64-w64-mingw32" || config.target == "x86_64-pc-windows-msvc") {
|
||||
artifact += ".exe";
|
||||
}
|
||||
artifact = fs::absolute(artifact);
|
||||
fs::path absDir = fs::absolute(dir);
|
||||
|
||||
// wasm targets need either a wasm runtime (wasi-cli) or an HTTP
|
||||
// server (browser build with index.html). std::system on the
|
||||
// .wasm path goes nowhere useful — replace with detection.
|
||||
if (config.target.starts_with("wasm32")) {
|
||||
bool browserBuild = fs::exists(absDir / "index.html");
|
||||
auto have = [](std::string_view exe) {
|
||||
#ifdef _WIN32
|
||||
std::string probe = std::format("where {} > NUL 2>&1", exe);
|
||||
#else
|
||||
std::string probe = std::format("command -v {} > /dev/null 2>&1", exe);
|
||||
#endif
|
||||
return std::system(probe.c_str()) == 0;
|
||||
};
|
||||
if (browserBuild) {
|
||||
// Try installed HTTP servers in priority order: lightweight
|
||||
// / dependency-free first, ad-hoc ones last. Foreground
|
||||
// (Ctrl-C to stop); we exec, not fork.
|
||||
const int port = 8080;
|
||||
std::string cmd;
|
||||
std::string_view picked;
|
||||
if (have("caddy")) {
|
||||
picked = "caddy";
|
||||
cmd = std::format("caddy file-server --listen :{} --root {}", port, absDir.string());
|
||||
} else if (have("python3")) {
|
||||
picked = "python3";
|
||||
cmd = std::format("python3 -m http.server --directory {} {}", absDir.string(), port);
|
||||
} else if (have("python")) {
|
||||
picked = "python";
|
||||
cmd = std::format("python -m http.server --directory {} {}", absDir.string(), port);
|
||||
} else if (have("php")) {
|
||||
picked = "php";
|
||||
cmd = std::format("php -S 0.0.0.0:{} -t {}", port, absDir.string());
|
||||
} else if (have("ruby")) {
|
||||
picked = "ruby";
|
||||
cmd = std::format("ruby -run -e httpd {} -p{}", absDir.string(), port);
|
||||
} else if (have("busybox")) {
|
||||
picked = "busybox httpd";
|
||||
cmd = std::format("busybox httpd -f -p {} -h {}", port, absDir.string());
|
||||
} else if (have("npx")) {
|
||||
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).");
|
||||
return 1;
|
||||
}
|
||||
std::println("serving {} via {} at http://localhost:{}/", absDir.string(), picked, port);
|
||||
return std::system(cmd.c_str()) == 0 ? 0 : 1;
|
||||
}
|
||||
// wasi-cli wasm — needs a standalone runtime.
|
||||
if (have("wasmtime")) {
|
||||
return std::system(std::format("wasmtime {}", artifact.string()).c_str()) == 0 ? 0 : 1;
|
||||
}
|
||||
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.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve to absolute — cmd.exe on Windows mishandles a leading
|
||||
// "./" by trying to interpret it as a command. system() invokes
|
||||
// through cmd /c, so the relative-prefixed path makes cmd error
|
||||
// with "'.' is not recognized as an internal or external command".
|
||||
artifact = fs::absolute(artifact);
|
||||
// Run from the artifact's own directory so relative file opens
|
||||
// (shaders, assets copied alongside the exe via cfg.files) resolve
|
||||
// against the bin dir rather than the user's cwd. We exit the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue