cmCTestLaunch.cxx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /*=========================================================================
  2. Program: CMake - Cross-Platform Makefile Generator
  3. Module: $RCSfile$
  4. Language: C++
  5. Date: $Date$
  6. Version: $Revision$
  7. Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
  8. See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
  9. This software is distributed WITHOUT ANY WARRANTY; without even
  10. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the above copyright notices for more information.
  12. =========================================================================*/
  13. #include "cmCTestLaunch.h"
  14. #include "cmGeneratedFileStream.h"
  15. #include "cmSystemTools.h"
  16. #include "cmXMLSafe.h"
  17. #include "cmake.h"
  18. #include <cmsys/MD5.h>
  19. #include <cmsys/Process.h>
  20. #include <cmsys/RegularExpression.hxx>
  21. //----------------------------------------------------------------------------
  22. cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
  23. {
  24. this->Passthru = true;
  25. this->Process = 0;
  26. this->ExitCode = 1;
  27. this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
  28. if(!this->ParseArguments(argc, argv))
  29. {
  30. return;
  31. }
  32. this->ComputeFileNames();
  33. this->ScrapeRulesLoaded = false;
  34. this->HaveOut = false;
  35. this->HaveErr = false;
  36. this->Process = cmsysProcess_New();
  37. }
  38. //----------------------------------------------------------------------------
  39. cmCTestLaunch::~cmCTestLaunch()
  40. {
  41. cmsysProcess_Delete(this->Process);
  42. if(!this->Passthru)
  43. {
  44. cmSystemTools::RemoveFile(this->LogOut.c_str());
  45. cmSystemTools::RemoveFile(this->LogErr.c_str());
  46. }
  47. }
  48. //----------------------------------------------------------------------------
  49. bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
  50. {
  51. // Launcher options occur first and are separated from the real
  52. // command line by a '--' option.
  53. enum Doing { DoingNone,
  54. DoingOutput,
  55. DoingSource,
  56. DoingLanguage,
  57. DoingTargetName,
  58. DoingTargetType,
  59. DoingBuildDir,
  60. DoingCount };
  61. Doing doing = DoingNone;
  62. int arg0 = 0;
  63. for(int i=1; !arg0 && i < argc; ++i)
  64. {
  65. const char* arg = argv[i];
  66. if(strcmp(arg, "--") == 0)
  67. {
  68. arg0 = i+1;
  69. }
  70. else if(strcmp(arg, "--output") == 0)
  71. {
  72. doing = DoingOutput;
  73. }
  74. else if(strcmp(arg, "--source") == 0)
  75. {
  76. doing = DoingSource;
  77. }
  78. else if(strcmp(arg, "--language") == 0)
  79. {
  80. doing = DoingLanguage;
  81. }
  82. else if(strcmp(arg, "--target-name") == 0)
  83. {
  84. doing = DoingTargetName;
  85. }
  86. else if(strcmp(arg, "--target-type") == 0)
  87. {
  88. doing = DoingTargetType;
  89. }
  90. else if(strcmp(arg, "--build-dir") == 0)
  91. {
  92. doing = DoingBuildDir;
  93. }
  94. else if(doing == DoingOutput)
  95. {
  96. this->OptionOutput = arg;
  97. doing = DoingNone;
  98. }
  99. else if(doing == DoingSource)
  100. {
  101. this->OptionSource = arg;
  102. doing = DoingNone;
  103. }
  104. else if(doing == DoingLanguage)
  105. {
  106. this->OptionLanguage = arg;
  107. if(this->OptionLanguage == "CXX")
  108. {
  109. this->OptionLanguage = "C++";
  110. }
  111. doing = DoingNone;
  112. }
  113. else if(doing == DoingTargetName)
  114. {
  115. this->OptionTargetName = arg;
  116. doing = DoingNone;
  117. }
  118. else if(doing == DoingTargetType)
  119. {
  120. this->OptionTargetType = arg;
  121. doing = DoingNone;
  122. }
  123. else if(doing == DoingBuildDir)
  124. {
  125. this->OptionBuildDir = arg;
  126. doing = DoingNone;
  127. }
  128. }
  129. // Extract the real command line.
  130. if(arg0)
  131. {
  132. this->RealArgC = argc - arg0;
  133. this->RealArgV = argv + arg0;
  134. for(int i=0; i < this->RealArgC; ++i)
  135. {
  136. this->HandleRealArg(this->RealArgV[i]);
  137. }
  138. return true;
  139. }
  140. else
  141. {
  142. this->RealArgC = 0;
  143. this->RealArgV = 0;
  144. std::cerr << "No launch/command separator ('--') found!\n";
  145. return false;
  146. }
  147. }
  148. //----------------------------------------------------------------------------
  149. void cmCTestLaunch::HandleRealArg(const char* arg)
  150. {
  151. #ifdef _WIN32
  152. // Expand response file arguments.
  153. if(arg[0] == '@' && cmSystemTools::FileExists(arg+1))
  154. {
  155. std::ifstream fin(arg+1);
  156. std::string line;
  157. while(cmSystemTools::GetLineFromStream(fin, line))
  158. {
  159. cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs);
  160. }
  161. return;
  162. }
  163. #endif
  164. this->RealArgs.push_back(arg);
  165. }
  166. //----------------------------------------------------------------------------
  167. void cmCTestLaunch::ComputeFileNames()
  168. {
  169. // We just passthru the behavior of the real command unless the
  170. // CTEST_LAUNCH_LOGS environment variable is set.
  171. const char* d = getenv("CTEST_LAUNCH_LOGS");
  172. if(!(d && *d))
  173. {
  174. return;
  175. }
  176. this->Passthru = false;
  177. // The environment variable specifies the directory into which we
  178. // generate build logs.
  179. this->LogDir = d;
  180. cmSystemTools::ConvertToUnixSlashes(this->LogDir);
  181. this->LogDir += "/";
  182. // We hash the input command working dir and command line to obtain
  183. // a repeatable and (probably) unique name for log files.
  184. char hash[33] = {};
  185. cmsysMD5* md5 = cmsysMD5_New();
  186. cmsysMD5_Initialize(md5);
  187. cmsysMD5_Append(md5, (unsigned char const*)(this->CWD.c_str()), -1);
  188. for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
  189. ai != this->RealArgs.end(); ++ai)
  190. {
  191. cmsysMD5_Append(md5, (unsigned char const*)ai->c_str(), -1);
  192. }
  193. cmsysMD5_FinalizeHex(md5, hash);
  194. cmsysMD5_Delete(md5);
  195. this->LogHash = hash;
  196. // We store stdout and stderr in temporary log files.
  197. this->LogOut = this->LogDir;
  198. this->LogOut += "launch-";
  199. this->LogOut += this->LogHash;
  200. this->LogOut += "-out.txt";
  201. this->LogErr = this->LogDir;
  202. this->LogErr += "launch-";
  203. this->LogErr += this->LogHash;
  204. this->LogErr += "-err.txt";
  205. }
  206. //----------------------------------------------------------------------------
  207. void cmCTestLaunch::RunChild()
  208. {
  209. // Prepare to run the real command.
  210. cmsysProcess* cp = this->Process;
  211. cmsysProcess_SetCommand(cp, this->RealArgV);
  212. std::ofstream fout;
  213. std::ofstream ferr;
  214. if(this->Passthru)
  215. {
  216. // In passthru mode we just share the output pipes.
  217. cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
  218. cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
  219. }
  220. else
  221. {
  222. // In full mode we record the child output pipes to log files.
  223. fout.open(this->LogOut.c_str(),
  224. std::ios::out | std::ios::binary);
  225. ferr.open(this->LogErr.c_str(),
  226. std::ios::out | std::ios::binary);
  227. }
  228. // Run the real command.
  229. cmsysProcess_Execute(cp);
  230. // Record child stdout and stderr if necessary.
  231. if(!this->Passthru)
  232. {
  233. char* data = 0;
  234. int length = 0;
  235. while(int p = cmsysProcess_WaitForData(cp, &data, &length, 0))
  236. {
  237. if(p == cmsysProcess_Pipe_STDOUT)
  238. {
  239. fout.write(data, length);
  240. std::cout.write(data, length);
  241. this->HaveOut = true;
  242. }
  243. else if(p == cmsysProcess_Pipe_STDERR)
  244. {
  245. ferr.write(data, length);
  246. std::cerr.write(data, length);
  247. this->HaveErr = true;
  248. }
  249. }
  250. }
  251. // Wait for the real command to finish.
  252. cmsysProcess_WaitForExit(cp, 0);
  253. this->ExitCode = cmsysProcess_GetExitValue(cp);
  254. }
  255. //----------------------------------------------------------------------------
  256. int cmCTestLaunch::Run()
  257. {
  258. if(!this->Process)
  259. {
  260. std::cerr << "Could not allocate cmsysProcess instance!\n";
  261. return -1;
  262. }
  263. this->RunChild();
  264. if(this->CheckResults())
  265. {
  266. return this->ExitCode;
  267. }
  268. this->WriteXML();
  269. return this->ExitCode;
  270. }
  271. //----------------------------------------------------------------------------
  272. void cmCTestLaunch::LoadLabels()
  273. {
  274. if(this->OptionBuildDir.empty() || this->OptionTargetName.empty())
  275. {
  276. return;
  277. }
  278. // Labels are listed in per-target files.
  279. std::string fname = this->OptionBuildDir;
  280. fname += cmake::GetCMakeFilesDirectory();
  281. fname += "/";
  282. fname += this->OptionTargetName;
  283. fname += ".dir/Labels.txt";
  284. // We are interested in per-target labels for this source file.
  285. std::string source = this->OptionSource;
  286. cmSystemTools::ConvertToUnixSlashes(source);
  287. // Load the labels file.
  288. std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  289. if(!fin) { return; }
  290. bool inTarget = true;
  291. bool inSource = false;
  292. std::string line;
  293. while(cmSystemTools::GetLineFromStream(fin, line))
  294. {
  295. if(line.empty() || line[0] == '#')
  296. {
  297. // Ignore blank and comment lines.
  298. continue;
  299. }
  300. else if(line[0] == ' ')
  301. {
  302. // Label lines appear indented by one space.
  303. if(inTarget || inSource)
  304. {
  305. this->Labels.insert(line.c_str()+1);
  306. }
  307. }
  308. else if(!this->OptionSource.empty() && !inSource)
  309. {
  310. // Non-indented lines specify a source file name. The first one
  311. // is the end of the target-wide labels. Use labels following a
  312. // matching source.
  313. inTarget = false;
  314. inSource = this->SourceMatches(line, source);
  315. }
  316. else
  317. {
  318. return;
  319. }
  320. }
  321. }
  322. //----------------------------------------------------------------------------
  323. bool cmCTestLaunch::SourceMatches(std::string const& lhs,
  324. std::string const& rhs)
  325. {
  326. // TODO: Case sensitivity, UseRelativePaths, etc. Note that both
  327. // paths in the comparison get generated by CMake. This is done for
  328. // every source in the target, so it should be efficient (cannot use
  329. // cmSystemTools::IsSameFile).
  330. return lhs == rhs;
  331. }
  332. //----------------------------------------------------------------------------
  333. bool cmCTestLaunch::IsError() const
  334. {
  335. return this->ExitCode != 0;
  336. }
  337. //----------------------------------------------------------------------------
  338. void cmCTestLaunch::WriteXML()
  339. {
  340. // Name the xml file.
  341. std::string logXML = this->LogDir;
  342. logXML += this->IsError()? "error-" : "warning-";
  343. logXML += this->LogHash;
  344. logXML += ".xml";
  345. // Use cmGeneratedFileStream to atomically create the report file.
  346. cmGeneratedFileStream fxml(logXML.c_str());
  347. fxml << "\t<Failure type=\""
  348. << (this->IsError()? "Error" : "Warning") << "\">\n";
  349. this->WriteXMLAction(fxml);
  350. this->WriteXMLCommand(fxml);
  351. this->WriteXMLResult(fxml);
  352. this->WriteXMLLabels(fxml);
  353. fxml << "\t</Failure>\n";
  354. }
  355. //----------------------------------------------------------------------------
  356. void cmCTestLaunch::WriteXMLAction(std::ostream& fxml)
  357. {
  358. fxml << "\t\t<!-- Meta-information about the build action -->\n";
  359. fxml << "\t\t<Action>\n";
  360. // TargetName
  361. if(!this->OptionTargetName.empty())
  362. {
  363. fxml << "\t\t\t<TargetName>"
  364. << cmXMLSafe(this->OptionTargetName)
  365. << "</TargetName>\n";
  366. }
  367. // Language
  368. if(!this->OptionLanguage.empty())
  369. {
  370. fxml << "\t\t\t<Language>"
  371. << cmXMLSafe(this->OptionLanguage)
  372. << "</Language>\n";
  373. }
  374. // SourceFile
  375. if(!this->OptionSource.empty())
  376. {
  377. fxml << "\t\t\t<SourceFile>"
  378. << cmXMLSafe(this->OptionSource)
  379. << "</SourceFile>\n";
  380. }
  381. // OutputFile
  382. if(!this->OptionOutput.empty())
  383. {
  384. fxml << "\t\t\t<OutputFile>"
  385. << cmXMLSafe(this->OptionOutput)
  386. << "</OutputFile>\n";
  387. }
  388. // OutputType
  389. const char* outputType = 0;
  390. if(!this->OptionTargetType.empty())
  391. {
  392. if(this->OptionTargetType == "EXECUTABLE")
  393. {
  394. outputType = "executable";
  395. }
  396. else if(this->OptionTargetType == "SHARED_LIBRARY")
  397. {
  398. outputType = "shared library";
  399. }
  400. else if(this->OptionTargetType == "MODULE_LIBRARY")
  401. {
  402. outputType = "module library";
  403. }
  404. else if(this->OptionTargetType == "STATIC_LIBRARY")
  405. {
  406. outputType = "static library";
  407. }
  408. }
  409. else if(!this->OptionSource.empty())
  410. {
  411. outputType = "object file";
  412. }
  413. if(outputType)
  414. {
  415. fxml << "\t\t\t<OutputType>"
  416. << cmXMLSafe(outputType)
  417. << "</OutputType>\n";
  418. }
  419. fxml << "\t\t</Action>\n";
  420. }
  421. //----------------------------------------------------------------------------
  422. void cmCTestLaunch::WriteXMLCommand(std::ostream& fxml)
  423. {
  424. fxml << "\n";
  425. fxml << "\t\t<!-- Details of command -->\n";
  426. fxml << "\t\t<Command>\n";
  427. if(!this->CWD.empty())
  428. {
  429. fxml << "\t\t\t<WorkingDirectory>"
  430. << cmXMLSafe(this->CWD)
  431. << "</WorkingDirectory>\n";
  432. }
  433. for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
  434. ai != this->RealArgs.end(); ++ai)
  435. {
  436. fxml << "\t\t\t<Argument>"
  437. << cmXMLSafe(ai->c_str())
  438. << "</Argument>\n";
  439. }
  440. fxml << "\t\t</Command>\n";
  441. }
  442. //----------------------------------------------------------------------------
  443. void cmCTestLaunch::WriteXMLResult(std::ostream& fxml)
  444. {
  445. fxml << "\n";
  446. fxml << "\t\t<!-- Result of command -->\n";
  447. fxml << "\t\t<Result>\n";
  448. // StdOut
  449. fxml << "\t\t\t<StdOut>";
  450. this->DumpFileToXML(fxml, this->LogOut);
  451. fxml << "</StdOut>\n";
  452. // StdErr
  453. fxml << "\t\t\t<StdErr>";
  454. this->DumpFileToXML(fxml, this->LogErr);
  455. fxml << "</StdErr>\n";
  456. // ExitCondition
  457. fxml << "\t\t\t<ExitCondition>";
  458. cmsysProcess* cp = this->Process;
  459. switch (cmsysProcess_GetState(cp))
  460. {
  461. case cmsysProcess_State_Starting:
  462. fxml << "No process has been executed"; break;
  463. case cmsysProcess_State_Executing:
  464. fxml << "The process is still executing"; break;
  465. case cmsysProcess_State_Disowned:
  466. fxml << "Disowned"; break;
  467. case cmsysProcess_State_Killed:
  468. fxml << "Killed by parent"; break;
  469. case cmsysProcess_State_Expired:
  470. fxml << "Killed when timeout expired"; break;
  471. case cmsysProcess_State_Exited:
  472. fxml << this->ExitCode; break;
  473. case cmsysProcess_State_Exception:
  474. fxml << "Terminated abnormally: "
  475. << cmXMLSafe(cmsysProcess_GetExceptionString(cp)); break;
  476. case cmsysProcess_State_Error:
  477. fxml << "Error administrating child process: "
  478. << cmXMLSafe(cmsysProcess_GetErrorString(cp)); break;
  479. };
  480. fxml << "</ExitCondition>\n";
  481. fxml << "\t\t</Result>\n";
  482. }
  483. //----------------------------------------------------------------------------
  484. void cmCTestLaunch::WriteXMLLabels(std::ostream& fxml)
  485. {
  486. this->LoadLabels();
  487. if(!this->Labels.empty())
  488. {
  489. fxml << "\n";
  490. fxml << "\t\t<!-- Interested parties -->\n";
  491. fxml << "\t\t<Labels>\n";
  492. for(std::set<cmStdString>::const_iterator li = this->Labels.begin();
  493. li != this->Labels.end(); ++li)
  494. {
  495. fxml << "\t\t\t<Label>" << cmXMLSafe(*li) << "</Label>\n";
  496. }
  497. fxml << "\t\t</Labels>\n";
  498. }
  499. }
  500. //----------------------------------------------------------------------------
  501. void cmCTestLaunch::DumpFileToXML(std::ostream& fxml,
  502. std::string const& fname)
  503. {
  504. std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  505. std::string line;
  506. const char* sep = "";
  507. while(cmSystemTools::GetLineFromStream(fin, line))
  508. {
  509. fxml << sep << cmXMLSafe(line).Quotes(false);
  510. sep = "\n";
  511. }
  512. }
  513. //----------------------------------------------------------------------------
  514. bool cmCTestLaunch::CheckResults()
  515. {
  516. // Skip XML in passthru mode.
  517. if(this->Passthru)
  518. {
  519. return true;
  520. }
  521. // We always report failure for error conditions.
  522. if(this->IsError())
  523. {
  524. return false;
  525. }
  526. // Scrape the output logs to look for warnings.
  527. if((this->HaveErr && this->ScrapeLog(this->LogErr)) ||
  528. (this->HaveOut && this->ScrapeLog(this->LogOut)))
  529. {
  530. return false;
  531. }
  532. return true;
  533. }
  534. //----------------------------------------------------------------------------
  535. void cmCTestLaunch::LoadScrapeRules()
  536. {
  537. if(this->ScrapeRulesLoaded)
  538. {
  539. return;
  540. }
  541. this->ScrapeRulesLoaded = true;
  542. // Common compiler warning formats. These are much simpler than the
  543. // full log-scraping expressions because we do not need to extract
  544. // file and line information.
  545. this->RegexWarning.push_back("(^|[ :])[Ww][Aa][Rr][Nn][Ii][Nn][Gg]");
  546. this->RegexWarning.push_back("(^|[ :])[Rr][Ee][Mm][Aa][Rr][Kk]");
  547. this->RegexWarning.push_back("(^|[ :])[Nn][Oo][Tt][Ee]");
  548. // Load custom match rules given to us by CTest.
  549. this->LoadScrapeRules("Warning", this->RegexWarning);
  550. this->LoadScrapeRules("WarningSuppress", this->RegexWarningSuppress);
  551. }
  552. //----------------------------------------------------------------------------
  553. void
  554. cmCTestLaunch
  555. ::LoadScrapeRules(const char* purpose,
  556. std::vector<cmsys::RegularExpression>& regexps)
  557. {
  558. std::string fname = this->LogDir;
  559. fname += "Custom";
  560. fname += purpose;
  561. fname += ".txt";
  562. std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  563. std::string line;
  564. cmsys::RegularExpression rex;
  565. while(cmSystemTools::GetLineFromStream(fin, line))
  566. {
  567. if(rex.compile(line.c_str()))
  568. {
  569. regexps.push_back(rex);
  570. }
  571. }
  572. }
  573. //----------------------------------------------------------------------------
  574. bool cmCTestLaunch::ScrapeLog(std::string const& fname)
  575. {
  576. this->LoadScrapeRules();
  577. // Look for log file lines matching warning expressions but not
  578. // suppression expressions.
  579. std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  580. std::string line;
  581. while(cmSystemTools::GetLineFromStream(fin, line))
  582. {
  583. if(this->Match(line.c_str(), this->RegexWarning) &&
  584. !this->Match(line.c_str(), this->RegexWarningSuppress))
  585. {
  586. return true;
  587. }
  588. }
  589. return false;
  590. }
  591. //----------------------------------------------------------------------------
  592. bool cmCTestLaunch::Match(std::string const& line,
  593. std::vector<cmsys::RegularExpression>& regexps)
  594. {
  595. for(std::vector<cmsys::RegularExpression>::iterator ri = regexps.begin();
  596. ri != regexps.end(); ++ri)
  597. {
  598. if(ri->find(line.c_str()))
  599. {
  600. return true;
  601. }
  602. }
  603. return false;
  604. }
  605. //----------------------------------------------------------------------------
  606. int cmCTestLaunch::Main(int argc, const char* const argv[])
  607. {
  608. if(argc == 2)
  609. {
  610. std::cerr << "ctest --launch: this mode is for internal CTest use only"
  611. << std::endl;
  612. return 1;
  613. }
  614. cmCTestLaunch self(argc, argv);
  615. return self.Run();
  616. }