loom/weave.cc
2026-05-27 19:18:27 +02:00

1067 lines
30 KiB
C++
Executable file

#include <cstdio>
#include <utility>
#include <string>
#include <vector>
#include <unordered_map>
#include <variant>
#include <ostream>
#include <sstream>
#include <optional>
#include <stdexcept>
#include <cstdint>
#include <iostream>
#include <ranges>
// 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 <typename T>
using RefT = std::reference_wrapper<T>;
template <class... Ts> struct overloads : Ts... {
using Ts::operator()...;
};
template <usize N> 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 <class T> auto hash_combine(usize &seed, const T &value) -> usize {
seed =
hash_mix<sizeof(usize) * 8>{}(seed + 0x9e3779b9 + std::hash<T>{}(value));
return seed;
}
template <typename T>
concept hashable = requires(const T &a) {
{ std::hash<std::remove_cvref_t<T>>{}(a) } -> std::convertible_to<std::size_t>;
};
template <typename R, typename T>
concept range_of = std::ranges::input_range<R> &&
std::convertible_to<std::ranges::range_value_t<R>, T>;
template <typename T, typename R>
auto hash_range(std::size_t &seed, const R &range) -> usize requires range_of<R, T> && hashable<T> {
for (const auto &elem : range) {
hash_combine(seed, elem);
}
return seed;
}
template <typename T, typename U>
struct std::hash<std::pair<T, U>> {
std::size_t operator()(const std::pair<T, U> &p) const requires hashable<T> && hashable<U> {
std::size_t seed = 0;
hash_combine(seed, std::hash<std::remove_cvref_t<T>>{}(p.first));
hash_combine(seed, std::hash<std::remove_cvref_t<T>>{}(p.second));
return seed;
}
};
class CowStr {
std::variant<std::string_view, std::string> 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<std::string_view>(data_);
}
[[nodiscard]] bool is_owned() const {
return std::holds_alternative<std::string>(data_);
}
[[nodiscard]] std::string_view view() const {
if (const auto* borrowed = std::get_if<std::string_view>(&data_)) {
return *borrowed;
}
return std::get<std::string>(data_);
}
[[nodiscard]] const char* c_str() {
if (auto* borrowed = std::get_if<std::string_view>(&data_)) {
data_ = std::string(*borrowed);
}
return std::get<std::string>(data_).c_str();
}
[[nodiscard]] std::string into_owned() && {
if (auto* owned = std::get_if<std::string>(&data_)) {
return std::move(*owned);
}
return std::string(std::get<std::string_view>(data_));
}
[[nodiscard]] std::string to_owned() const {
if (const auto* owned = std::get_if<std::string>(&data_)) {
return *owned;
}
return std::string(std::get<std::string_view>(data_));
}
std::string& to_mut() {
if (auto* borrowed = std::get_if<std::string_view>(&data_)) {
data_ = std::string(*borrowed);
}
return std::get<std::string>(data_);
}
};
namespace json {
static void append_utf8(std::string &out, u32 cp) {
if (cp <= 0x7F) {
out.push_back(static_cast<char>(cp));
} else if (cp <= 0x7FF) {
out.push_back(static_cast<char>(0xC0 | ((cp >> 6) & 0x1F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else if (cp <= 0xFFFF) {
out.push_back(static_cast<char>(0xE0 | ((cp >> 12) & 0x0F)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else {
out.push_back(static_cast<char>(0xF0 | ((cp >> 18) & 0x07)));
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(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<uint32_t>(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<uint32_t>(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<std::string_view, JsonValue> members;
JsonObject(std::unordered_map<std::string_view, JsonValue> members)
: members(std::move(members)) {}
auto get_field(const std::string_view &key) const -> std::optional<RefT<const JsonValue>>;
auto get_field_mut(const std::string_view &key) -> std::optional<RefT<JsonValue>>;
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<JsonNull, bool, JsonNumber, CowStr,
std::vector<JsonValue>, 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<JsonValue> array) : value(std::move(array)) {}
JsonValue(JsonObject object) : value(std::move(object)) {}
auto is_null() const -> bool { return std::holds_alternative<JsonNull>(value); }
auto is_boolean() const -> bool {
return std::holds_alternative<bool>(value);
}
auto is_string() const -> bool {
return std::holds_alternative<CowStr>(value);
}
auto is_number() const -> bool {
return std::holds_alternative<JsonNumber>(value);
}
auto is_array() const -> bool {
return std::holds_alternative<std::vector<JsonValue>>(value);
}
auto is_object() const -> bool {
return std::holds_alternative<JsonObject>(value);
}
auto as_null() const -> std::optional<JsonNull> {
if (auto pval = std::get_if<JsonNull>(&value)) {
return *pval;
} else {
return std::nullopt;
}
}
auto as_boolean() const -> std::optional<bool> {
if (auto pval = std::get_if<bool>(&value)) {
return *pval;
} else {
return std::nullopt;
}
}
auto as_string() const -> std::optional<std::string_view> {
if (auto pval = std::get_if<CowStr>(&value)) {
return pval->view();
} else {
return std::nullopt;
}
}
auto as_number() const -> std::optional<RefT<const JsonNumber>> {
if (auto pval = std::get_if<JsonNumber>(&value)) {
return std::ref(*pval);
} else {
return std::nullopt;
}
}
auto as_array() const -> std::optional<RefT<const std::vector<JsonValue>>> {
if (auto pval = std::get_if<std::vector<JsonValue>>(&value)) {
return std::ref(*pval);
} else {
return std::nullopt;
}
}
auto as_object() const -> std::optional<RefT<const JsonObject>> {
if (auto pval = std::get_if<JsonObject>(&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<JsonValue> &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<RefT<const JsonValue>> {
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<RefT<JsonValue>> {
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<std::string_view> flags;
std::unordered_map<std::string_view, std::string_view> 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<std::string> 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<json::JsonValue> &args, const SourceFlags &sf) -> std::vector<json::JsonValue> & {
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<std::string_view>{}(source.path);
auto flags = hash_range<std::string_view>(path, source.flags);
auto defines = hash_range<std::pair<std::string_view, std::string_view>>(flags, source.defines);
return defines;
}
};
template <>
struct std::hash<Source> {
std::size_t operator()(const Source &source) const {
return SourceHash{}(source);
}
};
struct Project {
std::string_view name;
std::vector<Source> sources;
std::unordered_map<std::string, std::string> project_defines;
std::vector<std::string> project_flags;
std::vector<Module> 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<Source>{}(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<std::string>{};
for (const auto &source : sources) {
auto object_ss = std::ostringstream{};
object_ss << std::hex << std::hash<Source>{}(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<json::JsonValue> compile_commands{};
const auto project_args = [&]() -> std::vector<json::JsonValue> {
std::vector<json::JsonValue> 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<std::string_view, json::JsonValue>{};
fields["directory"] = json::JsonValue(working_dir);
fields["file"] = json::JsonValue(source.path);
auto arguments = std::vector<json::JsonValue>{
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<json::JsonValue> &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 <spawn.h>
#include <sys/wait.h>
}
namespace command {
/// returns {child_fd, parent_fd}
auto piped_stdio(bool readable) -> std::pair<u32, u32> {
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<u32>(fds[0]), static_cast<u32>(fds[1])};
} else {
// write end for child, read end for parent
return {static_cast<u32>(fds[1]), static_cast<u32>(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<u32> 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<u32> {
if (strategy == StdioStrategy::Fd) {
return fd_;
} else {
return std::nullopt;
}
}
auto into_child_parent(bool readable) -> std::pair<Stdio, std::optional<u32>> {
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<u32> stdout_fd{};
public:
Child(pid_t pid) : Process(pid) {}
Child(pid_t pid, std::optional<u32> stdout_fd) : Process(pid), stdout_fd(stdout_fd) {}
};
class Command {
std::vector<std::string> args;
std::unordered_map<std::string, std::string> 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<Child, std::error_code> {
std::vector<char *> argv;
argv.push_back(const_cast<char *>(cmd.c_str()));
for (const auto &arg : args) {
argv.push_back(const_cast<char *>(arg.c_str()));
}
argv.push_back(nullptr);
std::vector<std::string> env_strings;
std::vector<char *> envp;
for (const auto &[key, value] : env) {
env_strings.push_back(key + "=" + value);
envp.push_back(const_cast<char *>(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 <fstream>
#include <list>
#include <ext/stdio_filebuf.h>
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;
}