| /* 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 <assert.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 { |
| assert(this->Processes.size() >= 2); |
| auto& prev = *this->Processes[this->Processes.size() - 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; |
| } |