mirror of
https://github.com/boostorg/process.git
synced 2026-01-20 16:52:14 +00:00
Compare commits
25 Commits
group-v2
...
freebsd-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6960d5c40 | ||
|
|
292ac5ceb0 | ||
|
|
7422dfc9c8 | ||
|
|
b2c94f02d7 | ||
|
|
31494428ca | ||
|
|
41e3178727 | ||
|
|
4b4019500d | ||
|
|
50ccdf395f | ||
|
|
db1381ada8 | ||
|
|
9fba0a72c9 | ||
|
|
6ced49d6e1 | ||
|
|
a6291c19f6 | ||
|
|
701d161f15 | ||
|
|
0041a0b292 | ||
|
|
e049859d28 | ||
|
|
ecb384b253 | ||
|
|
8f47527724 | ||
|
|
05bce942c1 | ||
|
|
dcf5d8ce41 | ||
|
|
4209f8ee6e | ||
|
|
d9513269cc | ||
|
|
293f28dab6 | ||
|
|
fe1b629b5d | ||
|
|
278fa57214 | ||
|
|
1addfba12e |
@@ -14,7 +14,8 @@ windowsglobalimage="cppalliance/dronevs2019"
|
||||
|
||||
def main(ctx):
|
||||
return [
|
||||
#freebsd_cxx("FreeBSD", "g++10", packages="g++10", buildtype="boost", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "gcc", "COMPILER": "g++", "CXXSTD": "11" }, globalenv=globalenv),
|
||||
freebsd_cxx("gcc 11 freebsd", "g++-11", buildtype="boost", buildscript="drone", freebsd_version="13.1", environment={'B2_TOOLSET': 'gcc-11', 'B2_CXXSTD': '17,20', 'B2_LINKFLAGS': '-Wl,-rpath=/usr/local/lib/gcc11'}, globalenv=globalenv),
|
||||
freebsd_cxx("clang 14 freebsd", "clang++-14", buildtype="boost", buildscript="drone", freebsd_version="13.1", environment={'B2_TOOLSET': 'clang-14', 'B2_CXXSTD': '17,20'}, globalenv=globalenv),
|
||||
linux_cxx("docs", "", packages="docbook docbook-xml docbook-xsl xsltproc libsaxonhe-java default-jre-headless flex libfl-dev bison unzip rsync mlocate", image="cppalliance/droneubuntu1804:1", buildtype="docs", buildscript="drone", environment={"COMMENT": "docs"}, globalenv=globalenv),
|
||||
linux_cxx("asan", "g++-8", packages="g++-8", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={'COMMENT': 'asan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '11', 'B2_ASAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'DRONE_EXTRA_PRIVILEGED': 'True', 'DRONE_JOB_UUID': '356a192b79'}, globalenv=globalenv, privileged=True),
|
||||
linux_cxx("ubsan", "g++-8", packages="g++-8", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={'COMMENT': 'ubsan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '11', 'B2_UBSAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'B2_LINKFLAGS': '-fuse-ld=gold', 'DRONE_JOB_UUID': '77de68daec'}, globalenv=globalenv),
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -32,8 +32,3 @@
|
||||
/notes_p.txt
|
||||
.settings
|
||||
|
||||
*.make
|
||||
*.cmake
|
||||
*.rsp
|
||||
*.marks
|
||||
cmake-*
|
||||
@@ -38,12 +38,11 @@ namespace process {
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename ExitHandler>
|
||||
template<typename Handler>
|
||||
struct async_system_handler : ::boost::process::detail::api::async_handler
|
||||
{
|
||||
boost::asio::io_context & ios;
|
||||
boost::asio::async_completion<
|
||||
ExitHandler, void(boost::system::error_code, int)> init;
|
||||
Handler handler;
|
||||
|
||||
#if defined(BOOST_POSIX_API)
|
||||
bool errored = false;
|
||||
@@ -52,9 +51,8 @@ struct async_system_handler : ::boost::process::detail::api::async_handler
|
||||
template<typename ExitHandler_>
|
||||
async_system_handler(
|
||||
boost::asio::io_context & ios,
|
||||
ExitHandler_ && exit_handler) : ios(ios), init(exit_handler)
|
||||
ExitHandler_ && exit_handler) : ios(ios), handler(std::forward<ExitHandler_>(exit_handler))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -64,21 +62,15 @@ struct async_system_handler : ::boost::process::detail::api::async_handler
|
||||
#if defined(BOOST_POSIX_API)
|
||||
errored = true;
|
||||
#endif
|
||||
auto & h = init.completion_handler;
|
||||
auto h = std::make_shared<Handler>(std::move(handler));
|
||||
boost::asio::post(
|
||||
ios.get_executor(),
|
||||
[h, ec]() mutable
|
||||
{
|
||||
h(boost::system::error_code(ec.value(), boost::system::system_category()), -1);
|
||||
(*h)(boost::system::error_code(ec.value(), boost::system::system_category()), -1);
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(ExitHandler, void (boost::system::error_code, int))
|
||||
get_result()
|
||||
{
|
||||
return init.result.get();
|
||||
}
|
||||
|
||||
template<typename Executor>
|
||||
std::function<void(int, const std::error_code&)> on_exit_handler(Executor&)
|
||||
{
|
||||
@@ -86,10 +78,10 @@ struct async_system_handler : ::boost::process::detail::api::async_handler
|
||||
if (errored)
|
||||
return [](int , const std::error_code &){};
|
||||
#endif
|
||||
auto & h = init.completion_handler;
|
||||
auto h = std::make_shared<Handler>(std::move(handler));
|
||||
return [h](int exit_code, const std::error_code & ec) mutable
|
||||
{
|
||||
h(boost::system::error_code(ec.value(), boost::system::system_category()), exit_code);
|
||||
(*h)(boost::system::error_code(ec.value(), boost::system::system_category()), exit_code);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -120,21 +112,36 @@ inline boost::process::detail::dummy
|
||||
async_system(boost::asio::io_context & ios, ExitHandler && exit_handler, Args && ...args);
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct async_system_init_op
|
||||
{
|
||||
|
||||
template<typename Handler, typename ... Args>
|
||||
void operator()(Handler && handler, asio::io_context & ios, Args && ... args)
|
||||
{
|
||||
detail::async_system_handler<typename std::decay<Handler>::type> async_h{ios, std::forward<Handler>(handler)};
|
||||
child(ios, std::forward<Args>(args)..., async_h ).detach();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<typename ExitHandler, typename ...Args>
|
||||
inline BOOST_ASIO_INITFN_RESULT_TYPE(ExitHandler, void (boost::system::error_code, int))
|
||||
async_system(boost::asio::io_context & ios, ExitHandler && exit_handler, Args && ...args)
|
||||
{
|
||||
detail::async_system_handler<ExitHandler> async_h{ios, std::forward<ExitHandler>(exit_handler)};
|
||||
|
||||
|
||||
typedef typename ::boost::process::detail::has_error_handler<boost::fusion::tuple<Args...>>::type
|
||||
has_err_handling;
|
||||
|
||||
static_assert(!has_err_handling::value, "async_system cannot have custom error handling");
|
||||
|
||||
|
||||
child(ios, std::forward<Args>(args)..., async_h ).detach();
|
||||
|
||||
return async_h.get_result();
|
||||
return boost::asio::async_initiate<ExitHandler, void (boost::system::error_code, int)>(
|
||||
detail::async_system_init_op{}, exit_handler, ios, std::forward<Args>(args)...
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
240
include/boost/process/v2/bind_launcher.hpp
Normal file
240
include/boost/process/v2/bind_launcher.hpp
Normal file
@@ -0,0 +1,240 @@
|
||||
//
|
||||
// boost/process/v2/bind_launcher.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_PROCESS_V2_BIND_LAUNCHER_HPP
|
||||
#define BOOST_PROCESS_V2_BIND_LAUNCHER_HPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/default_launcher.hpp>
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<std::size_t ... Idx>
|
||||
struct index_sequence { };
|
||||
|
||||
template<std::size_t Size, typename T>
|
||||
struct make_index_sequence_impl;
|
||||
|
||||
template<std::size_t Size, std::size_t ... Idx>
|
||||
struct make_index_sequence_impl<Size, index_sequence<Idx...>>
|
||||
{
|
||||
constexpr make_index_sequence_impl() {}
|
||||
using type = typename make_index_sequence_impl<Size - 1u, index_sequence<Size - 1u, Idx...>>::type;
|
||||
};
|
||||
|
||||
template<std::size_t ... Idx>
|
||||
struct make_index_sequence_impl<0u, index_sequence<Idx...>>
|
||||
{
|
||||
constexpr make_index_sequence_impl() {}
|
||||
using type = index_sequence<Idx...>;
|
||||
};
|
||||
|
||||
|
||||
template<std::size_t Cnt>
|
||||
struct make_index_sequence
|
||||
{
|
||||
using type = typename make_index_sequence_impl<Cnt, index_sequence<>>::type;
|
||||
};
|
||||
|
||||
template<std::size_t Cnt>
|
||||
using make_index_sequence_t = typename make_index_sequence<Cnt>::type;
|
||||
|
||||
}
|
||||
|
||||
/** @brief Utility class to bind initializers to a launcher
|
||||
* @tparam Launcher The inner launcher to be used
|
||||
* @tparam ...Init The initializers to be prepended.
|
||||
*
|
||||
* This can be used when multiple processes shared some settings,
|
||||
* e.g.
|
||||
*
|
||||
*/
|
||||
template<typename Launcher, typename ... Init>
|
||||
struct bound_launcher
|
||||
{
|
||||
template<typename Launcher_, typename ... Init_>
|
||||
bound_launcher(Launcher_ && l, Init_ && ... init) :
|
||||
launcher_(std::forward<Launcher_>(l)), init_(std::forward<Init_>(init)...)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename ExecutionContext, typename Args, typename ... Inits>
|
||||
auto operator()(ExecutionContext & context,
|
||||
const typename std::enable_if<std::is_convertible<
|
||||
ExecutionContext&, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>
|
||||
{
|
||||
return invoke(detail::make_index_sequence_t<sizeof...(Init)>{},
|
||||
context,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
|
||||
template<typename ExecutionContext, typename Args, typename ... Inits>
|
||||
auto operator()(ExecutionContext & context,
|
||||
error_code & ec,
|
||||
const typename std::enable_if<std::is_convertible<
|
||||
ExecutionContext&, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<typename ExecutionContext::executor_type>
|
||||
{
|
||||
return invoke(detail::make_index_sequence_t<sizeof...(Init)>{},
|
||||
context, ec,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
template<typename Executor, typename Args, typename ... Inits>
|
||||
auto operator()(Executor exec,
|
||||
const typename std::enable_if<
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor<Executor>::value ||
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<Executor>
|
||||
{
|
||||
return invoke(detail::make_index_sequence_t<sizeof...(Init)>{},
|
||||
std::move(exec),
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
template<typename Executor, typename Args, typename ... Inits>
|
||||
auto operator()(Executor exec,
|
||||
error_code & ec,
|
||||
const typename std::enable_if<
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor<Executor>::value ||
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<Executor>
|
||||
{
|
||||
return invoke(detail::make_index_sequence_t<sizeof...(Init)>{},
|
||||
std::move(exec), ec,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
private:
|
||||
template<std::size_t ... Idx, typename ExecutionContext, typename Args, typename ... Inits>
|
||||
auto invoke(detail::index_sequence<Idx...>,
|
||||
ExecutionContext & context,
|
||||
const typename std::enable_if<std::is_convertible<
|
||||
ExecutionContext&, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>
|
||||
{
|
||||
return launcher_(context,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::get<Idx>(init_)...,
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
|
||||
template<std::size_t ... Idx, typename ExecutionContext, typename Args, typename ... Inits>
|
||||
auto invoke(detail::index_sequence<Idx...>,
|
||||
ExecutionContext & context,
|
||||
error_code & ec,
|
||||
const typename std::enable_if<std::is_convertible<
|
||||
ExecutionContext&, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<typename ExecutionContext::executor_type>
|
||||
{
|
||||
return launcher_(context, ec,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::get<Idx>(init_)...,
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
template<std::size_t ... Idx, typename Executor, typename Args, typename ... Inits>
|
||||
auto invoke(detail::index_sequence<Idx...>,
|
||||
Executor exec,
|
||||
const typename std::enable_if<
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor<Executor>::value ||
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<Executor>
|
||||
{
|
||||
return launcher_(std::move(exec),
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::get<Idx>(init_)...,
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
template<std::size_t ... Idx, typename Executor, typename Args, typename ... Inits>
|
||||
auto invoke(detail::index_sequence<Idx...>,
|
||||
Executor exec,
|
||||
error_code & ec,
|
||||
const typename std::enable_if<
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor<Executor>::value ||
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value,
|
||||
filesystem::path >::type & executable,
|
||||
Args && args,
|
||||
Inits && ... inits ) -> basic_process<Executor>
|
||||
{
|
||||
return launcher_(std::move(exec), ec,
|
||||
executable,
|
||||
std::forward<Args>(args),
|
||||
std::get<Idx>(init_)...,
|
||||
std::forward<Inits>(inits)...);
|
||||
}
|
||||
|
||||
Launcher launcher_;
|
||||
std::tuple<Init...> init_;
|
||||
};
|
||||
|
||||
|
||||
template<typename Launcher, typename ... Init>
|
||||
auto bind_launcher(Launcher && launcher, Init && ... init)
|
||||
-> bound_launcher<typename std::decay<Launcher>::type,
|
||||
typename std::decay<Init>::type...>
|
||||
{
|
||||
return bound_launcher<typename std::decay<Launcher>::type,
|
||||
typename std::decay<Init>::type...>(
|
||||
std::forward<Launcher>(launcher),
|
||||
std::forward<Init>(init)...);
|
||||
}
|
||||
|
||||
/// @brief @overload bind_launcher(Launcher && launcher, Init && init)
|
||||
/// @tparam ...Init The initializer types to bind to the default_launcher.
|
||||
/// @param ...init The initializers types to bind to the default_launcher.
|
||||
/// @return The new default_launcher.
|
||||
template<typename ... Init>
|
||||
auto bind_default_launcher(Init && ... init)
|
||||
-> bound_launcher<default_process_launcher,
|
||||
typename std::decay<Init>::type...>
|
||||
{
|
||||
return bound_launcher<default_process_launcher,
|
||||
typename std::decay<Init>::type...>(
|
||||
default_process_launcher(),
|
||||
std::forward<Init>(init)...);
|
||||
}
|
||||
|
||||
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
#endif // BOOST_PROCESS_V2_BIND_LAUNCHER_HPP
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
|
||||
#define BOOST_PROCESS_V2_ASIO_NAMESPACE ::asio
|
||||
#define BOOST_PROCESS_V2_ASIO_NAMESPACE asio
|
||||
#define BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(Sig) ASIO_COMPLETION_TOKEN_FOR(Sig)
|
||||
#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor)
|
||||
#define BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(Token, Signature) ASIO_INITFN_AUTO_RESULT_TYPE(Token, Signature)
|
||||
#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN(Executor) ASIO_DEFAULT_COMPLETION_TOKEN(Executor)
|
||||
|
||||
#define BOOST_PROCESS_V2_INITFN_DEDUCED_RESULT_TYPE(x,y,z) ASIO_INITFN_DEDUCED_RESULT_TYPE(x,y,z)
|
||||
|
||||
#include <asio/detail/config.hpp>
|
||||
#include <system_error>
|
||||
@@ -39,12 +39,12 @@
|
||||
|
||||
#else
|
||||
|
||||
#define BOOST_PROCESS_V2_ASIO_NAMESPACE ::boost::asio
|
||||
#define BOOST_PROCESS_V2_ASIO_NAMESPACE boost::asio
|
||||
#define BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(Sig) BOOST_ASIO_COMPLETION_TOKEN_FOR(Sig)
|
||||
#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor)
|
||||
#define BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(Token, Signature) BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(Token, Signature)
|
||||
#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN(Executor) BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(Executor)
|
||||
|
||||
#define BOOST_PROCESS_V2_INITFN_DEDUCED_RESULT_TYPE(x,y,z) BOOST_ASIO_INITFN_DEDUCED_RESULT_TYPE(x,y,z)
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/io/quoted.hpp>
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#ifndef BOOST_PROCESS_V2_GROUP_IMPL_HPP
|
||||
#define BOOST_PROCESS_V2_GROUP_IMPL_HPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
#include <asio/any_io_executor.hpp>
|
||||
#include <asio/append.hpp>
|
||||
#include <asio/compose.hpp>
|
||||
#include <asio/dispatch.hpp>
|
||||
#include <asio/post.hpp>
|
||||
#include <asio/windows/basic_object_handle.hpp>
|
||||
#else
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/windows/basic_object_handle.hpp>
|
||||
#endif
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
struct single_process_exit;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
BOOST_PROCESS_V2_DECL bool job_object_is_empty(HANDLE job_object, error_code & ec);
|
||||
|
||||
template<typename Allocator>
|
||||
BOOST_PROCESS_V2_DECL std::pair<DWORD, DWORD> job_object_something_exited(HANDLE job_object, std::vector<int> & store, Allocator alloc_, error_code & ec)
|
||||
{
|
||||
// let's start with a
|
||||
typename std::aligned_storage<sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) * 16, alignof(JOBOBJECT_BASIC_PROCESS_ID_LIST)>::type storage;
|
||||
JOBOBJECT_BASIC_PROCESS_ID_LIST * id_list = static_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(storage);
|
||||
|
||||
if (!QueryInformationJobObject(job_object,
|
||||
JobObjectBasicProcessIdList,
|
||||
id_list, sizeof(storage), nullptr))
|
||||
ec = detail::get_last_error();
|
||||
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
using allocator_type = std::allocator_traits<Allocator>::rebind_alloc<JOBOBJECT_BASIC_PROCESS_ID_LIST>;
|
||||
allocator_type alloc{alloc_};
|
||||
|
||||
std::size_t sz = (std::numeric_limits<std::size_t>::max)();
|
||||
// dit not fit in the buffer, alloc a buffer
|
||||
if (id_list.NumberOfAssignedProcesses != id_list.NumberOfProcessIdsInList)
|
||||
{
|
||||
// required size:
|
||||
const additional_size = id_list.NumberOfAssignedProcesses - 1;
|
||||
sz = (additional_size / (sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) / sizeof(ULONG_PTR))) + 1;
|
||||
id_list = std::allocator_traits<allocator_type>::allocate(alloc_, sz);
|
||||
|
||||
if (!QueryInformationJobObject(job_object,
|
||||
JobObjectBasicProcessIdList,
|
||||
id_list, sz, nullptr))
|
||||
{
|
||||
ec = detail::get_last_error();
|
||||
goto complete;
|
||||
}
|
||||
|
||||
}
|
||||
std::pair<DWORD, DWORD> result;
|
||||
|
||||
auto * begin = id_list->ProcessIdList,
|
||||
* end = id_list->ProcessIdList + id_list->NumberOfProcessIdsInList;
|
||||
|
||||
for (auto itr = store.begin(); itr != store.end(); itr++)
|
||||
{
|
||||
// cross check if it's in the job object
|
||||
auto it = std::find(begin, end, *itr);
|
||||
if (it == end) // missing a job
|
||||
{
|
||||
result.first = *itr;
|
||||
// ::GetExitCodeProcess(); // this can't be done based on PID, i need the f'in handle.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
complete:
|
||||
if (sz != (std::numeric_limits<std::size_t>::max)())
|
||||
std::allocator_traits<allocator_type>::deallocate(alloc_, id_list, sz);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
//extern template
|
||||
#endif
|
||||
|
||||
struct basic_group_impl_base
|
||||
{
|
||||
using native_handle_type = HANDLE;
|
||||
|
||||
struct job_object_deleter
|
||||
{
|
||||
bool terminate_on_delete = true;
|
||||
void operator()(HANDLE h)
|
||||
{
|
||||
if (h != nullptr && terminate_on_delete)
|
||||
::TerminateJobObject(h, 255u);
|
||||
if (h != nullptr && h != INVALID_HANDLE_VALUE)
|
||||
::CloseHandle(h);
|
||||
}
|
||||
};
|
||||
BOOST_PROCESS_V2_DECL void add(DWORD pid, HANDLE handle, error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL bool contains(DWORD pid, error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL void wait_one(DWORD &pid, int &exit_code, error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL void wait_all(error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL void interrupt(error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL void request_exit(error_code & ec);
|
||||
BOOST_PROCESS_V2_DECL void terminate(error_code & ec);
|
||||
bool is_open() const
|
||||
{
|
||||
return job_object_.get() != nullptr;
|
||||
}
|
||||
|
||||
struct initializer_t
|
||||
{
|
||||
basic_group_impl_base * self;
|
||||
error_code & success_ec;
|
||||
|
||||
error_code on_setup(windows::default_launcher & launcher,
|
||||
const filesystem::path &,
|
||||
const std::wstring &) const
|
||||
{
|
||||
launcher.creation_flags |= CREATE_SUSPENDED;
|
||||
return error_code {};
|
||||
};
|
||||
|
||||
|
||||
void on_success(windows::default_launcher & launcher,
|
||||
const filesystem::path &,
|
||||
const std::wstring &) const
|
||||
{
|
||||
if (!::AssignProcessToJobObject(self->job_object_.get(), launcher.process_information.hProcess))
|
||||
success_ec = detail::get_last_error();
|
||||
|
||||
if (!success_ec &&
|
||||
::ResumeThread(launcher.process_information.hThread) == static_cast<DWORD>(-1))
|
||||
success_ec = detail::get_last_error();
|
||||
};
|
||||
};
|
||||
initializer_t get_initializer(error_code & ec)
|
||||
{
|
||||
return initializer_t{this, ec};
|
||||
}
|
||||
basic_group_impl_base(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context & ctx)
|
||||
{
|
||||
}
|
||||
~basic_group_impl_base()
|
||||
{
|
||||
}
|
||||
void detach() {job_object_.get_deleter().terminate_on_delete = false;}
|
||||
protected:
|
||||
std::unique_ptr<void, job_object_deleter> job_object_{CreateJobObject(nullptr, nullptr)};
|
||||
};
|
||||
|
||||
template<typename Executor = BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>
|
||||
struct basic_group_impl : basic_group_impl_base
|
||||
{
|
||||
|
||||
// Get the native group handle
|
||||
native_handle_type native_handle() {return port_.native_handle();}
|
||||
|
||||
basic_group_impl(Executor exec)
|
||||
: basic_group_impl_base(BOOST_PROCESS_V2_ASIO_NAMESPACE::query(exec,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context)),
|
||||
port_(exec, ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1))
|
||||
{
|
||||
error_code ec;
|
||||
JOBOBJECT_ASSOCIATE_COMPLETION_PORT port{job_object_.get(), port_.native_handle()};
|
||||
|
||||
if (!::SetInformationJobObject(
|
||||
job_object_.get(),
|
||||
JobObjectAssociateCompletionPortInformation,
|
||||
&port, sizeof(port)))
|
||||
ec = v2::detail::get_last_error();
|
||||
}
|
||||
|
||||
|
||||
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, single_process_exit))
|
||||
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, single_process_exit))
|
||||
async_wait_one(
|
||||
WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
|
||||
{
|
||||
printf("FOOBAR %d %p\n", port_.native_handle(), this);
|
||||
|
||||
return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<
|
||||
WaitHandler,
|
||||
void (error_code, single_process_exit)>(
|
||||
wait_one_op{this},
|
||||
handler, port_);
|
||||
}
|
||||
|
||||
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code))
|
||||
async_wait_all(
|
||||
WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
|
||||
{
|
||||
return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<
|
||||
WaitHandler,
|
||||
void (error_code)>(
|
||||
wait_all_op{this},
|
||||
handler, port_);
|
||||
}
|
||||
|
||||
using executor_type = Executor;
|
||||
executor_type get_executor() {return port_.get_executor();}
|
||||
|
||||
void wait_all(error_code &ec)
|
||||
{
|
||||
if (job_object_is_empty(job_object_.get(), ec))
|
||||
return ;
|
||||
|
||||
DWORD completion_code;
|
||||
ULONG_PTR completion_key;
|
||||
LPOVERLAPPED overlapped;
|
||||
|
||||
int res;
|
||||
while (!!(res = GetQueuedCompletionStatus(
|
||||
port_.native_handle(),
|
||||
&completion_code,
|
||||
&completion_key,
|
||||
&overlapped,
|
||||
INFINITE)))
|
||||
{
|
||||
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res)
|
||||
ec = detail::get_last_error();
|
||||
}
|
||||
|
||||
void wait_one(DWORD & pid, int & exit_code, error_code &ec)
|
||||
{
|
||||
if (job_object_is_empty(job_object_.get(), ec))
|
||||
{
|
||||
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
|
||||
return ;
|
||||
}
|
||||
DWORD completion_code;
|
||||
ULONG_PTR completion_key;
|
||||
LPOVERLAPPED overlapped;
|
||||
|
||||
int res;
|
||||
while (!!(res = GetQueuedCompletionStatus(
|
||||
port_.native_handle(),
|
||||
&completion_code,
|
||||
&completion_key,
|
||||
&overlapped,
|
||||
INFINITE)))
|
||||
{
|
||||
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
|
||||
{
|
||||
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
|
||||
return;
|
||||
}
|
||||
else if (completion_code == JOB_OBJECT_MSG_EXIT_PROCESS
|
||||
|| completion_code == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
|
||||
{
|
||||
pid = *reinterpret_cast<DWORD*>(&overlapped);
|
||||
const auto p = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
|
||||
|
||||
if (p == INVALID_HANDLE_VALUE || p == nullptr)
|
||||
{
|
||||
ec = detail::get_last_error();
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD code;
|
||||
res = ::GetExitCodeProcess(p, &code);
|
||||
exit_code = static_cast<int>(code);
|
||||
break;
|
||||
}
|
||||
else if (job_object_is_empty(job_object_.get(), ec))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!res)
|
||||
ec = detail::get_last_error();
|
||||
}
|
||||
|
||||
private:
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::windows::basic_object_handle<Executor> port_;
|
||||
|
||||
struct wait_one_op
|
||||
{
|
||||
basic_group_impl * this_;
|
||||
|
||||
template<typename Self>
|
||||
void operator()(Self && self)
|
||||
{
|
||||
printf("FOOBAR %d %p\n", __LINE__, this_);
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::dispatch(this_->port_.get_executor(),
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::append(std::move(self), error_code{}));
|
||||
}
|
||||
|
||||
template<typename Self>
|
||||
void operator()(Self && self, error_code ec)
|
||||
{
|
||||
using namespace BOOST_PROCESS_V2_ASIO_NAMESPACE;
|
||||
single_process_exit res;
|
||||
|
||||
/*// we need to install our handler first, THEN check, THEN
|
||||
using allocator_type = associated_allocator_t<typename std::decay<Handler::type>;
|
||||
allocator_type allocator = get_associated_allocator(handler);
|
||||
*/
|
||||
printf("FOOBAR %d %p\n", __LINE__, this_);
|
||||
if (ec)
|
||||
return self.complete(ec, res);
|
||||
auto exec = self.get_executor();
|
||||
printf("FOOBAR %d %p\n", __LINE__, this_);
|
||||
if (job_object_is_empty(this_->job_object_.get(), ec))
|
||||
return self.complete(BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe, res);
|
||||
printf("FOOBAR %d %p\n", __LINE__, this_);
|
||||
|
||||
//check if done
|
||||
if (this->poll_one(ec, res))
|
||||
return self.complete(ec, res);
|
||||
else
|
||||
this_->port_.async_wait(std::move(self));
|
||||
printf("FOOBAR %d %p\n", __LINE__, this_);
|
||||
}
|
||||
|
||||
bool poll_one(error_code & ec, single_process_exit & se)
|
||||
{
|
||||
DWORD completion_code;
|
||||
ULONG_PTR completion_key;
|
||||
LPOVERLAPPED overlapped;
|
||||
auto res = GetQueuedCompletionStatus(
|
||||
this_->port_.native_handle(),
|
||||
&completion_code,
|
||||
&completion_key,
|
||||
&overlapped,
|
||||
0u);
|
||||
|
||||
if (!res && ::GetLastError() == ERROR_ABANDONED_WAIT_0)
|
||||
return false;
|
||||
|
||||
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
|
||||
{
|
||||
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
|
||||
return true;
|
||||
}
|
||||
else if (completion_code == JOB_OBJECT_MSG_EXIT_PROCESS
|
||||
|| completion_code == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
|
||||
{
|
||||
se.pid = *reinterpret_cast<DWORD*>(&overlapped);
|
||||
const auto p = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, se.pid);
|
||||
|
||||
if (p == INVALID_HANDLE_VALUE || p == nullptr)
|
||||
{
|
||||
ec = detail::get_last_error();
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD code;
|
||||
res = ::GetExitCodeProcess(p, &code);
|
||||
se.exit_code = static_cast<int>(code);
|
||||
return true;
|
||||
}
|
||||
else if (job_object_is_empty(this_->job_object_.get(), ec))
|
||||
{
|
||||
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct wait_all_op
|
||||
{
|
||||
basic_group_impl * this_;
|
||||
template<typename Self>
|
||||
void operator()(Self && self)
|
||||
{
|
||||
/*
|
||||
using namespace BOOST_PROCESS_V2_ASIO_NAMESPACE;
|
||||
using allocator_type = associated_allocator_t<typename std::decay<Handler::type>;
|
||||
allocator_type allocator = get_associated_allocator(handler);
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
extern template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
|
||||
#include <boost/process/v2/detail/impl/group_impl_windows.ipp>
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_GROUP_IMPL_HPP
|
||||
@@ -1,79 +0,0 @@
|
||||
//
|
||||
// boost/process/v2/windows/impl/job_object_service.ipp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP
|
||||
#define BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/detail/last_error.hpp>
|
||||
#include <boost/process/v2/detail/group_impl_windows.hpp>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
namespace detail
|
||||
{
|
||||
|
||||
bool job_object_is_empty(HANDLE job_object, error_code & ec)
|
||||
{
|
||||
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info;
|
||||
if (!QueryInformationJobObject(job_object,
|
||||
JobObjectBasicAccountingInformation,
|
||||
&info, sizeof(info), nullptr))
|
||||
ec = detail::get_last_error();
|
||||
return info.ActiveProcesses == 0u;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>;
|
||||
#endif
|
||||
|
||||
|
||||
void basic_group_impl_base::add(DWORD pid, HANDLE handle, error_code & ec)
|
||||
{
|
||||
if (!AssignProcessToJobObject(job_object_.get(), handle))
|
||||
ec = detail::get_last_error();
|
||||
}
|
||||
|
||||
void basic_group_impl_base::terminate(error_code & ec)
|
||||
{
|
||||
if (!::TerminateJobObject(job_object_.get(), 255u))
|
||||
ec = detail::get_last_error();
|
||||
}
|
||||
|
||||
|
||||
bool basic_group_impl_base::contains(DWORD pid, error_code & ec)
|
||||
{
|
||||
BOOL res = FALSE;
|
||||
//
|
||||
struct del
|
||||
{
|
||||
void operator()(HANDLE h)
|
||||
{
|
||||
if (h != nullptr && h != INVALID_HANDLE_VALUE)
|
||||
::CloseHandle(h);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<void, del> proc{::OpenProcess(PROCESS_QUERY_INFORMATION , FALSE, pid)};
|
||||
|
||||
if (proc.get() != INVALID_HANDLE_VALUE &&
|
||||
!IsProcessInJob(proc.get(), job_object_.get(), &res))
|
||||
ec = detail::get_last_error();
|
||||
|
||||
return res == TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <boost/process/v2/detail/last_error.hpp>
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
@@ -153,9 +153,12 @@ struct basic_process_handle_fd_or_signal
|
||||
|
||||
void wait(native_exit_code_type &exit_status, error_code &ec)
|
||||
{
|
||||
|
||||
if (pid_ <= 0)
|
||||
return;
|
||||
while (::waitpid(pid_, &exit_status, 0) < 0)
|
||||
|
||||
int res = 0;
|
||||
while ((res = ::waitpid(pid_, &exit_status, 0)) < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
@@ -163,7 +166,6 @@ struct basic_process_handle_fd_or_signal
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void wait(native_exit_code_type &exit_status)
|
||||
@@ -286,31 +288,37 @@ struct basic_process_handle_fd_or_signal
|
||||
|
||||
struct async_wait_op_
|
||||
{
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_descriptor<Executor> &descriptor;
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_stream_descriptor<Executor> &descriptor;
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set<Executor> &handle;
|
||||
pid_type pid_;
|
||||
bool needs_post = true;
|
||||
|
||||
template<typename Self>
|
||||
void operator()(Self &&self, error_code ec = {}, int = 0)
|
||||
void operator()(Self &&self, error_code ec = {}, int res = 0)
|
||||
{
|
||||
native_exit_code_type exit_code{};
|
||||
printf("RES : %d -> %s\n", res, ec.message().c_str());
|
||||
native_exit_code_type exit_code = -1;
|
||||
int wait_res = -1;
|
||||
if (pid_ <= 0) // error, complete early
|
||||
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::bad_descriptor;
|
||||
else
|
||||
{
|
||||
printf("test in %d\n", errno);
|
||||
wait_res = ::waitpid(pid_, &exit_code, WNOHANG);
|
||||
if (wait_res == -1)
|
||||
ec = get_last_error();
|
||||
else
|
||||
ec.clear();
|
||||
|
||||
}
|
||||
|
||||
if (!ec && (wait_res == 0))
|
||||
{
|
||||
needs_post = false;
|
||||
static int res[1] = {0};
|
||||
if (descriptor.is_open())
|
||||
descriptor.async_wait(
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::descriptor_base::wait_read,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::stream_descriptor::wait_read,
|
||||
std::move(self));
|
||||
else
|
||||
handle.async_wait(std::move(self));
|
||||
|
||||
@@ -12,7 +12,15 @@
|
||||
#define BOOST_PROCESS_V2_EXIT_CODE_HPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/pid.hpp>
|
||||
#include <boost/process/v2/error.hpp>
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
#include <asio/associator.hpp>
|
||||
#include <asio/async_result.hpp>
|
||||
#else
|
||||
#include <boost/asio/associator.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_POSIX)
|
||||
#include <sys/wait.h>
|
||||
@@ -20,15 +28,6 @@
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
/// Result of a single process exiting.
|
||||
struct single_process_exit
|
||||
{
|
||||
/// The pid of the process that exited
|
||||
pid_type pid;
|
||||
/// The exit code of the code
|
||||
int exit_code;
|
||||
};
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// The native exit-code type, usually an integral value
|
||||
@@ -95,6 +94,157 @@ inline int evaluate_exit_code(int code)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/** Convert the exit-code in a completion into an error if the actual error isn't set.
|
||||
* @code {.cpp}
|
||||
* process proc{ctx, "exit", {"1"}};
|
||||
*
|
||||
* proc.async_wait(code_as_error(
|
||||
* [](error_code ec)
|
||||
* {
|
||||
* assert(ec.value() == 10);
|
||||
* assert(ec.category() == error::get_exit_code_category());
|
||||
* }));
|
||||
*
|
||||
* @endcode
|
||||
*/
|
||||
template<typename CompletionToken>
|
||||
struct code_as_error_t
|
||||
{
|
||||
CompletionToken token_;
|
||||
const error_category & category;
|
||||
|
||||
template<typename Token_>
|
||||
code_as_error_t(Token_ && token, const error_category & category)
|
||||
: token_(std::forward<Token_>(token)), category(category)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Deduction function for code_as_error_t.
|
||||
template<typename CompletionToken>
|
||||
code_as_error_t<CompletionToken> code_as_error(
|
||||
CompletionToken && token,
|
||||
const error_category & category = error::get_exit_code_category())
|
||||
{
|
||||
return code_as_error_t<typename std::decay<CompletionToken>::type>(
|
||||
std::forward<CompletionToken>(token), category);
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename Handler>
|
||||
struct code_as_error_handler
|
||||
{
|
||||
typedef void result_type;
|
||||
|
||||
template<typename H>
|
||||
code_as_error_handler(H && h, const error_category & category)
|
||||
: handler_(std::forward<H>(h)), category(category)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(error_code ec, native_exit_code_type code)
|
||||
{
|
||||
if (!ec)
|
||||
ec.assign(code, category);
|
||||
std::move(handler_)(ec);
|
||||
}
|
||||
|
||||
|
||||
Handler handler_;
|
||||
const error_category & category;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
namespace boost
|
||||
{
|
||||
#endif
|
||||
namespace asio
|
||||
{
|
||||
|
||||
template <typename CompletionToken>
|
||||
struct async_result<
|
||||
BOOST_PROCESS_V2_NAMESPACE::code_as_error_t<CompletionToken>,
|
||||
void(BOOST_PROCESS_V2_NAMESPACE::error_code,
|
||||
BOOST_PROCESS_V2_NAMESPACE::native_exit_code_type)>
|
||||
{
|
||||
using signature = void(BOOST_PROCESS_V2_NAMESPACE::error_code);
|
||||
using return_type = typename async_result<CompletionToken, void(BOOST_PROCESS_V2_NAMESPACE::error_code)>::return_type;
|
||||
|
||||
|
||||
template <typename Initiation>
|
||||
struct init_wrapper
|
||||
{
|
||||
init_wrapper(Initiation init)
|
||||
: initiation_(std::move(init))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Handler, typename... Args>
|
||||
void operator()(
|
||||
Handler && handler,
|
||||
const BOOST_PROCESS_V2_NAMESPACE::error_category & cat,
|
||||
Args && ... args)
|
||||
{
|
||||
std::move(initiation_)(
|
||||
BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<typename decay<Handler>::type>(
|
||||
std::forward<Handler>(handler), cat),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
Initiation initiation_;
|
||||
|
||||
};
|
||||
|
||||
template <typename Initiation, typename RawCompletionToken, typename... Args>
|
||||
static BOOST_PROCESS_V2_INITFN_DEDUCED_RESULT_TYPE(CompletionToken, signature,
|
||||
(async_initiate<CompletionToken, signature>(
|
||||
declval<init_wrapper<typename decay<Initiation>::type> >(),
|
||||
declval<CompletionToken&>(),
|
||||
declval<BOOST_ASIO_MOVE_ARG(Args)>()...)))
|
||||
initiate(
|
||||
Initiation && initiation,
|
||||
RawCompletionToken && token,
|
||||
Args &&... args)
|
||||
{
|
||||
return async_initiate<CompletionToken, signature>(
|
||||
init_wrapper<typename decay<Initiation>::type>(
|
||||
std::forward<Initiation>(initiation)),
|
||||
token.token_,
|
||||
token.category,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
template<template <typename, typename> class Associator, typename Handler, typename DefaultCandidate>
|
||||
struct associator<Associator,
|
||||
BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<Handler>, DefaultCandidate>
|
||||
: Associator<Handler, DefaultCandidate>
|
||||
{
|
||||
static typename Associator<Handler, DefaultCandidate>::type get(
|
||||
const BOOST_PROCESS_V2_NAMESPACE::detail::code_as_error_handler<Handler> & h,
|
||||
const DefaultCandidate& c = DefaultCandidate()) noexcept
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_, c);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
#if !defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
} // boost
|
||||
#endif
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_EXIT_CODE_HPP
|
||||
@@ -1,321 +0,0 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#ifndef BOOST_PROCESS_V2_GROUP_HPP
|
||||
#define BOOST_PROCESS_V2_GROUP_HPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/default_launcher.hpp>
|
||||
#include <boost/process/v2/exit_code.hpp>
|
||||
#include <boost/process/v2/process_handle.hpp>
|
||||
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
#include <boost/process/v2/detail/group_impl_windows.hpp>
|
||||
#else
|
||||
#include <boost/process/v2/detail/group_impl_posix.hpp>
|
||||
#endif
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
/** A process handle is an unmanaged version of a process.
|
||||
* This means it does not terminate the proces on destruction and
|
||||
* will not keep track of the exit-code.
|
||||
*
|
||||
* Note that the exit code might be discovered early, during a call to `running`.
|
||||
* Thus it can only be discovered that process has exited already.
|
||||
*/
|
||||
template<typename Executor = BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor,
|
||||
typename Launcher = default_process_launcher>
|
||||
struct basic_group
|
||||
{
|
||||
/// The native handle of the process.
|
||||
/** This might be undefined on posix systems that only support signals */
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
using native_handle_type = implementation_defined;
|
||||
#else
|
||||
using native_handle_type = typename detail::basic_group_impl<Executor>::native_handle_type;
|
||||
#endif
|
||||
/// The executor_type of the group
|
||||
using executor_type = Executor;
|
||||
|
||||
/// The launcher used by the group for emplacing
|
||||
using launcher_type = Launcher;
|
||||
|
||||
// Get the native group handle
|
||||
native_handle_type native_handle() {return impl_.native_handle();}
|
||||
|
||||
/// Getter for the executor
|
||||
executor_type get_executor() { return impl_.get_executor();}
|
||||
|
||||
/// Rebinds the group to another executor.
|
||||
template<typename Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The socket type when rebound to the specified executor.
|
||||
typedef basic_group<Executor1, Launcher> other;
|
||||
};
|
||||
|
||||
|
||||
/// Construct a basic_group from an execution_context.
|
||||
/**
|
||||
* @tparam ExecutionContext The context must fulfill the asio::execution_context requirements
|
||||
*/
|
||||
template<typename ExecutionContext>
|
||||
basic_group(ExecutionContext &context,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
launcher_type>::type launcher = launcher_type())
|
||||
: basic_group(context.get_executor(), std::move(launcher))
|
||||
{}
|
||||
|
||||
/// Construct an empty group from an executor.
|
||||
basic_group(executor_type executor, launcher_type launcher = launcher_type())
|
||||
: impl_(std::move(executor)), launcher_(std::move(launcher)) {}
|
||||
|
||||
/// Construct an empty group from an executor and bind it to a pid and the native-handle
|
||||
/** On some non-linux posix systems this overload is not present.
|
||||
*/
|
||||
basic_group(executor_type executor, native_handle_type group,
|
||||
launcher_type launcher = launcher_type())
|
||||
: impl_(std::move(executor), group), , launcher_(std::move(launcher)) {}
|
||||
|
||||
/// Move construct and rebind the executor.
|
||||
template<typename Executor1>
|
||||
basic_group(basic_group<Executor1> &&handle)
|
||||
: procs_(std::move(handle.procs_)), impl_(std::move(handle.impl_)),
|
||||
launcher_(std::move(handle.launcher_)) {}
|
||||
|
||||
/// wait for one process to exit and store the exit code in exit_status.
|
||||
single_process_exit wait_one(error_code &ec)
|
||||
{
|
||||
single_process_exit res;
|
||||
impl_.wait_one(res.pid, res.exit_code, ec);
|
||||
return res;
|
||||
}
|
||||
/// Throwing @overload wait_one(error_code & ec)
|
||||
single_process_exit wait_one()
|
||||
{
|
||||
error_code ec;
|
||||
auto res = wait_one(ec);
|
||||
detail::throw_error(ec, "wait_one");
|
||||
return res;
|
||||
}
|
||||
|
||||
/// wait for all processes to exit. Returns the number of processes exited
|
||||
void wait_all(error_code &ec)
|
||||
{
|
||||
impl_.wait_all(ec);
|
||||
}
|
||||
/// Throwing @overload wait_all(error_code & ec)
|
||||
void wait_all()
|
||||
{
|
||||
error_code ec;
|
||||
wait_all(ec);
|
||||
detail::throw_error(ec, "wait_all");
|
||||
}
|
||||
|
||||
/// Sends the processes a signal to ask for an interrupt, which the process may interpret as a shutdown.
|
||||
/** Maybe be ignored by the subprocess. */
|
||||
void interrupt(error_code &ec)
|
||||
{
|
||||
impl_.interrupt(ec);
|
||||
}
|
||||
|
||||
/// Throwing @overload void interrupt()
|
||||
void interrupt()
|
||||
{
|
||||
error_code ec;
|
||||
interrupt(ec);
|
||||
detail::throw_error(ec, "interrupt");
|
||||
}
|
||||
|
||||
/// Sends the processes a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
|
||||
void request_exit(error_code &ec)
|
||||
{
|
||||
impl_.request_exit(ec);
|
||||
}
|
||||
|
||||
/// Throwing @overload void request_exit(error_code & ec)
|
||||
void request_exit()
|
||||
{
|
||||
error_code ec;
|
||||
auto res = request_exit(ec);
|
||||
detail::throw_error(ec, "request_exit");
|
||||
}
|
||||
|
||||
/// Unconditionally terminates the processes and stores the exit code in exit_status.
|
||||
void terminate(error_code &ec)
|
||||
{
|
||||
impl_.terminate(ec);
|
||||
}
|
||||
/// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
|
||||
void terminate()
|
||||
{
|
||||
error_code ec;
|
||||
terminate(ec);
|
||||
detail::throw_error(ec, "terminate");
|
||||
}
|
||||
|
||||
/// Check if the process handle is referring to an existing process.
|
||||
bool is_open() const
|
||||
{
|
||||
return impl_.is_open();
|
||||
}
|
||||
|
||||
/// Asynchronously wait for one process to exit and deliver the exit-code in the completion handler.
|
||||
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, single_process_exit))
|
||||
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, single_process_exit))
|
||||
async_wait_one(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
|
||||
{
|
||||
return impl_.async_wait_one(std::forward<WaitHandler>(handler));
|
||||
}
|
||||
|
||||
/// Asynchronously wait for all processes to exit and the gorup to be empty.
|
||||
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code))
|
||||
async_wait_all(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
|
||||
{
|
||||
return impl_.async_wait_all(std::forward<WaitHandler>(handler));
|
||||
}
|
||||
/// Throwing overload of @overload contains(pid_type pid, error_code & ec)
|
||||
bool contains(pid_type pid)
|
||||
{
|
||||
error_code ec;
|
||||
auto res = contains(ec);
|
||||
detail::throw_error(ec, "contains");
|
||||
return res;
|
||||
}
|
||||
/// Check if the pid is in the group
|
||||
bool contains(pid_type pid, error_code & ec)
|
||||
{
|
||||
impl_.contains(std::move(proc), ec);
|
||||
}
|
||||
|
||||
template<typename Executor_>
|
||||
void add(basic_process_handle<Executor_> proc)
|
||||
{
|
||||
error_code ec;
|
||||
add(proc.id(), proc.native_handle(), ec);
|
||||
detail::throw_error(ec, "add");
|
||||
}
|
||||
|
||||
template<typename Executor_>
|
||||
void add(basic_process_handle<Executor_> proc, error_code & ec)
|
||||
{
|
||||
impl_.add(std::move(proc), ec);
|
||||
}
|
||||
|
||||
template<typename ... Inits>
|
||||
pid_type emplace(
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<string_view> args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
error_code ec;
|
||||
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
detail::throw_error(ec);
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
template<typename ... Inits>
|
||||
pid_type emplace(
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<wstring_view> args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
error_code ec;
|
||||
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
detail::throw_error(ec);
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
template<typename Args, typename ... Inits>
|
||||
pid_type emplace(
|
||||
const filesystem::path& exe,
|
||||
Args && args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
error_code ec;
|
||||
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
detail::throw_error(ec);
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
template<typename ... Inits>
|
||||
pid_type emplace(
|
||||
error_code & ec,
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<string_view> args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
return pid_type{-1};
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
template<typename ... Inits>
|
||||
pid_type emplace(
|
||||
error_code & ec,
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<wstring_view> args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
return pid_type{-1};
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
template<typename Args, typename ... Inits>
|
||||
pid_type emplace(
|
||||
error_code & ec,
|
||||
const filesystem::path& exe,
|
||||
Args && args,
|
||||
Inits&&... inits)
|
||||
{
|
||||
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
|
||||
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
|
||||
if (ec)
|
||||
return pid_type{-1};
|
||||
return ph.detach().id();
|
||||
}
|
||||
|
||||
void detach() { impl_.detach(); }
|
||||
private:
|
||||
detail::basic_group_impl<Executor> impl_;
|
||||
launcher_type launcher_;
|
||||
};
|
||||
|
||||
/// Process group with the default executor.
|
||||
using group = basic_group<>;
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
extern template struct basic_group<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor, default_process_launcher>;
|
||||
#endif
|
||||
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
|
||||
#include <boost/process/v2/impl/group.ipp>
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_GROUP_HPP
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// boost/process/v2/windows/impl/job_object_service.ipp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#ifndef BOOST_PROCESS_V2_IMPL_GROUP_IPP
|
||||
#define BOOST_PROCESS_V2_IMPL_GROUP_IPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/group.hpp>
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
|
||||
template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor, default_process_launcher>;
|
||||
#endif
|
||||
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_IMPL_GROUP_IPP
|
||||
@@ -74,8 +74,6 @@ struct basic_popen : basic_process<Executor>
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename ... Inits>
|
||||
explicit basic_popen(
|
||||
@@ -85,30 +83,66 @@ struct basic_popen : basic_process<Executor>
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
*static_cast<basic_process<Executor>*>(this) =
|
||||
this->basic_process<Executor>::operator=(
|
||||
default_process_launcher()(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename Launcher, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
Launcher && launcher,
|
||||
executor_type executor,
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<string_view> args,
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
this->basic_process<Executor>::operator=(
|
||||
std::forward<Launcher>(launcher)(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename ... Inits>
|
||||
explicit basic_popen(
|
||||
executor_type executor,
|
||||
const filesystem::path& exe,
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<wstring_view> args,
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
*static_cast<basic_process<Executor>*>(this) =
|
||||
this->basic_process<Executor>::operator=(
|
||||
default_process_launcher()(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename Launcher, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
Launcher && launcher,
|
||||
executor_type executor,
|
||||
const filesystem::path& exe,
|
||||
std::initializer_list<wstring_view> args,
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
this->basic_process<Executor>::operator=(
|
||||
std::forward<Launcher>(launcher)(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
@@ -119,53 +153,112 @@ struct basic_popen : basic_process<Executor>
|
||||
Args&& args, Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
*static_cast<basic_process<Executor>*>(this) =
|
||||
this->basic_process<Executor>::operator=(
|
||||
default_process_launcher()(
|
||||
std::move(executor), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename Launcher, typename Args, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
Launcher && launcher,
|
||||
executor_type executor,
|
||||
const filesystem::path& exe,
|
||||
Args&& args, Inits&&... inits)
|
||||
: basic_process<Executor>(executor)
|
||||
{
|
||||
this->basic_process<Executor>::operator=(
|
||||
std::forward<Launcher>(launcher)(
|
||||
std::move(executor), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename ExecutionContext, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
ExecutionContext & context,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
const filesystem::path&>::type exe,
|
||||
std::initializer_list<string_view> args,
|
||||
Inits&&... inits)
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(context)
|
||||
{
|
||||
*static_cast<basic_process<Executor>*>(this) =
|
||||
this->basic_process<Executor>::operator=(
|
||||
default_process_launcher()(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename Launcher, typename ExecutionContext, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
Launcher && launcher,
|
||||
ExecutionContext & context,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
const filesystem::path&>::type exe,
|
||||
std::initializer_list<string_view> args,
|
||||
Inits&&... inits)
|
||||
: basic_process<Executor>(context)
|
||||
{
|
||||
this->basic_process<Executor>::operator=(
|
||||
std::forward<Launcher>(launcher)(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename ExecutionContext, typename Args, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
ExecutionContext & context,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
const filesystem::path&>::type exe,
|
||||
Args&& args, Inits&&... inits)
|
||||
: basic_process<Executor>(context)
|
||||
{
|
||||
*static_cast<basic_process<Executor>*>(this) =
|
||||
this->basic_process<Executor>::operator=(
|
||||
default_process_launcher()(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/// Construct a child from a property list and launch it using the default process launcher.
|
||||
template<typename Launcher, typename ExecutionContext, typename Args, typename ... Inits>
|
||||
explicit basic_popen(
|
||||
Launcher && launcher,
|
||||
ExecutionContext & context,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<ExecutionContext&,
|
||||
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
|
||||
const filesystem::path&>::type exe,
|
||||
Args&& args, Inits&&... inits)
|
||||
: basic_process<Executor>(context)
|
||||
{
|
||||
this->basic_process<Executor>::operator=(
|
||||
std::forward<Launcher>(launcher)(
|
||||
this->get_executor(), exe, args,
|
||||
std::forward<Inits>(inits)...,
|
||||
process_stdio{stdin_, stdout_}
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/// The type used for stdin on the parent process side.
|
||||
using stdin_type = BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor>;
|
||||
/// The type used for stdout on the parent process side.
|
||||
|
||||
@@ -24,8 +24,4 @@
|
||||
#include <boost/process/v2/impl/process_handle.ipp>
|
||||
#include <boost/process/v2/impl/shell.ipp>
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
#include <boost/process/v2/detail/impl/group_impl_windows.ipp>
|
||||
#endif
|
||||
|
||||
#endif //BOOST_PROCESS_V2_SRC_HPP
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
#ifndef BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
|
||||
#define BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
|
||||
#include <boost/process/v2/cstring_ref.hpp>
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/detail/last_error.hpp>
|
||||
#include <boost/process/v2/detail/utf8.hpp>
|
||||
#include <boost/process/v2/error.hpp>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// boost/process/v2/windows/impl/job_object_service.ipp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#ifndef BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP
|
||||
#define BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP
|
||||
|
||||
#include <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/windows/job_object_service.hpp>
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
namespace windows
|
||||
{
|
||||
}
|
||||
BOOST_PROCESS_V2_END_NAMESPACE
|
||||
|
||||
|
||||
#endif //BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP
|
||||
@@ -40,12 +40,19 @@ BOOST_AUTO_TEST_CASE(leak_test, *boost::unit_test::timeout(5))
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
|
||||
|
||||
|
||||
#if defined(BOOST_WINDOWS_API)
|
||||
const auto get_handle = [](FILE * f) {return reinterpret_cast<bt::native_handle_type>(_get_osfhandle(_fileno(f)));};
|
||||
const auto socket_to_handle = [](::boost::winapi::UINT_PTR_ sock){return reinterpret_cast<::boost::winapi::HANDLE_>(sock);};
|
||||
#else
|
||||
const auto get_handle = [](FILE * f) {return fileno(f);};
|
||||
const auto socket_to_handle = [](int i){ return i;};
|
||||
|
||||
#if !defined(__linux__)
|
||||
return ;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
@@ -33,5 +33,4 @@ function(boost_process_v2_test_with_target name)
|
||||
-- $<TARGET_FILE:boost_process_v2_test_target>)
|
||||
endfunction()
|
||||
|
||||
boost_process_v2_test_with_target(process)
|
||||
boost_process_v2_test_with_target(group)
|
||||
boost_process_v2_test_with_target(process)
|
||||
@@ -59,6 +59,5 @@ test-suite standalone :
|
||||
test-suite with_target :
|
||||
[ run process.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
|
||||
[ run windows.cpp test_impl : --log_level=all --catch_system_errors=no -- : target : <build>no <target-os>windows:<build>yes <target-os>windows:<source>Advapi32 ]
|
||||
[ run group.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
|
||||
;
|
||||
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Copyright (c) 2022 Klemens D. Morgenstern
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
|
||||
// Disable autolinking for unit tests.
|
||||
#if !defined(BOOST_ALL_NO_LIB)
|
||||
#define BOOST_ALL_NO_LIB 1
|
||||
#endif // !defined(BOOST_ALL_NO_LIB)
|
||||
|
||||
// Test that header file is self-contained.
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/process/v2/group.hpp>
|
||||
#include <boost/process/v2.hpp>
|
||||
|
||||
namespace bp2 = boost::process::v2;
|
||||
namespace bpw = boost::process::v2::windows;
|
||||
namespace bpd = boost::process::v2::detail;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(wait_one)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
asio::io_context ctx;
|
||||
const auto pth = master_test_suite().argv[1];
|
||||
|
||||
bp2::error_code ec;
|
||||
bp2::group grp{ctx};
|
||||
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
|
||||
auto pid1 = grp.emplace(pth, {"sleep", "100"});
|
||||
auto pid2 = grp.emplace(pth, {"sleep", "300"});
|
||||
auto pid3 = grp.emplace(pth, {"sleep", "500"});
|
||||
|
||||
auto res1 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
auto res2 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
auto res3 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
|
||||
|
||||
grp.wait_all(); //
|
||||
|
||||
BOOST_CHECK_EQUAL(res1.exit_code, 0);
|
||||
BOOST_CHECK_EQUAL(res2.exit_code, 0);
|
||||
BOOST_CHECK_EQUAL(res3.exit_code, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(res1.pid, pid1);
|
||||
BOOST_CHECK_EQUAL(res2.pid, pid2);
|
||||
BOOST_CHECK_EQUAL(res3.pid, pid3);
|
||||
|
||||
BOOST_CHECK(grp.is_open());
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(wait_all)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
asio::io_context ctx;
|
||||
const auto pth = master_test_suite().argv[1];
|
||||
|
||||
bp2::group grp{ctx};
|
||||
|
||||
|
||||
auto pid1 = grp.emplace(pth, {"sleep", "10"});
|
||||
auto pid2 = grp.emplace(pth, {"sleep", "30"});
|
||||
auto pid3 = grp.emplace(pth, {"sleep", "50"});
|
||||
|
||||
grp.wait_all(); //
|
||||
|
||||
BOOST_CHECK(grp.is_open());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(terminate_)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
asio::io_context ctx;
|
||||
const auto pth = master_test_suite().argv[1];
|
||||
|
||||
bp2::error_code ec;
|
||||
bp2::group grp{ctx};
|
||||
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
|
||||
const auto pid1 = grp.emplace(pth, {"sleep", "10000000"});
|
||||
const auto pid2 = grp.emplace(pth, {"sleep", "10000000"});
|
||||
const auto pid3 = grp.emplace(pth, {"sleep", "10000000"});
|
||||
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
|
||||
grp.terminate();
|
||||
grp.wait_all();
|
||||
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
BOOST_CHECK((start + std::chrono::milliseconds(5000)) > end);
|
||||
|
||||
BOOST_CHECK(grp.is_open());
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(async_wait_one)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
asio::io_context ctx;
|
||||
const auto pth = master_test_suite().argv[1];
|
||||
|
||||
bp2::error_code ec;
|
||||
bp2::group grp{ctx};
|
||||
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
|
||||
auto pid1 = grp.emplace(pth, {"sleep", "100"});
|
||||
auto pid2 = grp.emplace(pth, {"sleep", "300"});
|
||||
auto pid3 = grp.emplace(pth, {"sleep", "500"});
|
||||
|
||||
std::vector<bp2::pid_type> res;
|
||||
|
||||
grp.async_wait_one(
|
||||
[&](bp2::error_code ec, bp2::single_process_exit sp)
|
||||
{
|
||||
res.push_back(sp.pid);
|
||||
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
});
|
||||
grp.async_wait_one(
|
||||
[&](bp2::error_code ec, bp2::single_process_exit sp)
|
||||
{
|
||||
res.push_back(sp.pid);
|
||||
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
});
|
||||
grp.async_wait_one(
|
||||
[&](bp2::error_code ec, bp2::single_process_exit sp)
|
||||
{
|
||||
res.push_back(sp.pid);
|
||||
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
});
|
||||
|
||||
BOOST_CHECK_GE(ctx.run(), 0u);
|
||||
|
||||
BOOST_CHECK_EQUAL(res.at(0), pid1);
|
||||
BOOST_CHECK_EQUAL(res.at(1), pid2);
|
||||
BOOST_CHECK_EQUAL(res.at(2), pid3);
|
||||
|
||||
|
||||
BOOST_CHECK(grp.is_open());
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(async_wait_all)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
asio::io_context ctx;
|
||||
const auto pth = master_test_suite().argv[1];
|
||||
|
||||
bp2::error_code ec;
|
||||
bp2::group grp{ctx};
|
||||
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
|
||||
auto pid1 = grp.emplace(pth, {"sleep", "10"});
|
||||
auto pid2 = grp.emplace(pth, {"sleep", "30"});
|
||||
auto pid3 = grp.emplace(pth, {"sleep", "50"});
|
||||
|
||||
grp.async_wait_all(
|
||||
[](bp2::error_code ec)
|
||||
{
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
});
|
||||
|
||||
ctx.run();
|
||||
|
||||
|
||||
BOOST_CHECK(grp.is_open());
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <boost/process/v2/start_dir.hpp>
|
||||
#include <boost/process/v2/execute.hpp>
|
||||
#include <boost/process/v2/stdio.hpp>
|
||||
#include <boost/process/v2/bind_launcher.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
@@ -159,7 +160,12 @@ BOOST_AUTO_TEST_CASE(request_exit)
|
||||
|
||||
auto sh = closable();
|
||||
BOOST_CHECK_MESSAGE(!sh.empty(), sh);
|
||||
bpv::process proc(ctx, sh, {}
|
||||
|
||||
asio::readable_pipe rp{ctx};
|
||||
asio::writable_pipe wp{ctx};
|
||||
asio::connect_pipe(rp, wp);
|
||||
|
||||
bpv::process proc(ctx, sh, {}, bpv::process_stdio{wp}
|
||||
#if defined(ASIO_WINDOWS)
|
||||
, asio::windows::show_window_minimized_not_active
|
||||
#endif
|
||||
@@ -357,16 +363,16 @@ BOOST_AUTO_TEST_CASE(popen)
|
||||
|
||||
|
||||
// default CWD
|
||||
bpv::popen proc(ctx, pth, {"echo"});
|
||||
bpv::popen proc(/*bpv::default_process_launcher(), */ctx, pth, {"echo"});
|
||||
|
||||
auto written = asio::write(proc, asio::buffer("FOOBAR"));
|
||||
asio::write(proc, asio::buffer("FOOBAR"));
|
||||
proc.get_stdin().close();
|
||||
|
||||
std::string res;
|
||||
boost::system::error_code ec;
|
||||
std::size_t n = asio::read(proc, asio::dynamic_buffer(res), ec);
|
||||
BOOST_CHECK(ec == asio::error::eof || ec == asio::error::broken_pipe);
|
||||
BOOST_REQUIRE_GE(n, 1);
|
||||
BOOST_CHECK_MESSAGE(ec == asio::error::eof || ec == asio::error::broken_pipe, ec.message());
|
||||
BOOST_REQUIRE_GE(n, 1u);
|
||||
// remove EOF
|
||||
res.pop_back();
|
||||
BOOST_CHECK_EQUAL(res, "FOOBAR");
|
||||
@@ -473,5 +479,62 @@ BOOST_AUTO_TEST_CASE(environment)
|
||||
BOOST_CHECK_EQUAL(read_env("PATH", bpv::process_environment(bpv::environment::current())), ::getenv("PATH"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exit_code_as_error)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]);
|
||||
|
||||
asio::io_context ctx;
|
||||
|
||||
bpv::process proc1(ctx, pth, {"exit-code", "0"});
|
||||
bpv::process proc2(ctx, pth, {"exit-code", "2"});
|
||||
bpv::process proc3(ctx, pth, {"sleep", "2000"});
|
||||
|
||||
int called = 0;
|
||||
|
||||
proc3.terminate();
|
||||
|
||||
proc1.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK(!ec);}));
|
||||
proc2.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK_MESSAGE(ec, ec.message());}));
|
||||
proc3.async_wait(bpv::code_as_error([&](bpv::error_code ec){called ++; BOOST_CHECK_MESSAGE(ec, ec.message());}));
|
||||
|
||||
ctx.run();
|
||||
BOOST_CHECK_EQUAL(called, 3);
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bind_launcher)
|
||||
{
|
||||
using boost::unit_test::framework::master_test_suite;
|
||||
const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]);
|
||||
|
||||
asio::io_context ctx;
|
||||
|
||||
asio::readable_pipe rp{ctx};
|
||||
asio::writable_pipe wp{ctx};
|
||||
asio::connect_pipe(rp, wp);
|
||||
|
||||
auto target = bpv::filesystem::canonical(bpv::filesystem::temp_directory_path());
|
||||
|
||||
auto l = bpv::bind_default_launcher(bpv::process_start_dir(target));
|
||||
|
||||
std::vector<std::string> args = {"print-cwd"};
|
||||
// default CWD
|
||||
bpv::process proc = l(ctx, pth, args, bpv::process_stdio{/*.in=*/{}, /*.out=*/wp});
|
||||
wp.close();
|
||||
|
||||
std::string out;
|
||||
bpv::error_code ec;
|
||||
|
||||
auto sz = asio::read(rp, asio::dynamic_buffer(out), ec);
|
||||
BOOST_CHECK(sz != 0);
|
||||
BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message());
|
||||
BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == target,
|
||||
bpv::filesystem::path(out) << " != " << target);
|
||||
|
||||
proc.wait();
|
||||
BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code() << " from " << proc.native_exit_code());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END();
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ BOOST_AUTO_TEST_CASE(test_shell_parser)
|
||||
BOOST_CHECK(sh.argv()[2] == STR_VIEW("foo bar"));
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_POSIX)
|
||||
auto raw_shell = "sh -c false";
|
||||
auto raw_shell = "sh -c true";
|
||||
#else
|
||||
auto raw_shell = "cmd /c exit 1";
|
||||
auto raw_shell = "cmd /c exit 0";
|
||||
#endif
|
||||
sh = shell(raw_shell);
|
||||
|
||||
@@ -52,5 +52,5 @@ BOOST_AUTO_TEST_CASE(test_shell_parser)
|
||||
bpv::process proc{ctx, exe, sh.args()};
|
||||
|
||||
proc.wait();
|
||||
BOOST_CHECK_EQUAL(proc.exit_code(), 1);
|
||||
BOOST_CHECK_EQUAL(proc.exit_code(), 0);
|
||||
}
|
||||
Reference in New Issue
Block a user