354 lines
12 KiB
C++
354 lines
12 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#include "cmCTestScriptHandler.h"
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <map>
|
|
#include <ratio>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <cm/memory>
|
|
|
|
#include <cm3p/uv.h>
|
|
|
|
#include "cmCTest.h"
|
|
#include "cmCTestBuildCommand.h"
|
|
#include "cmCTestConfigureCommand.h"
|
|
#include "cmCTestCoverageCommand.h"
|
|
#include "cmCTestEmptyBinaryDirectoryCommand.h"
|
|
#include "cmCTestMemCheckCommand.h"
|
|
#include "cmCTestReadCustomFilesCommand.h"
|
|
#include "cmCTestRunScriptCommand.h"
|
|
#include "cmCTestSleepCommand.h"
|
|
#include "cmCTestStartCommand.h"
|
|
#include "cmCTestSubmitCommand.h"
|
|
#include "cmCTestTestCommand.h"
|
|
#include "cmCTestUpdateCommand.h"
|
|
#include "cmCTestUploadCommand.h"
|
|
#include "cmDuration.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmState.h"
|
|
#include "cmStateDirectory.h"
|
|
#include "cmStateSnapshot.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmUVHandlePtr.h"
|
|
#include "cmUVProcessChain.h"
|
|
#include "cmake.h"
|
|
|
|
cmCTestScriptHandler::cmCTestScriptHandler(cmCTest* ctest)
|
|
: CTest(ctest)
|
|
{
|
|
}
|
|
|
|
cmCTestScriptHandler::~cmCTestScriptHandler() = default;
|
|
|
|
// just adds an argument to the vector
|
|
void cmCTestScriptHandler::AddConfigurationScript(std::string const& script,
|
|
bool pscope)
|
|
{
|
|
this->ConfigurationScripts.emplace_back(script);
|
|
this->ScriptProcessScope.push_back(pscope);
|
|
}
|
|
|
|
// the generic entry point for handling scripts, this routine will run all
|
|
// the scripts provides a -S arguments
|
|
int cmCTestScriptHandler::ProcessHandler()
|
|
{
|
|
int res = 0;
|
|
for (size_t i = 0; i < this->ConfigurationScripts.size(); ++i) {
|
|
// for each script run it
|
|
res |= this->RunConfigurationScript(this->ConfigurationScripts[i],
|
|
this->ScriptProcessScope[i]);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void cmCTestScriptHandler::UpdateElapsedTime()
|
|
{
|
|
if (this->Makefile) {
|
|
// set the current elapsed time
|
|
auto itime = cmDurationTo<unsigned int>(this->CTest->GetElapsedTime());
|
|
auto timeString = std::to_string(itime);
|
|
this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString);
|
|
}
|
|
}
|
|
|
|
int cmCTestScriptHandler::ExecuteScript(std::string const& total_script_arg)
|
|
{
|
|
// execute the script passing in the arguments to the script as well as the
|
|
// arguments from this invocation of cmake
|
|
std::vector<std::string> argv;
|
|
argv.push_back(cmSystemTools::GetCTestCommand());
|
|
argv.push_back("-SR");
|
|
argv.push_back(total_script_arg);
|
|
|
|
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Executable for CTest is: " << cmSystemTools::GetCTestCommand()
|
|
<< "\n");
|
|
|
|
// 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) {
|
|
// in a nested subprocess, skip the parent's `-SR <path>` arguments.
|
|
if (initArgs[i] == "-SR") {
|
|
i++; // <path>
|
|
} else {
|
|
argv.push_back(initArgs[i]);
|
|
}
|
|
}
|
|
|
|
// Now create process object
|
|
cmUVProcessChainBuilder builder;
|
|
builder.AddCommand(argv)
|
|
.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());
|
|
|
|
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) {
|
|
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Output: " << line << "\n");
|
|
if (pipe == cmSystemTools::WaitForLineResult::STDERR) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n");
|
|
} else if (pipe == cmSystemTools::WaitForLineResult::STDOUT) {
|
|
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n");
|
|
}
|
|
pipe =
|
|
cmSystemTools::WaitForLine(&process.GetLoop(), outPipe, errPipe, 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();
|
|
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 (failed) {
|
|
std::ostringstream message;
|
|
message << "Error running command: [";
|
|
message << static_cast<int>(result.first) << "] ";
|
|
for (std::string const& arg : argv) {
|
|
message << arg << " ";
|
|
}
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, message.str() << std::endl);
|
|
return -1;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
void cmCTestScriptHandler::CreateCMake()
|
|
{
|
|
// create a cmake instance to read the configuration script
|
|
this->CMake = cm::make_unique<cmake>(cmake::RoleScript, cmState::CTest);
|
|
this->CMake->SetHomeDirectory("");
|
|
this->CMake->SetHomeOutputDirectory("");
|
|
this->CMake->GetCurrentSnapshot().SetDefaultDefinitions();
|
|
this->CMake->AddCMakePaths();
|
|
this->CMake->SetWorkingMode(cmake::SCRIPT_MODE,
|
|
cmake::CommandFailureAction::EXIT_CODE);
|
|
this->GlobalGenerator =
|
|
cm::make_unique<cmGlobalGenerator>(this->CMake.get());
|
|
|
|
cmStateSnapshot snapshot = this->CMake->GetCurrentSnapshot();
|
|
std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
|
|
snapshot.GetDirectory().SetCurrentSource(cwd);
|
|
snapshot.GetDirectory().SetCurrentBinary(cwd);
|
|
this->Makefile =
|
|
cm::make_unique<cmMakefile>(this->GlobalGenerator.get(), snapshot);
|
|
if (this->ParentMakefile) {
|
|
this->Makefile->SetRecursionDepth(
|
|
this->ParentMakefile->GetRecursionDepth());
|
|
}
|
|
|
|
this->CMake->SetProgressCallback(
|
|
[this](std::string const& m, float /*unused*/) {
|
|
if (!m.empty()) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "-- " << m << std::endl);
|
|
}
|
|
});
|
|
|
|
cmState* state = this->CMake->GetState();
|
|
state->AddBuiltinCommand("ctest_build", cmCTestBuildCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_configure",
|
|
cmCTestConfigureCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_coverage",
|
|
cmCTestCoverageCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_empty_binary_directory",
|
|
cmCTestEmptyBinaryDirectoryCommand);
|
|
state->AddBuiltinCommand("ctest_memcheck",
|
|
cmCTestMemCheckCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_read_custom_files",
|
|
cmCTestReadCustomFilesCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_run_script",
|
|
cmCTestRunScriptCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_sleep", cmCTestSleepCommand);
|
|
state->AddBuiltinCommand("ctest_start", cmCTestStartCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_submit", cmCTestSubmitCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_test", cmCTestTestCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_update", cmCTestUpdateCommand(this->CTest));
|
|
state->AddBuiltinCommand("ctest_upload", cmCTestUploadCommand(this->CTest));
|
|
}
|
|
|
|
// this sets up some variables for the script to use, creates the required
|
|
// cmake instance and generators, and then reads in the script
|
|
int cmCTestScriptHandler::ReadInScript(std::string const& total_script_arg)
|
|
{
|
|
// Reset the error flag so that the script is read in no matter what
|
|
cmSystemTools::ResetErrorOccurredFlag();
|
|
|
|
// if the argument has a , in it then it needs to be broken into the fist
|
|
// argument (which is the script) and the second argument which will be
|
|
// passed into the scripts as S_ARG
|
|
std::string script;
|
|
std::string script_arg;
|
|
std::string::size_type const comma_pos = total_script_arg.find(',');
|
|
if (comma_pos != std::string::npos) {
|
|
script = total_script_arg.substr(0, comma_pos);
|
|
script_arg = total_script_arg.substr(comma_pos + 1);
|
|
} else {
|
|
script = total_script_arg;
|
|
}
|
|
// make sure the file exists
|
|
if (!cmSystemTools::FileExists(script)) {
|
|
cmSystemTools::Error("Cannot find file: " + script);
|
|
return 1;
|
|
}
|
|
|
|
// read in the list file to fill the cache
|
|
// create a cmake instance to read the configuration script
|
|
this->CreateCMake();
|
|
|
|
// set a variable with the path to the current script
|
|
this->Makefile->AddDefinition("CTEST_SCRIPT_DIRECTORY",
|
|
cmSystemTools::GetFilenamePath(script));
|
|
this->Makefile->AddDefinition("CTEST_SCRIPT_NAME",
|
|
cmSystemTools::GetFilenameName(script));
|
|
this->Makefile->AddDefinition("CTEST_EXECUTABLE_NAME",
|
|
cmSystemTools::GetCTestCommand());
|
|
this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME",
|
|
cmSystemTools::GetCMakeCommand());
|
|
this->UpdateElapsedTime();
|
|
|
|
// set the CTEST_CONFIGURATION_TYPE variable to the current value of the
|
|
// the -C argument on the command line.
|
|
if (!this->CTest->GetConfigType().empty()) {
|
|
this->Makefile->AddDefinition("CTEST_CONFIGURATION_TYPE",
|
|
this->CTest->GetConfigType());
|
|
}
|
|
|
|
// add the script arg if defined
|
|
if (!script_arg.empty()) {
|
|
this->Makefile->AddDefinition("CTEST_SCRIPT_ARG", script_arg);
|
|
}
|
|
|
|
// set a callback function to update the elapsed time
|
|
this->Makefile->OnExecuteCommand([this] { this->UpdateElapsedTime(); });
|
|
|
|
/* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and
|
|
CMakeSystemSpecificInformation, so
|
|
that variables like CMAKE_SYSTEM and also the search paths for libraries,
|
|
header and executables are set correctly and can be used. Makes new-style
|
|
ctest scripting easier. */
|
|
std::string systemFile =
|
|
this->Makefile->GetModulesFile("CTestScriptMode.cmake");
|
|
if (!this->Makefile->ReadListFile(systemFile) ||
|
|
cmSystemTools::GetErrorOccurredFlag()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error in read:" << systemFile << "\n");
|
|
return -1;
|
|
}
|
|
|
|
// Add definitions of variables passed in on the command line:
|
|
std::map<std::string, std::string> const& defs =
|
|
this->CTest->GetDefinitions();
|
|
for (auto const& d : defs) {
|
|
this->Makefile->AddDefinition(d.first, d.second);
|
|
}
|
|
|
|
int res = 0;
|
|
|
|
// finally read in the script
|
|
if (!this->Makefile->ReadListFile(script) ||
|
|
cmSystemTools::GetErrorOccurredFlag()) {
|
|
// Reset the error flag so that it can run more than
|
|
// one script with an error when you use ctest_run_script.
|
|
cmSystemTools::ResetErrorOccurredFlag();
|
|
res = -1;
|
|
}
|
|
|
|
return this->CMake->HasScriptModeExitCode()
|
|
? this->CMake->GetScriptModeExitCode()
|
|
: res;
|
|
}
|
|
|
|
// run a specific script
|
|
int cmCTestScriptHandler::RunConfigurationScript(
|
|
std::string const& total_script_arg, bool pscope)
|
|
{
|
|
#ifndef CMAKE_BOOTSTRAP
|
|
cmSystemTools::SaveRestoreEnvironment sre;
|
|
#endif
|
|
|
|
int result;
|
|
|
|
// read in the script
|
|
if (pscope) {
|
|
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Reading Script: " << total_script_arg << std::endl);
|
|
result = this->ReadInScript(total_script_arg);
|
|
} else {
|
|
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Executing Script: " << total_script_arg << std::endl);
|
|
result = this->ExecuteScript(total_script_arg);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool cmCTestScriptHandler::RunScript(cmCTest* ctest, cmMakefile* mf,
|
|
std::string const& sname, bool InProcess,
|
|
int* returnValue)
|
|
{
|
|
auto sh = cm::make_unique<cmCTestScriptHandler>(ctest);
|
|
sh->ParentMakefile = mf;
|
|
sh->AddConfigurationScript(sname, InProcess);
|
|
int res = sh->ProcessHandler();
|
|
if (returnValue) {
|
|
*returnValue = res;
|
|
}
|
|
return true;
|
|
}
|