Revert use of libuv for process execution for 3.28

Wide use of CMake 3.28.{1,0[-rcN]} has uncovered some hangs and crashes
in libuv SIGCHLD handling on some platforms, particularly in virtualization
environments on macOS hosts.  Although the bug does not seem to be in CMake,
we can restore stability in the CMake 3.28 release series for users of such
platforms by reverting our new uses of libuv for process execution.

Revert implementation changes merged by commit 4771544386 (Merge topic
'replace-cmsysprocess-with-cmuvprocesschain', 2023-09-06, v3.28.0-rc1~138),
but keep test suite updates.

Issue: #25414, #25500, #25562, #25589
stage/master/nightly/2024/01/26
Brad King 2024-01-22 14:55:47 -05:00
parent adb3e13d32
commit bcbb212df7
32 changed files with 1185 additions and 1224 deletions

View File

@ -7,8 +7,6 @@
#include <string>
#include <vector>
#include <fcntl.h>
#include "cmsys/FStream.hxx"
#include "cm_sys_stat.h"

View File

@ -135,14 +135,14 @@ private:
std::string cmCTestBZR::LoadInfo()
{
// Run "bzr info" to get the repository info from the work tree.
std::string bzr = this->CommandLineTool;
std::vector<std::string> bzr_info = { bzr, "info" };
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_info[] = { bzr, "info", nullptr };
InfoParser iout(this, "info-out> ");
OutputLogger ierr(this->Log, "info-err> ");
this->RunChild(bzr_info, &iout, &ierr);
// Run "bzr revno" to get the repository revision number from the work tree.
std::vector<std::string> bzr_revno = { bzr, "revno" };
const char* bzr_revno[] = { bzr, "revno", nullptr };
std::string rev;
RevnoParser rout(this, "revno-out> ", rev);
OutputLogger rerr(this->Log, "revno-err> ");
@ -372,18 +372,22 @@ bool cmCTestBZR::UpdateImpl()
// TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
// Use "bzr pull" to update the working tree.
std::vector<std::string> bzr_update;
bzr_update.push_back(this->CommandLineTool);
std::vector<char const*> bzr_update;
bzr_update.push_back(this->CommandLineTool.c_str());
bzr_update.push_back("pull");
cm::append(bzr_update, args);
for (std::string const& arg : args) {
bzr_update.push_back(arg.c_str());
}
bzr_update.push_back(this->URL);
bzr_update.push_back(this->URL.c_str());
bzr_update.push_back(nullptr);
// For some reason bzr uses stderr to display the update status.
OutputLogger out(this->Log, "pull-out> ");
UpdateParser err(this, "pull-err> ");
return this->RunUpdateCommand(bzr_update, &out, &err);
return this->RunUpdateCommand(bzr_update.data(), &out, &err);
}
bool cmCTestBZR::LoadRevisions()
@ -404,9 +408,10 @@ bool cmCTestBZR::LoadRevisions()
}
// Run "bzr log" to get all global revisions of interest.
std::string bzr = this->CommandLineTool;
std::vector<std::string> bzr_log = { bzr, "log", "-v", "-r",
revs, "--xml", this->URL };
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_log[] = {
bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), nullptr
};
{
LogParser out(this, "log-out> ");
OutputLogger err(this->Log, "log-err> ");
@ -462,8 +467,8 @@ private:
bool cmCTestBZR::LoadModifications()
{
// Run "bzr status" which reports local modifications.
std::string bzr = this->CommandLineTool;
std::vector<std::string> bzr_status = { bzr, "status", "-SV" };
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_status[] = { bzr, "status", "-SV", nullptr };
StatusParser out(this, "status-out> ");
OutputLogger err(this->Log, "status-err> ");
this->RunChild(bzr_status, &out, &err);

View File

@ -7,6 +7,8 @@
#include <cstring>
#include <ratio>
#include "cmsys/Process.h"
#include "cmBuildOptions.h"
#include "cmCTest.h"
#include "cmCTestTestHandler.h"
@ -306,11 +308,12 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
return 1;
}
std::vector<std::string> testCommand;
testCommand.push_back(fullPath);
std::vector<const char*> testCommand;
testCommand.push_back(fullPath.c_str());
for (std::string const& testCommandArg : this->TestCommandArgs) {
testCommand.push_back(testCommandArg);
testCommand.push_back(testCommandArg.c_str());
}
testCommand.push_back(nullptr);
std::string outs;
int retval = 0;
// run the test from the this->BuildRunDir if set
@ -346,10 +349,10 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
}
}
bool runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, nullptr,
remainingTime, nullptr);
int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, nullptr,
remainingTime, nullptr);
if (!runTestRes || retval != 0) {
if (runTestRes != cmsysProcess_State_Exited || retval != 0) {
out << "Test command failed: " << testCommand[0] << "\n";
retval = 1;
}

View File

@ -3,17 +3,15 @@
#include "cmCTestBuildHandler.h"
#include <cstdlib>
#include <memory>
#include <ratio>
#include <set>
#include <utility>
#include <cmext/algorithm>
#include <cm3p/uv.h>
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
#include "cmCTest.h"
#include "cmCTestLaunchReporter.h"
@ -26,9 +24,6 @@
#include "cmStringAlgorithms.h"
#include "cmStringReplaceHelper.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
#include "cmValue.h"
#include "cmXMLWriter.h"
@ -425,7 +420,7 @@ int cmCTestBuildHandler::ProcessHandler()
cmStringReplaceHelper colorRemover("\x1b\\[[0-9;]*m", "", nullptr);
this->ColorRemover = &colorRemover;
int retVal = 0;
bool res = true;
int res = cmsysProcess_State_Exited;
if (!this->CTest->GetShowOnly()) {
res = this->RunMakeCommand(makeCommand, &retVal, buildDirectory.c_str(), 0,
ofs);
@ -480,7 +475,7 @@ int cmCTestBuildHandler::ProcessHandler()
}
this->GenerateXMLFooter(xml, elapsed_build_time);
if (!res || retVal || this->TotalErrors > 0) {
if (res != cmsysProcess_State_Exited || retVal || this->TotalErrors > 0) {
cmCTestLog(this->CTest, ERROR_MESSAGE,
"Error(s) when building project" << std::endl);
}
@ -769,10 +764,10 @@ void cmCTestBuildHandler::LaunchHelper::WriteScrapeMatchers(
}
}
bool cmCTestBuildHandler::RunMakeCommand(const std::string& command,
int* retVal, const char* dir,
int timeout, std::ostream& ofs,
Encoding encoding)
int cmCTestBuildHandler::RunMakeCommand(const std::string& command,
int* retVal, const char* dir,
int timeout, std::ostream& ofs,
Encoding encoding)
{
// First generate the command and arguments
std::vector<std::string> args = cmSystemTools::ParseArguments(command);
@ -781,9 +776,19 @@ bool cmCTestBuildHandler::RunMakeCommand(const std::string& command,
return false;
}
std::vector<const char*> argv;
argv.reserve(args.size() + 1);
for (std::string const& arg : args) {
argv.push_back(arg.c_str());
}
argv.push_back(nullptr);
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Run command:", this->Quiet);
for (auto const& arg : args) {
for (char const* arg : argv) {
if (!arg) {
break;
}
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
" \"" << arg << "\"", this->Quiet);
}
@ -795,20 +800,21 @@ bool cmCTestBuildHandler::RunMakeCommand(const std::string& command,
static_cast<void>(launchHelper);
// Now create process object
cmUVProcessChainBuilder builder;
builder.AddCommand(args)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
if (dir) {
builder.SetWorkingDirectory(dir);
}
auto chain = builder.Start();
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
cmsysProcess_SetTimeout(cp, timeout);
cmsysProcess_Execute(cp);
// Initialize tick's
std::string::size_type tick = 0;
static constexpr std::string::size_type tick_len = 1024;
const std::string::size_type tick_len = 1024;
char* data;
int length;
cmProcessOutput processOutput(encoding);
std::string strdata;
cmCTestOptionalLog(
this->CTest, HANDLER_PROGRESS_OUTPUT,
" Each symbol represents "
@ -830,65 +836,39 @@ bool cmCTestBuildHandler::RunMakeCommand(const std::string& command,
this->WarningQuotaReached = false;
this->ErrorQuotaReached = false;
cm::uv_timer_ptr timer;
bool timedOut = false;
timer.init(chain.GetLoop(), &timedOut);
if (timeout > 0) {
timer.start(
[](uv_timer_t* t) {
auto* timedOutPtr = static_cast<bool*>(t->data);
*timedOutPtr = true;
},
timeout * 1000, 0);
}
// For every chunk of data
cm::uv_pipe_ptr outputStream;
bool outFinished = false;
cm::uv_pipe_ptr errorStream;
bool errFinished = false;
auto startRead = [this, &chain, &processOutput, &tick,
&ofs](cm::uv_pipe_ptr& pipe, int stream,
t_BuildProcessingQueueType& queue, bool& finished,
int id) -> std::unique_ptr<cmUVStreamReadHandle> {
pipe.init(chain.GetLoop(), 0);
uv_pipe_open(pipe, stream);
return cmUVStreamRead(
pipe,
[this, &processOutput, &queue, id, &tick, &ofs](std::vector<char> data) {
// Replace '\0' with '\n', since '\0' does not really make sense. This
// is for Visual Studio output
for (auto& c : data) {
if (c == 0) {
c = '\n';
}
}
int res;
while ((res = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
// Replace '\0' with '\n', since '\0' does not really make sense. This is
// for Visual Studio output
for (int cc = 0; cc < length; ++cc) {
if (data[cc] == 0) {
data[cc] = '\n';
}
}
// Process the chunk of data
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata, id);
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len,
ofs, &queue);
},
[this, &processOutput, &queue, id, &tick, &ofs, &finished]() {
std::string strdata;
processOutput.DecodeText(std::string(), strdata, id);
if (!strdata.empty()) {
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len,
ofs, &queue);
}
finished = true;
});
};
auto outputHandle = startRead(outputStream, chain.OutputStream(),
this->BuildProcessingQueue, outFinished, 1);
auto errorHandle =
startRead(errorStream, chain.ErrorStream(),
this->BuildProcessingErrorQueue, errFinished, 2);
while (!timedOut && !(outFinished && errFinished && chain.Finished())) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
// Process the chunk of data
if (res == cmsysProcess_Pipe_STDERR) {
processOutput.DecodeText(data, length, strdata, 1);
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs,
&this->BuildProcessingErrorQueue);
} else {
processOutput.DecodeText(data, length, strdata, 2);
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs,
&this->BuildProcessingQueue);
}
}
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs,
&this->BuildProcessingErrorQueue);
}
processOutput.DecodeText(std::string(), strdata, 2);
if (!strdata.empty()) {
this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs,
&this->BuildProcessingQueue);
}
this->ProcessBuffer(nullptr, 0, tick, tick_len, ofs,
&this->BuildProcessingQueue);
this->ProcessBuffer(nullptr, 0, tick, tick_len, ofs,
@ -899,93 +879,90 @@ bool cmCTestBuildHandler::RunMakeCommand(const std::string& command,
<< std::endl,
this->Quiet);
if (chain.Finished()) {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
if (retVal) {
*retVal = static_cast<int>(status.ExitStatus);
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Command exited with the value: " << *retVal
<< std::endl,
this->Quiet);
// if a non zero return value
if (*retVal) {
// If there was an error running command, report that on the
// dashboard.
if (this->UseCTestLaunch) {
// For launchers, do not record this top-level error if other
// more granular build errors have already been captured.
bool launcherXMLFound = false;
cmsys::Directory launchDir;
launchDir.Load(this->CTestLaunchDir);
unsigned long n = launchDir.GetNumberOfFiles();
for (unsigned long i = 0; i < n; ++i) {
const char* fname = launchDir.GetFile(i);
if (cmHasLiteralSuffix(fname, ".xml")) {
launcherXMLFound = true;
break;
}
}
if (!launcherXMLFound) {
cmCTestLaunchReporter reporter;
reporter.RealArgs = args;
reporter.ComputeFileNames();
reporter.ExitCode = *retVal;
reporter.Status = status;
// Use temporary BuildLog file to populate this error for
// CDash.
ofs.flush();
reporter.LogOut = this->LogFileNames["Build"];
reporter.LogOut += ".tmp";
reporter.WriteXML();
}
} else {
cmCTestBuildErrorWarning errorwarning;
errorwarning.LineNumber = 0;
errorwarning.LogLine = 1;
errorwarning.Text = cmStrCat(
"*** WARNING non-zero return value in ctest from: ", args[0]);
errorwarning.PreContext.clear();
errorwarning.PostContext.clear();
errorwarning.Error = false;
this->ErrorsAndWarnings.push_back(std::move(errorwarning));
this->TotalWarnings++;
// Properly handle output of the build command
cmsysProcess_WaitForExit(cp, nullptr);
int result = cmsysProcess_GetState(cp);
if (result == cmsysProcess_State_Exited) {
if (retVal) {
*retVal = cmsysProcess_GetExitValue(cp);
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Command exited with the value: " << *retVal
<< std::endl,
this->Quiet);
// if a non zero return value
if (*retVal) {
// If there was an error running command, report that on the
// dashboard.
if (this->UseCTestLaunch) {
// For launchers, do not record this top-level error if other
// more granular build errors have already been captured.
bool launcherXMLFound = false;
cmsys::Directory launchDir;
launchDir.Load(this->CTestLaunchDir);
unsigned long n = launchDir.GetNumberOfFiles();
for (unsigned long i = 0; i < n; ++i) {
const char* fname = launchDir.GetFile(i);
if (cmHasLiteralSuffix(fname, ".xml")) {
launcherXMLFound = true;
break;
}
}
if (!launcherXMLFound) {
cmCTestLaunchReporter reporter;
reporter.RealArgs = args;
reporter.ComputeFileNames();
reporter.ExitCode = *retVal;
reporter.Process = cp;
// Use temporary BuildLog file to populate this error for CDash.
ofs.flush();
reporter.LogOut = this->LogFileNames["Build"];
reporter.LogOut += ".tmp";
reporter.WriteXML();
}
} else {
cmCTestBuildErrorWarning errorwarning;
errorwarning.LineNumber = 0;
errorwarning.LogLine = 1;
errorwarning.Text = cmStrCat(
"*** WARNING non-zero return value in ctest from: ", argv[0]);
errorwarning.PreContext.clear();
errorwarning.PostContext.clear();
errorwarning.Error = false;
this->ErrorsAndWarnings.push_back(std::move(errorwarning));
this->TotalWarnings++;
}
break;
case cmUVProcessChain::ExceptionCode::Spawn: {
// If there was an error running command, report that on the dashboard.
cmCTestBuildErrorWarning errorwarning;
errorwarning.LineNumber = 0;
errorwarning.LogLine = 1;
errorwarning.Text =
cmStrCat("*** ERROR executing: ", exception.second);
errorwarning.PreContext.clear();
errorwarning.PostContext.clear();
errorwarning.Error = true;
this->ErrorsAndWarnings.push_back(std::move(errorwarning));
this->TotalErrors++;
cmCTestLog(this->CTest, ERROR_MESSAGE,
"There was an error: " << exception.second << std::endl);
} break;
default:
if (retVal) {
*retVal = status.TermSignal;
cmCTestOptionalLog(
this->CTest, WARNING,
"There was an exception: " << *retVal << std::endl, this->Quiet);
}
break;
}
}
} else {
} else if (result == cmsysProcess_State_Exception) {
if (retVal) {
*retVal = cmsysProcess_GetExitException(cp);
cmCTestOptionalLog(this->CTest, WARNING,
"There was an exception: " << *retVal << std::endl,
this->Quiet);
}
} else if (result == cmsysProcess_State_Expired) {
cmCTestOptionalLog(this->CTest, WARNING,
"There was a timeout" << std::endl, this->Quiet);
} else if (result == cmsysProcess_State_Error) {
// If there was an error running command, report that on the dashboard.
cmCTestBuildErrorWarning errorwarning;
errorwarning.LineNumber = 0;
errorwarning.LogLine = 1;
errorwarning.Text =
cmStrCat("*** ERROR executing: ", cmsysProcess_GetErrorString(cp));
errorwarning.PreContext.clear();
errorwarning.PostContext.clear();
errorwarning.Error = true;
this->ErrorsAndWarnings.push_back(std::move(errorwarning));
this->TotalErrors++;
cmCTestLog(this->CTest, ERROR_MESSAGE,
"There was an error: " << cmsysProcess_GetErrorString(cp)
<< std::endl);
}
return true;
cmsysProcess_Delete(cp);
return result;
}
// ######################################################################

View File

@ -53,9 +53,9 @@ private:
//! Run command specialized for make and configure. Returns process status
// and retVal is return value or exception.
bool RunMakeCommand(const std::string& command, int* retVal, const char* dir,
int timeout, std::ostream& ofs,
Encoding encoding = cmProcessOutput::Auto);
int RunMakeCommand(const std::string& command, int* retVal, const char* dir,
int timeout, std::ostream& ofs,
Encoding encoding = cmProcessOutput::Auto);
enum
{

View File

@ -5,7 +5,6 @@
#include <utility>
#include <cm/string_view>
#include <cmext/algorithm>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
@ -90,15 +89,18 @@ bool cmCTestCVS::UpdateImpl()
}
// Run "cvs update" to update the work tree.
std::vector<std::string> cvs_update;
cvs_update.push_back(this->CommandLineTool);
std::vector<char const*> cvs_update;
cvs_update.push_back(this->CommandLineTool.c_str());
cvs_update.push_back("-z3");
cvs_update.push_back("update");
cm::append(cvs_update, args);
for (std::string const& arg : args) {
cvs_update.push_back(arg.c_str());
}
cvs_update.push_back(nullptr);
UpdateParser out(this, "up-out> ");
UpdateParser err(this, "up-err> ");
return this->RunUpdateCommand(cvs_update, &out, &err);
return this->RunUpdateCommand(cvs_update.data(), &out, &err);
}
class cmCTestCVS::LogParser : public cmCTestVC::LineParser
@ -219,8 +221,10 @@ void cmCTestCVS::LoadRevisions(std::string const& file, const char* branchFlag,
cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);
// Run "cvs log" to get revisions of this file on this branch.
std::string cvs = this->CommandLineTool;
std::vector<std::string> cvs_log = { cvs, "log", "-N", branchFlag, file };
const char* cvs = this->CommandLineTool.c_str();
const char* cvs_log[] = {
cvs, "log", "-N", branchFlag, file.c_str(), nullptr
};
LogParser out(this, "log-out> ", revisions);
OutputLogger err(this->Log, "log-err> ");

View File

@ -45,7 +45,7 @@ int cmCTestConfigureHandler::ProcessHandler()
auto elapsed_time_start = std::chrono::steady_clock::now();
std::string output;
int retVal = 0;
bool res = false;
int res = 0;
if (!this->CTest->GetShowOnly()) {
cmGeneratedFileStream os;
if (!this->StartResultingXML(cmCTest::PartConfigure, "Configure", os)) {

View File

@ -9,7 +9,6 @@
#include <cstring>
#include <iomanip>
#include <iterator>
#include <memory>
#include <ratio>
#include <sstream>
#include <type_traits>
@ -19,6 +18,7 @@
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmCTest.h"
@ -33,7 +33,6 @@
#include "cmParsePHPCoverage.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVProcessChain.h"
#include "cmWorkingDirectory.h"
#include "cmXMLWriter.h"
@ -41,6 +40,85 @@ class cmMakefile;
#define SAFEDIV(x, y) (((y) != 0) ? ((x) / (y)) : (0))
class cmCTestRunProcess
{
public:
cmCTestRunProcess()
{
this->Process = cmsysProcess_New();
this->PipeState = -1;
this->TimeOut = cmDuration(-1);
}
~cmCTestRunProcess()
{
if (this->PipeState != -1 && this->PipeState != cmsysProcess_Pipe_None &&
this->PipeState != cmsysProcess_Pipe_Timeout) {
this->WaitForExit();
}
cmsysProcess_Delete(this->Process);
}
cmCTestRunProcess(const cmCTestRunProcess&) = delete;
cmCTestRunProcess& operator=(const cmCTestRunProcess&) = delete;
void SetCommand(const char* command)
{
this->CommandLineStrings.clear();
this->CommandLineStrings.emplace_back(command);
}
void AddArgument(const char* arg)
{
if (arg) {
this->CommandLineStrings.emplace_back(arg);
}
}
void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir; }
void SetTimeout(cmDuration t) { this->TimeOut = t; }
bool StartProcess()
{
std::vector<const char*> args;
args.reserve(this->CommandLineStrings.size());
for (std::string const& cl : this->CommandLineStrings) {
args.push_back(cl.c_str());
}
args.push_back(nullptr); // null terminate
cmsysProcess_SetCommand(this->Process, args.data());
if (!this->WorkingDirectory.empty()) {
cmsysProcess_SetWorkingDirectory(this->Process,
this->WorkingDirectory.c_str());
}
cmsysProcess_SetOption(this->Process, cmsysProcess_Option_HideWindow, 1);
if (this->TimeOut >= cmDuration::zero()) {
cmsysProcess_SetTimeout(this->Process, this->TimeOut.count());
}
cmsysProcess_Execute(this->Process);
this->PipeState = cmsysProcess_GetState(this->Process);
// if the process is running or exited return true
return this->PipeState == cmsysProcess_State_Executing ||
this->PipeState == cmsysProcess_State_Exited;
}
void SetStdoutFile(const char* fname)
{
cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDOUT, fname);
}
void SetStderrFile(const char* fname)
{
cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDERR, fname);
}
int WaitForExit(double* timeout = nullptr)
{
this->PipeState = cmsysProcess_WaitForExit(this->Process, timeout);
return this->PipeState;
}
int GetProcessState() const { return this->PipeState; }
private:
int PipeState;
cmsysProcess* Process;
std::vector<std::string> CommandLineStrings;
std::string WorkingDirectory;
cmDuration TimeOut;
};
cmCTestCoverageHandler::cmCTestCoverageHandler() = default;
void cmCTestCoverageHandler::Initialize()
@ -1862,35 +1940,34 @@ int cmCTestCoverageHandler::RunBullseyeCommand(
cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find :" << cmd << "\n");
return 0;
}
std::vector<std::string> args{ cmd };
if (arg) {
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Run : " << program << " " << arg << "\n", this->Quiet);
args.emplace_back(arg);
} else {
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Run : " << program << "\n", this->Quiet);
}
// create a process object and start it
cmUVProcessChainBuilder builder;
cmCTestRunProcess runCoverageSrc;
runCoverageSrc.SetCommand(program.c_str());
runCoverageSrc.AddArgument(arg);
std::string stdoutFile =
cmStrCat(cont->BinaryDir, "/Testing/Temporary/",
this->GetCTestInstance()->GetCurrentTag(), '-', cmd);
std::string stderrFile = stdoutFile;
stdoutFile += ".stdout";
stderrFile += ".stderr";
std::unique_ptr<FILE, int (*)(FILE*)> stdoutHandle(
cmsys::SystemTools::Fopen(stdoutFile, "w"), fclose);
std::unique_ptr<FILE, int (*)(FILE*)> stderrHandle(
cmsys::SystemTools::Fopen(stderrFile, "w"), fclose);
builder.AddCommand(args)
.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT,
stdoutHandle.get())
.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR,
stderrHandle.get());
runCoverageSrc.SetStdoutFile(stdoutFile.c_str());
runCoverageSrc.SetStderrFile(stderrFile.c_str());
if (!runCoverageSrc.StartProcess()) {
cmCTestLog(this->CTest, ERROR_MESSAGE,
"Could not run : " << program << " " << arg << "\n"
<< "kwsys process state : "
<< runCoverageSrc.GetProcessState());
return 0;
}
// since we set the output file names wait for it to end
auto chain = builder.Start();
chain.Wait();
runCoverageSrc.WaitForExit();
outputFile = stdoutFile;
return 1;
}

View File

@ -9,9 +9,8 @@
#include <utility>
#include <vector>
#include <cmext/algorithm>
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
#include "cmCTest.h"
#include "cmCTestVC.h"
@ -19,7 +18,6 @@
#include "cmProcessOutput.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVProcessChain.h"
#include "cmValue.h"
static unsigned int cmCTestGITVersion(unsigned int epic, unsigned int major,
@ -60,9 +58,9 @@ private:
std::string cmCTestGIT::GetWorkingRevision()
{
// Run plumbing "git rev-list" to get work tree revision.
std::string git = this->CommandLineTool;
std::vector<std::string> git_rev_list = { git, "rev-list", "-n",
"1", "HEAD", "--" };
const char* git = this->CommandLineTool.c_str();
const char* git_rev_list[] = { git, "rev-list", "-n", "1",
"HEAD", "--", nullptr };
std::string rev;
OneLineParser out(this, "rl-out> ", rev);
OutputLogger err(this->Log, "rl-err> ");
@ -94,13 +92,13 @@ std::string cmCTestGIT::FindGitDir()
std::string git_dir;
// Run "git rev-parse --git-dir" to locate the real .git directory.
std::string git = this->CommandLineTool;
std::vector<std::string> git_rev_parse = { git, "rev-parse", "--git-dir" };
const char* git = this->CommandLineTool.c_str();
char const* git_rev_parse[] = { git, "rev-parse", "--git-dir", nullptr };
std::string git_dir_line;
OneLineParser rev_parse_out(this, "rev-parse-out> ", git_dir_line);
OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err,
std::string{}, cmProcessOutput::UTF8)) {
if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, nullptr,
cmProcessOutput::UTF8)) {
git_dir = git_dir_line;
}
if (git_dir.empty()) {
@ -119,10 +117,11 @@ std::string cmCTestGIT::FindGitDir()
std::string cygpath_exe =
cmStrCat(cmSystemTools::GetFilenamePath(git), "/cygpath.exe");
if (cmSystemTools::FileExists(cygpath_exe)) {
std::vector<std::string> cygpath = { cygpath_exe, "-w", git_dir };
char const* cygpath[] = { cygpath_exe.c_str(), "-w", git_dir.c_str(),
0 };
OneLineParser cygpath_out(this, "cygpath-out> ", git_dir_line);
OutputLogger cygpath_err(this->Log, "cygpath-err> ");
if (this->RunChild(cygpath, &cygpath_out, &cygpath_err, std::string{},
if (this->RunChild(cygpath, &cygpath_out, &cygpath_err, nullptr,
cmProcessOutput::UTF8)) {
git_dir = git_dir_line;
}
@ -137,12 +136,12 @@ std::string cmCTestGIT::FindTopDir()
std::string top_dir = this->SourceDirectory;
// Run "git rev-parse --show-cdup" to locate the top of the tree.
std::string git = this->CommandLineTool;
std::vector<std::string> git_rev_parse = { git, "rev-parse", "--show-cdup" };
const char* git = this->CommandLineTool.c_str();
char const* git_rev_parse[] = { git, "rev-parse", "--show-cdup", nullptr };
std::string cdup;
OneLineParser rev_parse_out(this, "rev-parse-out> ", cdup);
OutputLogger rev_parse_err(this->Log, "rev-parse-err> ");
if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, "",
if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, nullptr,
cmProcessOutput::UTF8) &&
!cdup.empty()) {
top_dir += "/";
@ -154,10 +153,10 @@ std::string cmCTestGIT::FindTopDir()
bool cmCTestGIT::UpdateByFetchAndReset()
{
std::string git = this->CommandLineTool;
const char* git = this->CommandLineTool.c_str();
// Use "git fetch" to get remote commits.
std::vector<std::string> git_fetch;
std::vector<char const*> git_fetch;
git_fetch.push_back(git);
git_fetch.push_back("fetch");
@ -167,12 +166,17 @@ bool cmCTestGIT::UpdateByFetchAndReset()
opts = this->CTest->GetCTestConfiguration("GITUpdateOptions");
}
std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
cm::append(git_fetch, args);
for (std::string const& arg : args) {
git_fetch.push_back(arg.c_str());
}
// Sentinel argument.
git_fetch.push_back(nullptr);
// Fetch upstream refs.
OutputLogger fetch_out(this->Log, "fetch-out> ");
OutputLogger fetch_err(this->Log, "fetch-err> ");
if (!this->RunUpdateCommand(git_fetch, &fetch_out, &fetch_err)) {
if (!this->RunUpdateCommand(git_fetch.data(), &fetch_out, &fetch_err)) {
return false;
}
@ -203,22 +207,25 @@ bool cmCTestGIT::UpdateByFetchAndReset()
}
// Reset the local branch to point at that tracked from upstream.
std::vector<std::string> git_reset = { git, "reset", "--hard", sha1 };
char const* git_reset[] = { git, "reset", "--hard", sha1.c_str(), nullptr };
OutputLogger reset_out(this->Log, "reset-out> ");
OutputLogger reset_err(this->Log, "reset-err> ");
return this->RunChild(git_reset, &reset_out, &reset_err);
return this->RunChild(&git_reset[0], &reset_out, &reset_err);
}
bool cmCTestGIT::UpdateByCustom(std::string const& custom)
{
cmList git_custom_command{ custom, cmList::EmptyElements::Yes };
std::vector<std::string> git_custom;
git_custom.reserve(git_custom_command.size());
cm::append(git_custom, git_custom_command);
std::vector<char const*> git_custom;
git_custom.reserve(git_custom_command.size() + 1);
for (std::string const& i : git_custom_command) {
git_custom.push_back(i.c_str());
}
git_custom.push_back(nullptr);
OutputLogger custom_out(this->Log, "custom-out> ");
OutputLogger custom_err(this->Log, "custom-err> ");
return this->RunUpdateCommand(git_custom, &custom_out, &custom_err);
return this->RunUpdateCommand(git_custom.data(), &custom_out, &custom_err);
}
bool cmCTestGIT::UpdateInternal()
@ -237,14 +244,13 @@ bool cmCTestGIT::UpdateImpl()
}
std::string top_dir = this->FindTopDir();
std::string git = this->CommandLineTool;
std::string recursive = "--recursive";
std::string sync_recursive = "--recursive";
const char* git = this->CommandLineTool.c_str();
const char* recursive = "--recursive";
const char* sync_recursive = "--recursive";
// Git < 1.6.5 did not support submodule --recursive
bool support_recursive = true;
if (this->GetGitVersion() < cmCTestGITVersion(1, 6, 5, 0)) {
support_recursive = false;
recursive = nullptr;
// No need to require >= 1.6.5 if there are no submodules.
if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
this->Log << "Git < 1.6.5 cannot update submodules recursively\n";
@ -252,9 +258,8 @@ bool cmCTestGIT::UpdateImpl()
}
// Git < 1.8.1 did not support sync --recursive
bool support_sync_recursive = true;
if (this->GetGitVersion() < cmCTestGITVersion(1, 8, 1, 0)) {
support_sync_recursive = false;
sync_recursive = nullptr;
// No need to require >= 1.8.1 if there are no submodules.
if (cmSystemTools::FileExists(top_dir + "/.gitmodules")) {
this->Log << "Git < 1.8.1 cannot synchronize submodules recursively\n";
@ -269,39 +274,35 @@ bool cmCTestGIT::UpdateImpl()
std::string init_submodules =
this->CTest->GetCTestConfiguration("GITInitSubmodules");
if (cmIsOn(init_submodules)) {
std::vector<std::string> git_submodule_init = { git, "submodule", "init" };
char const* git_submodule_init[] = { git, "submodule", "init", nullptr };
ret = this->RunChild(git_submodule_init, &submodule_out, &submodule_err,
top_dir);
top_dir.c_str());
if (!ret) {
return false;
}
}
std::vector<std::string> git_submodule_sync = { git, "submodule", "sync" };
if (support_sync_recursive) {
git_submodule_sync.push_back(sync_recursive);
}
char const* git_submodule_sync[] = { git, "submodule", "sync",
sync_recursive, nullptr };
ret = this->RunChild(git_submodule_sync, &submodule_out, &submodule_err,
top_dir);
top_dir.c_str());
if (!ret) {
return false;
}
std::vector<std::string> git_submodule = { git, "submodule", "update" };
if (support_recursive) {
git_submodule.push_back(recursive);
}
char const* git_submodule[] = { git, "submodule", "update", recursive,
nullptr };
return this->RunChild(git_submodule, &submodule_out, &submodule_err,
top_dir);
top_dir.c_str());
}
unsigned int cmCTestGIT::GetGitVersion()
{
if (!this->CurrentGitVersion) {
std::string git = this->CommandLineTool;
std::vector<std::string> git_version = { git, "--version" };
const char* git = this->CommandLineTool.c_str();
char const* git_version[] = { git, "--version", nullptr };
std::string version;
OneLineParser version_out(this, "version-out> ", version);
OutputLogger version_err(this->Log, "version-err> ");
@ -604,49 +605,50 @@ bool cmCTestGIT::LoadRevisions()
{
// Use 'git rev-list ... | git diff-tree ...' to get revisions.
std::string range = this->OldRevision + ".." + this->NewRevision;
std::string git = this->CommandLineTool;
std::vector<std::string> git_rev_list = { git, "rev-list", "--reverse",
range, "--" };
std::vector<std::string> git_diff_tree = {
git, "diff-tree", "--stdin", "--always",
"-z", "-r", "--pretty=raw", "--encoding=utf-8"
const char* git = this->CommandLineTool.c_str();
const char* git_rev_list[] = { git, "rev-list", "--reverse",
range.c_str(), "--", nullptr };
const char* git_diff_tree[] = {
git, "diff-tree", "--stdin", "--always", "-z",
"-r", "--pretty=raw", "--encoding=utf-8", nullptr
};
this->Log << cmCTestGIT::ComputeCommandLine(git_rev_list) << " | "
<< cmCTestGIT::ComputeCommandLine(git_diff_tree) << "\n";
cmUVProcessChainBuilder builder;
builder.AddCommand(git_rev_list)
.AddCommand(git_diff_tree)
.SetWorkingDirectory(this->SourceDirectory);
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_AddCommand(cp, git_rev_list);
cmsysProcess_AddCommand(cp, git_diff_tree);
cmsysProcess_SetWorkingDirectory(cp, this->SourceDirectory.c_str());
CommitParser out(this, "dt-out> ");
OutputLogger err(this->Log, "dt-err> ");
cmCTestGIT::RunProcess(builder, &out, &err, cmProcessOutput::UTF8);
cmCTestGIT::RunProcess(cp, &out, &err, cmProcessOutput::UTF8);
// Send one extra zero-byte to terminate the last record.
out.Process("", 1);
cmsysProcess_Delete(cp);
return true;
}
bool cmCTestGIT::LoadModifications()
{
std::string git = this->CommandLineTool;
const char* git = this->CommandLineTool.c_str();
// Use 'git update-index' to refresh the index w.r.t. the work tree.
std::vector<std::string> git_update_index = { git, "update-index",
"--refresh" };
const char* git_update_index[] = { git, "update-index", "--refresh",
nullptr };
OutputLogger ui_out(this->Log, "ui-out> ");
OutputLogger ui_err(this->Log, "ui-err> ");
this->RunChild(git_update_index, &ui_out, &ui_err, "",
this->RunChild(git_update_index, &ui_out, &ui_err, nullptr,
cmProcessOutput::UTF8);
// Use 'git diff-index' to get modified files.
std::vector<std::string> git_diff_index = { git, "diff-index", "-z", "HEAD",
"--" };
const char* git_diff_index[] = { git, "diff-index", "-z",
"HEAD", "--", nullptr };
DiffParser out(this, "di-out> ");
OutputLogger err(this->Log, "di-err> ");
this->RunChild(git_diff_index, &out, &err, "", cmProcessOutput::UTF8);
this->RunChild(git_diff_index, &out, &err, nullptr, cmProcessOutput::UTF8);
for (Change const& c : out.Changes) {
this->DoModification(PathModified, c.Path);

View File

@ -95,8 +95,8 @@ private:
std::string cmCTestHG::GetWorkingRevision()
{
// Run plumbing "hg identify" to get work tree revision.
std::string hg = this->CommandLineTool;
std::vector<std::string> hg_identify = { hg, "identify", "-i" };
const char* hg = this->CommandLineTool.c_str();
const char* hg_identify[] = { hg, "identify", "-i", nullptr };
std::string rev;
IdentifyParser out(this, "rev-out> ", rev);
OutputLogger err(this->Log, "rev-err> ");
@ -127,16 +127,16 @@ bool cmCTestHG::UpdateImpl()
{
// Use "hg pull" followed by "hg update" to update the working tree.
{
std::string hg = this->CommandLineTool;
std::vector<std::string> hg_pull = { hg, "pull", "-v" };
const char* hg = this->CommandLineTool.c_str();
const char* hg_pull[] = { hg, "pull", "-v", nullptr };
OutputLogger out(this->Log, "pull-out> ");
OutputLogger err(this->Log, "pull-err> ");
this->RunChild(hg_pull, &out, &err);
this->RunChild(&hg_pull[0], &out, &err);
}
// TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
std::vector<std::string> hg_update;
std::vector<char const*> hg_update;
hg_update.push_back(this->CommandLineTool.c_str());
hg_update.push_back("update");
hg_update.push_back("-v");
@ -147,11 +147,16 @@ bool cmCTestHG::UpdateImpl()
opts = this->CTest->GetCTestConfiguration("HGUpdateOptions");
}
std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
cm::append(hg_update, args);
for (std::string const& arg : args) {
hg_update.push_back(arg.c_str());
}
// Sentinel argument.
hg_update.push_back(nullptr);
OutputLogger out(this->Log, "update-out> ");
OutputLogger err(this->Log, "update-err> ");
return this->RunUpdateCommand(hg_update, &out, &err);
return this->RunUpdateCommand(hg_update.data(), &out, &err);
}
class cmCTestHG::LogParser
@ -272,8 +277,8 @@ bool cmCTestHG::LoadRevisions()
// the project has spaces in the path. Also, they may not have
// proper XML escapes.
std::string range = this->OldRevision + ":" + this->NewRevision;
std::string hg = this->CommandLineTool;
std::string hgXMLTemplate = "<logentry\n"
const char* hg = this->CommandLineTool.c_str();
const char* hgXMLTemplate = "<logentry\n"
" revision=\"{node|short}\">\n"
" <author>{author|person}</author>\n"
" <email>{author|email}</email>\n"
@ -283,8 +288,10 @@ bool cmCTestHG::LoadRevisions()
" <file_adds>{file_adds}</file_adds>\n"
" <file_dels>{file_dels}</file_dels>\n"
"</logentry>\n";
std::vector<std::string> hg_log = { hg, "log", "--removed", "-r",
range, "--template", hgXMLTemplate };
const char* hg_log[] = {
hg, "log", "--removed", "-r", range.c_str(),
"--template", hgXMLTemplate, nullptr
};
LogParser out(this, "log-out> ");
out.Process("<?xml version=\"1.0\"?>\n"
@ -298,8 +305,8 @@ bool cmCTestHG::LoadRevisions()
bool cmCTestHG::LoadModifications()
{
// Use 'hg status' to get modified files.
std::string hg = this->CommandLineTool;
std::vector<std::string> hg_status = { hg, "status" };
const char* hg = this->CommandLineTool.c_str();
const char* hg_status[] = { hg, "status", nullptr };
StatusParser out(this, "status-out> ");
OutputLogger err(this->Log, "status-err> ");
this->RunChild(hg_status, &out, &err);

View File

@ -2,15 +2,11 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestLaunch.h"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <memory>
#include <utility>
#include <cm3p/uv.h>
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmCTestLaunchReporter.h"
@ -21,9 +17,6 @@
#include "cmStateSnapshot.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
#include "cmake.h"
#ifdef _WIN32
@ -35,6 +28,8 @@
cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
{
this->Process = nullptr;
if (!this->ParseArguments(argc, argv)) {
return;
}
@ -45,9 +40,13 @@ cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
this->ScrapeRulesLoaded = false;
this->HaveOut = false;
this->HaveErr = false;
this->Process = cmsysProcess_New();
}
cmCTestLaunch::~cmCTestLaunch() = default;
cmCTestLaunch::~cmCTestLaunch()
{
cmsysProcess_Delete(this->Process);
}
bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
{
@ -114,12 +113,15 @@ bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
// Extract the real command line.
if (arg0) {
for (int i = 0; i < argc - arg0; ++i) {
this->RealArgV.emplace_back((argv + arg0)[i]);
this->HandleRealArg((argv + arg0)[i]);
this->RealArgC = argc - arg0;
this->RealArgV = argv + arg0;
for (int i = 0; i < this->RealArgC; ++i) {
this->HandleRealArg(this->RealArgV[i]);
}
return true;
}
this->RealArgC = 0;
this->RealArgV = nullptr;
std::cerr << "No launch/command separator ('--') found!\n";
return false;
}
@ -149,19 +151,17 @@ void cmCTestLaunch::RunChild()
}
// Prepare to run the real command.
cmUVProcessChainBuilder builder;
builder.AddCommand(this->RealArgV);
cmsysProcess* cp = this->Process;
cmsysProcess_SetCommand(cp, this->RealArgV);
cmsys::ofstream fout;
cmsys::ofstream ferr;
if (this->Reporter.Passthru) {
// In passthru mode we just share the output pipes.
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, stdout)
.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, stderr);
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
} else {
// In full mode we record the child output pipes to log files.
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
fout.open(this->Reporter.LogOut.c_str(), std::ios::out | std::ios::binary);
ferr.open(this->Reporter.LogErr.c_str(), std::ios::out | std::ios::binary);
}
@ -174,65 +174,51 @@ void cmCTestLaunch::RunChild()
#endif
// Run the real command.
auto chain = builder.Start();
cmsysProcess_Execute(cp);
// Record child stdout and stderr if necessary.
cm::uv_pipe_ptr outPipe;
cm::uv_pipe_ptr errPipe;
bool outFinished = true;
bool errFinished = true;
cmProcessOutput processOutput;
std::unique_ptr<cmUVStreamReadHandle> outputHandle;
std::unique_ptr<cmUVStreamReadHandle> errorHandle;
if (!this->Reporter.Passthru) {
auto beginRead = [&chain, &processOutput](
cm::uv_pipe_ptr& pipe, int stream, std::ostream& out,
cmsys::ofstream& file, bool& haveData, bool& finished,
int id) -> std::unique_ptr<cmUVStreamReadHandle> {
pipe.init(chain.GetLoop(), 0);
uv_pipe_open(pipe, stream);
finished = false;
return cmUVStreamRead(
pipe,
[&processOutput, &out, &file, id, &haveData](std::vector<char> data) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata, id);
file.write(strdata.c_str(), strdata.size());
out.write(strdata.c_str(), strdata.size());
haveData = true;
},
[&processOutput, &out, &file, &finished, id]() {
std::string strdata;
processOutput.DecodeText(std::string(), strdata, id);
if (!strdata.empty()) {
file.write(strdata.c_str(), strdata.size());
out.write(strdata.c_str(), strdata.size());
}
finished = true;
});
};
outputHandle = beginRead(outPipe, chain.OutputStream(), std::cout, fout,
this->HaveOut, outFinished, 1);
errorHandle = beginRead(errPipe, chain.ErrorStream(), std::cerr, ferr,
this->HaveErr, errFinished, 2);
char* data = nullptr;
int length = 0;
cmProcessOutput processOutput;
std::string strdata;
while (int p = cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
if (p == cmsysProcess_Pipe_STDOUT) {
processOutput.DecodeText(data, length, strdata, 1);
fout.write(strdata.c_str(), strdata.size());
std::cout.write(strdata.c_str(), strdata.size());
this->HaveOut = true;
} else if (p == cmsysProcess_Pipe_STDERR) {
processOutput.DecodeText(data, length, strdata, 2);
ferr.write(strdata.c_str(), strdata.size());
std::cerr.write(strdata.c_str(), strdata.size());
this->HaveErr = true;
}
}
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
fout.write(strdata.c_str(), strdata.size());
std::cout.write(strdata.c_str(), strdata.size());
}
processOutput.DecodeText(std::string(), strdata, 2);
if (!strdata.empty()) {
ferr.write(strdata.c_str(), strdata.size());
std::cerr.write(strdata.c_str(), strdata.size());
}
}
// Wait for the real command to finish.
while (!(chain.Finished() && outFinished && errFinished)) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
}
this->Reporter.Status = chain.GetStatus(0);
if (this->Reporter.Status.GetException().first ==
cmUVProcessChain::ExceptionCode::Spawn) {
this->Reporter.ExitCode = 1;
} else {
this->Reporter.ExitCode =
static_cast<int>(this->Reporter.Status.ExitStatus);
}
cmsysProcess_WaitForExit(cp, nullptr);
this->Reporter.ExitCode = cmsysProcess_GetExitValue(cp);
}
int cmCTestLaunch::Run()
{
if (!this->Process) {
std::cerr << "Could not allocate cmsysProcess instance!\n";
return -1;
}
this->RunChild();
if (this->CheckResults()) {
@ -240,6 +226,7 @@ int cmCTestLaunch::Run()
}
this->LoadConfig();
this->Reporter.Process = this->Process;
this->Reporter.WriteXML();
return this->Reporter.ExitCode;

View File

@ -43,12 +43,15 @@ private:
bool ParseArguments(int argc, const char* const* argv);
// The real command line appearing after launcher arguments.
std::vector<std::string> RealArgV;
int RealArgC;
const char* const* RealArgV;
// The real command line after response file expansion.
std::vector<std::string> RealArgs;
void HandleRealArg(const char* arg);
struct cmsysProcess_s* Process;
// Whether or not any data have been written to stdout or stderr.
bool HaveOut;
bool HaveErr;

View File

@ -2,9 +2,8 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestLaunchReporter.h"
#include <utility>
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmCryptoHash.h"
@ -23,7 +22,6 @@
cmCTestLaunchReporter::cmCTestLaunchReporter()
{
this->Passthru = true;
this->Status.Finished = true;
this->ExitCode = 1;
this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
@ -233,23 +231,35 @@ void cmCTestLaunchReporter::WriteXMLResult(cmXMLElement& e2)
// ExitCondition
cmXMLElement e4(e3, "ExitCondition");
if (this->Status.Finished) {
auto exception = this->Status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
e4.Content(this->ExitCode);
break;
case cmUVProcessChain::ExceptionCode::Spawn:
e4.Content("Error administrating child process: ");
e4.Content(exception.second);
break;
default:
e4.Content("Terminated abnormally: ");
e4.Content(exception.second);
break;
}
} else {
e4.Content("Killed when timeout expired");
cmsysProcess* cp = this->Process;
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Starting:
e4.Content("No process has been executed");
break;
case cmsysProcess_State_Executing:
e4.Content("The process is still executing");
break;
case cmsysProcess_State_Disowned:
e4.Content("Disowned");
break;
case cmsysProcess_State_Killed:
e4.Content("Killed by parent");
break;
case cmsysProcess_State_Expired:
e4.Content("Killed when timeout expired");
break;
case cmsysProcess_State_Exited:
e4.Content(this->ExitCode);
break;
case cmsysProcess_State_Exception:
e4.Content("Terminated abnormally: ");
e4.Content(cmsysProcess_GetExceptionString(cp));
break;
case cmsysProcess_State_Error:
e4.Content("Error administrating child process: ");
e4.Content(cmsysProcess_GetErrorString(cp));
break;
}
}

View File

@ -10,8 +10,6 @@
#include "cmsys/RegularExpression.hxx"
#include "cmUVProcessChain.h"
class cmXMLElement;
/** \class cmCTestLaunchReporter
@ -50,7 +48,7 @@ public:
void ComputeFileNames();
bool Passthru;
cmUVProcessChain::Status Status;
struct cmsysProcess_s* Process;
int ExitCode;
// Temporary log files for stdout and stderr of real command.

View File

@ -149,16 +149,17 @@ cmCTestP4::User cmCTestP4::GetUserData(const std::string& username)
auto it = this->Users.find(username);
if (it == this->Users.end()) {
std::vector<std::string> p4_users;
std::vector<char const*> p4_users;
this->SetP4Options(p4_users);
p4_users.push_back("users");
p4_users.push_back("-m");
p4_users.push_back("1");
p4_users.push_back(username);
p4_users.push_back(username.c_str());
p4_users.push_back(nullptr);
UserParser out(this, "users-out> ");
OutputLogger err(this->Log, "users-err> ");
this->RunChild(p4_users, &out, &err);
this->RunChild(p4_users.data(), &out, &err);
// The user should now be added to the map. Search again.
it = this->Users.find(username);
@ -302,10 +303,10 @@ private:
}
};
void cmCTestP4::SetP4Options(std::vector<std::string>& CommandOptions)
void cmCTestP4::SetP4Options(std::vector<char const*>& CommandOptions)
{
if (this->P4Options.empty()) {
std::string p4 = this->CommandLineTool;
const char* p4 = this->CommandLineTool.c_str();
this->P4Options.emplace_back(p4);
// The CTEST_P4_CLIENT variable sets the P4 client used when issuing
@ -327,12 +328,15 @@ void cmCTestP4::SetP4Options(std::vector<std::string>& CommandOptions)
cm::append(this->P4Options, cmSystemTools::ParseArguments(opts));
}
CommandOptions = this->P4Options;
CommandOptions.clear();
for (std::string const& o : this->P4Options) {
CommandOptions.push_back(o.c_str());
}
}
std::string cmCTestP4::GetWorkingRevision()
{
std::vector<std::string> p4_identify;
std::vector<char const*> p4_identify;
this->SetP4Options(p4_identify);
p4_identify.push_back("changes");
@ -341,13 +345,14 @@ std::string cmCTestP4::GetWorkingRevision()
p4_identify.push_back("-t");
std::string source = this->SourceDirectory + "/...#have";
p4_identify.push_back(source);
p4_identify.push_back(source.c_str());
p4_identify.push_back(nullptr);
std::string rev;
IdentifyParser out(this, "p4_changes-out> ", rev);
OutputLogger err(this->Log, "p4_changes-err> ");
bool result = this->RunChild(p4_identify, &out, &err);
bool result = this->RunChild(p4_identify.data(), &out, &err);
// If there was a problem contacting the server return "<unknown>"
if (!result) {
@ -383,7 +388,7 @@ bool cmCTestP4::NoteNewRevision()
bool cmCTestP4::LoadRevisions()
{
std::vector<std::string> p4_changes;
std::vector<char const*> p4_changes;
this->SetP4Options(p4_changes);
// Use 'p4 changes ...@old,new' to get a list of changelists
@ -404,36 +409,38 @@ bool cmCTestP4::LoadRevisions()
.append(this->NewRevision);
p4_changes.push_back("changes");
p4_changes.push_back(range);
p4_changes.push_back(range.c_str());
p4_changes.push_back(nullptr);
ChangesParser out(this, "p4_changes-out> ");
OutputLogger err(this->Log, "p4_changes-err> ");
this->ChangeLists.clear();
this->RunChild(p4_changes, &out, &err);
this->RunChild(p4_changes.data(), &out, &err);
if (this->ChangeLists.empty()) {
return true;
}
// p4 describe -s ...@1111111,2222222
std::vector<std::string> p4_describe;
std::vector<char const*> p4_describe;
for (std::string const& i : cmReverseRange(this->ChangeLists)) {
this->SetP4Options(p4_describe);
p4_describe.push_back("describe");
p4_describe.push_back("-s");
p4_describe.push_back(i);
p4_describe.push_back(i.c_str());
p4_describe.push_back(nullptr);
DescribeParser outDescribe(this, "p4_describe-out> ");
OutputLogger errDescribe(this->Log, "p4_describe-err> ");
this->RunChild(p4_describe, &outDescribe, &errDescribe);
this->RunChild(p4_describe.data(), &outDescribe, &errDescribe);
}
return true;
}
bool cmCTestP4::LoadModifications()
{
std::vector<std::string> p4_diff;
std::vector<char const*> p4_diff;
this->SetP4Options(p4_diff);
p4_diff.push_back("diff");
@ -441,11 +448,12 @@ bool cmCTestP4::LoadModifications()
// Ideally we would use -Od but not all clients support it
p4_diff.push_back("-dn");
std::string source = this->SourceDirectory + "/...";
p4_diff.push_back(source);
p4_diff.push_back(source.c_str());
p4_diff.push_back(nullptr);
DiffParser out(this, "p4_diff-out> ");
OutputLogger err(this->Log, "p4_diff-err> ");
this->RunChild(p4_diff, &out, &err);
this->RunChild(p4_diff.data(), &out, &err);
return true;
}
@ -453,14 +461,17 @@ bool cmCTestP4::UpdateCustom(const std::string& custom)
{
cmList p4_custom_command{ custom, cmList::EmptyElements::Yes };
std::vector<std::string> p4_custom;
p4_custom.reserve(p4_custom_command.size());
cm::append(p4_custom, p4_custom_command);
std::vector<char const*> p4_custom;
p4_custom.reserve(p4_custom_command.size() + 1);
for (std::string const& i : p4_custom_command) {
p4_custom.push_back(i.c_str());
}
p4_custom.push_back(nullptr);
OutputLogger custom_out(this->Log, "p4_customsync-out> ");
OutputLogger custom_err(this->Log, "p4_customsync-err> ");
return this->RunUpdateCommand(p4_custom, &custom_out, &custom_err);
return this->RunUpdateCommand(p4_custom.data(), &custom_out, &custom_err);
}
bool cmCTestP4::UpdateImpl()
@ -477,7 +488,7 @@ bool cmCTestP4::UpdateImpl()
return false;
}
std::vector<std::string> p4_sync;
std::vector<char const*> p4_sync;
this->SetP4Options(p4_sync);
p4_sync.push_back("sync");
@ -488,7 +499,9 @@ bool cmCTestP4::UpdateImpl()
opts = this->CTest->GetCTestConfiguration("P4UpdateOptions");
}
std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
cm::append(p4_sync, args);
for (std::string const& arg : args) {
p4_sync.push_back(arg.c_str());
}
std::string source = this->SourceDirectory + "/...";
@ -502,10 +515,11 @@ bool cmCTestP4::UpdateImpl()
source.append("@\"").append(date).append("\"");
}
p4_sync.push_back(source);
p4_sync.push_back(source.c_str());
p4_sync.push_back(nullptr);
OutputLogger out(this->Log, "p4_sync-out> ");
OutputLogger err(this->Log, "p4_sync-err> ");
return this->RunUpdateCommand(p4_sync, &out, &err);
return this->RunUpdateCommand(p4_sync.data(), &out, &err);
}

View File

@ -39,7 +39,7 @@ private:
std::vector<std::string> P4Options;
User GetUserData(const std::string& username);
void SetP4Options(std::vector<std::string>& options);
void SetP4Options(std::vector<char const*>& options);
std::string GetWorkingRevision();
bool NoteOldRevision() override;

View File

@ -33,7 +33,7 @@ cmCTestSVN::~cmCTestSVN() = default;
void cmCTestSVN::CleanupImpl()
{
std::vector<std::string> svn_cleanup;
std::vector<const char*> svn_cleanup;
svn_cleanup.push_back("cleanup");
OutputLogger out(this->Log, "cleanup-out> ");
OutputLogger err(this->Log, "cleanup-err> ");
@ -88,9 +88,9 @@ static bool cmCTestSVNPathStarts(std::string const& p1, std::string const& p2)
std::string cmCTestSVN::LoadInfo(SVNInfo& svninfo)
{
// Run "svn info" to get the repository info from the work tree.
std::vector<std::string> svn_info;
std::vector<const char*> svn_info;
svn_info.push_back("info");
svn_info.push_back(svninfo.LocalPath);
svn_info.push_back(svninfo.LocalPath.c_str());
std::string rev;
InfoParser out(this, "info-out> ", rev, svninfo);
OutputLogger err(this->Log, "info-err> ");
@ -251,24 +251,26 @@ bool cmCTestSVN::UpdateImpl()
args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
}
std::vector<std::string> svn_update;
std::vector<char const*> svn_update;
svn_update.push_back("update");
cm::append(svn_update, args);
for (std::string const& arg : args) {
svn_update.push_back(arg.c_str());
}
UpdateParser out(this, "up-out> ");
OutputLogger err(this->Log, "up-err> ");
return this->RunSVNCommand(svn_update, &out, &err);
}
bool cmCTestSVN::RunSVNCommand(std::vector<std::string> const& parameters,
bool cmCTestSVN::RunSVNCommand(std::vector<char const*> const& parameters,
OutputParser* out, OutputParser* err)
{
if (parameters.empty()) {
return false;
}
std::vector<std::string> args;
args.push_back(this->CommandLineTool);
std::vector<char const*> args;
args.push_back(this->CommandLineTool.c_str());
cm::append(args, parameters);
args.push_back("--non-interactive");
@ -276,12 +278,16 @@ bool cmCTestSVN::RunSVNCommand(std::vector<std::string> const& parameters,
std::vector<std::string> parsedUserOptions =
cmSystemTools::ParseArguments(userOptions);
cm::append(args, parsedUserOptions);
if (parameters[0] == "update") {
return this->RunUpdateCommand(args, out, err);
for (std::string const& opt : parsedUserOptions) {
args.push_back(opt.c_str());
}
return this->RunChild(args, out, err);
args.push_back(nullptr);
if (strcmp(parameters[0], "update") == 0) {
return this->RunUpdateCommand(args.data(), out, err);
}
return this->RunChild(args.data(), out, err);
}
class cmCTestSVN::LogParser
@ -387,7 +393,7 @@ bool cmCTestSVN::LoadRevisions(SVNInfo& svninfo)
}
// Run "svn log" to get all global revisions of interest.
std::vector<std::string> svn_log;
std::vector<const char*> svn_log;
svn_log.push_back("log");
svn_log.push_back("--xml");
svn_log.push_back("-v");
@ -466,7 +472,7 @@ private:
bool cmCTestSVN::LoadModifications()
{
// Run "svn status" which reports local modifications.
std::vector<std::string> svn_status;
std::vector<const char*> svn_status;
svn_status.push_back("status");
StatusParser out(this, "status-out> ");
OutputLogger err(this->Log, "status-err> ");
@ -528,7 +534,7 @@ bool cmCTestSVN::LoadRepositories()
this->RootInfo = &(this->Repositories.back());
// Run "svn status" to get the list of external repositories
std::vector<std::string> svn_status;
std::vector<const char*> svn_status;
svn_status.push_back("status");
ExternalParser out(this, "external-out> ");
OutputLogger err(this->Log, "external-err> ");

View File

@ -33,7 +33,7 @@ private:
bool NoteNewRevision() override;
bool UpdateImpl() override;
bool RunSVNCommand(std::vector<std::string> const& parameters,
bool RunSVNCommand(std::vector<char const*> const& parameters,
OutputParser* out, OutputParser* err);
// Information about an SVN repository (root repository or external)

View File

@ -11,9 +11,8 @@
#include <cm/memory>
#include <cm3p/uv.h>
#include "cmsys/Directory.hxx"
#include "cmsys/Process.h"
#include "cmCTest.h"
#include "cmCTestBuildCommand.h"
@ -41,8 +40,6 @@
#include "cmStateSnapshot.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmValue.h"
#include "cmake.h"
@ -151,65 +148,66 @@ int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg)
// now pass through all the other arguments
std::vector<std::string>& initArgs =
this->CTest->GetInitialCommandLineArguments();
//*** need to make sure this does not have the current script ***
for (size_t i = 1; i < initArgs.size(); ++i) {
argv.push_back(initArgs[i].c_str());
}
argv.push_back(nullptr);
// Now create process object
cmUVProcessChainBuilder builder;
builder.AddCommand(initArgs)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
auto process = builder.Start();
cm::uv_pipe_ptr outPipe;
outPipe.init(process.GetLoop(), 0);
uv_pipe_open(outPipe, process.OutputStream());
cm::uv_pipe_ptr errPipe;
errPipe.init(process.GetLoop(), 0);
uv_pipe_open(errPipe, process.ErrorStream());
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
// cmsysProcess_SetWorkingDirectory(cp, dir);
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
// cmsysProcess_SetTimeout(cp, timeout);
cmsysProcess_Execute(cp);
std::vector<char> out;
std::vector<char> err;
std::string line;
auto pipe =
cmSystemTools::WaitForLine(&process.GetLoop(), outPipe, errPipe, line,
std::chrono::seconds(100), out, err);
while (pipe != cmSystemTools::WaitForLineResult::None) {
int pipe =
cmSystemTools::WaitForLine(cp, line, std::chrono::seconds(100), out, err);
while (pipe != cmsysProcess_Pipe_None) {
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Output: " << line << "\n");
if (pipe == cmSystemTools::WaitForLineResult::STDERR) {
if (pipe == cmsysProcess_Pipe_STDERR) {
cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n");
} else if (pipe == cmSystemTools::WaitForLineResult::STDOUT) {
} else if (pipe == cmsysProcess_Pipe_STDOUT) {
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n");
}
pipe =
cmSystemTools::WaitForLine(&process.GetLoop(), outPipe, errPipe, line,
std::chrono::seconds(100), out, err);
pipe = cmSystemTools::WaitForLine(cp, line, std::chrono::seconds(100), out,
err);
}
// Properly handle output of the build command
process.Wait();
auto const& status = process.GetStatus(0);
auto result = status.GetException();
cmsysProcess_WaitForExit(cp, nullptr);
int result = cmsysProcess_GetState(cp);
int retVal = 0;
bool failed = false;
switch (result.first) {
case cmUVProcessChain::ExceptionCode::None:
retVal = static_cast<int>(status.ExitStatus);
break;
case cmUVProcessChain::ExceptionCode::Spawn:
cmCTestLog(this->CTest, ERROR_MESSAGE,
"\tError executing ctest: " << result.second << std::endl);
failed = true;
break;
default:
retVal = status.TermSignal;
cmCTestLog(this->CTest, ERROR_MESSAGE,
"\tThere was an exception: " << result.second << " " << retVal
<< std::endl);
failed = true;
if (result == cmsysProcess_State_Exited) {
retVal = cmsysProcess_GetExitValue(cp);
} else if (result == cmsysProcess_State_Exception) {
retVal = cmsysProcess_GetExitException(cp);
cmCTestLog(this->CTest, ERROR_MESSAGE,
"\tThere was an exception: "
<< cmsysProcess_GetExceptionString(cp) << " " << retVal
<< std::endl);
failed = true;
} else if (result == cmsysProcess_State_Expired) {
cmCTestLog(this->CTest, ERROR_MESSAGE,
"\tThere was a timeout" << std::endl);
failed = true;
} else if (result == cmsysProcess_State_Error) {
cmCTestLog(this->CTest, ERROR_MESSAGE,
"\tError executing ctest: " << cmsysProcess_GetErrorString(cp)
<< std::endl);
failed = true;
}
cmsysProcess_Delete(cp);
if (failed) {
std::ostringstream message;
message << "Error running command: [";
message << static_cast<int>(result.first) << "] ";
message << result << "] ";
for (const char* arg : argv) {
if (arg) {
message << arg << " ";

View File

@ -7,9 +7,10 @@
#include <sstream>
#include <vector>
#include "cmsys/Process.h"
#include "cmCTest.h"
#include "cmSystemTools.h"
#include "cmUVProcessChain.h"
#include "cmValue.h"
#include "cmXMLWriter.h"
@ -54,12 +55,18 @@ bool cmCTestVC::InitialCheckout(const std::string& command)
// Construct the initial checkout command line.
std::vector<std::string> args = cmSystemTools::ParseArguments(command);
std::vector<char const*> vc_co;
vc_co.reserve(args.size() + 1);
for (std::string const& arg : args) {
vc_co.push_back(arg.c_str());
}
vc_co.push_back(nullptr);
// Run the initial checkout command and log its output.
this->Log << "--- Begin Initial Checkout ---\n";
OutputLogger out(this->Log, "co-out> ");
OutputLogger err(this->Log, "co-err> ");
bool result = this->RunChild(args, &out, &err, parent);
bool result = this->RunChild(vc_co.data(), &out, &err, parent.c_str());
this->Log << "--- End Initial Checkout ---\n";
if (!result) {
cmCTestLog(this->CTest, ERROR_MESSAGE,
@ -68,35 +75,35 @@ bool cmCTestVC::InitialCheckout(const std::string& command)
return result;
}
bool cmCTestVC::RunChild(const std::vector<std::string>& cmd,
OutputParser* out, OutputParser* err,
std::string workDir, Encoding encoding)
bool cmCTestVC::RunChild(char const* const* cmd, OutputParser* out,
OutputParser* err, const char* workDir,
Encoding encoding)
{
this->Log << cmCTestVC::ComputeCommandLine(cmd) << "\n";
cmUVProcessChainBuilder builder;
if (workDir.empty()) {
workDir = this->SourceDirectory;
}
builder.AddCommand(cmd).SetWorkingDirectory(workDir);
auto status = cmCTestVC::RunProcess(builder, out, err, encoding);
return status.front().SpawnResult == 0 && status.front().ExitStatus == 0;
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, cmd);
workDir = workDir ? workDir : this->SourceDirectory.c_str();
cmsysProcess_SetWorkingDirectory(cp, workDir);
cmCTestVC::RunProcess(cp, out, err, encoding);
int result = cmsysProcess_GetExitValue(cp);
cmsysProcess_Delete(cp);
return result == 0;
}
std::string cmCTestVC::ComputeCommandLine(const std::vector<std::string>& cmd)
std::string cmCTestVC::ComputeCommandLine(char const* const* cmd)
{
std::ostringstream line;
const char* sep = "";
for (auto const& arg : cmd) {
line << sep << "\"" << arg << "\"";
for (const char* const* arg = cmd; *arg; ++arg) {
line << sep << "\"" << *arg << "\"";
sep = " ";
}
return line.str();
}
bool cmCTestVC::RunUpdateCommand(const std::vector<std::string>& cmd,
OutputParser* out, OutputParser* err,
Encoding encoding)
bool cmCTestVC::RunUpdateCommand(char const* const* cmd, OutputParser* out,
OutputParser* err, Encoding encoding)
{
// Report the command line.
this->UpdateCommandLine = this->ComputeCommandLine(cmd);
@ -106,7 +113,7 @@ bool cmCTestVC::RunUpdateCommand(const std::vector<std::string>& cmd,
}
// Run the command.
return this->RunChild(cmd, out, err, "", encoding);
return this->RunChild(cmd, out, err, nullptr, encoding);
}
std::string cmCTestVC::GetNightlyTime()

View File

@ -6,7 +6,6 @@
#include <iosfwd>
#include <string>
#include <vector>
#include "cmProcessOutput.h"
#include "cmProcessTools.h"
@ -109,15 +108,15 @@ protected:
};
/** Convert a list of arguments to a human-readable command line. */
static std::string ComputeCommandLine(const std::vector<std::string>& cmd);
static std::string ComputeCommandLine(char const* const* cmd);
/** Run a command line and send output to given parsers. */
bool RunChild(const std::vector<std::string>& cmd, OutputParser* out,
OutputParser* err, std::string workDir = {},
bool RunChild(char const* const* cmd, OutputParser* out, OutputParser* err,
const char* workDir = nullptr,
Encoding encoding = cmProcessOutput::Auto);
/** Run VC update command line and send output to given parsers. */
bool RunUpdateCommand(const std::vector<std::string>& cmd, OutputParser* out,
bool RunUpdateCommand(char const* const* cmd, OutputParser* out,
OutputParser* err = nullptr,
Encoding encoding = cmProcessOutput::Auto);

View File

@ -667,10 +667,6 @@ Modify cmCTestResourceGroupsLexer.cxx:
#include <cstddef>
#ifndef _WIN32
# include <termios.h>
#endif
/*--------------------------------------------------------------------------*/
#define INITIAL 0

View File

@ -26,10 +26,6 @@ Modify cmCTestResourceGroupsLexer.cxx:
#include <cstddef>
#ifndef _WIN32
# include <termios.h>
#endif
/*--------------------------------------------------------------------------*/
%}

View File

@ -5,7 +5,6 @@
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@ -25,13 +24,13 @@
#include <cmext/string_view>
#include <cm3p/curl/curl.h>
#include <cm3p/uv.h>
#include <cm3p/zlib.h>
#include "cmsys/Base64.h"
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/SystemInformation.hxx"
#if defined(_WIN32)
@ -65,9 +64,6 @@
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
#include "cmValue.h"
#include "cmVersion.h"
#include "cmVersionConfig.h"
@ -1077,9 +1073,9 @@ int cmCTest::GetTestModelFromString(const std::string& str)
// ######################################################################
// ######################################################################
bool cmCTest::RunMakeCommand(const std::string& command, std::string& output,
int* retVal, const char* dir, cmDuration timeout,
std::ostream& ofs, Encoding encoding)
int cmCTest::RunMakeCommand(const std::string& command, std::string& output,
int* retVal, const char* dir, cmDuration timeout,
std::ostream& ofs, Encoding encoding)
{
// First generate the command and arguments
std::vector<std::string> args = cmSystemTools::ParseArguments(command);
@ -1088,107 +1084,107 @@ bool cmCTest::RunMakeCommand(const std::string& command, std::string& output,
return false;
}
std::vector<const char*> argv;
argv.reserve(args.size() + 1);
for (std::string const& a : args) {
argv.push_back(a.c_str());
}
argv.push_back(nullptr);
output.clear();
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "Run command:");
for (auto const& arg : args) {
for (char const* arg : argv) {
if (!arg) {
break;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, " \"" << arg << "\"");
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, std::endl);
// Now create process object
cmUVProcessChainBuilder builder;
builder.AddCommand(args).SetMergedBuiltinStreams();
if (dir) {
builder.SetWorkingDirectory(dir);
}
auto chain = builder.Start();
cm::uv_pipe_ptr outputStream;
outputStream.init(chain.GetLoop(), 0);
uv_pipe_open(outputStream, chain.OutputStream());
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
// Initialize tick's
std::string::size_type tick = 0;
std::string::size_type tick_len = 1024;
std::string::size_type tick_line_len = 50;
char* data;
int length;
cmProcessOutput processOutput(encoding);
std::string strdata;
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Each . represents " << tick_len
<< " bytes of output\n"
" "
<< std::flush);
auto outputHandle = cmUVStreamRead(
outputStream,
[this, &processOutput, &output, &tick, &tick_len, &tick_line_len,
&ofs](std::vector<char> data) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata);
for (char& cc : strdata) {
if (cc == 0) {
cc = '\n';
}
while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
processOutput.DecodeText(data, length, strdata);
for (char& cc : strdata) {
if (cc == 0) {
cc = '\n';
}
output.append(strdata);
while (output.size() > (tick * tick_len)) {
tick++;
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT, "." << std::flush);
if (tick % tick_line_len == 0 && tick > 0) {
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Size: " << int((double(output.size()) / 1024.0) + 1)
<< "K\n " << std::flush);
}
}
output.append(strdata);
while (output.size() > (tick * tick_len)) {
tick++;
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT, "." << std::flush);
if (tick % tick_line_len == 0 && tick > 0) {
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Size: " << int((double(output.size()) / 1024.0) + 1)
<< "K\n " << std::flush);
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
},
[this, &processOutput, &output, &ofs]() {
std::string strdata;
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
output.append(strdata);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
}
});
bool finished = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
}
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
output.append(strdata);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
}
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Size of output: " << int(double(output.size()) / 1024.0) << "K"
<< std::endl);
if (finished) {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
*retVal = static_cast<int>(status.ExitStatus);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Command exited with the value: " << *retVal << std::endl);
break;
case cmUVProcessChain::ExceptionCode::Spawn:
output += "\n*** ERROR executing: ";
output += exception.second;
output += "\n***The build process failed.";
cmCTestLog(this, ERROR_MESSAGE,
"There was an error: " << exception.second << std::endl);
break;
default:
*retVal = static_cast<int>(exception.first);
cmCTestLog(this, WARNING,
"There was an exception: " << *retVal << std::endl);
break;
}
} else {
cmsysProcess_WaitForExit(cp, nullptr);
int result = cmsysProcess_GetState(cp);
if (result == cmsysProcess_State_Exited) {
*retVal = cmsysProcess_GetExitValue(cp);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Command exited with the value: " << *retVal << std::endl);
} else if (result == cmsysProcess_State_Exception) {
*retVal = cmsysProcess_GetExitException(cp);
cmCTestLog(this, WARNING,
"There was an exception: " << *retVal << std::endl);
} else if (result == cmsysProcess_State_Expired) {
cmCTestLog(this, WARNING, "There was a timeout" << std::endl);
} else if (result == cmsysProcess_State_Error) {
output += "\n*** ERROR executing: ";
output += cmsysProcess_GetErrorString(cp);
output += "\n***The build process failed.";
cmCTestLog(this, ERROR_MESSAGE,
"There was an error: " << cmsysProcess_GetErrorString(cp)
<< std::endl);
}
return true;
cmsysProcess_Delete(cp);
return result;
}
// ######################################################################
@ -1196,10 +1192,9 @@ bool cmCTest::RunMakeCommand(const std::string& command, std::string& output,
// ######################################################################
// ######################################################################
bool cmCTest::RunTest(const std::vector<std::string>& argv,
std::string* output, int* retVal, std::ostream* log,
cmDuration testTimeOut,
std::vector<std::string>* environment, Encoding encoding)
int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
int* retVal, std::ostream* log, cmDuration testTimeOut,
std::vector<std::string>* environment, Encoding encoding)
{
bool modifyEnv = (environment && !environment->empty());
@ -1238,16 +1233,19 @@ bool cmCTest::RunTest(const std::vector<std::string>& argv,
inst.SetStreams(&oss, &oss);
std::vector<std::string> args;
for (auto const& i : argv) {
// make sure we pass the timeout in for any build and test
// invocations. Since --build-generator is required this is a
// good place to check for it, and to add the arguments in
if (i == "--build-generator" && timeout != cmCTest::MaxDuration() &&
timeout > cmDuration::zero()) {
args.emplace_back("--test-timeout");
args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
for (char const* i : argv) {
if (i) {
// make sure we pass the timeout in for any build and test
// invocations. Since --build-generator is required this is a
// good place to check for it, and to add the arguments in
if (strcmp(i, "--build-generator") == 0 &&
timeout != cmCTest::MaxDuration() &&
timeout > cmDuration::zero()) {
args.emplace_back("--test-timeout");
args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
}
args.emplace_back(i);
}
args.emplace_back(i);
}
if (log) {
*log << "* Run internal CTest" << std::endl;
@ -1273,7 +1271,7 @@ bool cmCTest::RunTest(const std::vector<std::string>& argv,
<< std::endl);
}
return true;
return cmsysProcess_State_Exited;
}
std::vector<char> tempOutput;
if (output) {
@ -1286,43 +1284,41 @@ bool cmCTest::RunTest(const std::vector<std::string>& argv,
cmSystemTools::AppendEnv(*environment);
}
cmUVProcessChainBuilder builder;
builder.AddCommand(argv).SetMergedBuiltinStreams();
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmCTestLog(this, DEBUG, "Command is: " << argv[0] << std::endl);
auto chain = builder.Start();
if (cmSystemTools::GetRunCommandHideConsole()) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
char* data;
int length;
cmProcessOutput processOutput(encoding);
cm::uv_pipe_ptr outputStream;
outputStream.init(chain.GetLoop(), 0);
uv_pipe_open(outputStream, chain.OutputStream());
auto outputHandle = cmUVStreamRead(
outputStream,
[this, &processOutput, &output, &tempOutput,
&log](std::vector<char> data) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata);
if (output) {
cm::append(tempOutput, data.data(), data.data() + data.size());
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
},
[this, &processOutput, &log]() {
std::string strdata;
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
}
});
std::string strdata;
while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
processOutput.DecodeText(data, length, strdata);
if (output) {
cm::append(tempOutput, data, data + length);
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
}
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
}
bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
cmsysProcess_WaitForExit(cp, nullptr);
processOutput.DecodeText(tempOutput, tempOutput);
if (output && tempOutput.begin() != tempOutput.end()) {
output->append(tempOutput.data(), tempOutput.size());
@ -1330,41 +1326,33 @@ bool cmCTest::RunTest(const std::vector<std::string>& argv,
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"-- Process completed" << std::endl);
bool result = false;
int result = cmsysProcess_GetState(cp);
if (complete) {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
*retVal = static_cast<int>(status.ExitStatus);
if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
this->OutputTestErrors(tempOutput);
}
result = true;
break;
case cmUVProcessChain::ExceptionCode::Spawn: {
std::string outerr =
cmStrCat("\n*** ERROR executing: ", exception.second);
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
} break;
default: {
if (this->Impl->OutputTestOutputOnTestFailure) {
this->OutputTestErrors(tempOutput);
}
*retVal = status.TermSignal;
std::string outerr =
cmStrCat("\n*** Exception executing: ", exception.second);
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
} break;
if (result == cmsysProcess_State_Exited) {
*retVal = cmsysProcess_GetExitValue(cp);
if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
this->OutputTestErrors(tempOutput);
}
} else if (result == cmsysProcess_State_Exception) {
if (this->Impl->OutputTestOutputOnTestFailure) {
this->OutputTestErrors(tempOutput);
}
*retVal = cmsysProcess_GetExitException(cp);
std::string outerr = cmStrCat("\n*** Exception executing: ",
cmsysProcess_GetExceptionString(cp));
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
} else if (result == cmsysProcess_State_Error) {
std::string outerr =
cmStrCat("\n*** ERROR executing: ", cmsysProcess_GetErrorString(cp));
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
}
cmsysProcess_Delete(cp);
return result;
}
@ -3482,70 +3470,49 @@ bool cmCTest::RunCommand(std::vector<std::string> const& args,
stdOut->clear();
stdErr->clear();
cmUVProcessChainBuilder builder;
builder.AddCommand(args)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
if (dir) {
builder.SetWorkingDirectory(dir);
}
auto chain = builder.Start();
cm::uv_timer_ptr timer;
bool timedOut = false;
if (timeout.count()) {
timer.init(chain.GetLoop(), &timedOut);
timer.start(
[](uv_timer_t* t) {
auto* timedOutPtr = static_cast<bool*>(t->data);
*timedOutPtr = true;
},
static_cast<uint64_t>(timeout.count() * 1000.0), 0);
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
if (cmSystemTools::GetRunCommandHideConsole()) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
std::vector<char> tempOutput;
bool outFinished = false;
cm::uv_pipe_ptr outStream;
std::vector<char> tempError;
bool errFinished = false;
cm::uv_pipe_ptr errStream;
char* data;
int length;
cmProcessOutput processOutput(encoding);
auto startRead = [this, &chain, &processOutput](
cm::uv_pipe_ptr& pipe, int stream,
std::vector<char>& temp,
bool& finished) -> std::unique_ptr<cmUVStreamReadHandle> {
pipe.init(chain.GetLoop(), 0);
uv_pipe_open(pipe, stream);
return cmUVStreamRead(
pipe,
[this, &temp, &processOutput](std::vector<char> data) {
cm::append(temp, data);
if (this->Impl->ExtraVerbose) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata);
cmSystemTools::Stdout(strdata);
}
},
[&finished]() { finished = true; });
};
auto outputHandle =
startRead(outStream, chain.OutputStream(), tempOutput, outFinished);
auto errorHandle =
startRead(errStream, chain.ErrorStream(), tempError, errFinished);
while (!timedOut && !(outFinished && errFinished)) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
std::string strdata;
int res;
bool done = false;
while (!done) {
res = cmsysProcess_WaitForData(cp, &data, &length, nullptr);
switch (res) {
case cmsysProcess_Pipe_STDOUT:
cm::append(tempOutput, data, data + length);
break;
case cmsysProcess_Pipe_STDERR:
cm::append(tempError, data, data + length);
break;
default:
done = true;
}
if ((res == cmsysProcess_Pipe_STDOUT || res == cmsysProcess_Pipe_STDERR) &&
this->Impl->ExtraVerbose) {
processOutput.DecodeText(data, length, strdata);
cmSystemTools::Stdout(strdata);
}
}
if (this->Impl->ExtraVerbose) {
std::string strdata;
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
cmSystemTools::Stdout(strdata);
}
}
while (!timedOut && !chain.Finished()) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
}
cmsysProcess_WaitForExit(cp, nullptr);
if (!tempOutput.empty()) {
processOutput.DecodeText(tempOutput, tempOutput);
stdOut->append(tempOutput.data(), tempOutput.size());
@ -3556,32 +3523,32 @@ bool cmCTest::RunCommand(std::vector<std::string> const& args,
}
bool result = true;
if (timedOut) {
if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
if (retVal) {
*retVal = cmsysProcess_GetExitValue(cp);
} else {
if (cmsysProcess_GetExitValue(cp) != 0) {
result = false;
}
}
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) {
const char* exception_str = cmsysProcess_GetExceptionString(cp);
cmCTestLog(this, ERROR_MESSAGE, exception_str << std::endl);
stdErr->append(exception_str, strlen(exception_str));
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Error) {
const char* error_str = cmsysProcess_GetErrorString(cp);
cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
stdErr->append(error_str, strlen(error_str));
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Expired) {
const char* error_str = "Process terminated due to timeout\n";
cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
stdErr->append(error_str, strlen(error_str));
result = false;
} else {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
if (retVal) {
*retVal = static_cast<int>(status.ExitStatus);
} else {
if (status.ExitStatus != 0) {
result = false;
}
}
break;
default: {
cmCTestLog(this, ERROR_MESSAGE, exception.second << std::endl);
stdErr->append(exception.second);
result = false;
} break;
}
}
cmsysProcess_Delete(cp);
return result;
}

View File

@ -254,10 +254,10 @@ public:
* Run command specialized for make and configure. Returns process status
* and retVal is return value or exception.
*/
bool RunMakeCommand(const std::string& command, std::string& output,
int* retVal, const char* dir, cmDuration timeout,
std::ostream& ofs,
Encoding encoding = cmProcessOutput::Auto);
int RunMakeCommand(const std::string& command, std::string& output,
int* retVal, const char* dir, cmDuration timeout,
std::ostream& ofs,
Encoding encoding = cmProcessOutput::Auto);
/** Return the current tag */
std::string GetCurrentTag();
@ -303,10 +303,10 @@ public:
* environment variables prior to running the test. After running the test,
* environment variables are restored to their previous values.
*/
bool RunTest(const std::vector<std::string>& args, std::string* output,
int* retVal, std::ostream* logfile, cmDuration testTimeOut,
std::vector<std::string>* environment,
Encoding encoding = cmProcessOutput::Auto);
int RunTest(std::vector<const char*> args, std::string* output, int* retVal,
std::ostream* logfile, cmDuration testTimeOut,
std::vector<std::string>* environment,
Encoding encoding = cmProcessOutput::Auto);
/**
* Get the handler object

View File

@ -2,8 +2,8 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmExecuteProcessCommand.h"
#include <algorithm>
#include <cctype> /* isspace */
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <map>
@ -16,7 +16,7 @@
#include <cmext/algorithm>
#include <cmext/string_view>
#include <cm3p/uv.h>
#include "cmsys/Process.h"
#include "cmArgumentParser.h"
#include "cmExecutionStatus.h"
@ -26,9 +26,6 @@
#include "cmProcessOutput.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
namespace {
bool cmExecuteProcessCommandIsWhitespace(char c)
@ -39,7 +36,7 @@ bool cmExecuteProcessCommandIsWhitespace(char c)
void cmExecuteProcessCommandFixText(std::vector<char>& output,
bool strip_trailing_whitespace);
void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data,
std::size_t length);
int length);
}
// cmExecuteProcessCommand
@ -164,68 +161,57 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
}
}
// Create a process instance.
cmUVProcessChainBuilder builder;
std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp_ptr(
cmsysProcess_New(), cmsysProcess_Delete);
cmsysProcess* cp = cp_ptr.get();
// Set the command sequence.
for (std::vector<std::string> const& cmd : arguments.Commands) {
builder.AddCommand(cmd);
std::vector<const char*> argv(cmd.size() + 1);
std::transform(cmd.begin(), cmd.end(), argv.begin(),
[](std::string const& s) { return s.c_str(); });
argv.back() = nullptr;
cmsysProcess_AddCommand(cp, argv.data());
}
// Set the process working directory.
if (!arguments.WorkingDirectory.empty()) {
builder.SetWorkingDirectory(arguments.WorkingDirectory);
cmsysProcess_SetWorkingDirectory(cp, arguments.WorkingDirectory.c_str());
}
// Always hide the process window.
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
// Check the output variables.
std::unique_ptr<FILE, int (*)(FILE*)> inputFile(nullptr, fclose);
if (!inputFilename.empty()) {
inputFile.reset(cmsys::SystemTools::Fopen(inputFilename, "rb"));
if (inputFile) {
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_INPUT,
inputFile.get());
}
} else {
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_INPUT, stdin);
bool merge_output = false;
if (!arguments.InputFile.empty()) {
cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN,
arguments.InputFile.c_str());
}
std::unique_ptr<FILE, int (*)(FILE*)> outputFile(nullptr, fclose);
if (!outputFilename.empty()) {
outputFile.reset(cmsys::SystemTools::Fopen(outputFilename, "wb"));
if (outputFile) {
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT,
outputFile.get());
}
} else {
if (arguments.OutputVariable == arguments.ErrorVariable &&
!arguments.ErrorVariable.empty()) {
builder.SetMergedBuiltinStreams();
if (!arguments.OutputFile.empty()) {
cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT,
arguments.OutputFile.c_str());
}
if (!arguments.ErrorFile.empty()) {
if (arguments.ErrorFile == arguments.OutputFile) {
merge_output = true;
} else {
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT);
cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR,
arguments.ErrorFile.c_str());
}
}
std::unique_ptr<FILE, int (*)(FILE*)> errorFile(nullptr, fclose);
if (!errorFilename.empty()) {
if (errorFilename == outputFilename) {
if (outputFile) {
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR,
outputFile.get());
}
} else {
errorFile.reset(cmsys::SystemTools::Fopen(errorFilename, "wb"));
if (errorFile) {
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR,
errorFile.get());
}
}
} else if (arguments.ErrorVariable.empty() ||
(!arguments.ErrorVariable.empty() &&
arguments.OutputVariable != arguments.ErrorVariable)) {
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
if (!arguments.OutputVariable.empty() &&
arguments.OutputVariable == arguments.ErrorVariable) {
merge_output = true;
}
if (merge_output) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1);
}
// Set the timeout if any.
int64_t timeoutMillis = static_cast<int64_t>(timeout * 1000.0);
if (timeout >= 0) {
cmsysProcess_SetTimeout(cp, timeout);
}
bool echo_stdout = false;
bool echo_stderr = false;
@ -273,86 +259,36 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
}
}
// Start the process.
auto chain = builder.Start();
bool timedOut = false;
cm::uv_timer_ptr timer;
if (timeoutMillis >= 0) {
timer.init(chain.GetLoop(), &timedOut);
timer.start(
[](uv_timer_t* handle) {
auto* timeoutPtr = static_cast<bool*>(handle->data);
*timeoutPtr = true;
},
timeoutMillis, 0);
}
cmsysProcess_Execute(cp);
// Read the process output.
struct ReadData
{
bool Finished = false;
std::vector<char> Output;
cm::uv_pipe_ptr Stream;
};
ReadData outputData;
ReadData errorData;
std::vector<char> tempOutput;
std::vector<char> tempError;
int length;
char* data;
int p;
cmProcessOutput processOutput(
cmProcessOutput::FindEncoding(arguments.Encoding));
std::string strdata;
std::unique_ptr<cmUVStreamReadHandle> outputHandle;
if (chain.OutputStream() >= 0) {
outputData.Stream.init(chain.GetLoop(), 0);
uv_pipe_open(outputData.Stream, chain.OutputStream());
outputHandle = cmUVStreamRead(
outputData.Stream,
[&arguments, &processOutput, &outputData,
&strdata](std::vector<char> data) {
if (!arguments.OutputQuiet) {
if (arguments.OutputVariable.empty() ||
arguments.EchoOutputVariable) {
processOutput.DecodeText(data.data(), data.size(), strdata, 1);
cmSystemTools::Stdout(strdata);
}
if (!arguments.OutputVariable.empty()) {
cmExecuteProcessCommandAppend(outputData.Output, data.data(),
data.size());
}
}
},
[&outputData]() { outputData.Finished = true; });
} else {
outputData.Finished = true;
}
std::unique_ptr<cmUVStreamReadHandle> errorHandle;
if (chain.ErrorStream() >= 0 &&
chain.ErrorStream() != chain.OutputStream()) {
errorData.Stream.init(chain.GetLoop(), 0);
uv_pipe_open(errorData.Stream, chain.ErrorStream());
errorHandle = cmUVStreamRead(
errorData.Stream,
[&arguments, &processOutput, &errorData,
&strdata](std::vector<char> data) {
if (!arguments.ErrorQuiet) {
if (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable) {
processOutput.DecodeText(data.data(), data.size(), strdata, 2);
cmSystemTools::Stderr(strdata);
}
if (!arguments.ErrorVariable.empty()) {
cmExecuteProcessCommandAppend(errorData.Output, data.data(),
data.size());
}
}
},
[&errorData]() { errorData.Finished = true; });
} else {
errorData.Finished = true;
}
while (chain.Valid() && !timedOut &&
!(chain.Finished() && outputData.Finished && errorData.Finished)) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
while ((p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
// Put the output in the right place.
if (p == cmsysProcess_Pipe_STDOUT && !arguments.OutputQuiet) {
if (arguments.OutputVariable.empty() || arguments.EchoOutputVariable) {
processOutput.DecodeText(data, length, strdata, 1);
cmSystemTools::Stdout(strdata);
}
if (!arguments.OutputVariable.empty()) {
cmExecuteProcessCommandAppend(tempOutput, data, length);
}
} else if (p == cmsysProcess_Pipe_STDERR && !arguments.ErrorQuiet) {
if (arguments.ErrorVariable.empty() || arguments.EchoErrorVariable) {
processOutput.DecodeText(data, length, strdata, 2);
cmSystemTools::Stderr(strdata);
}
if (!arguments.ErrorVariable.empty()) {
cmExecuteProcessCommandAppend(tempError, data, length);
}
}
}
if (!arguments.OutputQuiet &&
(arguments.OutputVariable.empty() || arguments.EchoOutputVariable)) {
@ -369,102 +305,151 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
}
}
// All output has been read.
processOutput.DecodeText(outputData.Output, outputData.Output);
processOutput.DecodeText(errorData.Output, errorData.Output);
// All output has been read. Wait for the process to exit.
cmsysProcess_WaitForExit(cp, nullptr);
processOutput.DecodeText(tempOutput, tempOutput);
processOutput.DecodeText(tempError, tempError);
// Fix the text in the output strings.
cmExecuteProcessCommandFixText(outputData.Output,
cmExecuteProcessCommandFixText(tempOutput,
arguments.OutputStripTrailingWhitespace);
cmExecuteProcessCommandFixText(errorData.Output,
cmExecuteProcessCommandFixText(tempError,
arguments.ErrorStripTrailingWhitespace);
// Store the output obtained.
if (!arguments.OutputVariable.empty() && !outputData.Output.empty()) {
if (!arguments.OutputVariable.empty() && !tempOutput.empty()) {
status.GetMakefile().AddDefinition(arguments.OutputVariable,
outputData.Output.data());
tempOutput.data());
}
if (arguments.ErrorVariable != arguments.OutputVariable &&
!arguments.ErrorVariable.empty() && !errorData.Output.empty()) {
if (!merge_output && !arguments.ErrorVariable.empty() &&
!tempError.empty()) {
status.GetMakefile().AddDefinition(arguments.ErrorVariable,
errorData.Output.data());
tempError.data());
}
// Store the result of running the process.
if (!arguments.ResultVariable.empty()) {
if (timedOut) {
status.GetMakefile().AddDefinition(arguments.ResultVariable,
"Process terminated due to timeout");
} else {
auto const* lastStatus = chain.GetStatus().back();
auto exception = lastStatus->GetException();
if (exception.first == cmUVProcessChain::ExceptionCode::None) {
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Exited: {
int v = cmsysProcess_GetExitValue(cp);
char buf[16];
snprintf(buf, sizeof(buf), "%d", v);
status.GetMakefile().AddDefinition(arguments.ResultVariable, buf);
} break;
case cmsysProcess_State_Exception:
status.GetMakefile().AddDefinition(
arguments.ResultVariable,
std::to_string(static_cast<int>(lastStatus->ExitStatus)));
} else {
arguments.ResultVariable, cmsysProcess_GetExceptionString(cp));
break;
case cmsysProcess_State_Error:
status.GetMakefile().AddDefinition(arguments.ResultVariable,
exception.second);
}
cmsysProcess_GetErrorString(cp));
break;
case cmsysProcess_State_Expired:
status.GetMakefile().AddDefinition(
arguments.ResultVariable, "Process terminated due to timeout");
break;
}
}
// Store the result of running the processes.
if (!arguments.ResultsVariable.empty()) {
if (timedOut) {
status.GetMakefile().AddDefinition(arguments.ResultsVariable,
"Process terminated due to timeout");
} else {
std::vector<std::string> res;
for (auto const* processStatus : chain.GetStatus()) {
auto exception = processStatus->GetException();
if (exception.first == cmUVProcessChain::ExceptionCode::None) {
res.emplace_back(
std::to_string(static_cast<int>(processStatus->ExitStatus)));
} else {
res.emplace_back(exception.second);
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Exited: {
std::vector<std::string> res;
for (size_t i = 0; i < arguments.Commands.size(); ++i) {
switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(i))) {
case kwsysProcess_StateByIndex_Exited: {
int exitCode =
cmsysProcess_GetExitValueByIndex(cp, static_cast<int>(i));
char buf[16];
snprintf(buf, sizeof(buf), "%d", exitCode);
res.emplace_back(buf);
} break;
case kwsysProcess_StateByIndex_Exception:
res.emplace_back(cmsysProcess_GetExceptionStringByIndex(
cp, static_cast<int>(i)));
break;
case kwsysProcess_StateByIndex_Error:
default:
res.emplace_back("Error getting the child return code");
break;
}
}
}
status.GetMakefile().AddDefinition(arguments.ResultsVariable,
cmList::to_string(res));
status.GetMakefile().AddDefinition(arguments.ResultsVariable,
cmList::to_string(res));
} break;
case cmsysProcess_State_Exception:
status.GetMakefile().AddDefinition(
arguments.ResultsVariable, cmsysProcess_GetExceptionString(cp));
break;
case cmsysProcess_State_Error:
status.GetMakefile().AddDefinition(arguments.ResultsVariable,
cmsysProcess_GetErrorString(cp));
break;
case cmsysProcess_State_Expired:
status.GetMakefile().AddDefinition(
arguments.ResultsVariable, "Process terminated due to timeout");
break;
}
}
auto queryProcessStatusByIndex = [&chain](std::size_t index) -> std::string {
auto const& processStatus = chain.GetStatus(index);
auto exception = processStatus.GetException();
if (exception.first == cmUVProcessChain::ExceptionCode::None) {
if (processStatus.ExitStatus) {
return cmStrCat("Child return code: ", processStatus.ExitStatus);
auto queryProcessStatusByIndex = [&cp](int index) -> std::string {
std::string processStatus;
switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(index))) {
case kwsysProcess_StateByIndex_Exited: {
int exitCode = cmsysProcess_GetExitValueByIndex(cp, index);
if (exitCode) {
processStatus = "Child return code: " + std::to_string(exitCode);
}
} break;
case kwsysProcess_StateByIndex_Exception: {
processStatus = cmStrCat(
"Abnormal exit with child return code: ",
cmsysProcess_GetExceptionStringByIndex(cp, static_cast<int>(index)));
break;
}
return "";
case kwsysProcess_StateByIndex_Error:
default:
processStatus = "Error getting the child return code";
break;
}
return cmStrCat("Abnormal exit with child return code: ",
exception.second);
return processStatus;
};
if (arguments.CommandErrorIsFatal == "ANY"_s) {
bool ret = true;
if (timedOut) {
status.SetError("Process terminated due to timeout");
ret = false;
} else {
std::map<std::size_t, std::string> failureIndices;
auto statuses = chain.GetStatus();
for (std::size_t i = 0; i < statuses.size(); ++i) {
std::string processStatus = queryProcessStatusByIndex(i);
if (!processStatus.empty()) {
failureIndices[i] = processStatus;
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Exited: {
std::map<int, std::string> failureIndices;
for (int i = 0; i < static_cast<int>(arguments.Commands.size()); ++i) {
std::string processStatus = queryProcessStatusByIndex(i);
if (!processStatus.empty()) {
failureIndices[i] = processStatus;
}
if (!failureIndices.empty()) {
std::ostringstream oss;
oss << "failed command indexes:\n";
for (auto const& e : failureIndices) {
oss << " " << e.first + 1 << ": \"" << e.second << "\"\n";
}
status.SetError(oss.str());
ret = false;
}
}
}
if (!failureIndices.empty()) {
std::ostringstream oss;
oss << "failed command indexes:\n";
for (auto const& e : failureIndices) {
oss << " " << e.first + 1 << ": \"" << e.second << "\"\n";
}
status.SetError(oss.str());
} break;
case cmsysProcess_State_Exception:
status.SetError(
cmStrCat("abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
ret = false;
}
break;
case cmsysProcess_State_Error:
status.SetError(cmStrCat("error getting child return code: ",
cmsysProcess_GetErrorString(cp)));
ret = false;
break;
case cmsysProcess_State_Expired:
status.SetError("Process terminated due to timeout");
ret = false;
break;
}
if (!ret) {
@ -475,23 +460,29 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
if (arguments.CommandErrorIsFatal == "LAST"_s) {
bool ret = true;
if (timedOut) {
status.SetError("Process terminated due to timeout");
ret = false;
} else {
auto const& lastStatus = chain.GetStatus(arguments.Commands.size() - 1);
auto exception = lastStatus.GetException();
if (exception.first != cmUVProcessChain::ExceptionCode::None) {
status.SetError(cmStrCat("Abnormal exit: ", exception.second));
ret = false;
} else {
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Exited: {
int lastIndex = static_cast<int>(arguments.Commands.size() - 1);
const std::string processStatus = queryProcessStatusByIndex(lastIndex);
if (!processStatus.empty()) {
status.SetError("last command failed");
ret = false;
}
}
} break;
case cmsysProcess_State_Exception:
status.SetError(
cmStrCat("Abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
ret = false;
break;
case cmsysProcess_State_Error:
status.SetError(cmStrCat("Error getting child return code: ",
cmsysProcess_GetErrorString(cp)));
ret = false;
break;
case cmsysProcess_State_Expired:
status.SetError("Process terminated due to timeout");
ret = false;
break;
}
if (!ret) {
cmSystemTools::SetFatalErrorOccurred();
@ -534,7 +525,7 @@ void cmExecuteProcessCommandFixText(std::vector<char>& output,
}
void cmExecuteProcessCommandAppend(std::vector<char>& output, const char* data,
std::size_t length)
int length)
{
#if defined(__APPLE__)
// HACK on Apple to work around bug with inserting at the

View File

@ -2,68 +2,48 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmProcessTools.h"
#include <algorithm>
#include <iterator>
#include <ostream>
#include <cm3p/uv.h>
#include "cmsys/Process.h"
#include "cmProcessOutput.h"
#include "cmUVHandlePtr.h"
#include "cmUVStream.h"
std::vector<cmUVProcessChain::Status> cmProcessTools::RunProcess(
cmUVProcessChainBuilder& builder, OutputParser* out, OutputParser* err,
Encoding encoding)
void cmProcessTools::RunProcess(struct cmsysProcess_s* cp, OutputParser* out,
OutputParser* err, Encoding encoding)
{
cmsysProcess_Execute(cp);
char* data = nullptr;
int length = 0;
int p;
cmProcessOutput processOutput(encoding);
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
auto chain = builder.Start();
std::string strdata;
cm::uv_pipe_ptr outputPipe;
outputPipe.init(chain.GetLoop(), 0);
uv_pipe_open(outputPipe, chain.OutputStream());
auto outputHandle = cmUVStreamRead(
outputPipe,
[&out, &processOutput, &strdata](std::vector<char> data) {
if (out) {
processOutput.DecodeText(data.data(), data.size(), strdata, 1);
if (!out->Process(strdata.c_str(), static_cast<int>(strdata.size()))) {
out = nullptr;
}
while ((out || err) &&
(p = cmsysProcess_WaitForData(cp, &data, &length, nullptr))) {
if (out && p == cmsysProcess_Pipe_STDOUT) {
processOutput.DecodeText(data, length, strdata, 1);
if (!out->Process(strdata.c_str(), static_cast<int>(strdata.size()))) {
out = nullptr;
}
},
[&out]() { out = nullptr; });
cm::uv_pipe_ptr errorPipe;
errorPipe.init(chain.GetLoop(), 0);
uv_pipe_open(errorPipe, chain.ErrorStream());
auto errorHandle = cmUVStreamRead(
errorPipe,
[&err, &processOutput, &strdata](std::vector<char> data) {
if (err) {
processOutput.DecodeText(data.data(), data.size(), strdata, 2);
if (!err->Process(strdata.c_str(), static_cast<int>(strdata.size()))) {
err = nullptr;
}
} else if (err && p == cmsysProcess_Pipe_STDERR) {
processOutput.DecodeText(data, length, strdata, 2);
if (!err->Process(strdata.c_str(), static_cast<int>(strdata.size()))) {
err = nullptr;
}
},
[&err]() { err = nullptr; });
while (out || err || !chain.Finished()) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
}
}
std::vector<cmUVProcessChain::Status> result;
auto status = chain.GetStatus();
std::transform(
status.begin(), status.end(), std::back_inserter(result),
[](const cmUVProcessChain::Status* s) -> cmUVProcessChain::Status {
return *s;
});
return result;
if (out) {
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
out->Process(strdata.c_str(), static_cast<int>(strdata.size()));
}
}
if (err) {
processOutput.DecodeText(std::string(), strdata, 2);
if (!strdata.empty()) {
err->Process(strdata.c_str(), static_cast<int>(strdata.size()));
}
}
cmsysProcess_WaitForExit(cp, nullptr);
}
cmProcessTools::LineParser::LineParser(char sep, bool ignoreCR)

View File

@ -7,10 +7,8 @@
#include <cstring>
#include <iosfwd>
#include <string>
#include <vector>
#include "cmProcessOutput.h"
#include "cmUVProcessChain.h"
/** \class cmProcessTools
* \brief Helper classes for process output parsing
@ -83,7 +81,7 @@ public:
};
/** Run a process and send output to given parsers. */
static std::vector<cmUVProcessChain::Status> RunProcess(
cmUVProcessChainBuilder& builder, OutputParser* out,
OutputParser* err = nullptr, Encoding encoding = cmProcessOutput::Auto);
static void RunProcess(struct cmsysProcess_s* cp, OutputParser* out,
OutputParser* err = nullptr,
Encoding encoding = cmProcessOutput::Auto);
};

View File

@ -31,9 +31,6 @@
#include "cmProcessOutput.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
#include "cmValue.h"
#if !defined(CMAKE_BOOTSTRAP)
@ -62,14 +59,12 @@
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <sstream>
#include <utility>
#include <vector>
@ -573,111 +568,85 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
const char* dir, OutputOption outputflag,
cmDuration timeout, Encoding encoding)
{
cmUVProcessChainBuilder builder;
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_INPUT, stdin)
.AddCommand(command);
if (dir) {
builder.SetWorkingDirectory(dir);
std::vector<const char*> argv;
argv.reserve(command.size() + 1);
for (std::string const& cmd : command) {
argv.push_back(cmd.c_str());
}
argv.push_back(nullptr);
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
if (cmSystemTools::GetRunCommandHideConsole()) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
if (outputflag == OUTPUT_PASSTHROUGH) {
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
captureStdOut = nullptr;
captureStdErr = nullptr;
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, stdout)
.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, stderr);
} else if (outputflag == OUTPUT_MERGE ||
(captureStdErr && captureStdErr == captureStdOut)) {
builder.SetMergedBuiltinStreams();
cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1);
captureStdErr = nullptr;
} else {
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
}
assert(!captureStdErr || captureStdErr != captureStdOut);
auto chain = builder.Start();
bool timedOut = false;
cm::uv_timer_ptr timer;
if (timeout.count()) {
timer.init(chain.GetLoop(), &timedOut);
timer.start(
[](uv_timer_t* t) {
auto* timedOutPtr = static_cast<bool*>(t->data);
*timedOutPtr = true;
},
static_cast<uint64_t>(timeout.count() * 1000.0), 0);
}
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
std::vector<char> tempStdOut;
std::vector<char> tempStdErr;
cm::uv_pipe_ptr outStream;
bool outFinished = true;
cm::uv_pipe_ptr errStream;
bool errFinished = true;
char* data;
int length;
int pipe;
cmProcessOutput processOutput(encoding);
std::unique_ptr<cmUVStreamReadHandle> outputHandle;
std::unique_ptr<cmUVStreamReadHandle> errorHandle;
std::string strdata;
if (outputflag != OUTPUT_PASSTHROUGH &&
(captureStdOut || captureStdErr || outputflag != OUTPUT_NONE)) {
auto startRead =
[&outputflag, &processOutput,
&chain](cm::uv_pipe_ptr& pipe, int stream, std::string* captureStd,
std::vector<char>& tempStd, int id,
void (*outputFunc)(const std::string&),
bool& finished) -> std::unique_ptr<cmUVStreamReadHandle> {
if (stream < 0) {
return nullptr;
while ((pipe = cmsysProcess_WaitForData(cp, &data, &length, nullptr)) >
0) {
// Translate NULL characters in the output into valid text.
for (int i = 0; i < length; ++i) {
if (data[i] == '\0') {
data[i] = ' ';
}
}
pipe.init(chain.GetLoop(), 0);
uv_pipe_open(pipe, stream);
if (pipe == cmsysProcess_Pipe_STDOUT) {
if (outputflag != OUTPUT_NONE) {
processOutput.DecodeText(data, length, strdata, 1);
cmSystemTools::Stdout(strdata);
}
if (captureStdOut) {
cm::append(tempStdOut, data, data + length);
}
} else if (pipe == cmsysProcess_Pipe_STDERR) {
if (outputflag != OUTPUT_NONE) {
processOutput.DecodeText(data, length, strdata, 2);
cmSystemTools::Stderr(strdata);
}
if (captureStdErr) {
cm::append(tempStdErr, data, data + length);
}
}
}
finished = false;
return cmUVStreamRead(
pipe,
[outputflag, &processOutput, captureStd, &tempStd, id,
outputFunc](std::vector<char> data) {
// Translate NULL characters in the output into valid text.
for (auto& c : data) {
if (c == '\0') {
c = ' ';
}
}
if (outputflag != OUTPUT_NONE) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata, id);
outputFunc(strdata);
}
if (captureStd) {
cm::append(tempStd, data.data(), data.data() + data.size());
}
},
[&finished, outputflag, &processOutput, id, outputFunc]() {
finished = true;
if (outputflag != OUTPUT_NONE) {
std::string strdata;
processOutput.DecodeText(std::string(), strdata, id);
if (!strdata.empty()) {
outputFunc(strdata);
}
}
});
};
outputHandle =
startRead(outStream, chain.OutputStream(), captureStdOut, tempStdOut, 1,
cmSystemTools::Stdout, outFinished);
if (chain.OutputStream() != chain.ErrorStream()) {
errorHandle =
startRead(errStream, chain.ErrorStream(), captureStdErr, tempStdErr, 2,
cmSystemTools::Stderr, errFinished);
if (outputflag != OUTPUT_NONE) {
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
cmSystemTools::Stdout(strdata);
}
processOutput.DecodeText(std::string(), strdata, 2);
if (!strdata.empty()) {
cmSystemTools::Stderr(strdata);
}
}
}
while (!timedOut && !(chain.Finished() && outFinished && errFinished)) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
}
cmsysProcess_WaitForExit(cp, nullptr);
if (captureStdOut) {
captureStdOut->assign(tempStdOut.begin(), tempStdOut.end());
@ -689,7 +658,37 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
}
bool result = true;
if (timedOut) {
if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
if (retVal) {
*retVal = cmsysProcess_GetExitValue(cp);
} else {
if (cmsysProcess_GetExitValue(cp) != 0) {
result = false;
}
}
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) {
const char* exception_str = cmsysProcess_GetExceptionString(cp);
if (outputflag != OUTPUT_NONE) {
std::cerr << exception_str << std::endl;
}
if (captureStdErr) {
captureStdErr->append(exception_str, strlen(exception_str));
} else if (captureStdOut) {
captureStdOut->append(exception_str, strlen(exception_str));
}
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Error) {
const char* error_str = cmsysProcess_GetErrorString(cp);
if (outputflag != OUTPUT_NONE) {
std::cerr << error_str << std::endl;
}
if (captureStdErr) {
captureStdErr->append(error_str, strlen(error_str));
} else if (captureStdOut) {
captureStdOut->append(error_str, strlen(error_str));
}
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Expired) {
const char* error_str = "Process terminated due to timeout\n";
if (outputflag != OUTPUT_NONE) {
std::cerr << error_str << std::endl;
@ -698,34 +697,9 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
captureStdErr->append(error_str, strlen(error_str));
}
result = false;
} else {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
if (retVal) {
*retVal = static_cast<int>(status.ExitStatus);
} else {
if (status.ExitStatus != 0) {
result = false;
}
}
break;
default: {
if (outputflag != OUTPUT_NONE) {
std::cerr << exception.second << std::endl;
}
if (captureStdErr) {
captureStdErr->append(exception.second);
} else if (captureStdOut) {
captureStdOut->append(exception.second);
}
result = false;
} break;
}
}
cmsysProcess_Delete(cp);
return result;
}
@ -2239,10 +2213,9 @@ bool cmSystemTools::ListTar(const std::string& outFileName,
#endif
}
cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine(
uv_loop_t* loop, uv_stream_t* outPipe, uv_stream_t* errPipe,
std::string& line, cmDuration timeout, std::vector<char>& out,
std::vector<char>& err)
int cmSystemTools::WaitForLine(cmsysProcess* process, std::string& line,
cmDuration timeout, std::vector<char>& out,
std::vector<char>& err)
{
line.clear();
auto outiter = out.begin();
@ -2264,7 +2237,7 @@ cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine(
line.append(out.data(), length);
}
out.erase(out.begin(), outiter + 1);
return WaitForLineResult::STDOUT;
return cmsysProcess_Pipe_STDOUT;
}
}
@ -2282,66 +2255,33 @@ cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine(
line.append(err.data(), length);
}
err.erase(err.begin(), erriter + 1);
return WaitForLineResult::STDERR;
return cmsysProcess_Pipe_STDERR;
}
}
// No newlines found. Wait for more data from the process.
struct ReadData
{
uv_stream_t* Stream;
std::vector<char> Buffer;
bool Read = false;
bool Finished = false;
};
auto startRead =
[](uv_stream_t* stream,
ReadData& data) -> std::unique_ptr<cmUVStreamReadHandle> {
data.Stream = stream;
return cmUVStreamRead(
stream,
[&data](std::vector<char> buf) {
data.Buffer = std::move(buf);
data.Read = true;
uv_read_stop(data.Stream);
},
[&data]() { data.Finished = true; });
};
ReadData outData;
auto outHandle = startRead(outPipe, outData);
ReadData errData;
auto errHandle = startRead(errPipe, errData);
cm::uv_timer_ptr timer;
bool timedOut = false;
timer.init(*loop, &timedOut);
timer.start(
[](uv_timer_t* handle) {
auto* timedOutPtr = static_cast<bool*>(handle->data);
*timedOutPtr = true;
},
static_cast<uint64_t>(timeout.count() * 1000.0), 0);
uv_run(loop, UV_RUN_ONCE);
if (timedOut) {
int length;
char* data;
double timeoutAsDbl = timeout.count();
int pipe =
cmsysProcess_WaitForData(process, &data, &length, &timeoutAsDbl);
if (pipe == cmsysProcess_Pipe_Timeout) {
// Timeout has been exceeded.
return WaitForLineResult::Timeout;
return pipe;
}
if (outData.Read) {
processOutput.DecodeText(outData.Buffer.data(), outData.Buffer.size(),
strdata, 1);
if (pipe == cmsysProcess_Pipe_STDOUT) {
processOutput.DecodeText(data, length, strdata, 1);
// Append to the stdout buffer.
std::vector<char>::size_type size = out.size();
cm::append(out, strdata);
outiter = out.begin() + size;
} else if (errData.Read) {
processOutput.DecodeText(errData.Buffer.data(), errData.Buffer.size(),
strdata, 2);
} else if (pipe == cmsysProcess_Pipe_STDERR) {
processOutput.DecodeText(data, length, strdata, 2);
// Append to the stderr buffer.
std::vector<char>::size_type size = err.size();
cm::append(err, strdata);
erriter = err.begin() + size;
} else if (outData.Finished && errData.Finished) {
} else if (pipe == cmsysProcess_Pipe_None) {
// Both stdout and stderr pipes have broken. Return leftover data.
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
@ -2358,20 +2298,14 @@ cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine(
if (!out.empty()) {
line.append(out.data(), outiter - out.begin());
out.erase(out.begin(), out.end());
return WaitForLineResult::STDOUT;
return cmsysProcess_Pipe_STDOUT;
}
if (!err.empty()) {
line.append(err.data(), erriter - err.begin());
err.erase(err.begin(), err.end());
return WaitForLineResult::STDERR;
return cmsysProcess_Pipe_STDERR;
}
return WaitForLineResult::None;
}
if (!outData.Finished) {
uv_read_stop(outPipe);
}
if (!errData.Finished) {
uv_read_stop(errPipe);
return cmsysProcess_Pipe_None;
}
}
}

View File

@ -18,8 +18,7 @@
#include <cm/optional>
#include <cm/string_view>
#include <cm3p/uv.h>
#include "cmsys/Process.h"
#include "cmsys/Status.hxx" // IWYU pragma: export
#include "cmsys/SystemTools.hxx" // IWYU pragma: export
@ -340,20 +339,10 @@ public:
*/
static void ReportLastSystemError(const char* m);
enum class WaitForLineResult
{
None,
STDOUT,
STDERR,
Timeout,
};
/** a general output handler for libuv */
static WaitForLineResult WaitForLine(uv_loop_t* loop, uv_stream_t* outPipe,
uv_stream_t* errPipe, std::string& line,
cmDuration timeout,
std::vector<char>& out,
std::vector<char>& err);
/** a general output handler for cmsysProcess */
static int WaitForLine(cmsysProcess* process, std::string& line,
cmDuration timeout, std::vector<char>& out,
std::vector<char>& err);
static void SetForceUnixPaths(bool v) { s_ForceUnixPaths = v; }
static bool GetForceUnixPaths() { return s_ForceUnixPaths; }

View File

@ -72,6 +72,7 @@
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/Terminal.h"
@ -294,8 +295,14 @@ int CLCompileAndDependencies(const std::vector<std::string>& args)
}
}
cmUVProcessChainBuilder builder;
builder.AddCommand(command).SetWorkingDirectory(currentBinaryDir);
std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp(
cmsysProcess_New(), cmsysProcess_Delete);
std::vector<const char*> argv(command.size() + 1);
std::transform(command.begin(), command.end(), argv.begin(),
[](std::string const& s) { return s.c_str(); });
argv.back() = nullptr;
cmsysProcess_SetCommand(cp.get(), argv.data());
cmsysProcess_SetWorkingDirectory(cp.get(), currentBinaryDir.c_str());
cmsys::ofstream fout(depFile.c_str());
if (!fout) {
@ -306,18 +313,22 @@ int CLCompileAndDependencies(const std::vector<std::string>& args)
CLOutputLogger errLogger(std::cerr);
// Start the process.
auto result =
cmProcessTools::RunProcess(builder, &includeParser, &errLogger);
auto const& subStatus = result.front();
cmProcessTools::RunProcess(cp.get(), &includeParser, &errLogger);
int status = 0;
// handle status of process
if (subStatus.SpawnResult != 0) {
status = 2;
} else if (subStatus.TermSignal != 0) {
status = 1;
} else {
status = static_cast<int>(subStatus.ExitStatus);
switch (cmsysProcess_GetState(cp.get())) {
case cmsysProcess_State_Exited:
status = cmsysProcess_GetExitValue(cp.get());
break;
case cmsysProcess_State_Exception:
status = 1;
break;
case cmsysProcess_State_Error:
status = 2;
break;
default:
break;
}
if (status != 0) {
@ -1105,8 +1116,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
int ret = 0;
auto time_start = std::chrono::steady_clock::now();
cmSystemTools::RunSingleCommand(command, nullptr, nullptr, &ret, nullptr,
cmSystemTools::OUTPUT_PASSTHROUGH);
cmSystemTools::RunSingleCommand(command, nullptr, nullptr, &ret);
auto time_finish = std::chrono::steady_clock::now();
std::chrono::duration<double> time_elapsed = time_finish - time_start;
@ -1880,6 +1890,21 @@ int cmcmd::ExecuteLinkScript(std::vector<std::string> const& args)
}
}
// Allocate a process instance.
cmsysProcess* cp = cmsysProcess_New();
if (!cp) {
std::cerr << "Error allocating process instance in link script."
<< std::endl;
return 1;
}
// Children should share stdout and stderr with this process.
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
// Run the command lines verbatim.
cmsysProcess_SetOption(cp, cmsysProcess_Option_Verbatim, 1);
// Read command lines from the script.
cmsys::ifstream fin(args[2].c_str());
if (!fin) {
@ -1897,21 +1922,9 @@ int cmcmd::ExecuteLinkScript(std::vector<std::string> const& args)
continue;
}
// Allocate a process instance.
cmUVProcessChainBuilder builder;
// Children should share stdout and stderr with this process.
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, stdout)
.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, stderr);
// Setup this command line.
std::vector<std::string> args2;
#ifdef _WIN32
cmSystemTools::ParseWindowsCommandLine(command.c_str(), args2);
#else
cmSystemTools::ParseUnixCommandLine(command.c_str(), args2);
#endif
builder.AddCommand(args2);
const char* cmd[2] = { command.c_str(), nullptr };
cmsysProcess_SetCommand(cp, cmd);
// Report the command if verbose output is enabled.
if (verbose) {
@ -1919,29 +1932,35 @@ int cmcmd::ExecuteLinkScript(std::vector<std::string> const& args)
}
// Run the command and wait for it to exit.
auto chain = builder.Start();
chain.Wait();
cmsysProcess_Execute(cp);
cmsysProcess_WaitForExit(cp, nullptr);
// Report failure if any.
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
if (status.ExitStatus != 0) {
result = static_cast<int>(status.ExitStatus);
switch (cmsysProcess_GetState(cp)) {
case cmsysProcess_State_Exited: {
int value = cmsysProcess_GetExitValue(cp);
if (value != 0) {
result = value;
}
} break;
case cmsysProcess_State_Exception:
std::cerr << "Error running link command: "
<< cmsysProcess_GetExceptionString(cp) << std::endl;
result = 1;
break;
case cmUVProcessChain::ExceptionCode::Spawn:
std::cerr << "Error running link command: " << exception.second;
case cmsysProcess_State_Error:
std::cerr << "Error running link command: "
<< cmsysProcess_GetErrorString(cp) << std::endl;
result = 2;
break;
default:
std::cerr << "Error running link command: " << exception.second;
result = 1;
break;
}
}
// Free the process instance.
cmsysProcess_Delete(cp);
// Return the final resulting return value.
return result;
}

View File

@ -75,10 +75,6 @@
{ include: [ "<ostream>", public, "\"cmsys/FStream.hxx\"", public ] },
{ include: [ "<fstream>", public, "\"cmsys/FStream.hxx\"", public ] },
{ symbol: [ "mode_t", private, "\"cm_sys_stat.h\"", public ] },
{ symbol: [ "S_IWUSR", private, "\"cm_sys_stat.h\"", public ] },
{ symbol: [ "S_IWGRP", private, "\"cm_sys_stat.h\"", public ] },
{ include: [ "<filesystem>", public, "<cm/filesystem>", public ] },
{ include: [ "<optional>", public, "<cm/optional>", public ] },
{ include: [ "<shared_mutex>", public, "<cm/shared_mutex>", public ] },