|
|
|
/* 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 <cassert>
|
|
|
|
#include <istream> // IWYU pragma: keep
|
|
|
|
#include <iterator>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <cm/memory>
|
|
|
|
|
|
|
|
#include <cm3p/uv.h>
|
|
|
|
|
|
|
|
#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 <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;
|
|
|
|
arguments.reserve(config.Arguments.size());
|
|
|
|
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;
|
|
|
|
}
|