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