| /* 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 <cstdlib> | |
| #include <functional> | |
| #include <map> | |
| #include <utility> | |
| #include <cm/memory> | |
| #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 "cmStringAlgorithms.h" | |
| #include "cmSystemTools.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(); | |
| std::vector<char> out; | |
| std::vector<char> err; | |
| std::string line; | |
| cmSystemTools::WaitForLineResult pipe; | |
| while ((pipe = cmSystemTools::WaitForLine( | |
| &process.GetLoop(), process.OutputStream(), process.ErrorStream(), | |
| line, out, err)) != 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'); | |
| } | |
| } | |
| // 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, '\n'); | |
| failed = true; | |
| break; | |
| default: | |
| retVal = status.TermSignal; | |
| cmCTestLog(this->CTest, ERROR_MESSAGE, | |
| "\tThere was an exception: ", result.second, ' ', retVal, | |
| '\n'); | |
| failed = true; | |
| } | |
| if (failed) { | |
| cmCTestLog(this->CTest, ERROR_MESSAGE, "Error running command: [", | |
| static_cast<int>(result.first), "] ", cmWrap("", argv, " ", ""), | |
| '\n'); | |
| return -1; | |
| } | |
| return retVal; | |
| } | |
| void cmCTestScriptHandler::CreateCMake() | |
| { | |
| // create a cmake instance to read the configuration script | |
| this->CMake = cm::make_unique<cmake>(cmState::Role::CTest); | |
| this->CMake->GetCurrentSnapshot().SetDefaultDefinitions(); | |
| this->CMake->AddCMakePaths(); | |
| 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, '\n'); | |
| } | |
| }); | |
| 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, '\n'); | |
| result = this->ReadInScript(total_script_arg); | |
| } else { | |
| cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, | |
| "Executing Script: ", total_script_arg, '\n'); | |
| 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; | |
| } |