cmTryRunCommand.cxx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  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 "cmTryRunCommand.h"
  4. #include <cstdio>
  5. #include "cmsys/FStream.hxx"
  6. #include "cmCoreTryCompile.h"
  7. #include "cmDuration.h"
  8. #include "cmExecutionStatus.h"
  9. #include "cmMakefile.h"
  10. #include "cmMessageType.h"
  11. #include "cmRange.h"
  12. #include "cmState.h"
  13. #include "cmStateTypes.h"
  14. #include "cmStringAlgorithms.h"
  15. #include "cmSystemTools.h"
  16. #include "cmValue.h"
  17. #include "cmake.h"
  18. namespace {
  19. class TryRunCommandImpl : public cmCoreTryCompile
  20. {
  21. public:
  22. TryRunCommandImpl(cmMakefile* mf)
  23. : cmCoreTryCompile(mf)
  24. {
  25. }
  26. bool TryRunCode(std::vector<std::string> const& args);
  27. void RunExecutable(const std::string& runArgs,
  28. std::string* runOutputContents,
  29. std::string* runOutputStdOutContents,
  30. std::string* runOutputStdErrContents);
  31. void DoNotRunExecutable(const std::string& runArgs,
  32. const std::string& srcFile,
  33. std::string* runOutputContents,
  34. std::string* runOutputStdOutContents,
  35. std::string* runOutputStdErrContents);
  36. std::string CompileResultVariable;
  37. std::string RunResultVariable;
  38. std::string OutputVariable;
  39. std::string RunOutputVariable;
  40. std::string RunOutputStdOutVariable;
  41. std::string RunOutputStdErrVariable;
  42. std::string CompileOutputVariable;
  43. std::string WorkingDirectory;
  44. };
  45. bool TryRunCommandImpl::TryRunCode(std::vector<std::string> const& argv)
  46. {
  47. // build an arg list for TryCompile and extract the runArgs,
  48. std::vector<std::string> tryCompile;
  49. this->CompileResultVariable.clear();
  50. this->RunResultVariable.clear();
  51. this->OutputVariable.clear();
  52. this->RunOutputVariable.clear();
  53. this->RunOutputStdOutVariable.clear();
  54. this->RunOutputStdErrVariable.clear();
  55. this->CompileOutputVariable.clear();
  56. std::string runArgs;
  57. unsigned int i;
  58. for (i = 1; i < argv.size(); ++i) {
  59. if (argv[i] == "ARGS") {
  60. ++i;
  61. while (i < argv.size() && argv[i] != "COMPILE_DEFINITIONS" &&
  62. argv[i] != "CMAKE_FLAGS" && argv[i] != "LINK_OPTIONS" &&
  63. argv[i] != "LINK_LIBRARIES") {
  64. runArgs += " ";
  65. runArgs += argv[i];
  66. ++i;
  67. }
  68. if (i < argv.size()) {
  69. tryCompile.push_back(argv[i]);
  70. }
  71. } else {
  72. if (argv[i] == "OUTPUT_VARIABLE") {
  73. if (argv.size() <= (i + 1)) {
  74. cmSystemTools::Error(
  75. "OUTPUT_VARIABLE specified but there is no variable");
  76. return false;
  77. }
  78. i++;
  79. this->OutputVariable = argv[i];
  80. } else if (argv[i] == "RUN_OUTPUT_VARIABLE") {
  81. if (argv.size() <= (i + 1)) {
  82. cmSystemTools::Error(
  83. "RUN_OUTPUT_VARIABLE specified but there is no variable");
  84. return false;
  85. }
  86. i++;
  87. this->RunOutputVariable = argv[i];
  88. } else if (argv[i] == "RUN_OUTPUT_STDOUT_VARIABLE") {
  89. if (argv.size() <= (i + 1)) {
  90. cmSystemTools::Error(
  91. "RUN_OUTPUT_STDOUT_VARIABLE specified but there is no variable");
  92. return false;
  93. }
  94. i++;
  95. this->RunOutputStdOutVariable = argv[i];
  96. } else if (argv[i] == "RUN_OUTPUT_STDERR_VARIABLE") {
  97. if (argv.size() <= (i + 1)) {
  98. cmSystemTools::Error(
  99. "RUN_OUTPUT_STDERR_VARIABLE specified but there is no variable");
  100. return false;
  101. }
  102. i++;
  103. this->RunOutputStdErrVariable = argv[i];
  104. } else if (argv[i] == "COMPILE_OUTPUT_VARIABLE") {
  105. if (argv.size() <= (i + 1)) {
  106. cmSystemTools::Error(
  107. "COMPILE_OUTPUT_VARIABLE specified but there is no variable");
  108. return false;
  109. }
  110. i++;
  111. this->CompileOutputVariable = argv[i];
  112. } else if (argv[i] == "WORKING_DIRECTORY") {
  113. if (argv.size() <= (i + 1)) {
  114. cmSystemTools::Error(
  115. "WORKING_DIRECTORY specified but there is no variable");
  116. return false;
  117. }
  118. i++;
  119. this->WorkingDirectory = argv[i];
  120. } else {
  121. tryCompile.push_back(argv[i]);
  122. }
  123. }
  124. }
  125. // although they could be used together, don't allow it, because
  126. // using OUTPUT_VARIABLE makes crosscompiling harder
  127. if (!this->OutputVariable.empty() &&
  128. (!this->RunOutputVariable.empty() ||
  129. !this->CompileOutputVariable.empty() ||
  130. !this->RunOutputStdOutVariable.empty() ||
  131. !this->RunOutputStdErrVariable.empty())) {
  132. cmSystemTools::Error(
  133. "You cannot use OUTPUT_VARIABLE together with COMPILE_OUTPUT_VARIABLE "
  134. ", RUN_OUTPUT_VARIABLE, RUN_OUTPUT_STDOUT_VARIABLE or "
  135. "RUN_OUTPUT_STDERR_VARIABLE. "
  136. "Please use only COMPILE_OUTPUT_VARIABLE, RUN_OUTPUT_VARIABLE, "
  137. "RUN_OUTPUT_STDOUT_VARIABLE "
  138. "and/or RUN_OUTPUT_STDERR_VARIABLE.");
  139. return false;
  140. }
  141. if ((!this->RunOutputStdOutVariable.empty() ||
  142. !RunOutputStdErrVariable.empty()) &&
  143. !this->RunOutputVariable.empty()) {
  144. cmSystemTools::Error(
  145. "You cannot use RUN_OUTPUT_STDOUT_VARIABLE or "
  146. "RUN_OUTPUT_STDERR_VARIABLE together "
  147. "with RUN_OUTPUT_VARIABLE. Please use only COMPILE_OUTPUT_VARIABLE or "
  148. "RUN_OUTPUT_STDOUT_VARIABLE and/or RUN_OUTPUT_STDERR_VARIABLE.");
  149. return false;
  150. }
  151. if (!this->WorkingDirectory.empty()) {
  152. if (!cmSystemTools::MakeDirectory(this->WorkingDirectory)) {
  153. cmSystemTools::Error(cmStrCat("Error creating working directory \"",
  154. this->WorkingDirectory, "\"."));
  155. return false;
  156. }
  157. }
  158. bool captureRunOutput = false;
  159. bool captureRunOutputStdOutErr = false;
  160. if (!this->OutputVariable.empty()) {
  161. captureRunOutput = true;
  162. tryCompile.emplace_back("OUTPUT_VARIABLE");
  163. tryCompile.push_back(this->OutputVariable);
  164. }
  165. if (!this->CompileOutputVariable.empty()) {
  166. tryCompile.emplace_back("OUTPUT_VARIABLE");
  167. tryCompile.push_back(this->CompileOutputVariable);
  168. }
  169. if (!this->RunOutputStdOutVariable.empty() ||
  170. !RunOutputStdErrVariable.empty()) {
  171. captureRunOutputStdOutErr = true;
  172. } else if (!this->RunOutputVariable.empty()) {
  173. captureRunOutput = true;
  174. }
  175. this->RunResultVariable = argv[0];
  176. this->CompileResultVariable = argv[1];
  177. // do the try compile
  178. int res = this->TryCompileCode(tryCompile, true);
  179. // now try running the command if it compiled
  180. if (!res) {
  181. if (this->OutputFile.empty()) {
  182. cmSystemTools::Error(this->FindErrorMessage);
  183. } else {
  184. // "run" it and capture the output
  185. std::string runOutputContents;
  186. std::string runOutputStdOutContents;
  187. std::string runOutputStdErrContents;
  188. if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") &&
  189. !this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) {
  190. this->DoNotRunExecutable(
  191. runArgs, argv[3], captureRunOutput ? &runOutputContents : nullptr,
  192. captureRunOutputStdOutErr && !RunOutputStdOutVariable.empty()
  193. ? &runOutputStdOutContents
  194. : nullptr,
  195. captureRunOutputStdOutErr && !RunOutputStdErrVariable.empty()
  196. ? &runOutputStdErrContents
  197. : nullptr);
  198. } else {
  199. this->RunExecutable(
  200. runArgs, captureRunOutput ? &runOutputContents : nullptr,
  201. captureRunOutputStdOutErr && !RunOutputStdOutVariable.empty()
  202. ? &runOutputStdOutContents
  203. : nullptr,
  204. captureRunOutputStdOutErr && !RunOutputStdErrVariable.empty()
  205. ? &runOutputStdErrContents
  206. : nullptr);
  207. }
  208. // now put the output into the variables
  209. if (!this->RunOutputVariable.empty()) {
  210. this->Makefile->AddDefinition(this->RunOutputVariable,
  211. runOutputContents);
  212. }
  213. if (!this->RunOutputStdOutVariable.empty()) {
  214. this->Makefile->AddDefinition(this->RunOutputStdOutVariable,
  215. runOutputStdOutContents);
  216. }
  217. if (!this->RunOutputStdErrVariable.empty()) {
  218. this->Makefile->AddDefinition(this->RunOutputStdErrVariable,
  219. runOutputStdErrContents);
  220. }
  221. if (!this->OutputVariable.empty()) {
  222. // if the TryCompileCore saved output in this outputVariable then
  223. // prepend that output to this output
  224. cmValue compileOutput =
  225. this->Makefile->GetDefinition(this->OutputVariable);
  226. if (compileOutput) {
  227. runOutputContents = *compileOutput + runOutputContents;
  228. }
  229. this->Makefile->AddDefinition(this->OutputVariable, runOutputContents);
  230. }
  231. }
  232. }
  233. // if we created a directory etc, then cleanup after ourselves
  234. if (!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
  235. this->CleanupFiles(this->BinaryDirectory);
  236. }
  237. return true;
  238. }
  239. void TryRunCommandImpl::RunExecutable(const std::string& runArgs,
  240. std::string* out, std::string* stdOut,
  241. std::string* stdErr)
  242. {
  243. int retVal = -1;
  244. std::string finalCommand;
  245. const std::string& emulator =
  246. this->Makefile->GetSafeDefinition("CMAKE_CROSSCOMPILING_EMULATOR");
  247. if (!emulator.empty()) {
  248. std::vector<std::string> emulatorWithArgs = cmExpandedList(emulator);
  249. finalCommand +=
  250. cmSystemTools::ConvertToRunCommandPath(emulatorWithArgs[0]);
  251. finalCommand += " ";
  252. for (std::string const& arg : cmMakeRange(emulatorWithArgs).advance(1)) {
  253. finalCommand += "\"";
  254. finalCommand += arg;
  255. finalCommand += "\"";
  256. finalCommand += " ";
  257. }
  258. }
  259. finalCommand += cmSystemTools::ConvertToRunCommandPath(this->OutputFile);
  260. if (!runArgs.empty()) {
  261. finalCommand += runArgs;
  262. }
  263. bool worked = cmSystemTools::RunSingleCommand(
  264. finalCommand, stdOut || stdErr ? stdOut : out,
  265. stdOut || stdErr ? stdErr : out, &retVal,
  266. this->WorkingDirectory.empty() ? nullptr : this->WorkingDirectory.c_str(),
  267. cmSystemTools::OUTPUT_NONE, cmDuration::zero());
  268. // set the run var
  269. char retChar[16];
  270. const char* retStr;
  271. if (worked) {
  272. snprintf(retChar, sizeof(retChar), "%i", retVal);
  273. retStr = retChar;
  274. } else {
  275. retStr = "FAILED_TO_RUN";
  276. }
  277. this->Makefile->AddCacheDefinition(this->RunResultVariable, retStr,
  278. "Result of try_run()",
  279. cmStateEnums::INTERNAL);
  280. }
  281. /* This is only used when cross compiling. Instead of running the
  282. executable, two cache variables are created which will hold the results
  283. the executable would have produced.
  284. */
  285. void TryRunCommandImpl::DoNotRunExecutable(const std::string& runArgs,
  286. const std::string& srcFile,
  287. std::string* out,
  288. std::string* stdOut,
  289. std::string* stdErr)
  290. {
  291. // copy the executable out of the CMakeFiles/ directory, so it is not
  292. // removed at the end of try_run() and the user can run it manually
  293. // on the target platform.
  294. std::string copyDest =
  295. cmStrCat(this->Makefile->GetHomeOutputDirectory(), "/CMakeFiles/",
  296. cmSystemTools::GetFilenameWithoutExtension(this->OutputFile), '-',
  297. this->RunResultVariable,
  298. cmSystemTools::GetFilenameExtension(this->OutputFile));
  299. cmSystemTools::CopyFileAlways(this->OutputFile, copyDest);
  300. std::string resultFileName =
  301. cmStrCat(this->Makefile->GetHomeOutputDirectory(), "/TryRunResults.cmake");
  302. std::string detailsString = cmStrCat("For details see ", resultFileName);
  303. std::string internalRunOutputName =
  304. this->RunResultVariable + "__TRYRUN_OUTPUT";
  305. std::string internalRunOutputStdOutName =
  306. this->RunResultVariable + "__TRYRUN_OUTPUT_STDOUT";
  307. std::string internalRunOutputStdErrName =
  308. this->RunResultVariable + "__TRYRUN_OUTPUT_STDERR";
  309. bool error = false;
  310. if (!this->Makefile->GetDefinition(this->RunResultVariable)) {
  311. // if the variables doesn't exist, create it with a helpful error text
  312. // and mark it as advanced
  313. std::string comment =
  314. cmStrCat("Run result of try_run(), indicates whether the executable "
  315. "would have been able to run on its target platform.\n",
  316. detailsString);
  317. this->Makefile->AddCacheDefinition(this->RunResultVariable,
  318. "PLEASE_FILL_OUT-FAILED_TO_RUN",
  319. comment.c_str(), cmStateEnums::STRING);
  320. cmState* state = this->Makefile->GetState();
  321. cmValue existingValue = state->GetCacheEntryValue(this->RunResultVariable);
  322. if (existingValue) {
  323. state->SetCacheEntryProperty(this->RunResultVariable, "ADVANCED", "1");
  324. }
  325. error = true;
  326. }
  327. // is the output from the executable used ?
  328. if (stdOut || stdErr) {
  329. if (!this->Makefile->GetDefinition(internalRunOutputStdOutName)) {
  330. // if the variables doesn't exist, create it with a helpful error text
  331. // and mark it as advanced
  332. std::string comment = cmStrCat(
  333. "Output of try_run(), contains the text, which the executable "
  334. "would have printed on stdout on its target platform.\n",
  335. detailsString);
  336. this->Makefile->AddCacheDefinition(
  337. internalRunOutputStdOutName, "PLEASE_FILL_OUT-NOTFOUND",
  338. comment.c_str(), cmStateEnums::STRING);
  339. cmState* state = this->Makefile->GetState();
  340. cmValue existing =
  341. state->GetCacheEntryValue(internalRunOutputStdOutName);
  342. if (existing) {
  343. state->SetCacheEntryProperty(internalRunOutputStdOutName, "ADVANCED",
  344. "1");
  345. }
  346. error = true;
  347. }
  348. if (!this->Makefile->GetDefinition(internalRunOutputStdErrName)) {
  349. // if the variables doesn't exist, create it with a helpful error text
  350. // and mark it as advanced
  351. std::string comment = cmStrCat(
  352. "Output of try_run(), contains the text, which the executable "
  353. "would have printed on stderr on its target platform.\n",
  354. detailsString);
  355. this->Makefile->AddCacheDefinition(
  356. internalRunOutputStdErrName, "PLEASE_FILL_OUT-NOTFOUND",
  357. comment.c_str(), cmStateEnums::STRING);
  358. cmState* state = this->Makefile->GetState();
  359. cmValue existing =
  360. state->GetCacheEntryValue(internalRunOutputStdErrName);
  361. if (existing) {
  362. state->SetCacheEntryProperty(internalRunOutputStdErrName, "ADVANCED",
  363. "1");
  364. }
  365. error = true;
  366. }
  367. } else if (out) {
  368. if (!this->Makefile->GetDefinition(internalRunOutputName)) {
  369. // if the variables doesn't exist, create it with a helpful error text
  370. // and mark it as advanced
  371. std::string comment = cmStrCat(
  372. "Output of try_run(), contains the text, which the executable "
  373. "would have printed on stdout and stderr on its target platform.\n",
  374. detailsString);
  375. this->Makefile->AddCacheDefinition(
  376. internalRunOutputName, "PLEASE_FILL_OUT-NOTFOUND", comment.c_str(),
  377. cmStateEnums::STRING);
  378. cmState* state = this->Makefile->GetState();
  379. cmValue existing = state->GetCacheEntryValue(internalRunOutputName);
  380. if (existing) {
  381. state->SetCacheEntryProperty(internalRunOutputName, "ADVANCED", "1");
  382. }
  383. error = true;
  384. }
  385. }
  386. if (error) {
  387. static bool firstTryRun = true;
  388. cmsys::ofstream file(resultFileName.c_str(),
  389. firstTryRun ? std::ios::out : std::ios::app);
  390. if (file) {
  391. if (firstTryRun) {
  392. /* clang-format off */
  393. file << "# This file was generated by CMake because it detected "
  394. "try_run() commands\n"
  395. "# in crosscompiling mode. It will be overwritten by the next "
  396. "CMake run.\n"
  397. "# Copy it to a safe location, set the variables to "
  398. "appropriate values\n"
  399. "# and use it then to preset the CMake cache (using -C).\n\n";
  400. /* clang-format on */
  401. }
  402. std::string comment =
  403. cmStrCat('\n', this->RunResultVariable,
  404. "\n indicates whether the executable would have been able "
  405. "to run on its\n"
  406. " target platform. If so, set ",
  407. this->RunResultVariable,
  408. " to\n"
  409. " the exit code (in many cases 0 for success), otherwise "
  410. "enter \"FAILED_TO_RUN\".\n");
  411. if (stdOut || stdErr) {
  412. if (stdOut) {
  413. comment += internalRunOutputStdOutName;
  414. comment +=
  415. "\n contains the text the executable "
  416. "would have printed on stdout.\n"
  417. " If the executable would not have been able to run, set ";
  418. comment += internalRunOutputStdOutName;
  419. comment += " empty.\n"
  420. " Otherwise check if the output is evaluated by the "
  421. "calling CMake code. If so,\n"
  422. " check what the source file would have printed when "
  423. "called with the given arguments.\n";
  424. }
  425. if (stdErr) {
  426. comment += internalRunOutputStdErrName;
  427. comment +=
  428. "\n contains the text the executable "
  429. "would have printed on stderr.\n"
  430. " If the executable would not have been able to run, set ";
  431. comment += internalRunOutputStdErrName;
  432. comment += " empty.\n"
  433. " Otherwise check if the output is evaluated by the "
  434. "calling CMake code. If so,\n"
  435. " check what the source file would have printed when "
  436. "called with the given arguments.\n";
  437. }
  438. } else if (out) {
  439. comment += internalRunOutputName;
  440. comment +=
  441. "\n contains the text the executable "
  442. "would have printed on stdout and stderr.\n"
  443. " If the executable would not have been able to run, set ";
  444. comment += internalRunOutputName;
  445. comment += " empty.\n"
  446. " Otherwise check if the output is evaluated by the "
  447. "calling CMake code. If so,\n"
  448. " check what the source file would have printed when "
  449. "called with the given arguments.\n";
  450. }
  451. comment += "The ";
  452. comment += this->CompileResultVariable;
  453. comment += " variable holds the build result for this try_run().\n\n"
  454. "Source file : ";
  455. comment += srcFile + "\n";
  456. comment += "Executable : ";
  457. comment += copyDest + "\n";
  458. comment += "Run arguments : ";
  459. comment += runArgs;
  460. comment += "\n";
  461. comment += " Called from: " + this->Makefile->FormatListFileStack();
  462. cmsys::SystemTools::ReplaceString(comment, "\n", "\n# ");
  463. file << comment << "\n\n";
  464. file << "set( " << this->RunResultVariable << " \n \""
  465. << this->Makefile->GetSafeDefinition(this->RunResultVariable)
  466. << "\"\n CACHE STRING \"Result from try_run\" FORCE)\n\n";
  467. if (out) {
  468. file << "set( " << internalRunOutputName << " \n \""
  469. << this->Makefile->GetSafeDefinition(internalRunOutputName)
  470. << "\"\n CACHE STRING \"Output from try_run\" FORCE)\n\n";
  471. }
  472. file.close();
  473. }
  474. firstTryRun = false;
  475. std::string errorMessage =
  476. cmStrCat("try_run() invoked in cross-compiling mode, "
  477. "please set the following cache variables "
  478. "appropriately:\n ",
  479. this->RunResultVariable, " (advanced)\n");
  480. if (out) {
  481. errorMessage += " " + internalRunOutputName + " (advanced)\n";
  482. }
  483. errorMessage += detailsString;
  484. cmSystemTools::Error(errorMessage);
  485. return;
  486. }
  487. if (stdOut || stdErr) {
  488. if (stdOut) {
  489. (*stdOut) = *this->Makefile->GetDefinition(internalRunOutputStdOutName);
  490. }
  491. if (stdErr) {
  492. (*stdErr) = *this->Makefile->GetDefinition(internalRunOutputStdErrName);
  493. }
  494. } else if (out) {
  495. (*out) = *this->Makefile->GetDefinition(internalRunOutputName);
  496. }
  497. }
  498. }
  499. bool cmTryRunCommand(std::vector<std::string> const& args,
  500. cmExecutionStatus& status)
  501. {
  502. if (args.size() < 4) {
  503. return false;
  504. }
  505. cmMakefile& mf = status.GetMakefile();
  506. if (mf.GetCMakeInstance()->GetWorkingMode() == cmake::FIND_PACKAGE_MODE) {
  507. mf.IssueMessage(
  508. MessageType::FATAL_ERROR,
  509. "The try_run() command is not supported in --find-package mode.");
  510. return false;
  511. }
  512. TryRunCommandImpl tr(&mf);
  513. return tr.TryRunCode(args);
  514. }