From 43fae9108dcf40e6db8229ea3560d662c768da4a Mon Sep 17 00:00:00 2001 From: klemens-morgenstern Date: Sat, 3 Dec 2016 02:11:09 +0100 Subject: [PATCH] added documentation for the extensions --- doc/Jamfile.jam | 8 + doc/extend.qbk | 210 +++++++++++++++++- doc/images/plantuml.txt | 118 ++++++++++ doc/images/posix_exec_err.svg | 1 + doc/images/posix_fork_err.svg | 1 + doc/images/posix_success.svg | 1 + doc/images/windows_exec.svg | 1 + doc/introduction.qbk | 2 - doc/posix_pseudocode.xml | 10 +- doc/tutorial.qbk | 8 +- doc/windows_pseudocode.xml | 42 ++++ .../boost/process/detail/windows/env_init.hpp | 2 +- .../boost/process/detail/windows/executor.hpp | 4 +- include/boost/process/extend.hpp | 63 +++--- test/extensions.cpp | 4 +- 15 files changed, 430 insertions(+), 45 deletions(-) create mode 100644 doc/images/plantuml.txt create mode 100644 doc/images/posix_exec_err.svg create mode 100644 doc/images/posix_fork_err.svg create mode 100644 doc/images/posix_success.svg create mode 100644 doc/images/windows_exec.svg create mode 100644 doc/windows_pseudocode.xml diff --git a/doc/Jamfile.jam b/doc/Jamfile.jam index 9e65a854..696f8df3 100644 --- a/doc/Jamfile.jam +++ b/doc/Jamfile.jam @@ -11,6 +11,11 @@ using boostbook ; using quickbook ; using doxygen ; + +local images = [ glob images/*.svg ] ; +install images : $(images) : html/boost_process ; +install images_glob : $(images) : ../../../doc/html/boost_process ; + doxygen autodoc : ../../../boost/process.hpp @@ -19,6 +24,8 @@ doxygen autodoc PREDEFINED=BOOST_PROCESS_DOXYGEN HIDE_UNDOC_CLASSES=YES HIDE_UNDOC_MEMBERS=YES + EXAMPLE_PATH=. + . ; boostbook standalone @@ -26,6 +33,7 @@ boostbook standalone process.qbk : autodoc + images boost.root=../../../.. html.stylesheet=../../../../doc/src/boostbook.css ; diff --git a/doc/extend.qbk b/doc/extend.qbk index 0aaa441b..2a7c7913 100644 --- a/doc/extend.qbk +++ b/doc/extend.qbk @@ -1,3 +1,211 @@ -[section:extend Extending the Library] +[def __on_exit__ [globalref boost::process::on_exit on_exit]] +[def __on_success__ [globalref boost::process::extend::on_success ex::on_success]] +[def __child__ [classref boost::process::child child]] +[def __handler__ [classref boost::process::extend::handler handler]] +[def __on_success__ [memberref boost::process::extend::handler::on_success on_success]] +[def __posix_executor__ [classref boost::process::extend::posix_executor ex::posix_executor]] +[def __windows_executor__ [classref boost::process::extend::windows_executor ex::windows_executor]] +[def io_service [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_service.html boost::asio::io_service]] +[def __require_io_service__ [globalref boost::process::extend::require_io_service ex::require_io_service]] +[def __async_handler__ [globalref boost::process::extend::async_handler ex::async_handler]] +[def __get_io_service__ [funcref boost::process::extend::get_io_service ex::get_io_service]] + +[section:extend Extensions] +To extend the library, the header [headerref boost/process/extend.hpp extend] is provided. + +It only provides the explicit style for custom properties, but no implicit style. + +What this means is, that a custom initializer can be implemented, a reference to which can be passed to one of the launching functions. +If a class inherits [classref boost::process::extend::handler] it will be regarded as a initializer and thus directly put into the sequence +the executor gets passed. + +[section:structure Structure] + +The executor calls different handlers of the initializers during the process launch. +The basic structure is consists of three functions, as given below: + +* [globalref boost::process::extend::on_setup on_setup] +* [globalref boost::process::extend::on_error on_error] +* [globalref boost::process::extend::on_success on_success] + +''' + +''' + +Additionally posix provides three more handlers, listed below: + +* [globalref boost::process::extend::on_fork_error on_fork_error] +* [globalref boost::process::extend::on_exec_setup on_exec_setup] +* [globalref boost::process::extend::on_exec_error on_exec_error] + +For more information see the reference of [classref boost::process::extend::posix_executor posix_executor]. + +[endsect] +[section:simple Simple extensions] + +The simplest extension just takes a single handler, which can be done in a functional style. +So let's start with a simple hello-world example, while we use a C++14 generic lambda. + +``` +using namespace boost::process; +namespace ex = bp::extend; + +__child__ c("foo", ex::__on_success__=[](auto & exec) {std::cout << "hello world" << std::endl;}); +``` + +Considering that lambda can also capture values, data can easily be shared between handlers. + +To see which members the executor has, refer to [classref boost::process::extend::windows_executor windows_executor] +and [classref boost::process::extend::posix_executor posix_executor]. + +[note Combined with __on_exit__ this can also handle the process exit.] + +[caution The posix handler symbols are not defined on windows.] + +[endsect] + +[section:handler Handler Types] + +Since the previous example is in a functional style, it is not very reusable. +To solve that problem, the [classref boost::process::extend::handler handler] has an alias in the `boost::process::extend` namespace, to be inherited. +So let's implement the hello world example in a class. + +``` +struct hello_world : __handler__ +{ + template + void __on_success__(Executor & exec) const + { + std::cout << "hello world" << std::endl; + } +}; + +//in our function +__child__ c("foo", hello_world()); +``` + +[note The implementation is done via overloading, not overriding.] + +Every handler not implemented dafaults to [classref boost::process::extend::handler handler], where an empty handler is defined for each event. + +[endsect] + + + +[section:async Asynchronous Functionality] + +Since `boost.process` provides an interface for [@http://www.boost.org/doc/libs/release/libs/asio/ boost.asio], +this functionality is also available for extensions. If the class needs the io_service for some reason, the following code will do that. + +``` +struct async_foo : __handler__, __require_io_service__ +{ + tempalte + void on_setup(Executor & exec) + { + __io_service__ & ios = __get_io_service__(exec.seq); //gives us a reference and a compiler error if not present. + //do something with ios + } +}; +``` + +[note Inheriting [globalref boost::process::extend::require_io_service require_io_service] is necessary, so [funcref boost::process::system system] provides one.] + +Additionally the handler can provide a function that is invoked when the child process exits. This is done through __async_handler__. + +[note [globalref boost::process::extend::async_handler async_handler] implies [globalref boost::process::extend::require_io_service require_io_service] .] + +``` +struct async_bar : __handler, __async_handler__ +{ + template + std::function on_exit_handler(Executor & exec) + { + auto handler = this->handler; + return [handler](int exit_code, const std::error_code & ec) + { + std::cout << "hello world, I exited with " << exit_code << std::endl; + }; + + } +}; +``` + + +[caution `on_exit_handler` does not default and is always required when [classref boost::process::extend::async_handler async_handler] is inherited. ] + +[endsect] + +[section:error Error handling] + +If an error occurs in the initializers it shall be told to the executor and not handles directly. This is because +the behaviour can be changed through arguments passed to the launching function. Hence the the executor +has the function `set_error`, which takes an [@http://en.cppreference.com/w/cpp/error/error_code std::error_code] and a string. +Depending on the cofiguration of the executor, this may either throw, set an internal `error_code`, or do nothing. + +So let's take a simple example, where we set a randomly chosen `error_code`. + +``` +auto set_error = [](auto & exec) + { + std::error_code ec{42, std::system_category()}; + exec.set_error(ec, "a fake error"); + + }; +__child__ c("foo", on_setup=set_error); +``` + +Since we do not specify the error-handling mode in this example, this will throw [classref boost::process::process_error process_error]. + +[endsect] + +[section:exec_over Executor Overloading] + +Now that we have a custom initializer, let's consider how we can handle differences between different executors. +The distinction is between posix and windows and `char` and `wchar_t` on windows. +One solution is to use the [@http://www.boost.org/doc/libs/master/boost/system/api_config.hpp BOOST_WINDOWS_API and BOOST_POSIX_API] macros, +which are automatically available as soon as any process-header is included. + +Another variant are the type aliases __posix_executor__ and __windows_executor__, where the executor, not on the current system is a forward-declaration. +This works fine, because the function will never get invoked. So let's implement another example, which prints the executable name __on_success__. + +``` +struct hello_exe : __handler__ +{ + template + void __on_success__(__posix_executor__ & exec) + { + std::cout << "posix-exe: " << exec.exe << std::endl; + } + + template + void __on_success__(__windows_executor__ & exec) + { + //note: exe might be a nullptr on windows. + if (exec.exe != nullptr) + std::cout << "windows-exe: " << exec.exe << std::endl; + else + std::cout << "windows didn't use exe" << std::endl; + } + + template + void __on_success__(__windows_executor__ & exec) + { + //note: exe might be a nullptr on windows. + if (exec.exe != nullptr) + std::wcout << L"windows-exe: " << exec.exe << std::endl; + else + std::cout << "windows didn't use exe" << std::endl; + } + +}; +``` + +So given our example, the definitions with the non-native exectur are still a template so that they will not be evaluated if not used. Hence this provides a +way to implement systems-specific code without using the preprocessor. + +[note If you only write a partial implementation, e.g. only for __posix_executor__, the other variants will default to __handler__]. + +[endsect] [endsect] \ No newline at end of file diff --git a/doc/images/plantuml.txt b/doc/images/plantuml.txt new file mode 100644 index 00000000..b8a7cdb3 --- /dev/null +++ b/doc/images/plantuml.txt @@ -0,0 +1,118 @@ +Plantuml source file (for later edit) +// Style + +skinparam backgroundColor #FFFFFF + +skinparam sequence { + ActorBorderColor DeepSkyBlue + ArrowColor #4a6484 + + LifeLineBorderColor #4a6484 + ParticipantBackgroundColor #91c6ff + ParticipantBorderColor black + BoxBorderColor black +} + + +//posix no error +/** +\plantuml +activate Father + +box "Child Process" #LightGrey + participant Child + participant Exe +end box + +Father->Father : on_setup +activate Father +deactivate Father +Father->Child : fork +activate Child +Father -> Father : wait for error +deactivate Father + + +Child->Child : on_exec_setup +activate Child +deactivate Child +Child->Exe : execve +deactivate Child +activate Father +activate Exe + +Father -> Father : on_success +activate Father +deactivate Father + +\endplantuml */ + +//posix exec error +/** +\plantuml +activate Father + +Father->Father : on_setup +activate Father +deactivate Father +Father->Child : fork +activate Child +Father -> Father : wait for error +deactivate Father + +Child->Child : on_exec_setup +activate Child +deactivate Child +Child->Child : execve +Child->Child : on_exec_error +activate Child +deactivate Child +Child->Father : report +deactivate Child +activate Father +Father -> Father : on_error +activate Father +deactivate Father +\endplantuml + +//posix fork error +\plantuml +activate Father + +Father->Father : on_setup +activate Father +deactivate Father +Father->Father : fork +Father -> Father : on_fork_error +activate Father +deactivate Father +Father -> Father : on_error +activate Father +deactivate Father +\endplantuml + + +//windows. +\plantuml +activate Father + +Father->Father : on_setup +activate Father +deactivate Father +Father->Child : CreateProcess +activate Child + +alt Successful Launch + +Father -> Father : on_success +activate Father +deactivate Father + +else Error during launch + +Father -> Father : on_error +activate Father +deactivate Father + +end +\endplantuml diff --git a/doc/images/posix_exec_err.svg b/doc/images/posix_exec_err.svg new file mode 100644 index 00000000..134b36bc --- /dev/null +++ b/doc/images/posix_exec_err.svg @@ -0,0 +1 @@ +FatherFatherChildChildon_setupforkwait for erroron_exec_setupexecveon_exec_errorreporton_error \ No newline at end of file diff --git a/doc/images/posix_fork_err.svg b/doc/images/posix_fork_err.svg new file mode 100644 index 00000000..f335cd73 --- /dev/null +++ b/doc/images/posix_fork_err.svg @@ -0,0 +1 @@ +FatherFatheron_setupforkon_fork_erroron_error \ No newline at end of file diff --git a/doc/images/posix_success.svg b/doc/images/posix_success.svg new file mode 100644 index 00000000..a24188d6 --- /dev/null +++ b/doc/images/posix_success.svg @@ -0,0 +1 @@ +Child ProcessFatherFatherChildChildExeExeon_setupforkwait for erroron_exec_setupexecveon_success \ No newline at end of file diff --git a/doc/images/windows_exec.svg b/doc/images/windows_exec.svg new file mode 100644 index 00000000..a5f8482d --- /dev/null +++ b/doc/images/windows_exec.svg @@ -0,0 +1 @@ +FatherFatherChildChildon_setupCreateProcessalt[Successful Launch]on_success[Error during launch]on_error \ No newline at end of file diff --git a/doc/introduction.qbk b/doc/introduction.qbk index 07b6ef8e..7f5b9360 100644 --- a/doc/introduction.qbk +++ b/doc/introduction.qbk @@ -19,6 +19,4 @@ Here's a simple example of how to start a program with Boost.Process: [import ../example/intro.cpp] [intro] -[caution This is not yet an official Boost C++ library. It wasn't reviewed and can't be downloaded from [@http://www.boost.org/ www.boost.org]. It is however the latest version of an ongoing effort to create a process management library for Boost. For now the library can be downloaded or cloned from here [@https://github.com/klemens-morgenstern/boost-process/tree/develop https://github.com/klemens-morgenstern/boost-process/tree/develop].] - [endsect] diff --git a/doc/posix_pseudocode.xml b/doc/posix_pseudocode.xml index 678674de..ef7348d8 100644 --- a/doc/posix_pseudocode.xml +++ b/doc/posix_pseudocode.xml @@ -13,7 +13,7 @@ if (errorfork() on_setup(*this); -if (pid == -1) //fork error +if (pid == -1) //fork error { set_error(get_last_error()); for (auto & s : seq) @@ -37,7 +37,7 @@ else if (pid == 0) //child process return child(); //for C++ compliance } -child(pid, exit_code); +child c(pid, exit_code); unspecified();//here, we read the the error from the child process @@ -50,7 +50,11 @@ else //now we check again, because a on_success handler might've errored. if (error()) +{ for (auto & s : seq) s.on_error(*this, error()); - + return child(); +} +else + return c; \ No newline at end of file diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index 024f0b1f..607dbc2e 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -25,11 +25,9 @@ [def bp::native_environment [classref boost::process::basic_native_environment bp::native_environment]] [def boost::this_process::environment [funcref boost::this_process::environment boost::this_process::environment]] - -[def __wait_for__ [memberref boost::process::child::wait_for wait_for]] -[def __wait_until__ [memberref boost::process::child::wait_until wait_until]] -[def __detach__ [memberref boost::process::child::detach detach]] - +[def __wait_for__ [memberref boost::process::child::wait_for wait_for]] +[def __wait_until__ [memberref boost::process::child::wait_until wait_until]] +[def __detach__ [memberref boost::process::child::detach detach]] [def __reference__ [link process.reference reference]] diff --git a/doc/windows_pseudocode.xml b/doc/windows_pseudocode.xml new file mode 100644 index 00000000..377da22e --- /dev/null +++ b/doc/windows_pseudocode.xml @@ -0,0 +1,42 @@ + + +for (auto & s : seq) + s.on_setup(*this); + +if (error()) +{ + for (auto & s : seq) + s.on_error(*this, error()); + return child(); +} +int err_code = CreateProcess( + exe, + cmd_line, + proc_attrs, + thread_attrs, + creation_flags, + env, + work_dir, + startup_info, + proc_info); + +child c(proc_info, exit_code); + +if (error()) + for (auto & s : seq) + s.on_error(*this, error()); +else + for (auto & s : seq) + s.on_success(*this); + +//now we check again, because a on_success handler might've errored. +if (error()) +{ + for (auto & s : seq) + s.on_error(*this, error()); + return child(); +} +else + return c; + + \ No newline at end of file diff --git a/include/boost/process/detail/windows/env_init.hpp b/include/boost/process/detail/windows/env_init.hpp index 3c941004..036a2980 100644 --- a/include/boost/process/detail/windows/env_init.hpp +++ b/include/boost/process/detail/windows/env_init.hpp @@ -28,7 +28,7 @@ struct env_init : public ::boost::process::detail::handler_base constexpr static ::boost::detail::winapi::DWORD_ creation_flag(char) {return 0u;} constexpr static ::boost::detail::winapi::DWORD_ creation_flag(wchar_t) { - return ::boost::detail::winapi::CREATE_UNICODE_ENVIRONMENT_; + return ::boost::detail::winapi::CREATE_UNICODE_ENVIRONMENT_; } template diff --git a/include/boost/process/detail/windows/executor.hpp b/include/boost/process/detail/windows/executor.hpp index 286baa44..add26f0f 100644 --- a/include/boost/process/detail/windows/executor.hpp +++ b/include/boost/process/detail/windows/executor.hpp @@ -83,8 +83,8 @@ struct startup_info_impl void set_startup_info_ex() { - startup_info.cb = sizeof(startup_info_ex_t); - creation_flags = ::boost::detail::winapi::EXTENDED_STARTUPINFO_PRESENT_; + startup_info.cb = sizeof(startup_info_ex_t); + creation_flags = ::boost::detail::winapi::EXTENDED_STARTUPINFO_PRESENT_; } }; diff --git a/include/boost/process/extend.hpp b/include/boost/process/extend.hpp index fa039a2d..0eaec0f3 100644 --- a/include/boost/process/extend.hpp +++ b/include/boost/process/extend.hpp @@ -11,9 +11,11 @@ #if defined(BOOST_WINDOWS_API) #include #include +#include #else #include #include +#include #endif @@ -29,10 +31,6 @@ namespace boost { namespace process { -namespace asio { -class io_service; -} - namespace detail { template inline asio::io_service& get_io_service(const Tuple & tup); @@ -52,7 +50,7 @@ struct posix_executor; #elif defined(BOOST_POSIX_API) template -using posix_executor = ::boost::process::detail::posix::executor; +using posix_executor = ::boost::process::detail::posix::executor; template struct windows_executor; @@ -65,20 +63,20 @@ using ::boost::process::detail::get_io_service; using ::boost::process::detail::get_last_error; using ::boost::process::detail::throw_last_error; -///This handler is invoked before the process in launched, to setup parameters. +///This handler is invoked before the process in launched, to setup parameters. The required signature is `void(Exec &)`, where `Exec` is a template parameter. constexpr boost::process::detail::make_handler_t on_setup; -///This handler is invoked if an error occured. +///This handler is invoked if an error occured. The required signature is `void(auto & exec, const std::error_code&)`, where `Exec` is a template parameter. constexpr boost::process::detail::make_handler_t on_error; -///This handler is invoked if launching the process has succeeded. +///This handler is invoked if launching the process has succeeded. The required signature is `void(auto & exec)`, where `Exec` is a template parameter. constexpr boost::process::detail::make_handler_t on_success; #if defined(BOOST_POSIX_API) || defined(BOOST_PROCESS_DOXYGEN) -///This handler is invoked if the fork failed. \note Only available on posix. +///This handler is invoked if the fork failed. The required signature is `void(auto & exec)`, where `Exec` is a template parameter. \note Only available on posix. constexpr ::boost::process::detail::make_handler_t<::boost::process::detail::posix::on_fork_error_ > on_fork_error; -///This handler is invoked if the fork succeeded. \note Only available on posix. +///This handler is invoked if the fork succeeded. The required signature is `void(Exec &)`, where `Exec` is a template parameter. \note Only available on posix. constexpr ::boost::process::detail::make_handler_t<::boost::process::detail::posix::on_exec_setup_ > on_exec_setup; -///This handler is invoked if the exec call errored. \note Only available on posix. -constexpr ::boost::process::detail::make_handler_t<::boost::process::detail::posix::on_exec_error_ > on_exec_error; +///This handler is invoked if the exec call errored. The required signature is `void(auto & exec)`, where `Exec` is a template parameter. \note Only available on posix. +constexpr ::boost::process::detail::make_handler_t<::boost::process::detail::posix::on_exec_error_ > on_exec_error; #endif #if defined(BOOST_PROCESS_DOXYGEN) @@ -164,15 +162,7 @@ struct require_io_service {}; * \code{.cpp} template -std::function on_exit_handler(Executor & exec) -{ - auto handler = this->handler; - return [handler](int exit_code, const std::error_code & ec) - { - handler(static_cast(exit_code), ec); - }; - -} +std::function on_exit_handler(Executor & exec); \endcode The callback will be obtained by calling this function on setup and it will be @@ -191,17 +181,25 @@ struct async_handler : handler, require_io_service * \note It is an alias for the implementation on posix, and a forward-declaration on windows. * * \tparam Sequence The used initializer-sequence, it is fulfills the boost.fusion [sequence](http://www.boost.org/doc/libs/master/libs/fusion/doc/html/fusion/sequence.html) concept. - * + + \xmlonly -The basic structure of the invocation is given below (in pseudo-code). +As information for extension development, here is the structure of the process launching (in pseudo-code and uml) + - +The sequence if when no error occurs. + +The sequence if the execution fails. + + +The sequence if the fork fails. + \endxmlonly - * - * \note Error handling is done through a pipe, unless \ref ignore_error is used. + + +\note Error handling if execve fails is done through a pipe, unless \ref ignore_error is used. + */ template struct posix_executor @@ -236,7 +234,14 @@ struct posix_executor * \tparam Sequence The used initializer-sequence, it is fulfills the boost.fusion [sequence](http://www.boost.org/doc/libs/master/libs/fusion/doc/html/fusion/sequence.html) concept. * \tparam Char The used char-type, either `char` or `wchar_t`. * - * + +\xmlonly +As information for extension development, here is the structure of the process launching (in pseudo-code and uml) + +The sequence for windows process creation. + +\endxmlonly + */ template diff --git a/test/extensions.cpp b/test/extensions.cpp index 24566de2..600e1182 100644 --- a/test/extensions.cpp +++ b/test/extensions.cpp @@ -70,8 +70,8 @@ std::string st = "not called"; struct overload_handler : ex::handler { - template - void on_setup(ex::windows_executor& exec) const + template + void on_setup(ex::windows_executor& exec) const { st = "windows"; const char* env = exec.env;