cmCTestScriptHandler.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCTestScriptHandler.h"
  4. #include <chrono>
  5. #include <cstdlib>
  6. #include <map>
  7. #include <ratio>
  8. #include <sstream>
  9. #include <utility>
  10. #include <cm/memory>
  11. #include <cm3p/uv.h>
  12. #include "cmCTest.h"
  13. #include "cmCTestBuildCommand.h"
  14. #include "cmCTestCommand.h"
  15. #include "cmCTestConfigureCommand.h"
  16. #include "cmCTestCoverageCommand.h"
  17. #include "cmCTestEmptyBinaryDirectoryCommand.h"
  18. #include "cmCTestMemCheckCommand.h"
  19. #include "cmCTestReadCustomFilesCommand.h"
  20. #include "cmCTestRunScriptCommand.h"
  21. #include "cmCTestSleepCommand.h"
  22. #include "cmCTestStartCommand.h"
  23. #include "cmCTestSubmitCommand.h"
  24. #include "cmCTestTestCommand.h"
  25. #include "cmCTestUpdateCommand.h"
  26. #include "cmCTestUploadCommand.h"
  27. #include "cmCommand.h"
  28. #include "cmDuration.h"
  29. #include "cmGlobalGenerator.h"
  30. #include "cmMakefile.h"
  31. #include "cmState.h"
  32. #include "cmStateDirectory.h"
  33. #include "cmStateSnapshot.h"
  34. #include "cmSystemTools.h"
  35. #include "cmUVHandlePtr.h"
  36. #include "cmUVProcessChain.h"
  37. #include "cmake.h"
  38. cmCTestScriptHandler::cmCTestScriptHandler() = default;
  39. void cmCTestScriptHandler::Initialize()
  40. {
  41. this->Superclass::Initialize();
  42. this->Makefile.reset();
  43. this->ParentMakefile = nullptr;
  44. this->GlobalGenerator.reset();
  45. this->CMake.reset();
  46. }
  47. cmCTestScriptHandler::~cmCTestScriptHandler() = default;
  48. // just adds an argument to the vector
  49. void cmCTestScriptHandler::AddConfigurationScript(const std::string& script,
  50. bool pscope)
  51. {
  52. this->ConfigurationScripts.emplace_back(script);
  53. this->ScriptProcessScope.push_back(pscope);
  54. }
  55. // the generic entry point for handling scripts, this routine will run all
  56. // the scripts provides a -S arguments
  57. int cmCTestScriptHandler::ProcessHandler()
  58. {
  59. int res = 0;
  60. for (size_t i = 0; i < this->ConfigurationScripts.size(); ++i) {
  61. // for each script run it
  62. res |= this->RunConfigurationScript(this->ConfigurationScripts[i],
  63. this->ScriptProcessScope[i]);
  64. }
  65. if (res) {
  66. return -1;
  67. }
  68. return 0;
  69. }
  70. void cmCTestScriptHandler::UpdateElapsedTime()
  71. {
  72. if (this->Makefile) {
  73. // set the current elapsed time
  74. auto itime = cmDurationTo<unsigned int>(this->CTest->GetElapsedTime());
  75. auto timeString = std::to_string(itime);
  76. this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString);
  77. }
  78. }
  79. void cmCTestScriptHandler::AddCTestCommand(
  80. std::string const& name, std::unique_ptr<cmCTestCommand> command)
  81. {
  82. command->CTest = this->CTest;
  83. this->CMake->GetState()->AddBuiltinCommand(name, std::move(command));
  84. }
  85. int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg)
  86. {
  87. // execute the script passing in the arguments to the script as well as the
  88. // arguments from this invocation of cmake
  89. std::vector<std::string> argv;
  90. argv.push_back(cmSystemTools::GetCTestCommand());
  91. argv.push_back("-SR");
  92. argv.push_back(total_script_arg);
  93. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  94. "Executable for CTest is: " << cmSystemTools::GetCTestCommand()
  95. << "\n");
  96. // now pass through all the other arguments
  97. std::vector<std::string>& initArgs =
  98. this->CTest->GetInitialCommandLineArguments();
  99. //*** need to make sure this does not have the current script ***
  100. for (size_t i = 1; i < initArgs.size(); ++i) {
  101. argv.push_back(initArgs[i]);
  102. }
  103. // Now create process object
  104. cmUVProcessChainBuilder builder;
  105. builder.AddCommand(argv)
  106. .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
  107. .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
  108. auto process = builder.Start();
  109. cm::uv_pipe_ptr outPipe;
  110. outPipe.init(process.GetLoop(), 0);
  111. uv_pipe_open(outPipe, process.OutputStream());
  112. cm::uv_pipe_ptr errPipe;
  113. errPipe.init(process.GetLoop(), 0);
  114. uv_pipe_open(errPipe, process.ErrorStream());
  115. std::vector<char> out;
  116. std::vector<char> err;
  117. std::string line;
  118. auto pipe =
  119. cmSystemTools::WaitForLine(&process.GetLoop(), outPipe, errPipe, line,
  120. std::chrono::seconds(100), out, err);
  121. while (pipe != cmSystemTools::WaitForLineResult::None) {
  122. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  123. "Output: " << line << "\n");
  124. if (pipe == cmSystemTools::WaitForLineResult::STDERR) {
  125. cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n");
  126. } else if (pipe == cmSystemTools::WaitForLineResult::STDOUT) {
  127. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n");
  128. }
  129. pipe =
  130. cmSystemTools::WaitForLine(&process.GetLoop(), outPipe, errPipe, line,
  131. std::chrono::seconds(100), out, err);
  132. }
  133. // Properly handle output of the build command
  134. process.Wait();
  135. auto const& status = process.GetStatus(0);
  136. auto result = status.GetException();
  137. int retVal = 0;
  138. bool failed = false;
  139. switch (result.first) {
  140. case cmUVProcessChain::ExceptionCode::None:
  141. retVal = static_cast<int>(status.ExitStatus);
  142. break;
  143. case cmUVProcessChain::ExceptionCode::Spawn:
  144. cmCTestLog(this->CTest, ERROR_MESSAGE,
  145. "\tError executing ctest: " << result.second << std::endl);
  146. failed = true;
  147. break;
  148. default:
  149. retVal = status.TermSignal;
  150. cmCTestLog(this->CTest, ERROR_MESSAGE,
  151. "\tThere was an exception: " << result.second << " " << retVal
  152. << std::endl);
  153. failed = true;
  154. }
  155. if (failed) {
  156. std::ostringstream message;
  157. message << "Error running command: [";
  158. message << static_cast<int>(result.first) << "] ";
  159. for (std::string const& arg : argv) {
  160. message << arg << " ";
  161. }
  162. cmCTestLog(this->CTest, ERROR_MESSAGE, message.str() << std::endl);
  163. return -1;
  164. }
  165. return retVal;
  166. }
  167. void cmCTestScriptHandler::CreateCMake()
  168. {
  169. // create a cmake instance to read the configuration script
  170. this->CMake = cm::make_unique<cmake>(cmake::RoleScript, cmState::CTest);
  171. this->CMake->SetHomeDirectory("");
  172. this->CMake->SetHomeOutputDirectory("");
  173. this->CMake->GetCurrentSnapshot().SetDefaultDefinitions();
  174. this->CMake->AddCMakePaths();
  175. this->GlobalGenerator =
  176. cm::make_unique<cmGlobalGenerator>(this->CMake.get());
  177. cmStateSnapshot snapshot = this->CMake->GetCurrentSnapshot();
  178. std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
  179. snapshot.GetDirectory().SetCurrentSource(cwd);
  180. snapshot.GetDirectory().SetCurrentBinary(cwd);
  181. this->Makefile =
  182. cm::make_unique<cmMakefile>(this->GlobalGenerator.get(), snapshot);
  183. if (this->ParentMakefile) {
  184. this->Makefile->SetRecursionDepth(
  185. this->ParentMakefile->GetRecursionDepth());
  186. }
  187. this->CMake->SetProgressCallback(
  188. [this](const std::string& m, float /*unused*/) {
  189. if (!m.empty()) {
  190. cmCTestLog(this->CTest, HANDLER_OUTPUT, "-- " << m << std::endl);
  191. }
  192. });
  193. cmState* state = this->CMake->GetState();
  194. this->AddCTestCommand("ctest_build", cm::make_unique<cmCTestBuildCommand>());
  195. this->AddCTestCommand("ctest_configure",
  196. cm::make_unique<cmCTestConfigureCommand>());
  197. this->AddCTestCommand("ctest_coverage",
  198. cm::make_unique<cmCTestCoverageCommand>());
  199. state->AddBuiltinCommand("ctest_empty_binary_directory",
  200. cmCTestEmptyBinaryDirectoryCommand);
  201. this->AddCTestCommand("ctest_memcheck",
  202. cm::make_unique<cmCTestMemCheckCommand>());
  203. this->AddCTestCommand("ctest_read_custom_files",
  204. cm::make_unique<cmCTestReadCustomFilesCommand>());
  205. this->AddCTestCommand("ctest_run_script",
  206. cm::make_unique<cmCTestRunScriptCommand>());
  207. state->AddBuiltinCommand("ctest_sleep", cmCTestSleepCommand);
  208. this->AddCTestCommand("ctest_start", cm::make_unique<cmCTestStartCommand>());
  209. this->AddCTestCommand("ctest_submit",
  210. cm::make_unique<cmCTestSubmitCommand>());
  211. this->AddCTestCommand("ctest_test", cm::make_unique<cmCTestTestCommand>());
  212. this->AddCTestCommand("ctest_update",
  213. cm::make_unique<cmCTestUpdateCommand>());
  214. this->AddCTestCommand("ctest_upload",
  215. cm::make_unique<cmCTestUploadCommand>());
  216. }
  217. // this sets up some variables for the script to use, creates the required
  218. // cmake instance and generators, and then reads in the script
  219. int cmCTestScriptHandler::ReadInScript(const std::string& total_script_arg)
  220. {
  221. // Reset the error flag so that the script is read in no matter what
  222. cmSystemTools::ResetErrorOccurredFlag();
  223. // if the argument has a , in it then it needs to be broken into the fist
  224. // argument (which is the script) and the second argument which will be
  225. // passed into the scripts as S_ARG
  226. std::string script;
  227. std::string script_arg;
  228. const std::string::size_type comma_pos = total_script_arg.find(',');
  229. if (comma_pos != std::string::npos) {
  230. script = total_script_arg.substr(0, comma_pos);
  231. script_arg = total_script_arg.substr(comma_pos + 1);
  232. } else {
  233. script = total_script_arg;
  234. }
  235. // make sure the file exists
  236. if (!cmSystemTools::FileExists(script)) {
  237. cmSystemTools::Error("Cannot find file: " + script);
  238. return 1;
  239. }
  240. // read in the list file to fill the cache
  241. // create a cmake instance to read the configuration script
  242. this->CreateCMake();
  243. // set a variable with the path to the current script
  244. this->Makefile->AddDefinition("CTEST_SCRIPT_DIRECTORY",
  245. cmSystemTools::GetFilenamePath(script));
  246. this->Makefile->AddDefinition("CTEST_SCRIPT_NAME",
  247. cmSystemTools::GetFilenameName(script));
  248. this->Makefile->AddDefinition("CTEST_EXECUTABLE_NAME",
  249. cmSystemTools::GetCTestCommand());
  250. this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME",
  251. cmSystemTools::GetCMakeCommand());
  252. this->UpdateElapsedTime();
  253. // set the CTEST_CONFIGURATION_TYPE variable to the current value of the
  254. // the -C argument on the command line.
  255. if (!this->CTest->GetConfigType().empty()) {
  256. this->Makefile->AddDefinition("CTEST_CONFIGURATION_TYPE",
  257. this->CTest->GetConfigType());
  258. }
  259. // add the script arg if defined
  260. if (!script_arg.empty()) {
  261. this->Makefile->AddDefinition("CTEST_SCRIPT_ARG", script_arg);
  262. }
  263. // set a callback function to update the elapsed time
  264. this->Makefile->OnExecuteCommand([this] { this->UpdateElapsedTime(); });
  265. /* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and
  266. CMakeSystemSpecificInformation, so
  267. that variables like CMAKE_SYSTEM and also the search paths for libraries,
  268. header and executables are set correctly and can be used. Makes new-style
  269. ctest scripting easier. */
  270. std::string systemFile =
  271. this->Makefile->GetModulesFile("CTestScriptMode.cmake");
  272. if (!this->Makefile->ReadListFile(systemFile) ||
  273. cmSystemTools::GetErrorOccurredFlag()) {
  274. cmCTestLog(this->CTest, ERROR_MESSAGE,
  275. "Error in read:" << systemFile << "\n");
  276. return 2;
  277. }
  278. // Add definitions of variables passed in on the command line:
  279. const std::map<std::string, std::string>& defs =
  280. this->CTest->GetDefinitions();
  281. for (auto const& d : defs) {
  282. this->Makefile->AddDefinition(d.first, d.second);
  283. }
  284. // finally read in the script
  285. if (!this->Makefile->ReadListFile(script) ||
  286. cmSystemTools::GetErrorOccurredFlag()) {
  287. // Reset the error flag so that it can run more than
  288. // one script with an error when you use ctest_run_script.
  289. cmSystemTools::ResetErrorOccurredFlag();
  290. return 2;
  291. }
  292. return 0;
  293. }
  294. // run a specific script
  295. int cmCTestScriptHandler::RunConfigurationScript(
  296. const std::string& total_script_arg, bool pscope)
  297. {
  298. #ifndef CMAKE_BOOTSTRAP
  299. cmSystemTools::SaveRestoreEnvironment sre;
  300. #endif
  301. int result;
  302. // read in the script
  303. if (pscope) {
  304. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  305. "Reading Script: " << total_script_arg << std::endl);
  306. result = this->ReadInScript(total_script_arg);
  307. } else {
  308. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  309. "Executing Script: " << total_script_arg << std::endl);
  310. result = this->ExecuteScript(total_script_arg);
  311. }
  312. return result;
  313. }
  314. bool cmCTestScriptHandler::RunScript(cmCTest* ctest, cmMakefile* mf,
  315. const std::string& sname, bool InProcess,
  316. int* returnValue)
  317. {
  318. auto sh = cm::make_unique<cmCTestScriptHandler>();
  319. sh->SetCTestInstance(ctest);
  320. sh->ParentMakefile = mf;
  321. sh->AddConfigurationScript(sname, InProcess);
  322. int res = sh->ProcessHandler();
  323. if (returnValue) {
  324. *returnValue = res;
  325. }
  326. return true;
  327. }