#include #include #include #include #include #include #include #include #include #include #include #include #include // Type aliases for convenience using u8 = uint8_t; using u16 = uint16_t; using u32 = uint32_t; using u64 = uint64_t; using usize = std::size_t; using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; using i64 = int64_t; using isize = std::ptrdiff_t; using f32 = float; using f64 = double; template using RefT = std::reference_wrapper; template struct overloads : Ts... { using Ts::operator()...; }; template struct hash_mix; template <> struct hash_mix<64> { auto operator()(u64 x) const -> u64 { auto const m = 0xe9846af9b1a615d; x ^= x >> 32; x *= m; x ^= x >> 32; x *= m; x ^= x >> 28; return x; } }; template<> struct hash_mix<32> { auto operator()(u32 x) const -> u32 { const u32 m1 = 0x21f0aaad; const u32 m2 = 0x735a2d97; x ^= x >> 16; x *= m1; x ^= x >> 15; x *= m2; x ^= x >> 15; return x; } }; template auto hash_combine(usize &seed, const T &value) -> usize { seed = hash_mix{}(seed + 0x9e3779b9 + std::hash{}(value)); return seed; } template concept hashable = requires(const T &a) { { std::hash>{}(a) } -> std::convertible_to; }; template concept range_of = std::ranges::input_range && std::convertible_to, T>; template auto hash_range(std::size_t &seed, const R &range) -> usize requires range_of && hashable { for (const auto &elem : range) { hash_combine(seed, elem); } return seed; } template struct std::hash> { std::size_t operator()(const std::pair &p) const requires hashable && hashable { std::size_t seed = 0; hash_combine(seed, std::hash>{}(p.first)); hash_combine(seed, std::hash>{}(p.second)); return seed; } }; class CowStr { std::variant data_; public: CowStr() : data_(std::string_view{}) {} CowStr(std::string_view s) : data_(s) {} CowStr(const char* s) : data_(std::string_view{s}) {} CowStr(std::string s) : data_(std::move(s)) {} [[nodiscard]] bool is_borrowed() const { return std::holds_alternative(data_); } [[nodiscard]] bool is_owned() const { return std::holds_alternative(data_); } [[nodiscard]] std::string_view view() const { if (const auto* borrowed = std::get_if(&data_)) { return *borrowed; } return std::get(data_); } [[nodiscard]] const char* c_str() { if (auto* borrowed = std::get_if(&data_)) { data_ = std::string(*borrowed); } return std::get(data_).c_str(); } [[nodiscard]] std::string into_owned() && { if (auto* owned = std::get_if(&data_)) { return std::move(*owned); } return std::string(std::get(data_)); } [[nodiscard]] std::string to_owned() const { if (const auto* owned = std::get_if(&data_)) { return *owned; } return std::string(std::get(data_)); } std::string& to_mut() { if (auto* borrowed = std::get_if(&data_)) { data_ = std::string(*borrowed); } return std::get(data_); } }; namespace json { static void append_utf8(std::string &out, u32 cp) { if (cp <= 0x7F) { out.push_back(static_cast(cp)); } else if (cp <= 0x7FF) { out.push_back(static_cast(0xC0 | ((cp >> 6) & 0x1F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } else if (cp <= 0xFFFF) { out.push_back(static_cast(0xE0 | ((cp >> 12) & 0x0F))); out.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } else { out.push_back(static_cast(0xF0 | ((cp >> 18) & 0x07))); out.push_back(static_cast(0x80 | ((cp >> 12) & 0x3F))); out.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } } static int hex_value(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return 10 + (c - 'a'); if ('A' <= c && c <= 'F') return 10 + (c - 'A'); return -1; } // Unescape a JSON string literal body (without surrounding quotes). // Throws std::runtime_error on malformed escape sequences. std::string json_unescape(const std::string &in) { std::string out; out.reserve(in.size()); for (size_t i = 0; i < in.size(); ++i) { char c = in[i]; if (c != '\\') { out.push_back(c); continue; } // Escape sequence if (++i >= in.size()) throw std::runtime_error("Invalid escape: trailing backslash"); char e = in[i]; switch (e) { case '"': out.push_back('"'); break; case '\\': out.push_back('\\'); break; case '/': out.push_back('/'); break; case 'b': out.push_back('\b'); break; case 'f': out.push_back('\f'); break; case 'n': out.push_back('\n'); break; case 'r': out.push_back('\r'); break; case 't': out.push_back('\t'); break; case 'u': { // parse 4 hex digits if (i + 4 >= in.size()) throw std::runtime_error("Invalid \\u escape: too short"); int v = 0; for (int k = 1; k <= 4; ++k) { int hv = hex_value(in[i + k]); if (hv < 0) throw std::runtime_error("Invalid hex digit in \\u escape"); v = (v << 4) | hv; } i += 4; // consumed 4 hex digits uint32_t codepoint = static_cast(v); // handle UTF-16 surrogate pair if (0xD800 <= codepoint && codepoint <= 0xDBFF) { // high surrogate; expect another \uXXXX for low surrogate if (i + 2 >= in.size() || in[i + 1] != '\\' || in[i + 2] != 'u') throw std::runtime_error("Missing low surrogate after high surrogate"); // parse low surrogate if (i + 6 >= in.size()) throw std::runtime_error("Invalid low surrogate \\u escape"); int lv = 0; for (int k = 3; k <= 6; ++k) { int hv = hex_value(in[i + k]); if (hv < 0) throw std::runtime_error("Invalid hex digit in low surrogate"); lv = (lv << 4) | hv; } i += 6; // consumed '\' 'u' and 4 hex digits (positions i+1..i+6) uint32_t low = static_cast(lv); if (!(0xDC00 <= low && low <= 0xDFFF)) throw std::runtime_error("Invalid low surrogate value"); // combine surrogates to code point codepoint = 0x10000 + (((codepoint - 0xD800) << 10) | (low - 0xDC00)); } append_utf8(out, codepoint); break; } default: throw std::runtime_error(std::string("Invalid escape character: \\") + e); } } return out; } std::string json_escape(const std::string_view &str) { std::ostringstream ss; for (const auto &c : str) { switch (c) { case '\"': ss << "\\\""; break; case '\\': ss << "\\\\"; break; case '\b': ss << "\\b"; break; case '\f': ss << "\\f"; break; case '\n': ss << "\\n"; break; case '\r': ss << "\\r"; break; case '\t': ss << "\\t"; break; default: if (c >= 0 && c <= 0x1F) { ss << "\\u" << std::hex << std::uppercase << (int)c << std::dec << std::nouppercase; } else { ss << c; } } } return ss.str(); } enum class JsonType { Null, Boolean, Number, String, Array, Object, }; struct JsonValue; struct JsonObject { std::unordered_map members; JsonObject(std::unordered_map members) : members(std::move(members)) {} auto get_field(const std::string_view &key) const -> std::optional>; auto get_field_mut(const std::string_view &key) -> std::optional>; auto insert_field(const std::string_view &key, const JsonValue &value) -> void; }; struct JsonNull {}; struct JsonNumber { std::string_view value; JsonNumber(std::string_view value) : value(value) {} }; struct JsonValue { std::variant, JsonObject> value; JsonValue() : value(JsonNull{}) {} JsonValue(JsonNull null) : value(null) {} JsonValue(bool boolean) : value(boolean) {} JsonValue(CowStr str) : value(str) {} JsonValue(std::string_view str) : value(CowStr(str)) {} JsonValue(std::string str) : value(CowStr(str)) {} JsonValue(const char* str) : value(CowStr(str)) {} JsonValue(JsonNumber number) : value(number) {} JsonValue(std::vector array) : value(std::move(array)) {} JsonValue(JsonObject object) : value(std::move(object)) {} auto is_null() const -> bool { return std::holds_alternative(value); } auto is_boolean() const -> bool { return std::holds_alternative(value); } auto is_string() const -> bool { return std::holds_alternative(value); } auto is_number() const -> bool { return std::holds_alternative(value); } auto is_array() const -> bool { return std::holds_alternative>(value); } auto is_object() const -> bool { return std::holds_alternative(value); } auto as_null() const -> std::optional { if (auto pval = std::get_if(&value)) { return *pval; } else { return std::nullopt; } } auto as_boolean() const -> std::optional { if (auto pval = std::get_if(&value)) { return *pval; } else { return std::nullopt; } } auto as_string() const -> std::optional { if (auto pval = std::get_if(&value)) { return pval->view(); } else { return std::nullopt; } } auto as_number() const -> std::optional> { if (auto pval = std::get_if(&value)) { return std::ref(*pval); } else { return std::nullopt; } } auto as_array() const -> std::optional>> { if (auto pval = std::get_if>(&value)) { return std::ref(*pval); } else { return std::nullopt; } } auto as_object() const -> std::optional> { if (auto pval = std::get_if(&value)) { return std::ref(*pval); } else { return std::nullopt; } } friend auto operator<<(std::ostream &os, const JsonValue& val) -> std::ostream &; }; auto operator<<(std::ostream &os, const JsonValue& val) -> std::ostream & { const auto visitor = overloads{ [&](JsonNull _) { os << "null"; }, [&](bool b) { os << (b ? "true" : "false"); }, [&](const JsonNumber &num) { os << num.value; }, [&](const CowStr &str) { os << "\"" << str.view() << "\""; }, [&](const JsonObject &obj) { os << "{"; auto first = true; for (const auto &[key, value] : obj.members) { if (!first) { os << ","; } os << "\"" << key << "\": " << value; first = false; } os << "}"; }, [&](const std::vector &array) { os << "["; auto first = true; for (const auto &value : array) { if (!first) { os << ","; } os << value; first = false; } os << "]"; } }; std::visit(visitor, val.value); return os; } auto JsonObject::get_field(const std::string_view &key) const -> std::optional> { auto it = members.find(key); if (it != members.end()) { return std::ref(it->second); } else { return std::nullopt; } } auto JsonObject::get_field_mut(const std::string_view &key) -> std::optional> { auto it = members.find(key); if (it != members.end()) { return std::ref(it->second); } else { return std::nullopt; } } auto JsonObject::insert_field(const std::string_view &key, const JsonValue &value) -> void { members[key] = value; } } struct SourceFlags; struct Source { std::string_view path; std::vector flags; std::unordered_map defines; Source(std::string_view path) : path(std::move(path)), flags({}), defines({}) {} auto with_flag(std::string_view flag) -> Source & { flags.push_back(flag); return *this; } auto as_flags() const -> SourceFlags; }; struct Module : public Source { std::optional name_; Module(std::string_view path) : Source(std::move(path)), name_(std::nullopt) {} Module(std::string_view path, std::string name) : Source(std::move(path)), name_(std::move(name)) {} Module(Source source, std::string name) : Source(source), name_(name) {} Module(Source source) : Source(source), name_(std::nullopt) {} auto name() const -> CowStr { if (name_) { return CowStr(*name_); } else { auto name = std::string(path.substr( 0, path.find_last_of('.'))); // remove extension for (auto &slash : name | std::views::filter([](char c) { return c == '/'; })) { slash = '.'; } return CowStr(name); } } }; struct SourceFlags { const Source &source; SourceFlags(const Source &source) : source(source) {} friend auto operator<<(std::ostream &os, const SourceFlags &sf) -> std::ostream & { for (const auto &flag : sf.source.flags) { os << flag << " "; } for (const auto &[k, v] : sf.source.defines) { os << "-D" << k << "=" << v << " "; } return os; } friend auto operator<<(std::vector &args, const SourceFlags &sf) -> std::vector & { for (const auto &flag : sf.source.flags) { args.push_back(json::JsonValue(flag)); } for (const auto &[k, v] : sf.source.defines) { const auto define = "-D" + std::string(k) + "=" + std::string(v); args.push_back(json::JsonValue(define)); } return args; } }; auto Source::as_flags() const -> SourceFlags { return SourceFlags(*this); } struct SourceHash { std::size_t operator()(const Source &source) const { auto path = std::hash{}(source.path); auto flags = hash_range(path, source.flags); auto defines = hash_range>(flags, source.defines); return defines; } }; template <> struct std::hash { std::size_t operator()(const Source &source) const { return SourceHash{}(source); } }; struct Project { std::string_view name; std::vector sources; std::unordered_map project_defines; std::vector project_flags; std::vector modules; // rule of five Project(const Project &) = delete; Project(Project &&) = delete; Project &operator=(const Project &) = delete; Project &operator=(Project &&) = delete; Project(std::string_view name) : name(std::move(name)), sources({}), project_defines({}), project_flags({}), modules({}) {} auto add_source(std::string_view source) -> void { sources.push_back(Source(std::move(source))); } auto add_source(Source source) -> void { sources.push_back(std::move(source)); } auto add_source(Source &&source) -> void { sources.push_back(std::move(source)); } auto add_module(Module module) -> void { modules.push_back(module); } auto add_module(Source &&module) -> void { modules.push_back({std::move(module)}); } auto add_module(std::string_view module) -> void { modules.push_back(Module(std::move(module))); } auto into_ninja_commands() -> std::string { const auto build_dir = "target/artifacts/" + std::string(name); auto ninja = std::ostringstream{}; ninja << "ninja_required_version = 1.3\n"; ninja << "build_dir = " << build_dir << "\n"; ninja << "target = " << name << "\n\n"; ninja << "cxxflags = "; auto flags = project_flags | std::views::join_with(' '); for (const auto& flag : flags) { ninja << flag; } ninja << "\n"; ninja << "cxxdefs = "; auto defines = project_defines | std::views::transform([](const auto &kv) { return "-D" + kv.first + "=" + kv.second; }) | std::views::join_with(' '); for (const auto& define : defines) { ninja << define; } ninja << "\n\n"; const std::string link_cmd = "clang++ -fuse-ld=mold"; const std::string cxx_cmd = "clang++"; // link rule { ninja << "rule link\n"; ninja << " command = " << link_cmd << " -o $out $in\n\n"; } // compile rule { ninja << "rule cc\n"; ninja << " command = " << cxx_cmd << " $cxxflags $cxxdefs $fileflags $modules -c $in -o $out -MMD -MF $depfile\n"; ninja << " depfile = $depfile\n"; ninja << " deps = gcc\n\n"; } // module rule { ninja << "rule cc_mod\n"; ninja << " command = " << cxx_cmd << " $cxxflags $cxxdefs $fileflags $in " "--precompile -o $out -MMD -MF $depfile\n"; ninja << " depfile = $depfile\n"; ninja << " deps = gcc\n\n"; } // precompile modules auto module_files = std::ostringstream{}; for (const auto &module : modules) { auto module_ss = std::ostringstream{}; module_ss << std::hex << std::hash{}(module); const auto module_obj = module_ss.str(); ninja << "build $build_dir/" << module_obj << ".pcm: cc_mod " << module.path << "\n"; ninja << " depfile = $build_dir/" << module_obj << ".d\n"; ninja << " fileflags = " << module.as_flags() << "\n\n"; auto module_name = module.name(); module_files << "-fmodule-file=" << module_name.view() << "=" << "$build_dir/" << module_obj << ".pcm "; } ninja << "modules = " << module_files.str() << "\n\n"; // compile sources auto objects = std::vector{}; for (const auto &source : sources) { auto object_ss = std::ostringstream{}; object_ss << std::hex << std::hash{}(source); const auto object = object_ss.str(); ninja << "build $build_dir/" << object << ".o: cc " << source.path << "\n"; ninja << " depfile = $build_dir/" << object << ".d\n"; ninja << " fileflags = " << source.as_flags() << "\n\n"; objects.push_back(object); } ninja << "\nbuild $target: link "; auto joined = objects | std::views::transform([](const std::string &obj) -> std::string { return "$build_dir/" + obj + ".o"; }) | std::views::join_with(' '); for (const auto &part : joined) { ninja << part; } ninja << "\n\n"; return ninja.str(); } auto into_compile_commands(const std::string_view& working_dir) -> json::JsonValue { std::vector compile_commands{}; const auto project_args = [&]() -> std::vector { std::vector args{}; for (const auto &flag : project_flags) { args.push_back(json::JsonValue(flag)); } for (const auto &[key, value] : project_defines) { args.push_back(json::JsonValue("-D" + key + "=" + value)); } return args; }(); for (const auto &source : sources) { std::cout << std::format("Processing source: {}\n", source.path); auto fields = std::unordered_map{}; fields["directory"] = json::JsonValue(working_dir); fields["file"] = json::JsonValue(source.path); auto arguments = std::vector{ json::JsonValue("g++"), json::JsonValue("-c"), json::JsonValue(std::string_view(source.path)), json::JsonValue(std::string_view("-o")), json::JsonValue(std::string(source.path) + std::string(".o"))}; arguments.append_range(project_args); arguments << source.as_flags(); fields["arguments"] = json::JsonValue(arguments); fields["output"] = json::JsonValue(std::string(source.path) + ".o"); compile_commands.push_back(json::JsonValue(json::JsonObject(fields))); } return compile_commands; } }; static auto ninja_escape(std::string_view s) -> std::string { std::string out; out.reserve(s.size()); for (char c : s) { switch (c) { case '$': out += "$$"; break; case ':': out += "$:"; break; case ' ': out += "$ "; break; case '\n': out += "$\n"; break; default: out.push_back(c); break; } } return out; } static auto shell_join_arguments(const std::vector &args) -> std::string { std::string out; bool first = true; for (const auto &arg0 : args) { if (auto&& arg = arg0.as_string()) { if (!first) { out.push_back(' '); } const bool needs_quotes = arg->find_first_of(" \t\n\"'\\$") != std::string_view::npos; if (!needs_quotes) { out.append(arg->begin(), arg->end()); } else { out.push_back('"'); for (char c : *arg) { if (c == '"' || c == '\\') { out.push_back('\\'); } out.push_back(c); } out.push_back('"'); } first = false; } else { throw std::runtime_error("compile_commands entry has non-string argument"); } } return out; } static auto default_object_path(std::string_view file) -> std::string { return std::string(file) + ".o"; } auto compile_commands_into_ninja(const json::JsonValue &compile_commands) -> void { if (auto arr = compile_commands.as_array()) { std::ostringstream ninja; ninja << "ninja_required_version = 1.3\n\n"; ninja << "rule cc\n"; ninja << " command = $cmd\n"; ninja << " description = CC $in\n\n"; for (const auto &entry : arr->get()) { const json::JsonObject& obj = entry.as_object().value(); const json::JsonValue& file_field = obj.get_field("file").value(); auto file = file_field.as_string().value(); auto output = default_object_path(file); if (const auto &output_field = obj.get_field("output")) { if (const auto out = output_field->get().as_string()) { output = std::string(*out); } else { throw std::runtime_error("'output' must be a string"); } } std::string cmd{}; if (auto args_field = obj.get_field("arguments")) { cmd = shell_join_arguments(args_field->get().as_array().value()); } else if (auto command_field = obj.get_field("command")) { cmd = ninja_escape(command_field->get().as_string().value()); } else { throw std::runtime_error( "compile_commands entry must contain either 'arguments' or 'command'"); } ninja << "build " << ninja_escape(output) << ": cc " << ninja_escape(file) << "\n"; ninja << " cmd = " << cmd << "\n\n"; } ninja << "rule link\n"; ninja << " command = g++ -fuse-ld=mold -o $out $in\n"; std::cout << ninja.str(); } else { throw std::runtime_error("compile_commands root must be an array"); } } namespace libc { #include #include } namespace command { /// returns {child_fd, parent_fd} auto piped_stdio(bool readable) -> std::pair { int fds[2]; if (::libc::pipe(fds) == -1) { throw std::runtime_error("Failed to create pipe"); } if (readable) { // read end for child, write end for parent return {static_cast(fds[0]), static_cast(fds[1])}; } else { // write end for child, read end for parent return {static_cast(fds[1]), static_cast(fds[0])}; } } enum class StdioStrategy { Inherit, Piped, Fd, Null, }; struct StdioInherit : std::monostate {}; struct StdioPiped : std::monostate {}; // struct StdioNull : std::monostate {}; struct StdioFd { u32 fd; StdioFd(u32 fd) : fd(fd) {} }; class Stdio { StdioStrategy strategy; std::optional fd_; public: Stdio(u32 fd) : strategy(StdioStrategy::Fd), fd_(fd) {} Stdio(StdioFd stdio_fd) : strategy(StdioStrategy::Fd), fd_(stdio_fd.fd) {} Stdio(StdioInherit) : strategy(StdioStrategy::Inherit), fd_(std::nullopt) {} Stdio(StdioPiped) : strategy(StdioStrategy::Piped), fd_(std::nullopt) {} auto operator=(u32 fd) -> Stdio & { strategy = StdioStrategy::Fd; fd_ = fd; return *this; } auto operator=(StdioFd stdio_fd) -> Stdio & { strategy = StdioStrategy::Fd; fd_ = stdio_fd.fd; return *this; } auto operator=(StdioInherit) -> Stdio & { strategy = StdioStrategy::Inherit; fd_ = std::nullopt; return *this; } auto operator=(StdioPiped) -> Stdio & { strategy = StdioStrategy::Piped; fd_ = std::nullopt; return *this; } auto fd() const -> std::optional { if (strategy == StdioStrategy::Fd) { return fd_; } else { return std::nullopt; } } auto into_child_parent(bool readable) -> std::pair> { switch (strategy) { case StdioStrategy::Inherit: return {StdioInherit{}, std::nullopt}; case StdioStrategy::Fd: return {{StdioFd(fd_.value())}, std::nullopt}; case StdioStrategy::Piped: { auto [child_fd, parent_fd] = piped_stdio(readable); return {{StdioFd(child_fd)}, std::make_optional(parent_fd)}; } default: throw std::runtime_error("Invalid Stdio strategy"); } } }; class Process { pid_t pid_; public: Process(pid_t pid) : pid_(pid) {} auto pid() const -> pid_t { return pid_; } auto wait() -> int { int status; if (::libc::waitpid(pid_, &status, 0) == -1) { throw std::runtime_error("Failed to wait for child process"); } return status; } auto send_signal(int signal) -> void { if (::libc::kill(pid_, signal) == -1) { throw std::runtime_error("Failed to send signal to child process"); } } auto kill() -> void { send_signal(SIGKILL); } }; class Child : public Process { std::optional stdout_fd{}; public: Child(pid_t pid) : Process(pid) {} Child(pid_t pid, std::optional stdout_fd) : Process(pid), stdout_fd(stdout_fd) {} }; class Command { std::vector args; std::unordered_map env; std::string cmd; std::string current_dir; Stdio stdout{StdioInherit{}}; public: Command(std::string cmd) : cmd(std::move(cmd)) {} auto with_arg(std::string arg) -> Command & { args.push_back(std::move(arg)); return *this; } auto with_env(std::string key, std::string value) -> Command & { env[std::move(key)] = std::move(value); return *this; } auto with_current_dir(std::string dir) -> Command & { current_dir = std::move(dir); return *this; } auto spawn() -> std::variant { std::vector argv; argv.push_back(const_cast(cmd.c_str())); for (const auto &arg : args) { argv.push_back(const_cast(arg.c_str())); } argv.push_back(nullptr); std::vector env_strings; std::vector envp; for (const auto &[key, value] : env) { env_strings.push_back(key + "=" + value); envp.push_back(const_cast(env_strings.back().c_str())); } envp.push_back(nullptr); auto [child_stdout, stdout_fd] = stdout.into_child_parent(false); ::libc::posix_spawn_file_actions_t file_actions; ::libc::posix_spawn_file_actions_init(&file_actions); if (child_stdout.fd()) { ::libc::posix_spawn_file_actions_adddup2(&file_actions, *stdout_fd, STDOUT_FILENO); } pid_t pid; int result = ::libc::posix_spawnp(&pid, cmd.c_str(), &file_actions, nullptr, argv.data(), envp.data()); ::libc::posix_spawn_file_actions_destroy(&file_actions); if (result != 0) { return std::error_code(result, std::generic_category()); } return Child{pid, stdout_fd}; } }; } #include #include #include auto main() -> int { std::printf("Hello, weave!\n"); // Example usage Project project("Weave"); project.add_source("weave.cc"); auto std_mod = Module("std.cppm", "std"); std_mod.with_flag("--std=c++26") .with_flag("-Wno-reserved-identifier") .with_flag("-Wno-reserved-module-identifier") .with_flag("-Wno-unused-command-line-argument") .with_flag("-I/nix/store/wi8d6w3a8l82kppk060s7bjjw83601xi-gcc-16.1.0/" "include/c++/16.1.0/backward"); project.add_module(std_mod); project.project_flags.append_range( std::list{"-Wall", "-Wextra", "--std=c++26", "-O1"}); const auto ninja = project.into_ninja_commands(); { std::ofstream os("build.ninja"); os << ninja; os.close(); command::Command cmd("ninja"); cmd.with_arg("-f").with_arg("build.ninja"); cmd.spawn().visit(overloads{ [](command::Child child) { std::printf("Spawned ninja with PID %d\n", child.pid()); int status = child.wait(); std::printf("Ninja exited with status %d\n", status); }, [](std::error_code ec) { std::fprintf(stderr, "Failed to spawn ninja: %s\n", ec.message().c_str()); }}); } return 0; }