Enable basic windows support (#33)

* windows: fix implementation error
- repair configure_pipe
- allow vector initialization
- add draft destructor
- close pipes correctly

* windows: add some test compatibility

* windows: update readme

* windows: add vector args to check_output
This commit is contained in:
xoviat 2020-01-18 08:31:03 -06:00 committed by Arun Muralidharan
parent 126ee2978f
commit 9c624ce4e3
4 changed files with 64 additions and 43 deletions

View File

@ -13,8 +13,10 @@ This library had these design goals:
## Supported Platforms ## Supported Platforms
Unlike python2.7 subprocess module, this library currently only supports MAC OS and Linux. This library supports MAC OS and Linux.
It has no support for Windows in its current state.
Support for Windows is limited at this time. Please report any specific use-cases that fail,
and they will be fixed as they are reported.
## Integration ## Integration
Subprocess library has just a single source `subprocess.hpp` present at the top directory of this repository. All you need to do is add Subprocess library has just a single source `subprocess.hpp` present at the top directory of this repository. All you need to do is add
@ -34,6 +36,7 @@ Checkout http://templated-thoughts.blogspot.in/2016/03/sub-processing-with-moder
## Compiler Support ## Compiler Support
Linux - g++ 4.8 and above Linux - g++ 4.8 and above
Mac OS - Clang 3.4 and later Mac OS - Clang 3.4 and later
Windows - MSVC 2015 and above
## Examples ## Examples
Here are few examples to show how to get started: Here are few examples to show how to get started:

View File

@ -272,11 +272,11 @@ namespace util
// Create a pipe for the child process's STDIN. // Create a pipe for the child process's STDIN.
if (!CreatePipe(read_handle, write_handle, &saAttr,0)) if (!CreatePipe(read_handle, write_handle, &saAttr,0))
throw OSError("Stdin CreatePipe", 0); throw OSError("CreatePipe", 0);
// Ensure the write handle to the pipe for STDIN is not inherited. // Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(child_handle, HANDLE_FLAG_INHERIT, 0)) if (!SetHandleInformation(*child_handle, HANDLE_FLAG_INHERIT, 0))
throw OSError("Stdin SetHandleInformation", 0); throw OSError("SetHandleInformation", 0);
} }
#endif #endif
@ -1131,6 +1131,26 @@ public:
if (!defer_process_start_) execute_process(); if (!defer_process_start_) execute_process();
} }
template <typename... Args>
Popen(std::vector<std::string> vargs_, Args &&... args) : vargs_(vargs_)
{
init_args(std::forward<Args>(args)...);
// Setup the communication channels of the Popen class
stream_.setup_comm_channels();
if (!defer_process_start_) execute_process();
}
/*
~Popen()
{
#ifdef _MSC_VER
CloseHandle(this->process_handle_);
#endif
}
*/
void start_process() noexcept(false); void start_process() noexcept(false);
int pid() const noexcept { return child_pid_; } int pid() const noexcept { return child_pid_; }
@ -1409,43 +1429,21 @@ inline void Popen::execute_process() noexcept(false)
this->process_handle_ = piProcInfo.hProcess; this->process_handle_ = piProcInfo.hProcess;
try { std::async(std::launch::async, [this] {
char err_buf[SP_MAX_ERR_BUF_SIZ] = {0,}; WaitForSingleObject(this->process_handle_, INFINITE);
int read_bytes = util::read_atmost_n( CloseHandle(this->stream_.g_hChildStd_ERR_Wr);
this->error(), CloseHandle(this->stream_.g_hChildStd_OUT_Wr);
err_buf, CloseHandle(this->stream_.g_hChildStd_IN_Rd);
SP_MAX_ERR_BUF_SIZ); });
fclose(this->error());
if (read_bytes || strlen(err_buf)) {
// Throw whatever information we have about child failure
throw CalledProcessError(err_buf);
}
} catch (std::exception& exp) {
stream_.cleanup_fds();
throw;
}
/* /*
this->hExited_ = NOTE: In the linux version, there is a check to make sure that the process
std::shared_future<int>(std::async(std::launch::async, [this] { has been started. Here, we do nothing because CreateProcess will throw
WaitForSingleObject(this->hProcess_, INFINITE); if we fail to create the process.
CloseHandle(this->stream_.g_hChildStd_ERR_Wr);
CloseHandle(this->stream_.g_hChildStd_OUT_Wr);
CloseHandle(this->stream_.g_hChildStd_IN_Rd);
DWORD exit_code;
if (FALSE == GetExitCodeProcess(this->hProcess_, &exit_code))
throw OSError("GetExitCodeProcess", 0);
CloseHandle(this->hProcess_);
return (int)exit_code;
}));
*/ */
#else #else
int err_rd_pipe, err_wr_pipe; int err_rd_pipe, err_wr_pipe;
@ -1683,7 +1681,7 @@ namespace detail {
inline void Streams::setup_comm_channels() inline void Streams::setup_comm_channels()
{ {
#ifdef _MSC_VER #ifdef _MSC_VER
util::configure_pipe(this->g_hChildStd_IN_Rd, &this->g_hChildStd_IN_Wr, &this->g_hChildStd_IN_Wr); util::configure_pipe(&this->g_hChildStd_IN_Rd, &this->g_hChildStd_IN_Wr, &this->g_hChildStd_IN_Wr);
this->input(util::file_from_handle(this->g_hChildStd_IN_Wr, "w")); this->input(util::file_from_handle(this->g_hChildStd_IN_Wr, "w"));
this->write_to_child_ = _fileno(this->input()); this->write_to_child_ = _fileno(this->input());
@ -1935,6 +1933,12 @@ OutBuffer check_output(const std::string& arg, Args&&... args)
return (detail::check_output_impl(arg, std::forward<Args>(args)...)); return (detail::check_output_impl(arg, std::forward<Args>(args)...));
} }
template <typename... Args>
OutBuffer check_output(std::vector<std::string> plist, Args &&... args)
{
return (detail::check_output_impl(plist, std::forward<Args>(args)...));
}
/*! /*!
* An easy way to pipeline easy commands. * An easy way to pipeline easy commands.

View File

@ -8,7 +8,9 @@ void test_ret_code()
std::cout << "Test::test_poll_ret_code" << std::endl; std::cout << "Test::test_poll_ret_code" << std::endl;
auto p = sp::Popen({"/usr/bin/false"}); auto p = sp::Popen({"/usr/bin/false"});
while (p.poll() == -1) { while (p.poll() == -1) {
#ifndef _MSC_VER
usleep(1000 * 100); usleep(1000 * 100);
#endif
} }
assert (p.retcode() == 1); assert (p.retcode() == 1);
} }

View File

@ -5,7 +5,11 @@ using namespace subprocess;
void test_exename() void test_exename()
{ {
#ifdef _MSC_VER
auto ret = call({"--version"}, executable{"cmake"}, shell{false});
#else
auto ret = call({"-l"}, executable{"ls"}, shell{false}); auto ret = call({"-l"}, executable{"ls"}, shell{false});
#endif
std::cout << ret << std::endl; std::cout << ret << std::endl;
} }
@ -35,7 +39,11 @@ void test_easy_piping()
void test_shell() void test_shell()
{ {
#ifdef _MSC_VER
auto obuf = check_output({"cmake", "--version"}, shell{false});
#else
auto obuf = check_output({"ls", "-l"}, shell{false}); auto obuf = check_output({"ls", "-l"}, shell{false});
#endif
std::cout << obuf.buf.data() << std::endl; std::cout << obuf.buf.data() << std::endl;
} }
@ -43,9 +51,13 @@ void test_sleep()
{ {
auto p = Popen({"sleep", "30"}, shell{true}); auto p = Popen({"sleep", "30"}, shell{true});
while (p.poll() == -1) { while (p.poll() == -1)
{
std::cout << "Waiting..." << std::endl; std::cout << "Waiting..." << std::endl;
#ifdef _MSC_VER
#else
sleep(1); sleep(1);
#endif
} }
std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl; std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl;
@ -53,7 +65,7 @@ void test_sleep()
void test_read_all() void test_read_all()
{ {
Popen p = Popen({"echo","12345678"}, output{PIPE}); Popen p = Popen({"echo", "12345678"}, output{PIPE});
std::vector<char> buf(6); std::vector<char> buf(6);
int rbytes = util::read_all(p.output(), buf); int rbytes = util::read_all(p.output(), buf);
@ -61,7 +73,7 @@ void test_read_all()
std::string out(buf.begin(), buf.end()); std::string out(buf.begin(), buf.end());
assert(out == "12345678\n" && rbytes == 9); // echo puts a new line at the end of output assert(out == "12345678\n" && rbytes == 9); // echo puts a new line at the end of output
std::cout<<"read_all() succeeded"<<std::endl; std::cout << "read_all() succeeded" << std::endl;
} }
void test_simple_cmd() void test_simple_cmd()