#ifndef LIBREPOMGR_SUB_PROCESS_H #define LIBREPOMGR_SUB_PROCESS_H #include "./subprocessfwd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace LibRepoMgr { /// \brief The ProcessResult struct holds data about a concluded BaseProcessSession/ProcessSession/BuildProcessSession. struct ProcessResult { ProcessResult() = default; ProcessResult(const ProcessResult &other) = delete; ProcessResult(ProcessResult &&other) = default; std::string output, error; std::error_code errorCode; int exitCode = -1; }; /// \brief The BaseProcessSession class is the base for ProcessSession and BuildProcessSession. class BaseProcessSession { public: using Handler = ProcessHandler; explicit BaseProcessSession(boost::asio::io_context &ioContext, Handler &&handler); ~BaseProcessSession(); boost::process::group group; boost::process::child child; ProcessResult result; protected: boost::asio::io_context &m_ioContext; Handler m_handler; }; inline BaseProcessSession::BaseProcessSession(boost::asio::io_context &ioContext, Handler &&handler) : child() , m_ioContext(ioContext) , m_handler(std::move(handler)) { } inline BaseProcessSession::~BaseProcessSession() { boost::asio::post(m_ioContext, [child = std::move(this->child), result = std::move(this->result), handler = std::move(m_handler)]() mutable { handler(std::move(child), std::move(result)); }); } class BasicProcessSession : public std::enable_shared_from_this, public BaseProcessSession { public: explicit BasicProcessSession(boost::asio::io_context &ioContext, Handler &&handler); template void launch(ChildArgs &&...childArgs); }; inline BasicProcessSession::BasicProcessSession(boost::asio::io_context &ioContext, Handler &&handler) : BaseProcessSession(ioContext, std::move(handler)) { } template void BasicProcessSession::launch(ChildArgs &&...childArgs) { try { child = boost::process::child( m_ioContext, group, std::forward(childArgs)..., boost::process::on_exit = [session = shared_from_this()](int exitCode, const std::error_code &errorCode) { session->result.exitCode = exitCode; session->result.errorCode = errorCode; }, boost::process::extend::on_error = [session = shared_from_this()](auto &, const std::error_code &errorCode) { session->result.errorCode = errorCode; }); } catch (const boost::process::process_error &e) { result.errorCode = e.code(); result.error = CppUtilities::argsToString("unable to launch: ", e.what()); return; } } /// \brief The ProcessSession class is used to spawn a process, e.g. from a build action, capturing stdout/stderr in-memory. class ProcessSession : public std::enable_shared_from_this, public BaseProcessSession { public: explicit ProcessSession(boost::asio::io_context &ioContext, Handler &&handler); template void launch(ChildArgs &&...childArgs); boost::process::async_pipe outputPipe, errorPipe; private: static std::string streambufToString(boost::asio::streambuf &buf); boost::asio::streambuf m_outputBuffer, m_errorBuffer; }; inline ProcessSession::ProcessSession(boost::asio::io_context &ioContext, Handler &&handler) : BaseProcessSession(ioContext, std::move(handler)) , outputPipe(ioContext) , errorPipe(ioContext) { } inline std::string ProcessSession::streambufToString(boost::asio::streambuf &buf) { const auto begin = boost::asio::buffers_begin(buf.data()); return std::string(begin, begin + static_cast(buf.size())); } template void ProcessSession::launch(ChildArgs &&...childArgs) { try { child = boost::process::child( m_ioContext, group, std::forward(childArgs)..., boost::process::std_out > outputPipe, boost::process::std_err > errorPipe, boost::process::on_exit = [session = shared_from_this()](int exitCode, const std::error_code &errorCode) { session->result.exitCode = exitCode; session->result.errorCode = errorCode; }, boost::process::extend::on_error = [session = shared_from_this()](auto &, const std::error_code &errorCode) { session->result.errorCode = errorCode; }); } catch (const boost::process::process_error &e) { result.errorCode = e.code(); result.error = CppUtilities::argsToString("unable to launch: ", e.what()); return; } boost::asio::async_read(outputPipe, m_outputBuffer, [session = shared_from_this()](const auto &ec, auto bytesTransferred) { CPP_UTILITIES_UNUSED(ec) CPP_UTILITIES_UNUSED(bytesTransferred) session->result.output = streambufToString(session->m_outputBuffer); }); boost::asio::async_read(errorPipe, m_errorBuffer, [session = shared_from_this()](const auto &ec, auto bytesTransferred) { CPP_UTILITIES_UNUSED(ec) CPP_UTILITIES_UNUSED(bytesTransferred) session->result.error = streambufToString(session->m_errorBuffer); }); } inline boost::filesystem::path findExecutable(const std::string &nameOrPath) { return nameOrPath.find('/') == std::string::npos ? boost::process::search_path(nameOrPath) : boost::filesystem::path(nameOrPath); } inline bool checkExecutable(const boost::filesystem::path &path) { return !path.empty() && boost::filesystem::exists(path); } } // namespace LibRepoMgr #endif // LIBREPOMGR_SUB_PROCESS_H