From 79a1d566b0c76d167855ce0a3032d88c40a7cf3c Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 27 May 2026 19:18:27 +0200 Subject: [PATCH] asdf2 --- weave.cc | 555 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 520 insertions(+), 35 deletions(-) diff --git a/weave.cc b/weave.cc index 5db1a8d..76f4624 100755 --- a/weave.cc +++ b/weave.cc @@ -1,4 +1,3 @@ -///$(which true);FLAGS="-g -Wall -Wextra --std=c++23 -O3";c++ $FLAGS -o weave "$0" || { echo "Compilation failed"; exit 1; }; ./weave; exit 0 #include #include #include @@ -9,9 +8,9 @@ #include #include #include -#include #include #include +#include // Type aliases for convenience using u8 = uint8_t; @@ -35,6 +34,71 @@ 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_; @@ -250,14 +314,17 @@ enum class JsonType { 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(const std::string_view& str) : value(str) {} + 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)) {} @@ -268,7 +335,7 @@ enum class JsonType { } auto is_string() const -> bool { - return std::holds_alternative(value); + return std::holds_alternative(value); } auto is_number() const -> bool { return std::holds_alternative(value); @@ -294,8 +361,8 @@ enum class JsonType { } } auto as_string() const -> std::optional { - if (auto pval = std::get_if(&value)) { - return *pval; + if (auto pval = std::get_if(&value)) { + return pval->view(); } else { return std::nullopt; } @@ -333,7 +400,7 @@ enum class JsonType { [&](JsonNull _) { os << "null"; }, [&](bool b) { os << (b ? "true" : "false"); }, [&](const JsonNumber &num) { os << num.value; }, - [&](const std::string_view &str) { os << "\"" << str << "\""; }, + [&](const CowStr &str) { os << "\"" << str.view() << "\""; }, [&](const JsonObject &obj) { os << "{"; auto first = true; @@ -389,25 +456,103 @@ enum class JsonType { } +struct SourceFlags; struct Source { std::string_view path; - std::string_view flags; - std::string_view defines; + std::vector flags; + std::unordered_map defines; Source(std::string_view path) - : path(std::move(path)), flags(""), defines("") {} - // rule of five - Source(const Source &) = delete; - Source(Source &&) = delete; - Source &operator=(const Source &) = delete; - Source &operator=(Source &&) = delete; + : 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::vector sources; std::unordered_map project_defines; std::vector project_flags; + std::vector modules; // rule of five Project(const Project &) = delete; @@ -416,10 +561,122 @@ struct Project { Project &operator=(Project &&) = delete; Project(std::string_view name) - : name(std::move(name)), sources({}), project_defines({}), project_flags({}) {} + : 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{}; @@ -435,15 +692,19 @@ struct Project { }(); for (const auto &source : sources) { - std::printf("Processing source: %s\n", source.data()); + 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); - fields["arguments"] = json::JsonValue(std::vector{ - json::JsonValue(std::string_view("g++")), json::JsonValue(std::string_view("-c")), - json::JsonValue(std::string_view(source)), json::JsonValue(std::string_view("-o")), - json::JsonValue(std::string_view(std::string(source) + std::string(".o"))), - json::JsonValue(std::vector(project_args))}); + 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))); } @@ -452,6 +713,7 @@ struct Project { return compile_commands; } }; + static auto ninja_escape(std::string_view s) -> std::string { std::string out; out.reserve(s.size()); @@ -542,40 +804,263 @@ auto compile_commands_into_ninja(const json::JsonValue &compile_commands) 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 = command_field->get().as_string().value(); + 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 = " << ninja_escape(cmd) << "\n\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("MyProject"); - project.add_source("src/main.cc"); - project.project_flags.append_range(std::list{"-g", "-Wall", "-Wextra", "--std=c++26", "-O3"}); + 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"}); - auto compile_commands_json = project.into_compile_commands("/var/code/cxx/loom/"); + const auto ninja = project.into_ninja_commands(); + { + std::ofstream os("build.ninja"); + os << ninja; + os.close(); - std::cout << compile_commands_json << std::endl; - // std::ofstream os("compile_commands.json"); - // os << compile_commands_json; - // 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; }