Skip to content

Commit

Permalink
Enable basic windows support (#33)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
xoviat authored and arun11299 committed Jan 18, 2020
1 parent 126ee29 commit 9c624ce
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 43 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ This library had these design goals:


## Supported Platforms
Unlike python2.7 subprocess module, this library currently only supports MAC OS and Linux.
It has no support for Windows in its current state.
This library supports MAC OS and Linux.

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
Subprocess library has just a single source `subprocess.hpp` present at the top directory of this repository. All you need to do is add
Expand All @@ -34,6 +36,7 @@ Checkout http://templated-thoughts.blogspot.in/2016/03/sub-processing-with-moder
## Compiler Support
Linux - g++ 4.8 and above
Mac OS - Clang 3.4 and later
Windows - MSVC 2015 and above

## Examples
Here are few examples to show how to get started:
Expand Down
76 changes: 40 additions & 36 deletions subprocess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ namespace util

// Create a pipe for the child process's STDIN.
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.
if (!SetHandleInformation(child_handle, HANDLE_FLAG_INHERIT, 0))
throw OSError("Stdin SetHandleInformation", 0);
if (!SetHandleInformation(*child_handle, HANDLE_FLAG_INHERIT, 0))
throw OSError("SetHandleInformation", 0);
}
#endif

Expand Down Expand Up @@ -1131,6 +1131,26 @@ class Popen
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);

int pid() const noexcept { return child_pid_; }
Expand Down Expand Up @@ -1409,43 +1429,21 @@ inline void Popen::execute_process() noexcept(false)

this->process_handle_ = piProcInfo.hProcess;

try {
char err_buf[SP_MAX_ERR_BUF_SIZ] = {0,};
std::async(std::launch::async, [this] {
WaitForSingleObject(this->process_handle_, INFINITE);

int read_bytes = util::read_atmost_n(
this->error(),
err_buf,
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;
}
CloseHandle(this->stream_.g_hChildStd_ERR_Wr);
CloseHandle(this->stream_.g_hChildStd_OUT_Wr);
CloseHandle(this->stream_.g_hChildStd_IN_Rd);
});

/*
this->hExited_ =
std::shared_future<int>(std::async(std::launch::async, [this] {
WaitForSingleObject(this->hProcess_, INFINITE);
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;
}));
NOTE: In the linux version, there is a check to make sure that the process
has been started. Here, we do nothing because CreateProcess will throw
if we fail to create the process.
*/


#else

int err_rd_pipe, err_wr_pipe;
Expand Down Expand Up @@ -1683,7 +1681,7 @@ namespace detail {
inline void Streams::setup_comm_channels()
{
#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->write_to_child_ = _fileno(this->input());

Expand Down Expand Up @@ -1935,6 +1933,12 @@ OutBuffer check_output(const std::string& arg, 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.
Expand Down
2 changes: 2 additions & 0 deletions test/test_ret_code.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ void test_ret_code()
std::cout << "Test::test_poll_ret_code" << std::endl;
auto p = sp::Popen({"/usr/bin/false"});
while (p.poll() == -1) {
#ifndef _MSC_VER
usleep(1000 * 100);
#endif
}
assert (p.retcode() == 1);
}
Expand Down
22 changes: 17 additions & 5 deletions test/test_subprocess.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ using namespace subprocess;

void test_exename()
{
#ifdef _MSC_VER
auto ret = call({"--version"}, executable{"cmake"}, shell{false});
#else
auto ret = call({"-l"}, executable{"ls"}, shell{false});
#endif
std::cout << ret << std::endl;
}

Expand Down Expand Up @@ -35,33 +39,41 @@ void test_easy_piping()

void test_shell()
{
auto obuf = check_output({"ls", "-l"}, shell{false});
#ifdef _MSC_VER
auto obuf = check_output({"cmake", "--version"}, shell{false});
#else
auto obuf = check_output({"ls", "-l"}, shell{false});
#endif
std::cout << obuf.buf.data() << std::endl;
}

void test_sleep()
{
auto p = Popen({"sleep", "30"}, shell{true});

while (p.poll() == -1) {
while (p.poll() == -1)
{
std::cout << "Waiting..." << std::endl;
#ifdef _MSC_VER
#else
sleep(1);
#endif
}

std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl;
}

void test_read_all()
{
Popen p = Popen({"echo","12345678"}, output{PIPE});
Popen p = Popen({"echo", "12345678"}, output{PIPE});

std::vector<char> buf(6);
int rbytes = util::read_all(p.output(), buf);

std::string out(buf.begin(), buf.end());

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()
Expand Down

0 comments on commit 9c624ce

Please sign in to comment.