summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmUVHandlePtr.cxx8
-rw-r--r--Source/cmUVProcessChain.cxx392
-rw-r--r--Source/cmUVProcessChain.h100
-rw-r--r--Source/cmUVStreambuf.h2
-rw-r--r--Tests/CMakeLib/CMakeLists.txt4
-rw-r--r--Tests/CMakeLib/testUVProcessChain.cxx335
-rw-r--r--Tests/CMakeLib/testUVProcessChainHelper.cxx72
-rwxr-xr-xbootstrap2
9 files changed, 912 insertions, 5 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 01c6cd7..42eed4d 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -388,6 +388,8 @@ set(SRCS
cmUuid.cxx
cmUVHandlePtr.cxx
cmUVHandlePtr.h
+ cmUVProcessChain.cxx
+ cmUVProcessChain.h
cmUVStreambuf.h
cmUVSignalHackRAII.h
cmVariableWatch.cxx
diff --git a/Source/cmUVHandlePtr.cxx b/Source/cmUVHandlePtr.cxx
index 27069ee..db67463 100644
--- a/Source/cmUVHandlePtr.cxx
+++ b/Source/cmUVHandlePtr.cxx
@@ -211,7 +211,6 @@ uv_pipe_ptr::operator uv_stream_t*() const
return reinterpret_cast<uv_stream_t*>(handle.get());
}
-#ifdef CMAKE_BUILD_WITH_CMAKE
int uv_process_ptr::spawn(uv_loop_t& loop, uv_process_options_t const& options,
void* data)
{
@@ -231,6 +230,7 @@ int uv_timer_ptr::start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat)
return uv_timer_start(*this, cb, timeout, repeat);
}
+#ifdef CMAKE_BUILD_WITH_CMAKE
uv_tty_ptr::operator uv_stream_t*() const
{
return reinterpret_cast<uv_stream_t*>(handle.get());
@@ -255,13 +255,13 @@ UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe)
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(stream)
-#ifdef CMAKE_BUILD_WITH_CMAKE
-UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)
-
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(process)
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(timer)
+#ifdef CMAKE_BUILD_WITH_CMAKE
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)
+
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty)
#endif
}
diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx
new file mode 100644
index 0000000..c4e30d4
--- /dev/null
+++ b/Source/cmUVProcessChain.cxx
@@ -0,0 +1,392 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmUVProcessChain.h"
+
+#include "cmAlgorithms.h"
+#include "cmGetPipes.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVStreambuf.h"
+#include "cm_uv.h"
+
+#include <iterator>
+#include <memory>
+#include <utility>
+
+struct cmUVProcessChain::InternalData
+{
+ struct BasicStreamData
+ {
+ cmUVStreambuf Streambuf;
+ cm::uv_pipe_ptr BuiltinStream;
+ uv_stdio_container_t Stdio;
+ };
+
+ template <typename IOStream>
+ struct StreamData : public BasicStreamData
+ {
+ StreamData()
+ : BuiltinIOStream(&this->Streambuf)
+ {
+ }
+
+ IOStream BuiltinIOStream;
+
+ IOStream* GetBuiltinStream()
+ {
+ if (this->BuiltinStream.get()) {
+ return &this->BuiltinIOStream;
+ }
+ return nullptr;
+ }
+ };
+
+ struct ProcessData
+ {
+ cmUVProcessChain::InternalData* Data;
+ cm::uv_process_ptr Process;
+ cm::uv_pipe_ptr OutputPipe;
+ bool Finished = false;
+ Status ProcessStatus;
+ };
+
+ const cmUVProcessChainBuilder* Builder = nullptr;
+
+ bool Valid = false;
+
+ cm::uv_loop_ptr Loop;
+
+ StreamData<std::istream> OutputStreamData;
+ StreamData<std::istream> ErrorStreamData;
+
+ unsigned int ProcessesCompleted = 0;
+ std::vector<std::unique_ptr<ProcessData>> Processes;
+
+ bool Prepare(const cmUVProcessChainBuilder* builder);
+ bool AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration& config,
+ bool first, bool last);
+ bool Finish();
+
+ static const Status* GetStatus(const ProcessData& data);
+};
+
+cmUVProcessChainBuilder::cmUVProcessChainBuilder()
+{
+ this->SetNoStream(Stream_INPUT)
+ .SetNoStream(Stream_OUTPUT)
+ .SetNoStream(Stream_ERROR);
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand(
+ const std::vector<std::string>& arguments)
+{
+ if (!arguments.empty()) {
+ this->Processes.emplace_back();
+ this->Processes.back().Arguments = arguments;
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = None;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream(
+ Stream stdio)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ // FIXME
+ break;
+
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = Builtin;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream(
+ Stream stdio, int fd)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ // FIXME
+ break;
+
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = External;
+ streamData.FileDescriptor = fd;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChain cmUVProcessChainBuilder::Start() const
+{
+ cmUVProcessChain chain;
+
+ if (!chain.Data->Prepare(this)) {
+ return chain;
+ }
+
+ for (auto it = this->Processes.begin(); it != this->Processes.end(); ++it) {
+ if (!chain.Data->AddCommand(*it, it == this->Processes.begin(),
+ it == std::prev(this->Processes.end()))) {
+ return chain;
+ }
+ }
+
+ chain.Data->Finish();
+
+ return chain;
+}
+
+const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus(
+ const cmUVProcessChain::InternalData::ProcessData& data)
+{
+ if (data.Finished) {
+ return &data.ProcessStatus;
+ }
+ return nullptr;
+}
+
+bool cmUVProcessChain::InternalData::Prepare(
+ const cmUVProcessChainBuilder* builder)
+{
+ this->Builder = builder;
+
+ auto const& output =
+ this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
+ auto& outputData = this->OutputStreamData;
+ switch (output.Type) {
+ case cmUVProcessChainBuilder::None:
+ outputData.Stdio.flags = UV_IGNORE;
+ break;
+
+ case cmUVProcessChainBuilder::Builtin:
+ outputData.BuiltinStream.init(*this->Loop, 0);
+ outputData.Stdio.flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ outputData.Stdio.data.stream = outputData.BuiltinStream;
+ break;
+
+ case cmUVProcessChainBuilder::External:
+ outputData.Stdio.flags = UV_INHERIT_FD;
+ outputData.Stdio.data.fd = output.FileDescriptor;
+ break;
+ }
+
+ auto const& error =
+ this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR];
+ auto& errorData = this->ErrorStreamData;
+ switch (error.Type) {
+ case cmUVProcessChainBuilder::None:
+ errorData.Stdio.flags = UV_IGNORE;
+ break;
+
+ case cmUVProcessChainBuilder::Builtin: {
+ int pipeFd[2];
+ if (cmGetPipes(pipeFd) < 0) {
+ return false;
+ }
+
+ errorData.BuiltinStream.init(*this->Loop, 0);
+ if (uv_pipe_open(errorData.BuiltinStream, pipeFd[0]) < 0) {
+ return false;
+ }
+ errorData.Stdio.flags = UV_INHERIT_FD;
+ errorData.Stdio.data.fd = pipeFd[1];
+ break;
+ }
+
+ case cmUVProcessChainBuilder::External:
+ errorData.Stdio.flags = UV_INHERIT_FD;
+ errorData.Stdio.data.fd = error.FileDescriptor;
+ break;
+ }
+
+ return true;
+}
+
+bool cmUVProcessChain::InternalData::AddCommand(
+ const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
+ bool last)
+{
+ this->Processes.emplace_back(cm::make_unique<ProcessData>());
+ auto& process = *this->Processes.back();
+ process.Data = this;
+
+ auto options = uv_process_options_t();
+
+ // Bounds were checked at add time, first element is guaranteed to exist
+ options.file = config.Arguments[0].c_str();
+
+ std::vector<const char*> arguments;
+ for (auto const& arg : config.Arguments) {
+ arguments.push_back(arg.c_str());
+ }
+ arguments.push_back(nullptr);
+ options.args = const_cast<char**>(arguments.data());
+ options.flags = UV_PROCESS_WINDOWS_HIDE;
+
+ std::array<uv_stdio_container_t, 3> stdio;
+ stdio[0] = uv_stdio_container_t();
+ if (first) {
+ stdio[0].flags = UV_IGNORE;
+ } else {
+ auto& prev = **std::prev(this->Processes.end(), 2);
+ stdio[0].flags = UV_INHERIT_STREAM;
+ stdio[0].data.stream = prev.OutputPipe;
+ }
+ if (last) {
+ stdio[1] = this->OutputStreamData.Stdio;
+ } else {
+ if (process.OutputPipe.init(*this->Loop, 0) < 0) {
+ return false;
+ }
+ stdio[1] = uv_stdio_container_t();
+ stdio[1].flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ stdio[1].data.stream = process.OutputPipe;
+ }
+ stdio[2] = this->ErrorStreamData.Stdio;
+
+ options.stdio = stdio.data();
+ options.stdio_count = 3;
+ options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
+ int termSignal) {
+ auto* processData = static_cast<ProcessData*>(handle->data);
+ processData->Finished = true;
+ processData->ProcessStatus.ExitStatus = exitStatus;
+ processData->ProcessStatus.TermSignal = termSignal;
+ processData->Data->ProcessesCompleted++;
+ };
+
+ return process.Process.spawn(*this->Loop, options, &process) >= 0;
+}
+
+bool cmUVProcessChain::InternalData::Finish()
+{
+ if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type ==
+ cmUVProcessChainBuilder::Builtin) {
+ this->OutputStreamData.Streambuf.open(
+ this->OutputStreamData.BuiltinStream);
+ }
+
+ if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR].Type ==
+ cmUVProcessChainBuilder::Builtin) {
+ cm::uv_pipe_ptr tmpPipe;
+ if (tmpPipe.init(*this->Loop, 0) < 0) {
+ return false;
+ }
+ if (uv_pipe_open(tmpPipe, this->ErrorStreamData.Stdio.data.fd) < 0) {
+ return false;
+ }
+ tmpPipe.reset();
+
+ this->ErrorStreamData.Streambuf.open(this->ErrorStreamData.BuiltinStream);
+ }
+
+ this->Valid = true;
+ return true;
+}
+
+cmUVProcessChain::cmUVProcessChain()
+ : Data(cm::make_unique<InternalData>())
+{
+ this->Data->Loop.init();
+}
+
+cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept
+ : Data(std::move(other.Data))
+{
+}
+
+cmUVProcessChain::~cmUVProcessChain() = default;
+
+cmUVProcessChain& cmUVProcessChain::operator=(
+ cmUVProcessChain&& other) noexcept
+{
+ this->Data = std::move(other.Data);
+ return *this;
+}
+
+uv_loop_t& cmUVProcessChain::GetLoop()
+{
+ return *this->Data->Loop;
+}
+
+std::istream* cmUVProcessChain::OutputStream()
+{
+ return this->Data->OutputStreamData.GetBuiltinStream();
+}
+
+std::istream* cmUVProcessChain::ErrorStream()
+{
+ return this->Data->ErrorStreamData.GetBuiltinStream();
+}
+
+bool cmUVProcessChain::Valid() const
+{
+ return this->Data->Valid;
+}
+
+bool cmUVProcessChain::Wait(int64_t milliseconds)
+{
+ bool timeout = false;
+ cm::uv_timer_ptr timer;
+
+ if (milliseconds >= 0) {
+ timer.init(*this->Data->Loop, &timeout);
+ timer.start(
+ [](uv_timer_t* handle) {
+ auto* timeoutPtr = static_cast<bool*>(handle->data);
+ *timeoutPtr = true;
+ },
+ milliseconds, 0);
+ }
+
+ while (!timeout &&
+ this->Data->ProcessesCompleted < this->Data->Processes.size()) {
+ uv_run(this->Data->Loop, UV_RUN_ONCE);
+ }
+
+ return !timeout;
+}
+
+std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus()
+ const
+{
+ std::vector<const cmUVProcessChain::Status*> statuses(
+ this->Data->Processes.size(), nullptr);
+ for (std::size_t i = 0; i < statuses.size(); i++) {
+ statuses[i] = this->GetStatus(i);
+ }
+ return statuses;
+}
+
+const cmUVProcessChain::Status* cmUVProcessChain::GetStatus(
+ std::size_t index) const
+{
+ auto const& process = *this->Data->Processes[index];
+ if (process.Finished) {
+ return &process.ProcessStatus;
+ }
+ return nullptr;
+}
diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h
new file mode 100644
index 0000000..2b33520
--- /dev/null
+++ b/Source/cmUVProcessChain.h
@@ -0,0 +1,100 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmUVProcessChain_h
+#define cmUVProcessChain_h
+
+#include "cm_uv.h"
+
+#include <array>
+#include <iosfwd>
+#include <memory> // IWYU pragma: keep
+#include <string>
+#include <vector>
+
+#include <stdint.h>
+
+class cmUVProcessChain;
+
+class cmUVProcessChainBuilder
+{
+public:
+ enum Stream
+ {
+ Stream_INPUT = 0,
+ Stream_OUTPUT = 1,
+ Stream_ERROR = 2,
+ };
+
+ cmUVProcessChainBuilder();
+
+ cmUVProcessChainBuilder& AddCommand(
+ const std::vector<std::string>& arguments);
+ cmUVProcessChainBuilder& SetNoStream(Stream stdio);
+ cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio);
+ cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd);
+
+ cmUVProcessChain Start() const;
+
+private:
+ enum StdioType
+ {
+ None,
+ Builtin,
+ External,
+ };
+
+ friend class cmUVProcessChain;
+
+ struct StdioConfiguration
+ {
+ StdioType Type;
+ int FileDescriptor;
+ };
+
+ struct ProcessConfiguration
+ {
+ std::vector<std::string> Arguments;
+ };
+
+ std::array<StdioConfiguration, 3> Stdio;
+ std::vector<ProcessConfiguration> Processes;
+};
+
+class cmUVProcessChain
+{
+public:
+ struct Status
+ {
+ int64_t ExitStatus;
+ int TermSignal;
+ };
+
+ cmUVProcessChain(const cmUVProcessChain& other) = delete;
+ cmUVProcessChain(cmUVProcessChain&& other) noexcept;
+
+ ~cmUVProcessChain();
+
+ cmUVProcessChain& operator=(const cmUVProcessChain& other) = delete;
+ cmUVProcessChain& operator=(cmUVProcessChain&& other) noexcept;
+
+ uv_loop_t& GetLoop();
+
+ // FIXME: Add stdin support
+ std::istream* OutputStream();
+ std::istream* ErrorStream();
+
+ bool Valid() const;
+ bool Wait(int64_t milliseconds = -1);
+ std::vector<const Status*> GetStatus() const;
+ const Status* GetStatus(std::size_t index) const;
+
+private:
+ friend class cmUVProcessChainBuilder;
+
+ cmUVProcessChain();
+
+ struct InternalData;
+ std::unique_ptr<InternalData> Data;
+};
+
+#endif
diff --git a/Source/cmUVStreambuf.h b/Source/cmUVStreambuf.h
index 0ae532b..873352b 100644
--- a/Source/cmUVStreambuf.h
+++ b/Source/cmUVStreambuf.h
@@ -208,7 +208,7 @@ void cmBasicUVStreambuf<CharT, Traits>::StreamRead(ssize_t nread)
this->setg(this->eback(), this->gptr(),
this->egptr() + nread / sizeof(CharT));
uv_read_stop(this->Stream);
- } else if (nread < 0 || nread == UV_EOF) {
+ } else if (nread < 0 /*|| nread == UV_EOF*/) {
this->EndOfFile = true;
uv_read_stop(this->Stream);
}
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index e04bba2..a25f25a 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -15,11 +15,15 @@ set(CMakeLib_TESTS
testXMLParser.cxx
testXMLSafe.cxx
testFindPackageCommand.cxx
+ testUVProcessChain.cxx
testUVRAII.cxx
testUVStreambuf.cxx
)
+add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx)
+
set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
+set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>)
set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>)
if(WIN32)
diff --git a/Tests/CMakeLib/testUVProcessChain.cxx b/Tests/CMakeLib/testUVProcessChain.cxx
new file mode 100644
index 0000000..72ae602
--- /dev/null
+++ b/Tests/CMakeLib/testUVProcessChain.cxx
@@ -0,0 +1,335 @@
+#include "cmUVProcessChain.h"
+
+#include "cmAlgorithms.h"
+#include "cmGetPipes.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVStreambuf.h"
+
+#include "cm_uv.h"
+
+#include <algorithm>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <csignal>
+
+struct ExpectedStatus
+{
+ bool Finished;
+ bool MatchExitStatus;
+ bool MatchTermSignal;
+ cmUVProcessChain::Status Status;
+};
+
+static const std::vector<ExpectedStatus> status1 = {
+ { false, false, false, { 0, 0 } },
+ { false, false, false, { 0, 0 } },
+ { false, false, false, { 0, 0 } },
+};
+
+static const std::vector<ExpectedStatus> status2 = {
+ { true, true, true, { 0, 0 } },
+ { false, false, false, { 0, 0 } },
+ { false, false, false, { 0, 0 } },
+};
+
+static const std::vector<ExpectedStatus> status3 = {
+ { true, true, true, { 0, 0 } },
+ { true, true, true, { 1, 0 } },
+#ifdef _WIN32
+ { true, true, true, { 2, 0 } },
+#else
+ { true, false, true, { 0, SIGABRT } },
+#endif
+};
+
+bool operator==(const cmUVProcessChain::Status* actual,
+ const ExpectedStatus& expected)
+{
+ if (!expected.Finished) {
+ return !actual;
+ } else if (!actual) {
+ return false;
+ }
+ if (expected.MatchExitStatus &&
+ expected.Status.ExitStatus != actual->ExitStatus) {
+ return false;
+ }
+ if (expected.MatchTermSignal &&
+ expected.Status.TermSignal != actual->TermSignal) {
+ return false;
+ }
+ return true;
+}
+
+bool resultsMatch(const std::vector<const cmUVProcessChain::Status*>& actual,
+ const std::vector<ExpectedStatus>& expected)
+{
+ return actual.size() == expected.size() &&
+ std::equal(actual.begin(), actual.end(), expected.begin());
+}
+
+std::string getInput(std::istream& input)
+{
+ char buffer[1024];
+ std::ostringstream str;
+ do {
+ input.read(buffer, 1024);
+ str.write(buffer, input.gcount());
+ } while (input.gcount() > 0);
+ return str.str();
+}
+
+template <typename T>
+std::function<std::ostream&(std::ostream&)> printExpected(bool match,
+ const T& value)
+{
+ return [match, value](std::ostream& stream) -> std::ostream& {
+ if (match) {
+ stream << value;
+ } else {
+ stream << "*";
+ }
+ return stream;
+ };
+}
+
+std::ostream& operator<<(
+ std::ostream& stream,
+ const std::function<std::ostream&(std::ostream&)>& func)
+{
+ return func(stream);
+}
+
+void printResults(const std::vector<const cmUVProcessChain::Status*>& actual,
+ const std::vector<ExpectedStatus>& expected)
+{
+ std::cout << "Expected: " << std::endl;
+ for (auto const& e : expected) {
+ if (e.Finished) {
+ std::cout << " ExitStatus: "
+ << printExpected(e.MatchExitStatus, e.Status.ExitStatus)
+ << ", TermSignal: "
+ << printExpected(e.MatchTermSignal, e.Status.TermSignal)
+ << std::endl;
+ } else {
+ std::cout << " null" << std::endl;
+ }
+ }
+ std::cout << "Actual:" << std::endl;
+ for (auto const& a : actual) {
+ if (a) {
+ std::cout << " ExitStatus: " << a->ExitStatus
+ << ", TermSignal: " << a->TermSignal << std::endl;
+ } else {
+ std::cout << " null" << std::endl;
+ }
+ }
+}
+
+bool checkExecution(cmUVProcessChainBuilder& builder,
+ std::unique_ptr<cmUVProcessChain>& chain)
+{
+ std::vector<const cmUVProcessChain::Status*> status;
+
+ chain = cm::make_unique<cmUVProcessChain>(builder.Start());
+ if (!chain->Valid()) {
+ std::cout << "Valid() returned false, should be true" << std::endl;
+ return false;
+ }
+ status = chain->GetStatus();
+ if (!resultsMatch(status, status1)) {
+ std::cout << "GetStatus() did not produce expected output" << std::endl;
+ printResults(status, status1);
+ return false;
+ }
+
+ if (chain->Wait(6000)) {
+ std::cout << "Wait() returned true, should be false" << std::endl;
+ return false;
+ }
+ status = chain->GetStatus();
+ if (!resultsMatch(status, status2)) {
+ std::cout << "GetStatus() did not produce expected output" << std::endl;
+ printResults(status, status2);
+ return false;
+ }
+
+ if (!chain->Wait()) {
+ std::cout << "Wait() returned false, should be true" << std::endl;
+ return false;
+ }
+ status = chain->GetStatus();
+ if (!resultsMatch(status, status3)) {
+ std::cout << "GetStatus() did not produce expected output" << std::endl;
+ printResults(status, status3);
+ return false;
+ }
+
+ return true;
+}
+
+bool checkOutput(std::istream& outputStream, std::istream& errorStream)
+{
+ std::string output = getInput(outputStream);
+ if (output != "HELO WRD!") {
+ std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\""
+ << std::endl;
+ return false;
+ }
+
+ std::string error = getInput(errorStream);
+ if (error.length() != 3 || error.find('1') == std::string::npos ||
+ error.find('2') == std::string::npos ||
+ error.find('3') == std::string::npos) {
+ std::cout << "Error was \"" << error << "\", expected \"123\""
+ << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+bool testUVProcessChainBuiltin(const char* helperCommand)
+{
+ cmUVProcessChainBuilder builder;
+ std::unique_ptr<cmUVProcessChain> chain;
+ builder.AddCommand({ helperCommand, "echo" })
+ .AddCommand({ helperCommand, "capitalize" })
+ .AddCommand({ helperCommand, "dedup" })
+ .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
+ .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
+
+ if (!checkExecution(builder, chain)) {
+ return false;
+ }
+
+ if (!chain->OutputStream()) {
+ std::cout << "OutputStream() was null, expecting not null" << std::endl;
+ return false;
+ }
+ if (!chain->ErrorStream()) {
+ std::cout << "ErrorStream() was null, expecting not null" << std::endl;
+ return false;
+ }
+
+ if (!checkOutput(*chain->OutputStream(), *chain->ErrorStream())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool testUVProcessChainExternal(const char* helperCommand)
+{
+ cmUVProcessChainBuilder builder;
+ std::unique_ptr<cmUVProcessChain> chain;
+ int outputPipe[2], errorPipe[2];
+ cm::uv_pipe_ptr outputInPipe, outputOutPipe, errorInPipe, errorOutPipe;
+
+ if (cmGetPipes(outputPipe) < 0) {
+ std::cout << "Error creating pipes" << std::endl;
+ return false;
+ }
+ if (cmGetPipes(errorPipe) < 0) {
+ std::cout << "Error creating pipes" << std::endl;
+ return false;
+ }
+
+ builder.AddCommand({ helperCommand, "echo" })
+ .AddCommand({ helperCommand, "capitalize" })
+ .AddCommand({ helperCommand, "dedup" })
+ .SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, outputPipe[1])
+ .SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, errorPipe[1]);
+
+ if (!checkExecution(builder, chain)) {
+ return false;
+ }
+
+ if (chain->OutputStream()) {
+ std::cout << "OutputStream() was not null, expecting null" << std::endl;
+ return false;
+ }
+ if (chain->ErrorStream()) {
+ std::cout << "ErrorStream() was not null, expecting null" << std::endl;
+ return false;
+ }
+
+ outputOutPipe.init(chain->GetLoop(), 0);
+ uv_pipe_open(outputOutPipe, outputPipe[1]);
+ outputOutPipe.reset();
+
+ errorOutPipe.init(chain->GetLoop(), 0);
+ uv_pipe_open(errorOutPipe, errorPipe[1]);
+ errorOutPipe.reset();
+
+ outputInPipe.init(chain->GetLoop(), 0);
+ uv_pipe_open(outputInPipe, outputPipe[0]);
+ cmUVStreambuf outputBuf;
+ outputBuf.open(outputInPipe);
+ std::istream outputStream(&outputBuf);
+
+ errorInPipe.init(chain->GetLoop(), 0);
+ uv_pipe_open(errorInPipe, errorPipe[0]);
+ cmUVStreambuf errorBuf;
+ errorBuf.open(errorInPipe);
+ std::istream errorStream(&errorBuf);
+
+ if (!checkOutput(outputStream, errorStream)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool testUVProcessChainNone(const char* helperCommand)
+{
+ cmUVProcessChainBuilder builder;
+ std::unique_ptr<cmUVProcessChain> chain;
+ builder.AddCommand({ helperCommand, "echo" })
+ .AddCommand({ helperCommand, "capitalize" })
+ .AddCommand({ helperCommand, "dedup" });
+
+ if (!checkExecution(builder, chain)) {
+ return false;
+ }
+
+ if (chain->OutputStream()) {
+ std::cout << "OutputStream() was not null, expecting null" << std::endl;
+ return false;
+ }
+ if (chain->ErrorStream()) {
+ std::cout << "ErrorStream() was not null, expecting null" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+int testUVProcessChain(int argc, char** const argv)
+{
+ if (argc < 2) {
+ std::cout << "Invalid arguments.\n";
+ return -1;
+ }
+
+ if (!testUVProcessChainBuiltin(argv[1])) {
+ std::cout << "While executing testUVProcessChainBuiltin().\n";
+ return -1;
+ }
+
+ if (!testUVProcessChainExternal(argv[1])) {
+ std::cout << "While executing testUVProcessChainExternal().\n";
+ return -1;
+ }
+
+ if (!testUVProcessChainNone(argv[1])) {
+ std::cout << "While executing testUVProcessChainNone().\n";
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/Tests/CMakeLib/testUVProcessChainHelper.cxx b/Tests/CMakeLib/testUVProcessChainHelper.cxx
new file mode 100644
index 0000000..263665d
--- /dev/null
+++ b/Tests/CMakeLib/testUVProcessChainHelper.cxx
@@ -0,0 +1,72 @@
+#include <chrono>
+#include <iostream>
+#include <set>
+#include <sstream>
+#include <string>
+#include <thread>
+
+#include <cctype>
+#include <cstdlib>
+
+std::string getStdin()
+{
+ char buffer[1024];
+ std::ostringstream str;
+ do {
+ std::cin.read(buffer, 1024);
+ str.write(buffer, std::cin.gcount());
+ } while (std::cin.gcount() > 0);
+ return str.str();
+}
+
+int main(int argc, char** argv)
+{
+ if (argc < 2) {
+ return -1;
+ }
+
+ std::string command = argv[1];
+ if (command == "echo") {
+ std::this_thread::sleep_for(std::chrono::milliseconds(3000));
+ std::cout << "HELLO world!" << std::flush;
+ std::cerr << "1" << std::flush;
+ return 0;
+ }
+ if (command == "capitalize") {
+ std::this_thread::sleep_for(std::chrono::milliseconds(9000));
+ std::string input = getStdin();
+ for (auto& c : input) {
+ c = static_cast<char>(std::toupper(c));
+ }
+ std::cout << input << std::flush;
+ std::cerr << "2" << std::flush;
+ return 1;
+ }
+ if (command == "dedup") {
+ // Use a nested scope to free all resources before aborting below.
+ {
+ std::string input = getStdin();
+ std::set<char> seen;
+ std::string output;
+ for (auto c : input) {
+ if (!seen.count(c)) {
+ seen.insert(c);
+ output += c;
+ }
+ }
+ std::cout << output << std::flush;
+ std::cerr << "3" << std::flush;
+ }
+
+ // On Windows, the exit code of abort() is different between debug and
+ // release builds, and does not yield a term_signal in libuv in either
+ // case. For the sake of simplicity, we just return another non-zero code.
+#ifdef _WIN32
+ return 2;
+#else
+ std::abort();
+#endif
+ }
+
+ return -1;
+}
diff --git a/bootstrap b/bootstrap
index 8b9c404..c5274ce 100755
--- a/bootstrap
+++ b/bootstrap
@@ -329,6 +329,7 @@ CMAKE_CXX_SOURCES="\
cmGetCMakePropertyCommand \
cmGetDirectoryPropertyCommand \
cmGetFilenameComponentCommand \
+ cmGetPipes \
cmGetPropertyCommand \
cmGetSourceFilePropertyCommand \
cmGetTargetPropertyCommand \
@@ -427,6 +428,7 @@ CMAKE_CXX_SOURCES="\
cmUnexpectedCommand \
cmUnsetCommand \
cmUVHandlePtr \
+ cmUVProcessChain \
cmVersion \
cmWhileCommand \
cmWorkingDirectory \