diff options
author | Kyle Edwards <kyle.edwards@kitware.com> | 2019-04-30 15:29:30 (GMT) |
---|---|---|
committer | Kyle Edwards <kyle.edwards@kitware.com> | 2019-05-07 17:40:06 (GMT) |
commit | 26025d6e106ffd4cd777fdc3a4343b33c8554c15 (patch) | |
tree | f40c983a734557f9137fa775fd051437079c89e4 /Source/cmUVProcessChain.cxx | |
parent | 6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 (diff) | |
download | CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.zip CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.tar.gz CMake-26025d6e106ffd4cd777fdc3a4343b33c8554c15.tar.bz2 |
cmUVProcessChain: Add cmUVProcessChain
This class is ultimately intended as a replacement for cmsys::Process.
It spawns a series of processes using libuv, piping the output of each
command into the next.
Note: input support has not yet been implemented because write
support has not yet been implemented on cmUVStreambuf.
Diffstat (limited to 'Source/cmUVProcessChain.cxx')
-rw-r--r-- | Source/cmUVProcessChain.cxx | 392 |
1 files changed, 392 insertions, 0 deletions
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; +} |