/* 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 #include #include #include #include #include #include #include #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(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 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& 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 ` arguments. if (initArgs[i] == "-SR") { i++; // } 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 out; std::vector 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(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(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::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(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(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 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(ctest); sh->ParentMakefile = mf; sh->AddConfigurationScript(sname, InProcess); int res = sh->ProcessHandler(); if (returnValue) { *returnValue = res; } return true; }