mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Optionally separate Tasks` stderr from stdout.
Fixes a serious problem where spurious output from xcrun breaks swift's discovery of libarclite. <rdar://problem/28573949>
This commit is contained in:
@@ -72,12 +72,15 @@ public:
|
||||
/// \param ReturnCode the return code of the task which finished execution.
|
||||
/// \param Output the output from the task which finished execution,
|
||||
/// if available. (This may not be available on all platforms.)
|
||||
/// \param Errors the errors from the task which finished execution, if
|
||||
/// available and SeparateErrors was true. (This may not be available on all
|
||||
/// platforms.)
|
||||
/// \param Context the context which was passed when the task was added
|
||||
///
|
||||
/// \returns true if further execution of tasks should stop,
|
||||
/// false if execution should continue
|
||||
typedef std::function<TaskFinishedResponse(ProcessId Pid, int ReturnCode,
|
||||
StringRef Output, void *Context)>
|
||||
StringRef Output, StringRef Errors, void *Context)>
|
||||
TaskFinishedCallback;
|
||||
|
||||
/// \brief A callback which will be executed if a task exited abnormally due
|
||||
@@ -88,12 +91,15 @@ public:
|
||||
/// no reason could be deduced, this may be empty.
|
||||
/// \param Output the output from the task which exited abnormally, if
|
||||
/// available. (This may not be available on all platforms.)
|
||||
/// \param Errors the errors from the task which exited abnormally, if
|
||||
/// available and SeparateErrors was true. (This may not be available on all
|
||||
/// platforms.)
|
||||
/// \param Context the context which was passed when the task was added
|
||||
///
|
||||
/// \returns a TaskFinishedResponse indicating whether or not execution
|
||||
/// should proceed
|
||||
typedef std::function<TaskFinishedResponse(ProcessId Pid, StringRef ErrorMsg,
|
||||
StringRef Output, void *Context)>
|
||||
StringRef Output, StringRef Errors, void *Context)>
|
||||
TaskSignalledCallback;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -120,9 +126,10 @@ public:
|
||||
/// \param Env the environment which should be used for the task;
|
||||
/// must be null-terminated. If empty, inherits the parent's environment.
|
||||
/// \param Context an optional context which will be associated with the task
|
||||
/// \param SeparateErrors Controls whether error output is reported separately
|
||||
virtual void addTask(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env = llvm::None,
|
||||
void *Context = nullptr);
|
||||
void *Context = nullptr, bool SeparateErrors = false);
|
||||
|
||||
/// \brief Synchronously executes the tasks in the TaskQueue.
|
||||
///
|
||||
@@ -153,10 +160,13 @@ class DummyTaskQueue : public TaskQueue {
|
||||
ArrayRef<const char *> Args;
|
||||
ArrayRef<const char *> Env;
|
||||
void *Context;
|
||||
bool SeparateErrors;
|
||||
|
||||
DummyTask(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env = llvm::None, void *Context = nullptr)
|
||||
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context) {}
|
||||
ArrayRef<const char *> Env = llvm::None, void *Context = nullptr,
|
||||
bool SeparateErrors = false)
|
||||
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context),
|
||||
SeparateErrors(SeparateErrors) {}
|
||||
};
|
||||
|
||||
std::queue<std::unique_ptr<DummyTask>> QueuedTasks;
|
||||
@@ -168,7 +178,7 @@ public:
|
||||
|
||||
virtual void addTask(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env = llvm::None,
|
||||
void *Context = nullptr);
|
||||
void *Context = nullptr, bool SeparateErrors = false);
|
||||
|
||||
virtual bool
|
||||
execute(TaskBeganCallback Began = TaskBeganCallback(),
|
||||
|
||||
@@ -112,8 +112,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
// Wait() returning a return code of -2 indicates the process received
|
||||
// a signal during execution.
|
||||
if (Signalled) {
|
||||
TaskFinishedResponse Response = Signalled(PI.Pid, ErrMsg, StringRef(),
|
||||
T->Context);
|
||||
TaskFinishedResponse Response =
|
||||
Signalled(PI.Pid, ErrMsg, StringRef(), StringRef(), T->Context);
|
||||
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
|
||||
} else {
|
||||
// If we don't have a Signalled callback, unconditionally stop.
|
||||
@@ -124,7 +124,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
// finished.
|
||||
if (Finished) {
|
||||
TaskFinishedResponse Response = Finished(PI.Pid, PI.ReturnCode,
|
||||
StringRef(), T->Context);
|
||||
StringRef(), StringRef(), T->Context);
|
||||
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
|
||||
} else if (PI.ReturnCode != 0) {
|
||||
ContinueExecution = false;
|
||||
|
||||
@@ -42,9 +42,10 @@ DummyTaskQueue::DummyTaskQueue(unsigned NumberOfParallelTasks)
|
||||
DummyTaskQueue::~DummyTaskQueue() = default;
|
||||
|
||||
void DummyTaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env, void *Context) {
|
||||
QueuedTasks.emplace(
|
||||
std::unique_ptr<DummyTask>(new DummyTask(ExecPath, Args, Env, Context)));
|
||||
ArrayRef<const char *> Env, void *Context,
|
||||
bool SeparateErrors) {
|
||||
QueuedTasks.emplace(std::unique_ptr<DummyTask>(
|
||||
new DummyTask(ExecPath, Args, Env, Context, SeparateErrors)));
|
||||
}
|
||||
|
||||
bool DummyTaskQueue::execute(TaskQueue::TaskBeganCallback Began,
|
||||
@@ -80,7 +81,9 @@ bool DummyTaskQueue::execute(TaskQueue::TaskBeganCallback Began,
|
||||
|
||||
if (Finished) {
|
||||
std::string Output = "Output placeholder\n";
|
||||
if (Finished(P.first, 0, Output, P.second->Context) ==
|
||||
std::string Errors =
|
||||
P.second->SeparateErrors ? "Error placeholder\n" : "";
|
||||
if (Finished(P.first, 0, Output, Errors, P.second->Context) ==
|
||||
TaskFinishedResponse::StopExecution)
|
||||
SubtaskFailed = true;
|
||||
}
|
||||
|
||||
@@ -58,12 +58,18 @@ class Task {
|
||||
/// Context which should be associated with this task.
|
||||
void *Context;
|
||||
|
||||
/// True if the errors of the Task should be stored in Errors instead of Output.
|
||||
bool SeparateErrors;
|
||||
|
||||
/// The pid of this Task when executing.
|
||||
pid_t Pid;
|
||||
|
||||
/// A pipe for reading output from the child process.
|
||||
int Pipe;
|
||||
|
||||
/// A pipe for reading errors from the child prcess, if SeparateErrors is true.
|
||||
int ErrorPipe;
|
||||
|
||||
/// The current state of the Task.
|
||||
enum {
|
||||
Preparing,
|
||||
@@ -74,11 +80,16 @@ class Task {
|
||||
/// Once the Task has finished, this contains the buffered output of the Task.
|
||||
std::string Output;
|
||||
|
||||
/// Once the Task has finished, if SeparateErrors is true, this contains the errors
|
||||
/// from the Task.
|
||||
std::string Errors;
|
||||
|
||||
public:
|
||||
Task(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env, void *Context)
|
||||
ArrayRef<const char *> Env, void *Context, bool SeparateErrors)
|
||||
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context),
|
||||
Pid(-1), Pipe(-1), State(Preparing) {
|
||||
SeparateErrors(SeparateErrors), Pid(-1), Pipe(-1), ErrorPipe(-1),
|
||||
State(Preparing) {
|
||||
assert((Env.empty() || Env.back() == nullptr) &&
|
||||
"Env must either be empty or null-terminated!");
|
||||
}
|
||||
@@ -86,17 +97,19 @@ public:
|
||||
const char *getExecPath() const { return ExecPath; }
|
||||
ArrayRef<const char *> getArgs() const { return Args; }
|
||||
StringRef getOutput() const { return Output; }
|
||||
StringRef getErrors() const { return Errors; }
|
||||
void *getContext() const { return Context; }
|
||||
pid_t getPid() const { return Pid; }
|
||||
int getPipe() const { return Pipe; }
|
||||
int getErrorPipe() const { return ErrorPipe; }
|
||||
|
||||
/// \brief Begins execution of this Task.
|
||||
/// \returns true on error, false on success
|
||||
bool execute();
|
||||
|
||||
/// \brief Reads data from the pipe, if any is available.
|
||||
/// \brief Reads data from the pipes, if any is available.
|
||||
/// \returns true on error, false on success
|
||||
bool readFromPipe();
|
||||
bool readFromPipes();
|
||||
|
||||
/// \brief Performs any post-execution work for this Task, such as reading
|
||||
/// piped output and closing the pipe.
|
||||
@@ -121,6 +134,12 @@ bool Task::execute() {
|
||||
pipe(FullPipe);
|
||||
Pipe = FullPipe[0];
|
||||
|
||||
int FullErrorPipe[2];
|
||||
if (SeparateErrors) {
|
||||
pipe(FullErrorPipe);
|
||||
ErrorPipe = FullErrorPipe[0];
|
||||
}
|
||||
|
||||
// Get the environment to pass down to the subtask.
|
||||
const char *const *envp = Env.empty() ? nullptr : Env.data();
|
||||
if (!envp) {
|
||||
@@ -138,8 +157,19 @@ bool Task::execute() {
|
||||
posix_spawn_file_actions_init(&FileActions);
|
||||
|
||||
posix_spawn_file_actions_adddup2(&FileActions, FullPipe[1], STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&FileActions, STDOUT_FILENO, STDERR_FILENO);
|
||||
|
||||
if (SeparateErrors) {
|
||||
posix_spawn_file_actions_adddup2(&FileActions, FullErrorPipe[1],
|
||||
STDERR_FILENO);
|
||||
} else {
|
||||
posix_spawn_file_actions_adddup2(&FileActions, STDOUT_FILENO,
|
||||
STDERR_FILENO);
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_addclose(&FileActions, FullPipe[0]);
|
||||
if (SeparateErrors) {
|
||||
posix_spawn_file_actions_addclose(&FileActions, FullErrorPipe[0]);
|
||||
}
|
||||
|
||||
// Spawn the subtask.
|
||||
int spawnErr = posix_spawn(&Pid, ExecPath, &FileActions, nullptr,
|
||||
@@ -148,9 +178,15 @@ bool Task::execute() {
|
||||
|
||||
posix_spawn_file_actions_destroy(&FileActions);
|
||||
close(FullPipe[1]);
|
||||
if (SeparateErrors) {
|
||||
close(FullErrorPipe[1]);
|
||||
}
|
||||
|
||||
if (spawnErr != 0 || Pid == 0) {
|
||||
close(FullPipe[0]);
|
||||
if (SeparateErrors) {
|
||||
close(FullErrorPipe[0]);
|
||||
}
|
||||
State = Finished;
|
||||
return true;
|
||||
}
|
||||
@@ -159,6 +195,9 @@ bool Task::execute() {
|
||||
switch (Pid) {
|
||||
case -1: {
|
||||
close(FullPipe[0]);
|
||||
if (SeparateErrors) {
|
||||
close(FullErrorPipe[0]);
|
||||
}
|
||||
State = Finished;
|
||||
Pid = 0;
|
||||
break;
|
||||
@@ -166,8 +205,15 @@ bool Task::execute() {
|
||||
case 0: {
|
||||
// Child process: Execute the program.
|
||||
dup2(FullPipe[1], STDOUT_FILENO);
|
||||
if (SeparateErrors) {
|
||||
dup2(FullErrorPipe[1], STDERR_FILENO);
|
||||
} else {
|
||||
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||||
}
|
||||
close(FullPipe[0]);
|
||||
if (SeparateErrors) {
|
||||
close(FullErrorPipe[0]);
|
||||
}
|
||||
execve(ExecPath, const_cast<char **>(argvp), const_cast<char **>(envp));
|
||||
|
||||
// If the execve() failed, we should exit. Follow Unix protocol and
|
||||
@@ -184,6 +230,9 @@ bool Task::execute() {
|
||||
}
|
||||
|
||||
close(FullPipe[1]);
|
||||
if (SeparateErrors) {
|
||||
close(FullErrorPipe[1]);
|
||||
}
|
||||
|
||||
if (Pid == 0)
|
||||
return true;
|
||||
@@ -192,7 +241,7 @@ bool Task::execute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Task::readFromPipe() {
|
||||
static bool readFromAPipe(int Pipe, std::string &Output) {
|
||||
char outputBuffer[1024];
|
||||
ssize_t readBytes = 0;
|
||||
while ((readBytes = read(Pipe, outputBuffer, sizeof(outputBuffer))) != 0) {
|
||||
@@ -209,6 +258,14 @@ bool Task::readFromPipe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Task::readFromPipes() {
|
||||
bool Ret = readFromAPipe(Pipe, Output);
|
||||
if (SeparateErrors) {
|
||||
Ret |= readFromAPipe(ErrorPipe, Errors);
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void Task::finishExecution() {
|
||||
assert(State == Executing &&
|
||||
"This Task must be executing to finish execution!");
|
||||
@@ -216,9 +273,12 @@ void Task::finishExecution() {
|
||||
State = Finished;
|
||||
|
||||
// Read the output of the command, so we can use it later.
|
||||
readFromPipe();
|
||||
readFromPipes();
|
||||
|
||||
close(Pipe);
|
||||
if (SeparateErrors) {
|
||||
close(ErrorPipe);
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskQueue::supportsBufferingOutput() {
|
||||
@@ -239,8 +299,10 @@ unsigned TaskQueue::getNumberOfParallelTasks() const {
|
||||
}
|
||||
|
||||
void TaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
|
||||
ArrayRef<const char *> Env, void *Context) {
|
||||
std::unique_ptr<Task> T(new Task(ExecPath, Args, Env, Context));
|
||||
ArrayRef<const char *> Env, void *Context,
|
||||
bool SeparateErrors) {
|
||||
std::unique_ptr<Task> T(
|
||||
new Task(ExecPath, Args, Env, Context, SeparateErrors));
|
||||
QueuedTasks.push(std::move(T));
|
||||
}
|
||||
|
||||
@@ -279,6 +341,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
}
|
||||
|
||||
PollFds.push_back({ T->getPipe(), POLLIN | POLLPRI | POLLHUP, 0 });
|
||||
// We should also poll T->getErrorPipe(), but this intrroduces timing
|
||||
// issues with shutting down the task after reading getPipe().
|
||||
ExecutingTasks[Pid] = std::move(T);
|
||||
}
|
||||
|
||||
@@ -310,7 +374,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
Task &T = *iter->second;
|
||||
if (fd.revents & POLLIN || fd.revents & POLLPRI) {
|
||||
// There's data available to read.
|
||||
T.readFromPipe();
|
||||
T.readFromPipes();
|
||||
}
|
||||
|
||||
if (fd.revents & POLLHUP || fd.revents & POLLERR) {
|
||||
@@ -339,7 +403,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
// If we have a TaskFinishedCallback, only set SubtaskFailed to
|
||||
// true if the callback returns StopExecution.
|
||||
SubtaskFailed = Finished(T.getPid(), Result, T.getOutput(),
|
||||
T.getContext()) ==
|
||||
T.getErrors(), T.getContext()) ==
|
||||
TaskFinishedResponse::StopExecution;
|
||||
} else if (Result != 0) {
|
||||
// Since we don't have a TaskFinishedCallback, treat a subtask
|
||||
@@ -353,8 +417,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
|
||||
StringRef ErrorMsg = strsignal(Signal);
|
||||
|
||||
if (Signalled) {
|
||||
TaskFinishedResponse Response = Signalled(T.getPid(), ErrorMsg,
|
||||
T.getOutput(),
|
||||
TaskFinishedResponse Response =
|
||||
Signalled(T.getPid(), ErrorMsg, T.getOutput(), T.getErrors(),
|
||||
T.getContext());
|
||||
if (Response == TaskFinishedResponse::StopExecution)
|
||||
// If we have a TaskCrashedCallback, only set SubtaskFailed to
|
||||
|
||||
@@ -482,6 +482,7 @@ int Compilation::performJobsImpl() {
|
||||
// it should also schedule any additional commands which we now know need
|
||||
// to run.
|
||||
auto taskFinished = [&](ProcessId Pid, int ReturnCode, StringRef Output,
|
||||
StringRef Errors,
|
||||
void *Context) -> TaskFinishedResponse {
|
||||
const Job *FinishedCmd = (const Job *)Context;
|
||||
|
||||
@@ -613,6 +614,7 @@ int Compilation::performJobsImpl() {
|
||||
};
|
||||
|
||||
auto taskSignalled = [&](ProcessId Pid, StringRef ErrorMsg, StringRef Output,
|
||||
StringRef Errors,
|
||||
void *Context) -> TaskFinishedResponse {
|
||||
const Job *SignalledCmd = (const Job *)Context;
|
||||
|
||||
|
||||
@@ -1204,6 +1204,7 @@ void Driver::buildOutputInfo(const ToolChain &TC, const DerivedArgList &Args,
|
||||
[&OI](sys::ProcessId PID,
|
||||
int returnCode,
|
||||
StringRef output,
|
||||
StringRef errors,
|
||||
void *unused) -> sys::TaskFinishedResponse {
|
||||
if (returnCode == 0) {
|
||||
output = output.rtrim();
|
||||
|
||||
@@ -787,11 +787,11 @@ static bool findXcodeClangPath(llvm::SmallVectorImpl<char> &path) {
|
||||
if (!xcrunPath.getError()) {
|
||||
const char *args[] = {"-f", "clang", nullptr};
|
||||
sys::TaskQueue queue;
|
||||
queue.addTask(xcrunPath->c_str(), args);
|
||||
queue.execute(nullptr,
|
||||
[&path](sys::ProcessId PID,
|
||||
int returnCode,
|
||||
StringRef output,
|
||||
queue.addTask(xcrunPath->c_str(), args, /*Env=*/llvm::None,
|
||||
/*Context=*/nullptr,
|
||||
/*SeparateErrors=*/true);
|
||||
queue.execute(nullptr, [&path](sys::ProcessId PID, int returnCode,
|
||||
StringRef output, StringRef errors,
|
||||
void *unused) -> sys::TaskFinishedResponse {
|
||||
if (returnCode == 0) {
|
||||
output = output.rtrim();
|
||||
|
||||
Reference in New Issue
Block a user