| /* 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; |
| } |