diff --git a/src/catter/core/app_config.cc b/src/catter/core/app_config.cc new file mode 100644 index 0000000..20baa2b --- /dev/null +++ b/src/catter/core/app_config.cc @@ -0,0 +1,79 @@ +#include "app_config.h" + +#include +#include +#include +#include +#include + +namespace catter::app { + +StartupConfig to_startup_config(const core::Option::CatterOption& opt) { + return StartupConfig{ + .log = true, + .mode = *opt.mode, + .script_path = *opt.script_path, + .script_args = opt.script_args.has_value() ? *opt.script_args : std::vector{}, + .build_system_command = *opt.args, + .working_dir = opt.working_dir.has_value() ? std::filesystem::absolute(*opt.working_dir) + : std::filesystem::current_path(), + }; +} + +RuntimePlan build_runtime_plan(const StartupConfig& config) { + struct ModeMeta { + ipc::ServiceMode mode; + js::CatterRuntime runtime; + }; + + const static std::unordered_map mode_map = { + {"inject", + {.mode = ipc::ServiceMode::INJECT, + .runtime = { + .supportActions = {js::ActionType::drop, + js::ActionType::skip, + js::ActionType::modify}, + .supportEvents = {js::EventType::finish}, + .type = js::CatterRuntime::Type::inject, + .supportParentId = true, + }}} + }; + + auto it = mode_map.find(config.mode); + if(it == mode_map.end()) { + throw std::runtime_error(std::format("Unsupported mode: {}", config.mode)); + } + + return RuntimePlan{ + .log = config.log, + .mode = it->second.mode, + .script_path = config.script_path, + .script_args = config.script_args, + .build_system_command = config.build_system_command, + .working_dir = config.working_dir, + .runtime = &it->second.runtime, + }; +} + +std::string load_script_content(const std::string& script_path) { + const static std::unordered_map internal_scripts = { + {"script::cdb", + R"( + import { scripts, service } from "catter"; + service.register(new scripts.CDB()); + )"} + }; + + if(auto it = internal_scripts.find(script_path); it != internal_scripts.end()) { + return std::string(it->second); + } + + std::ifstream ifs{script_path}; + if(!ifs.good()) { + throw std::runtime_error(std::format("Failed to open script file: {}", script_path)); + } + + return std::string((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); +} + +} // namespace catter::app diff --git a/src/catter/core/app_config.h b/src/catter/core/app_config.h new file mode 100644 index 0000000..bfd4e0e --- /dev/null +++ b/src/catter/core/app_config.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include "capi/type.h" +#include "ipc.h" +#include "opt/main/option.h" + +namespace catter::app { + +struct StartupConfig { + bool log; + std::string mode; + std::string script_path; + std::vector script_args; + std::vector build_system_command; + std::filesystem::path working_dir; +}; + +struct RuntimePlan { + bool log; + ipc::ServiceMode mode; + std::string script_path; + std::vector script_args; + std::vector build_system_command; + std::filesystem::path working_dir; + const js::CatterRuntime* runtime; +}; + +StartupConfig to_startup_config(const core::Option::CatterOption& opt); + +RuntimePlan build_runtime_plan(const StartupConfig& config); + +std::string load_script_content(const std::string& script_path); + +} // namespace catter::app diff --git a/src/catter/core/app_runner.cc b/src/catter/core/app_runner.cc new file mode 100644 index 0000000..1835f52 --- /dev/null +++ b/src/catter/core/app_runner.cc @@ -0,0 +1,148 @@ +#include "app_runner.h" + +#include +#include +#include +#include +#include +#include + +#include "app_config.h" +#include "capi/type.h" +#include "ipc.h" +#include "js.h" +#include "qjs.h" +#include "session.h" +#include "config/catter-proxy.h" +#include "util/crossplat.h" +#include "util/data.h" + +namespace catter::app { +namespace { + +class ServiceImpl : public ipc::InjectService { +public: + ServiceImpl(data::ipcid_t id, const js::CatterRuntime* runtime) : id(id), runtime(runtime) {} + + ~ServiceImpl() override = default; + + data::ipcid_t create(data::ipcid_t parent_id) override { + this->parent_id = parent_id; + return this->id; + } + + data::action make_decision(data::command cmd) override { + auto act = js::on_command(this->id, + js::CommandData{ + .cwd = cmd.cwd, + .exe = cmd.executable, + .argv = cmd.args, + .env = cmd.env, + .runtime = *runtime, + .parent = this->parent_id, + }); + + switch(act.type()) { + case js::ActionType::drop: { + return data::action{.type = data::action::DROP, .cmd = {}}; + } + case js::ActionType::skip: { + return data::action{.type = data::action::INJECT, .cmd = cmd}; + } + case js::ActionType::modify: { + auto& tag = act.get(); + return data::action{ + .type = data::action::INJECT, + .cmd = { + .cwd = std::move(tag.data.cwd), + .executable = std::move(tag.data.exe), + .args = std::move(tag.data.argv), + .env = std::move(tag.data.env), + } + }; + } + default: throw std::runtime_error("Unhandled action type"); + } + } + + void finish(int64_t code) override { + js::on_execution(this->id, js::Tag{.code = code}); + } + + void report_error(data::ipcid_t parent_id, std::string error_msg) override { + js::on_command(id, std::unexpected(js::CatterErr{.msg = std::move(error_msg)})); + } + + struct Factory { + const js::CatterRuntime* runtime; + + std::unique_ptr operator() (data::ipcid_t id) const { + return std::make_unique(id, runtime); + } + }; + +private: + data::ipcid_t id = 0; + data::ipcid_t parent_id = 0; + const js::CatterRuntime* runtime = nullptr; +}; + +int64_t inject(const js::CatterConfig& config) { + + auto proxy_path = util::get_catter_root_path() / config::proxy::EXE_NAME; + Session::ProcessLaunchPlan launch_plan{ + .executable = proxy_path.string(), + .args = + { + proxy_path.string(), + "-p", "0", + "--exec", config.buildSystemCommand.front(), + "--", }, + }; + append_range_to_vector(launch_plan.args, config.buildSystemCommand); + + Session session; + auto session_plan = Session::make_run_plan(std::move(launch_plan), + ServiceImpl::Factory{.runtime = &config.runtime}); + + return session.run(std::move(session_plan)); +} + +int64_t execute_service(ipc::ServiceMode mode, const js::CatterConfig& config) { + switch(mode) { + case ipc::ServiceMode::INJECT: { + return inject(config); + } + default: { + throw std::runtime_error( + std::format("UnExpected mode: {:0x}", static_cast(mode))); + } + } + throw std::runtime_error("Not implemented"); +} + +} // namespace + +void run(const core::Option::CatterOption& opt) { + auto startup = to_startup_config(opt); + auto plan = build_runtime_plan(startup); + auto script_content = load_script_content(plan.script_path); + + js::init_qjs({.pwd = plan.working_dir}); + js::run_js_file(script_content, plan.script_path); + + auto config = js::on_start({ + .scriptPath = plan.script_path, + .scriptArgs = plan.script_args, + .buildSystemCommand = plan.build_system_command, + .runtime = *plan.runtime, + .options = {.log = plan.log}, + .isScriptSupported = true, + }); + + js::on_finish(js::Tag{ + .code = execute_service(plan.mode, config), + }); +} + +} // namespace catter::app diff --git a/src/catter/core/app_runner.h b/src/catter/core/app_runner.h new file mode 100644 index 0000000..d79e00d --- /dev/null +++ b/src/catter/core/app_runner.h @@ -0,0 +1,9 @@ +#pragma once + +#include "opt/main/option.h" + +namespace catter::app { + +void run(const core::Option::CatterOption& opt); + +} // namespace catter::app diff --git a/src/catter/core/session.cc b/src/catter/core/session.cc index 95e89fa..4f546ff 100644 --- a/src/catter/core/session.cc +++ b/src/catter/core/session.cc @@ -1,8 +1,5 @@ #include -#include -#include #include -#include #include #include #include @@ -18,8 +15,36 @@ namespace catter { -eventide::task Session::loop( - eventide::function_ref(data::ipcid_t, eventide::pipe&&)> acceptor) { +int64_t Session::run(RunPlan run_plan) { +#ifndef _WIN32 + if(std::filesystem::exists(config::ipc::pipe_name())) { + std::filesystem::remove(config::ipc::pipe_name()); + } +#endif + auto acc_ret = + eventide::pipe::listen(config::ipc::pipe_name(), eventide::pipe::options(), default_loop()); + + if(!acc_ret) { + throw std::runtime_error( + std::format("Failed to create acceptor: {}", acc_ret.error().message())); + } + + this->acc = std::make_unique(std::move(*acc_ret)); + + auto loop_task = this->loop(std::move(run_plan.callback)); + auto spawn_task = this->spawn(std::move(run_plan.launch_plan.executable), + std::move(run_plan.launch_plan.args)); + + default_loop().schedule(loop_task); + default_loop().schedule(spawn_task); + + default_loop().run(); + + loop_task.result(); // Propagate exceptions from loop task + return spawn_task.result(); +} + +eventide::task Session::loop(ClientAcceptor acceptor) { std::list> linked_clients; for(auto i: std::views::iota(data::ipcid_t(1))) { auto client = co_await this->acc->accept(); diff --git a/src/catter/core/session.h b/src/catter/core/session.h index e6851d2..aac6dc9 100644 --- a/src/catter/core/session.h +++ b/src/catter/core/session.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,10 +16,6 @@ #include "ipc.h" #include "util/data.h" -#include "util/crossplat.h" -#include "util/eventide.h" -#include "config/ipc.h" -#include "config/catter-proxy.h" namespace catter { @@ -39,67 +36,52 @@ concept ServiceFactoryLike = class Session { public: - using Acceptor = eventide::acceptor; - - /** - * Run a session with the given shell and service factory. - * @param factory The factory should be a callable that takes an ipcid_t and returns a - * unique_ptr to a Service instance. - */ - template - requires ServiceFactoryLike - int64_t run(const std::vector& shell, ServiceFactoryType&& factory) { -#ifndef _WIN32 - if(std::filesystem::exists(config::ipc::pipe_name())) { - std::filesystem::remove(config::ipc::pipe_name()); - } -#endif - auto acc_ret = eventide::pipe::listen(config::ipc::pipe_name(), - eventide::pipe::options(), - default_loop()); - - if(!acc_ret) { - throw std::runtime_error( - std::format("Failed to create acceptor: {}", acc_ret.error().message())); - } - - this->acc = std::make_unique(std::move(*acc_ret)); + using PipeAcceptor = eventide::acceptor; + using ClientAcceptor = + eventide::function(data::ipcid_t, eventide::pipe&&)>; + struct ProcessLaunchPlan { std::string executable; std::vector args; + }; - using Result = ServiceFactoryResult; - if constexpr(std::convertible_to>) { - executable = (util::get_catter_root_path() / config::proxy::EXE_NAME).string(); - args = {executable, "-p", "0", "--exec", shell[0], "--"}; - append_range_to_vector(args, shell); - } else { - static_assert(false, "Unsupported service factory type"); - } - - auto acceptor = [&](data::ipcid_t id, eventide::pipe&& client) -> eventide::task { - return ipc::accept(factory(id), std::move(client)); - }; - - auto loop_task = this->loop(acceptor); - auto spawn_task = this->spawn(executable, args); - - default_loop().schedule(loop_task); - default_loop().schedule(spawn_task); - - default_loop().run(); + struct RunPlan { + ProcessLaunchPlan launch_plan; + ClientAcceptor callback; + }; - loop_task.result(); // Propagate exceptions from loop task - return spawn_task.result(); + /** + * Create a run plan with the given launch plan and client accepted callback. + * @param launch_plan The plan for launching the process. + * @param factory The factory for creating service instances when a client is accepted. + * @return A run plan containing the launch plan and client accepted callback. + */ + template + requires ServiceFactoryLike> + static auto make_run_plan(ProcessLaunchPlan launch_plan, ServiceFactoryType&& factory) { + return RunPlan{ + .launch_plan = std::move(launch_plan), + .callback = + [factory = std::forward(factory)](data::ipcid_t id, + eventide::pipe&& client) { + return ipc::accept(factory(id), std::move(client)); + }, + }; } + /** + * Run the session with the given run plan. + * @param run_plan The run plan containing the launch plan and service factory. + * @return The exit code of the spawned process. + */ + int64_t run(RunPlan run_plan); + private: - eventide::task loop( - eventide::function_ref(data::ipcid_t, eventide::pipe&&)> acceptor); + eventide::task loop(ClientAcceptor acceptor); eventide::task spawn(std::string executable, std::vector args); - std::unique_ptr acc = nullptr; + std::unique_ptr acc = nullptr; }; } // namespace catter diff --git a/src/catter/main.cc b/src/catter/main.cc index 53716a1..51caebf 100644 --- a/src/catter/main.cc +++ b/src/catter/main.cc @@ -1,199 +1,18 @@ -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include "capi/type.h" #include "opt/main/option.h" -#include "js.h" -#include "ipc.h" #include "qjs.h" -#include "session.h" +#include "app_runner.h" #include "util/crossplat.h" -#include "util/data.h" #include "util/log.h" #include "config/catter.h" using namespace catter; -class ServiceImpl : public ipc::InjectService { -public: - ServiceImpl(data::ipcid_t id) : id(id) {}; - ~ServiceImpl() override = default; - - data::ipcid_t create(data::ipcid_t parent_id) override { - this->parent_id = parent_id; - return this->id; - } - - data::action make_decision(data::command cmd) override { - auto act = js::on_command(this->id, - js::CommandData{ - .cwd = cmd.cwd, - .exe = cmd.executable, - .argv = cmd.args, - .env = cmd.env, - // .runtime = - .parent = this->parent_id, - }); - - switch(act.type()) { - case js::ActionType::drop: - case js::ActionType::skip: { - return data::action{.type = data::action::INJECT, .cmd = cmd}; - } - - case js::ActionType::modify: { - auto tag = act.get(); - - return data::action{ - .type = data::action::INJECT, - .cmd = { - .cwd = std::move(tag.data.cwd), - .executable = std::move(tag.data.exe), - .args = std::move(tag.data.argv), - .env = std::move(tag.data.env), - } - }; - } - default: throw std::runtime_error("Unhandled action type"); - } - } - - void finish(int64_t code) override { - js::on_execution(this->id, js::Tag{.code = code}); - } - - void report_error(data::ipcid_t parent_id, std::string error_msg) override { - js::on_command(id, std::unexpected(js::CatterErr{.msg = std::move(error_msg)})); - } - - struct Factory { - std::unique_ptr operator() (data::ipcid_t id) { - return std::make_unique(id); - } - }; - -private: - data::ipcid_t id = 0; - data::ipcid_t parent_id = 0; -}; - -struct Config { - bool log; - ipc::ServiceMode mode; - std::string script_path; - std::vector script_args; - std::vector build_system_command; - std::filesystem::path working_dir; - js::CatterRuntime runtime; -}; - -Config extract_config(const core::Option::CatterOption& opt) { - struct mode_meta { - ipc::ServiceMode mode; - js::CatterRuntime runtime; - }; - - static std::unordered_map mode_map = { - {"inject", - {.mode = ipc::ServiceMode::INJECT, - .runtime = { - .supportActions = {js::ActionType::drop, - js::ActionType::skip, - js::ActionType::modify}, - .supportEvents = {js::EventType::finish}, - .type = js::CatterRuntime::Type::inject, - .supportParentId = true, - }}} - }; - - Config config{ - .log = true, - .script_path = *opt.script_path, - .script_args = opt.script_args.has_value() ? *opt.script_args : std::vector{}, - .build_system_command = *opt.args, - .working_dir = opt.working_dir.has_value() ? std::filesystem::absolute(*opt.working_dir) - : std::filesystem::current_path(), - }; - - if(auto it = mode_map.find(*opt.mode); it != mode_map.end()) { - config.mode = it->second.mode; - config.runtime = it->second.runtime; - } else { - throw std::runtime_error(std::format("Unsupported mode: {}", *opt.mode)); - } - return config; -} - -void inject(const Config& config) { - if(config.script_path == "script::cdb") { - std::string_view script = R"( - import { scripts, service } from "catter"; - service.register(new scripts.CDB()); - )"; - js::run_js_file(script, config.script_path); - } else { - std::ifstream ifs{config.script_path}; - if(!ifs.good()) { - throw std::runtime_error( - std::format("Failed to open script file: {}", config.script_path)); - } - - std::string content((std::istreambuf_iterator(ifs)), - std::istreambuf_iterator()); - catter::js::run_js_file(content, config.script_path); - } - - auto new_config = js::on_start({ - .scriptPath = config.script_path, - .scriptArgs = config.script_args, - .buildSystemCommand = config.build_system_command, - .runtime = config.runtime, - .options = - { - .log = config.log, - }, - .isScriptSupported = true - }); - - Session session; - - auto ret = session.run(new_config.buildSystemCommand, ServiceImpl::Factory{}); - - js::on_finish(js::Tag{ - .code = ret, - }); -} - -void dispatch(const core::Option::CatterOption& opt) { - auto config = extract_config(opt); - - js::init_qjs({.pwd = config.working_dir}); - - switch(config.mode) { - case ipc::ServiceMode::INJECT: { - inject(config); - break; - } - default: { - throw std::runtime_error(std::format("UnExpected mode: {:0x}", (uint8_t)config.mode)); - } - } -} - int main(int argc, char* argv[]) { auto args = deco::util::argvify(argc, argv, 1); @@ -203,7 +22,7 @@ int main(int argc, char* argv[]) { cli.dispatch(core::Option::HelpOpt::category_info, [&](const core::Option& opt) { cli.usage(std::cout); }) .dispatch(core::Option::CatterOption::category_info, - [&](const auto& opt) { dispatch(opt.main_opt); }) + [&](const auto& opt) { app::run(opt.main_opt); }) .dispatch([&](const auto&) { cli.usage(std::cout); }) .when_err([&](const deco::cli::ParseError& err) { std::println("Error parsing options: {}", err.message); diff --git a/tests/integration/test/catter-proxy.cc b/tests/integration/test/catter-proxy.cc index 9d74cb6..06284bf 100644 --- a/tests/integration/test/catter-proxy.cc +++ b/tests/integration/test/catter-proxy.cc @@ -77,7 +77,7 @@ class ServiceImpl : public ipc::InjectService { } struct Factory { - std::unique_ptr operator() (data::ipcid_t id) { + std::unique_ptr operator() (data::ipcid_t id) const { return std::make_unique(id); } }; @@ -94,8 +94,22 @@ int main(int argc, char* argv[]) { // catter::log::mute_logger(); try { Session session; + Session::ProcessLaunchPlan launch_plan; + launch_plan.executable = (util::get_catter_root_path() / config::proxy::EXE_NAME).string(); + launch_plan.args = { + launch_plan.executable, + "-p", + "0", + "--exec", + "echo", + "--", + "echo", + "Hello, World!", + }; + std::println("Session started."); - auto ret = session.run({"echo", "Hello, World!"}, ServiceImpl::Factory{}); + auto session_plan = Session::make_run_plan(std::move(launch_plan), ServiceImpl::Factory{}); + auto ret = session.run(std::move(session_plan)); std::println("Session finished with code: {}", ret); return 0; } catch(const std::exception& ex) {