cmCTestLaunch.cxx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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 "cmCTestLaunch.h"
  4. #include <cstring>
  5. #include <iostream>
  6. #include "cmsys/FStream.hxx"
  7. #include "cmsys/Process.h"
  8. #include "cmsys/RegularExpression.hxx"
  9. #include "cmCTestLaunchReporter.h"
  10. #include "cmGlobalGenerator.h"
  11. #include "cmMakefile.h"
  12. #include "cmProcessOutput.h"
  13. #include "cmState.h"
  14. #include "cmStateSnapshot.h"
  15. #include "cmStringAlgorithms.h"
  16. #include "cmSystemTools.h"
  17. #include "cmake.h"
  18. #ifdef _WIN32
  19. # include <cstdio> // for std{out,err} and fileno
  20. # include <fcntl.h> // for _O_BINARY
  21. # include <io.h> // for _setmode
  22. #endif
  23. cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
  24. {
  25. this->Process = nullptr;
  26. if (!this->ParseArguments(argc, argv)) {
  27. return;
  28. }
  29. this->Reporter.RealArgs = this->RealArgs;
  30. this->Reporter.ComputeFileNames();
  31. this->ScrapeRulesLoaded = false;
  32. this->HaveOut = false;
  33. this->HaveErr = false;
  34. this->Process = cmsysProcess_New();
  35. }
  36. cmCTestLaunch::~cmCTestLaunch()
  37. {
  38. cmsysProcess_Delete(this->Process);
  39. }
  40. bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
  41. {
  42. // Launcher options occur first and are separated from the real
  43. // command line by a '--' option.
  44. enum Doing
  45. {
  46. DoingNone,
  47. DoingOutput,
  48. DoingSource,
  49. DoingLanguage,
  50. DoingTargetName,
  51. DoingTargetType,
  52. DoingBuildDir,
  53. DoingCount,
  54. DoingFilterPrefix
  55. };
  56. Doing doing = DoingNone;
  57. int arg0 = 0;
  58. for (int i = 1; !arg0 && i < argc; ++i) {
  59. const char* arg = argv[i];
  60. if (strcmp(arg, "--") == 0) {
  61. arg0 = i + 1;
  62. } else if (strcmp(arg, "--output") == 0) {
  63. doing = DoingOutput;
  64. } else if (strcmp(arg, "--source") == 0) {
  65. doing = DoingSource;
  66. } else if (strcmp(arg, "--language") == 0) {
  67. doing = DoingLanguage;
  68. } else if (strcmp(arg, "--target-name") == 0) {
  69. doing = DoingTargetName;
  70. } else if (strcmp(arg, "--target-type") == 0) {
  71. doing = DoingTargetType;
  72. } else if (strcmp(arg, "--build-dir") == 0) {
  73. doing = DoingBuildDir;
  74. } else if (strcmp(arg, "--filter-prefix") == 0) {
  75. doing = DoingFilterPrefix;
  76. } else if (doing == DoingOutput) {
  77. this->Reporter.OptionOutput = arg;
  78. doing = DoingNone;
  79. } else if (doing == DoingSource) {
  80. this->Reporter.OptionSource = arg;
  81. doing = DoingNone;
  82. } else if (doing == DoingLanguage) {
  83. this->Reporter.OptionLanguage = arg;
  84. if (this->Reporter.OptionLanguage == "CXX") {
  85. this->Reporter.OptionLanguage = "C++";
  86. }
  87. doing = DoingNone;
  88. } else if (doing == DoingTargetName) {
  89. this->Reporter.OptionTargetName = arg;
  90. doing = DoingNone;
  91. } else if (doing == DoingTargetType) {
  92. this->Reporter.OptionTargetType = arg;
  93. doing = DoingNone;
  94. } else if (doing == DoingBuildDir) {
  95. this->Reporter.OptionBuildDir = arg;
  96. doing = DoingNone;
  97. } else if (doing == DoingFilterPrefix) {
  98. this->Reporter.OptionFilterPrefix = arg;
  99. doing = DoingNone;
  100. }
  101. }
  102. // Extract the real command line.
  103. if (arg0) {
  104. this->RealArgC = argc - arg0;
  105. this->RealArgV = argv + arg0;
  106. for (int i = 0; i < this->RealArgC; ++i) {
  107. this->HandleRealArg(this->RealArgV[i]);
  108. }
  109. return true;
  110. }
  111. this->RealArgC = 0;
  112. this->RealArgV = nullptr;
  113. std::cerr << "No launch/command separator ('--') found!\n";
  114. return false;
  115. }
  116. void cmCTestLaunch::HandleRealArg(const char* arg)
  117. {
  118. #ifdef _WIN32
  119. // Expand response file arguments.
  120. if (arg[0] == '@' && cmSystemTools::FileExists(arg + 1)) {
  121. cmsys::ifstream fin(arg + 1);
  122. std::string line;
  123. while (cmSystemTools::GetLineFromStream(fin, line)) {
  124. cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs);
  125. }
  126. return;
  127. }
  128. #endif
  129. this->RealArgs.emplace_back(arg);
  130. }
  131. void cmCTestLaunch::RunChild()
  132. {
  133. // Ignore noopt make rules
  134. if (this->RealArgs.empty() || this->RealArgs[0] == ":") {
  135. this->Reporter.ExitCode = 0;
  136. return;
  137. }
  138. // Prepare to run the real command.
  139. cmsysProcess* cp = this->Process;
  140. cmsysProcess_SetCommand(cp, this->RealArgV);
  141. cmsys::ofstream fout;
  142. cmsys::ofstream ferr;
  143. if (this->Reporter.Passthru) {
  144. // In passthru mode we just share the output pipes.
  145. cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
  146. cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
  147. } else {
  148. // In full mode we record the child output pipes to log files.
  149. fout.open(this->Reporter.LogOut.c_str(), std::ios::out | std::ios::binary);
  150. ferr.open(this->Reporter.LogErr.c_str(), std::ios::out | std::ios::binary);
  151. }
  152. #ifdef _WIN32
  153. // Do this so that newline transformation is not done when writing to cout
  154. // and cerr below.
  155. _setmode(fileno(stdout), _O_BINARY);
  156. _setmode(fileno(stderr), _O_BINARY);
  157. #endif
  158. // Run the real command.
  159. cmsysProcess_Execute(cp);
  160. // Record child stdout and stderr if necessary.
  161. if (!this->Reporter.Passthru) {
  162. char* data = nullptr;
  163. int length = 0;
  164. cmProcessOutput processOutput;
  165. std::string strdata;
  166. while (int p = cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
  167. if (p == cmsysProcess_Pipe_STDOUT) {
  168. processOutput.DecodeText(data, length, strdata, 1);
  169. fout.write(strdata.c_str(), strdata.size());
  170. std::cout.write(strdata.c_str(), strdata.size());
  171. this->HaveOut = true;
  172. } else if (p == cmsysProcess_Pipe_STDERR) {
  173. processOutput.DecodeText(data, length, strdata, 2);
  174. ferr.write(strdata.c_str(), strdata.size());
  175. std::cerr.write(strdata.c_str(), strdata.size());
  176. this->HaveErr = true;
  177. }
  178. }
  179. processOutput.DecodeText(std::string(), strdata, 1);
  180. if (!strdata.empty()) {
  181. fout.write(strdata.c_str(), strdata.size());
  182. std::cout.write(strdata.c_str(), strdata.size());
  183. }
  184. processOutput.DecodeText(std::string(), strdata, 2);
  185. if (!strdata.empty()) {
  186. ferr.write(strdata.c_str(), strdata.size());
  187. std::cerr.write(strdata.c_str(), strdata.size());
  188. }
  189. }
  190. // Wait for the real command to finish.
  191. cmsysProcess_WaitForExit(cp, nullptr);
  192. this->Reporter.ExitCode = cmsysProcess_GetExitValue(cp);
  193. }
  194. int cmCTestLaunch::Run()
  195. {
  196. if (!this->Process) {
  197. std::cerr << "Could not allocate cmsysProcess instance!\n";
  198. return -1;
  199. }
  200. this->RunChild();
  201. if (this->CheckResults()) {
  202. return this->Reporter.ExitCode;
  203. }
  204. this->LoadConfig();
  205. this->Reporter.Process = this->Process;
  206. this->Reporter.WriteXML();
  207. return this->Reporter.ExitCode;
  208. }
  209. bool cmCTestLaunch::CheckResults()
  210. {
  211. // Skip XML in passthru mode.
  212. if (this->Reporter.Passthru) {
  213. return true;
  214. }
  215. // We always report failure for error conditions.
  216. if (this->Reporter.IsError()) {
  217. return false;
  218. }
  219. // Scrape the output logs to look for warnings.
  220. if ((this->HaveErr && this->ScrapeLog(this->Reporter.LogErr)) ||
  221. (this->HaveOut && this->ScrapeLog(this->Reporter.LogOut))) {
  222. return false;
  223. }
  224. return true;
  225. }
  226. void cmCTestLaunch::LoadScrapeRules()
  227. {
  228. if (this->ScrapeRulesLoaded) {
  229. return;
  230. }
  231. this->ScrapeRulesLoaded = true;
  232. // Load custom match rules given to us by CTest.
  233. this->LoadScrapeRules("Warning", this->Reporter.RegexWarning);
  234. this->LoadScrapeRules("WarningSuppress",
  235. this->Reporter.RegexWarningSuppress);
  236. }
  237. void cmCTestLaunch::LoadScrapeRules(
  238. const char* purpose, std::vector<cmsys::RegularExpression>& regexps) const
  239. {
  240. std::string fname =
  241. cmStrCat(this->Reporter.LogDir, "Custom", purpose, ".txt");
  242. cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  243. std::string line;
  244. cmsys::RegularExpression rex;
  245. while (cmSystemTools::GetLineFromStream(fin, line)) {
  246. if (rex.compile(line)) {
  247. regexps.push_back(rex);
  248. }
  249. }
  250. }
  251. bool cmCTestLaunch::ScrapeLog(std::string const& fname)
  252. {
  253. this->LoadScrapeRules();
  254. // Look for log file lines matching warning expressions but not
  255. // suppression expressions.
  256. cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
  257. std::string line;
  258. while (cmSystemTools::GetLineFromStream(fin, line)) {
  259. if (this->Reporter.MatchesFilterPrefix(line)) {
  260. continue;
  261. }
  262. if (this->Reporter.Match(line, this->Reporter.RegexWarning) &&
  263. !this->Reporter.Match(line, this->Reporter.RegexWarningSuppress)) {
  264. return true;
  265. }
  266. }
  267. return false;
  268. }
  269. int cmCTestLaunch::Main(int argc, const char* const argv[])
  270. {
  271. if (argc == 2) {
  272. std::cerr << "ctest --launch: this mode is for internal CTest use only"
  273. << std::endl;
  274. return 1;
  275. }
  276. cmCTestLaunch self(argc, argv);
  277. return self.Run();
  278. }
  279. void cmCTestLaunch::LoadConfig()
  280. {
  281. cmake cm(cmake::RoleScript, cmState::CTest);
  282. cm.SetHomeDirectory("");
  283. cm.SetHomeOutputDirectory("");
  284. cm.GetCurrentSnapshot().SetDefaultDefinitions();
  285. cmGlobalGenerator gg(&cm);
  286. cmMakefile mf(&gg, cm.GetCurrentSnapshot());
  287. std::string fname =
  288. cmStrCat(this->Reporter.LogDir, "CTestLaunchConfig.cmake");
  289. if (cmSystemTools::FileExists(fname) && mf.ReadListFile(fname)) {
  290. this->Reporter.SourceDir = mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
  291. cmSystemTools::ConvertToUnixSlashes(this->Reporter.SourceDir);
  292. }
  293. }