PageRenderTime 29ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/CTest/cmCTestLaunch.cxx

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