cmTryRunCommand.cxx 18 KB

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