/* 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 #include // IWYU pragma: keep #include #include #include #include #include "cmGetPipes.h" #include "cmUVHandlePtr.h" #include "cmUVStreambuf.h" struct cmUVProcessChain::InternalData { struct BasicStreamData { cmUVStreambuf Streambuf; cm::uv_pipe_ptr BuiltinStream; uv_stdio_container_t Stdio; }; template 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 OutputStreamData; StreamData ErrorStreamData; unsigned int ProcessesCompleted = 0; std::vector> 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& 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_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()); 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 arguments; for (auto const& arg : config.Arguments) { arguments.push_back(arg.c_str()); } arguments.push_back(nullptr); options.args = const_cast(arguments.data()); options.flags = UV_PROCESS_WINDOWS_HIDE; std::array 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_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(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()) { 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(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 cmUVProcessChain::GetStatus() const { std::vector 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; }