cmCTestBuildAndTestHandler.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. /*============================================================================
  2. CMake - Cross Platform Makefile Generator
  3. Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
  4. Distributed under the OSI-approved BSD License (the "License");
  5. see accompanying file Copyright.txt for details.
  6. This software is distributed WITHOUT ANY WARRANTY; without even the
  7. implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the License for more information.
  9. ============================================================================*/
  10. #include "cmCTestBuildAndTestHandler.h"
  11. #include "cmSystemTools.h"
  12. #include "cmCTest.h"
  13. #include "cmake.h"
  14. #include "cmGlobalGenerator.h"
  15. #include <cmsys/Process.h>
  16. #include "cmCTestTestHandler.h"
  17. //----------------------------------------------------------------------
  18. cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler()
  19. {
  20. this->BuildTwoConfig = false;
  21. this->BuildNoClean = false;
  22. this->BuildNoCMake = false;
  23. this->Timeout = 0;
  24. }
  25. //----------------------------------------------------------------------
  26. void cmCTestBuildAndTestHandler::Initialize()
  27. {
  28. this->BuildTargets.clear();
  29. this->Superclass::Initialize();
  30. }
  31. //----------------------------------------------------------------------
  32. const char* cmCTestBuildAndTestHandler::GetOutput()
  33. {
  34. return this->Output.c_str();
  35. }
  36. //----------------------------------------------------------------------
  37. int cmCTestBuildAndTestHandler::ProcessHandler()
  38. {
  39. this->Output = "";
  40. std::string output;
  41. cmSystemTools::ResetErrorOccuredFlag();
  42. int retv = this->RunCMakeAndTest(&this->Output);
  43. cmSystemTools::ResetErrorOccuredFlag();
  44. return retv;
  45. }
  46. //----------------------------------------------------------------------
  47. int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
  48. std::ostringstream &out, std::string &cmakeOutString, std::string &cwd,
  49. cmake *cm)
  50. {
  51. unsigned int k;
  52. std::vector<std::string> args;
  53. args.push_back(cmSystemTools::GetCMakeCommand());
  54. args.push_back(this->SourceDir);
  55. if(!this->BuildGenerator.empty())
  56. {
  57. std::string generator = "-G";
  58. generator += this->BuildGenerator;
  59. args.push_back(generator);
  60. }
  61. if(!this->BuildGeneratorPlatform.empty())
  62. {
  63. std::string platform = "-A";
  64. platform += this->BuildGeneratorPlatform;
  65. args.push_back(platform);
  66. }
  67. if(!this->BuildGeneratorToolset.empty())
  68. {
  69. std::string toolset = "-T";
  70. toolset += this->BuildGeneratorToolset;
  71. args.push_back(toolset);
  72. }
  73. const char* config = 0;
  74. if (!this->CTest->GetConfigType().empty())
  75. {
  76. config = this->CTest->GetConfigType().c_str();
  77. }
  78. #ifdef CMAKE_INTDIR
  79. if(!config)
  80. {
  81. config = CMAKE_INTDIR;
  82. }
  83. #endif
  84. if ( config )
  85. {
  86. std::string btype
  87. = "-DCMAKE_BUILD_TYPE:STRING=" + std::string(config);
  88. args.push_back(btype);
  89. }
  90. for(k=0; k < this->BuildOptions.size(); ++k)
  91. {
  92. args.push_back(this->BuildOptions[k]);
  93. }
  94. if (cm->Run(args) != 0)
  95. {
  96. out << "Error: cmake execution failed\n";
  97. out << cmakeOutString << "\n";
  98. // return to the original directory
  99. cmSystemTools::ChangeDirectory(cwd);
  100. if(outstring)
  101. {
  102. *outstring = out.str();
  103. }
  104. else
  105. {
  106. cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
  107. }
  108. return 1;
  109. }
  110. // do another config?
  111. if(this->BuildTwoConfig)
  112. {
  113. if (cm->Run(args) != 0)
  114. {
  115. out << "Error: cmake execution failed\n";
  116. out << cmakeOutString << "\n";
  117. // return to the original directory
  118. cmSystemTools::ChangeDirectory(cwd);
  119. if(outstring)
  120. {
  121. *outstring = out.str();
  122. }
  123. else
  124. {
  125. cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
  126. }
  127. return 1;
  128. }
  129. }
  130. out << "======== CMake output ======\n";
  131. out << cmakeOutString;
  132. out << "======== End CMake output ======\n";
  133. return 0;
  134. }
  135. //----------------------------------------------------------------------
  136. void CMakeMessageCallback(const char* m, const char*, bool&, void* s)
  137. {
  138. std::string* out = (std::string*)s;
  139. *out += m;
  140. *out += "\n";
  141. }
  142. void CMakeProgressCallback(const char*msg, float , void * s)
  143. {
  144. std::string* out = (std::string*)s;
  145. *out += msg;
  146. *out += "\n";
  147. }
  148. //----------------------------------------------------------------------
  149. void CMakeOutputCallback(const char* m, size_t len, void* s)
  150. {
  151. std::string* out = (std::string*)s;
  152. out->append(m, len);
  153. }
  154. //----------------------------------------------------------------------
  155. class cmCTestBuildAndTestCaptureRAII
  156. {
  157. cmake& CM;
  158. public:
  159. cmCTestBuildAndTestCaptureRAII(cmake& cm, std::string& s): CM(cm)
  160. {
  161. cmSystemTools::SetMessageCallback(CMakeMessageCallback, &s);
  162. cmSystemTools::SetStdoutCallback(CMakeOutputCallback, &s);
  163. cmSystemTools::SetStderrCallback(CMakeOutputCallback, &s);
  164. this->CM.SetProgressCallback(CMakeProgressCallback, &s);
  165. }
  166. ~cmCTestBuildAndTestCaptureRAII()
  167. {
  168. this->CM.SetProgressCallback(0, 0);
  169. cmSystemTools::SetStderrCallback(0, 0);
  170. cmSystemTools::SetStdoutCallback(0, 0);
  171. cmSystemTools::SetMessageCallback(0, 0);
  172. }
  173. };
  174. //----------------------------------------------------------------------
  175. int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
  176. {
  177. // if the generator and make program are not specified then it is an error
  178. if (this->BuildGenerator.empty())
  179. {
  180. if(outstring)
  181. {
  182. *outstring =
  183. "--build-and-test requires that the generator "
  184. "be provided using the --build-generator "
  185. "command line option. ";
  186. }
  187. return 1;
  188. }
  189. cmake cm;
  190. std::string cmakeOutString;
  191. cmCTestBuildAndTestCaptureRAII captureRAII(cm, cmakeOutString);
  192. static_cast<void>(captureRAII);
  193. std::ostringstream out;
  194. if ( this->CTest->GetConfigType().empty() &&
  195. !this->ConfigSample.empty())
  196. {
  197. // use the config sample to set the ConfigType
  198. std::string fullPath;
  199. std::string resultingConfig;
  200. std::vector<std::string> extraPaths;
  201. std::vector<std::string> failed;
  202. fullPath =
  203. cmCTestTestHandler::FindExecutable(this->CTest,
  204. this->ConfigSample.c_str(),
  205. resultingConfig,
  206. extraPaths,
  207. failed);
  208. if (!fullPath.empty() && !resultingConfig.empty())
  209. {
  210. this->CTest->SetConfigType(resultingConfig.c_str());
  211. }
  212. out << "Using config sample with results: "
  213. << fullPath << " and " << resultingConfig << std::endl;
  214. }
  215. // we need to honor the timeout specified, the timeout include cmake, build
  216. // and test time
  217. double clock_start = cmSystemTools::GetTime();
  218. // make sure the binary dir is there
  219. std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
  220. out << "Internal cmake changing into directory: "
  221. << this->BinaryDir << std::endl;
  222. if (!cmSystemTools::FileIsDirectory(this->BinaryDir))
  223. {
  224. cmSystemTools::MakeDirectory(this->BinaryDir.c_str());
  225. }
  226. cmSystemTools::ChangeDirectory(this->BinaryDir);
  227. if(this->BuildNoCMake)
  228. {
  229. // Make the generator available for the Build call below.
  230. cm.SetGlobalGenerator(cm.CreateGlobalGenerator(
  231. this->BuildGenerator));
  232. cm.SetGeneratorPlatform(this->BuildGeneratorPlatform);
  233. cm.SetGeneratorToolset(this->BuildGeneratorToolset);
  234. // Load the cache to make CMAKE_MAKE_PROGRAM available.
  235. cm.LoadCache(this->BinaryDir);
  236. }
  237. else
  238. {
  239. // do the cmake step, no timeout here since it is not a sub process
  240. if (this->RunCMake(outstring,out,cmakeOutString,cwd,&cm))
  241. {
  242. return 1;
  243. }
  244. }
  245. // do the build
  246. std::vector<std::string>::iterator tarIt;
  247. if (this->BuildTargets.empty())
  248. {
  249. this->BuildTargets.push_back("");
  250. }
  251. for ( tarIt = this->BuildTargets.begin();
  252. tarIt != this->BuildTargets.end(); ++ tarIt )
  253. {
  254. double remainingTime = 0;
  255. if (this->Timeout > 0)
  256. {
  257. remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start;
  258. if (remainingTime <= 0)
  259. {
  260. if(outstring)
  261. {
  262. *outstring = "--build-and-test timeout exceeded. ";
  263. }
  264. return 1;
  265. }
  266. }
  267. std::string output;
  268. const char* config = 0;
  269. if (!this->CTest->GetConfigType().empty())
  270. {
  271. config = this->CTest->GetConfigType().c_str();
  272. }
  273. #ifdef CMAKE_INTDIR
  274. if(!config)
  275. {
  276. config = CMAKE_INTDIR;
  277. }
  278. #endif
  279. if(!config)
  280. {
  281. config = "Debug";
  282. }
  283. int retVal = cm.GetGlobalGenerator()->Build(
  284. this->SourceDir, this->BinaryDir,
  285. this->BuildProject, *tarIt,
  286. output, this->BuildMakeProgram,
  287. config,
  288. !this->BuildNoClean,
  289. false, false, remainingTime);
  290. out << output;
  291. // if the build failed then return
  292. if (retVal)
  293. {
  294. if(outstring)
  295. {
  296. *outstring = out.str();
  297. }
  298. return 1;
  299. }
  300. }
  301. if(outstring)
  302. {
  303. *outstring = out.str();
  304. }
  305. // if no test was specified then we are done
  306. if (this->TestCommand.empty())
  307. {
  308. return 0;
  309. }
  310. // now run the compiled test if we can find it
  311. // store the final location in fullPath
  312. std::string fullPath;
  313. std::string resultingConfig;
  314. std::vector<std::string> extraPaths;
  315. // if this->ExecutableDirectory is set try that as well
  316. if (!this->ExecutableDirectory.empty())
  317. {
  318. std::string tempPath = this->ExecutableDirectory;
  319. tempPath += "/";
  320. tempPath += this->TestCommand;
  321. extraPaths.push_back(tempPath);
  322. }
  323. std::vector<std::string> failed;
  324. fullPath =
  325. cmCTestTestHandler::FindExecutable(this->CTest,
  326. this->TestCommand.c_str(),
  327. resultingConfig,
  328. extraPaths,
  329. failed);
  330. if(!cmSystemTools::FileExists(fullPath.c_str()))
  331. {
  332. out << "Could not find path to executable, perhaps it was not built: "
  333. << this->TestCommand << "\n";
  334. out << "tried to find it in these places:\n";
  335. out << fullPath.c_str() << "\n";
  336. for(unsigned int i=0; i < failed.size(); ++i)
  337. {
  338. out << failed[i] << "\n";
  339. }
  340. if(outstring)
  341. {
  342. *outstring = out.str();
  343. }
  344. else
  345. {
  346. cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());
  347. }
  348. // return to the original directory
  349. cmSystemTools::ChangeDirectory(cwd);
  350. return 1;
  351. }
  352. std::vector<const char*> testCommand;
  353. testCommand.push_back(fullPath.c_str());
  354. for(size_t k=0; k < this->TestCommandArgs.size(); ++k)
  355. {
  356. testCommand.push_back(this->TestCommandArgs[k].c_str());
  357. }
  358. testCommand.push_back(0);
  359. std::string outs;
  360. int retval = 0;
  361. // run the test from the this->BuildRunDir if set
  362. if(!this->BuildRunDir.empty())
  363. {
  364. out << "Run test in directory: " << this->BuildRunDir << "\n";
  365. cmSystemTools::ChangeDirectory(this->BuildRunDir);
  366. }
  367. out << "Running test command: \"" << fullPath << "\"";
  368. for(size_t k=0; k < this->TestCommandArgs.size(); ++k)
  369. {
  370. out << " \"" << this->TestCommandArgs[k] << "\"";
  371. }
  372. out << "\n";
  373. // how much time is remaining
  374. double remainingTime = 0;
  375. if (this->Timeout > 0)
  376. {
  377. remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start;
  378. if (remainingTime <= 0)
  379. {
  380. if(outstring)
  381. {
  382. *outstring = "--build-and-test timeout exceeded. ";
  383. }
  384. return 1;
  385. }
  386. }
  387. int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, 0,
  388. remainingTime, 0);
  389. if(runTestRes != cmsysProcess_State_Exited || retval != 0)
  390. {
  391. out << "Test command failed: " << testCommand[0] << "\n";
  392. retval = 1;
  393. }
  394. out << outs << "\n";
  395. if(outstring)
  396. {
  397. *outstring = out.str();
  398. }
  399. else
  400. {
  401. cmCTestLog(this->CTest, OUTPUT, out.str() << std::endl);
  402. }
  403. return retval;
  404. }
  405. //----------------------------------------------------------------------
  406. int cmCTestBuildAndTestHandler::ProcessCommandLineArguments(
  407. const std::string& currentArg, size_t& idx,
  408. const std::vector<std::string>& allArgs)
  409. {
  410. // --build-and-test options
  411. if(currentArg.find("--build-and-test",0) == 0 && idx < allArgs.size() - 1)
  412. {
  413. if(idx+2 < allArgs.size())
  414. {
  415. idx++;
  416. this->SourceDir = allArgs[idx];
  417. idx++;
  418. this->BinaryDir = allArgs[idx];
  419. // dir must exist before CollapseFullPath is called
  420. cmSystemTools::MakeDirectory(this->BinaryDir.c_str());
  421. this->BinaryDir
  422. = cmSystemTools::CollapseFullPath(this->BinaryDir);
  423. this->SourceDir
  424. = cmSystemTools::CollapseFullPath(this->SourceDir);
  425. }
  426. else
  427. {
  428. cmCTestLog(this->CTest, ERROR_MESSAGE,
  429. "--build-and-test must have source and binary dir" << std::endl);
  430. return 0;
  431. }
  432. }
  433. if(currentArg.find("--build-target",0) == 0 && idx < allArgs.size() - 1)
  434. {
  435. idx++;
  436. this->BuildTargets.push_back(allArgs[idx]);
  437. }
  438. if(currentArg.find("--build-nocmake",0) == 0)
  439. {
  440. this->BuildNoCMake = true;
  441. }
  442. if(currentArg.find("--build-run-dir",0) == 0 && idx < allArgs.size() - 1)
  443. {
  444. idx++;
  445. this->BuildRunDir = allArgs[idx];
  446. }
  447. if(currentArg.find("--build-two-config",0) == 0)
  448. {
  449. this->BuildTwoConfig = true;
  450. }
  451. if(currentArg.find("--build-exe-dir",0) == 0 && idx < allArgs.size() - 1)
  452. {
  453. idx++;
  454. this->ExecutableDirectory = allArgs[idx];
  455. }
  456. if(currentArg.find("--test-timeout",0) == 0 && idx < allArgs.size() - 1)
  457. {
  458. idx++;
  459. this->Timeout = atof(allArgs[idx].c_str());
  460. }
  461. if(currentArg == "--build-generator" && idx < allArgs.size() - 1)
  462. {
  463. idx++;
  464. this->BuildGenerator = allArgs[idx];
  465. }
  466. if(currentArg == "--build-generator-platform" &&
  467. idx < allArgs.size() - 1)
  468. {
  469. idx++;
  470. this->BuildGeneratorPlatform = allArgs[idx];
  471. }
  472. if(currentArg == "--build-generator-toolset" &&
  473. idx < allArgs.size() - 1)
  474. {
  475. idx++;
  476. this->BuildGeneratorToolset = allArgs[idx];
  477. }
  478. if(currentArg.find("--build-project",0) == 0 && idx < allArgs.size() - 1)
  479. {
  480. idx++;
  481. this->BuildProject = allArgs[idx];
  482. }
  483. if(currentArg.find("--build-makeprogram",0) == 0 &&
  484. idx < allArgs.size() - 1)
  485. {
  486. idx++;
  487. this->BuildMakeProgram = allArgs[idx];
  488. }
  489. if(currentArg.find("--build-config-sample",0) == 0 &&
  490. idx < allArgs.size() - 1)
  491. {
  492. idx++;
  493. this->ConfigSample = allArgs[idx];
  494. }
  495. if(currentArg.find("--build-noclean",0) == 0)
  496. {
  497. this->BuildNoClean = true;
  498. }
  499. if(currentArg.find("--build-options",0) == 0)
  500. {
  501. while(idx+1 < allArgs.size() &&
  502. allArgs[idx+1] != "--build-target" &&
  503. allArgs[idx+1] != "--test-command")
  504. {
  505. ++idx;
  506. this->BuildOptions.push_back(allArgs[idx]);
  507. }
  508. }
  509. if(currentArg.find("--test-command",0) == 0 && idx < allArgs.size() - 1)
  510. {
  511. ++idx;
  512. this->TestCommand = allArgs[idx];
  513. while(idx+1 < allArgs.size())
  514. {
  515. ++idx;
  516. this->TestCommandArgs.push_back(allArgs[idx]);
  517. }
  518. }
  519. return 1;
  520. }