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"
9 #include "cmsys/FStream.hxx"
11 #include "cmArgumentParserTypes.h"
12 #include "cmCoreTryCompile.h"
13 #include "cmDuration.h"
14 #include "cmExecutionStatus.h"
15 #include "cmMakefile.h"
16 #include "cmMessageType.h"
19 #include "cmStateTypes.h"
20 #include "cmStringAlgorithms.h"
21 #include "cmSystemTools.h"
27 class TryRunCommandImpl : public cmCoreTryCompile
30 TryRunCommandImpl(cmMakefile* mf)
31 : cmCoreTryCompile(mf)
35 bool TryRunCode(std::vector<std::string> const& args);
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);
50 std::string RunResultVariable;
53 bool TryRunCommandImpl::TryRunCode(std::vector<std::string> const& argv)
55 this->RunResultVariable = argv[0];
56 cmCoreTryCompile::Arguments arguments =
57 this->ParseArgs(cmMakeRange(argv).advance(1), true);
61 this->NoCache = arguments.NoCache;
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)) {
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.");
79 if ((arguments.RunOutputStdOutVariable ||
80 arguments.RunOutputStdErrVariable) &&
81 arguments.RunOutputVariable) {
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.");
90 if (arguments.RunWorkingDirectory) {
91 if (!cmSystemTools::MakeDirectory(*arguments.RunWorkingDirectory)) {
92 cmSystemTools::Error(cmStrCat("Error creating working directory \"",
93 *arguments.RunWorkingDirectory, "\"."));
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;
105 if (arguments.RunOutputStdOutVariable || arguments.RunOutputStdErrVariable) {
106 captureRunOutputStdOutErr = true;
107 } else if (arguments.RunOutputVariable) {
108 captureRunOutput = true;
111 // do the try compile
112 bool compiled = this->TryCompileCode(arguments, cmStateEnums::EXECUTABLE);
114 // now try running the command if it compiled
116 if (this->OutputFile.empty()) {
117 cmSystemTools::Error(this->FindErrorMessage);
120 if (arguments.RunArgs) {
121 runArgs = cmStrCat(" ", cmJoin(*arguments.RunArgs, " "));
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
137 captureRunOutputStdOutErr && arguments.RunOutputStdErrVariable
138 ? &runOutputStdErrContents
142 runArgs, arguments.RunWorkingDirectory,
143 captureRunOutput ? &runOutputContents : nullptr,
144 captureRunOutputStdOutErr && arguments.RunOutputStdOutVariable
145 ? &runOutputStdOutContents
147 captureRunOutputStdOutErr && arguments.RunOutputStdErrVariable
148 ? &runOutputStdErrContents
152 // now put the output into the variables
153 if (arguments.RunOutputVariable) {
154 this->Makefile->AddDefinition(*arguments.RunOutputVariable,
157 if (arguments.RunOutputStdOutVariable) {
158 this->Makefile->AddDefinition(*arguments.RunOutputStdOutVariable,
159 runOutputStdOutContents);
161 if (arguments.RunOutputStdErrVariable) {
162 this->Makefile->AddDefinition(*arguments.RunOutputStdErrVariable,
163 runOutputStdErrContents);
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);
172 runOutputContents = *compileOutput + runOutputContents;
174 this->Makefile->AddDefinition(*arguments.OutputVariable,
180 // if we created a directory etc, then cleanup after ourselves
181 if (!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
182 this->CleanupFiles(this->BinaryDirectory);
187 void TryRunCommandImpl::RunExecutable(const std::string& runArgs,
188 cm::optional<std::string> const& workDir,
189 std::string* out, std::string* stdOut,
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);
200 cmSystemTools::ConvertToRunCommandPath(emulatorWithArgs[0]);
202 for (std::string const& arg : cmMakeRange(emulatorWithArgs).advance(1)) {
203 finalCommand += "\"";
205 finalCommand += "\"";
209 finalCommand += cmSystemTools::ConvertToRunCommandPath(this->OutputFile);
210 if (!runArgs.empty()) {
211 finalCommand += runArgs;
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,
222 snprintf(retChar, sizeof(retChar), "%i", retVal);
225 retStr = "FAILED_TO_RUN";
228 this->Makefile->AddDefinition(this->RunResultVariable, retStr);
230 this->Makefile->AddCacheDefinition(this->RunResultVariable, retStr,
231 "Result of try_run()",
232 cmStateEnums::INTERNAL);
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.
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)
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);
255 std::string resultFileName =
256 cmStrCat(this->Makefile->GetHomeOutputDirectory(), "/TryRunResults.cmake");
258 std::string detailsString = cmStrCat("For details see ", resultFileName);
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";
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",
275 this->Makefile->AddCacheDefinition(this->RunResultVariable,
276 "PLEASE_FILL_OUT-FAILED_TO_RUN",
277 comment.c_str(), cmStateEnums::STRING);
279 cmState* state = this->Makefile->GetState();
280 cmValue existingValue = state->GetCacheEntryValue(this->RunResultVariable);
282 state->SetCacheEntryProperty(this->RunResultVariable, "ADVANCED", "1");
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",
298 this->Makefile->AddCacheDefinition(
299 internalRunOutputStdOutName, "PLEASE_FILL_OUT-NOTFOUND",
300 comment.c_str(), cmStateEnums::STRING);
301 cmState* state = this->Makefile->GetState();
303 state->GetCacheEntryValue(internalRunOutputStdOutName);
305 state->SetCacheEntryProperty(internalRunOutputStdOutName, "ADVANCED",
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",
320 this->Makefile->AddCacheDefinition(
321 internalRunOutputStdErrName, "PLEASE_FILL_OUT-NOTFOUND",
322 comment.c_str(), cmStateEnums::STRING);
323 cmState* state = this->Makefile->GetState();
325 state->GetCacheEntryValue(internalRunOutputStdErrName);
327 state->SetCacheEntryProperty(internalRunOutputStdErrName, "ADVANCED",
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",
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);
348 state->SetCacheEntryProperty(internalRunOutputName, "ADVANCED", "1");
356 static bool firstTryRun = true;
357 cmsys::ofstream file(resultFileName.c_str(),
358 firstTryRun ? std::ios::out : std::ios::app);
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 "
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 */
372 std::string comment =
373 cmStrCat('\n', this->RunResultVariable,
374 "\n indicates whether the executable would have been able "
376 " target platform. If so, set ",
377 this->RunResultVariable,
379 " the exit code (in many cases 0 for success), otherwise "
380 "enter \"FAILED_TO_RUN\".\n");
381 if (stdOut || stdErr) {
383 comment += internalRunOutputStdOutName;
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";
396 comment += internalRunOutputStdErrName;
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";
409 comment += internalRunOutputName;
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";
423 comment += compileResultVariable;
424 comment += " variable holds the build result for this try_run().\n\n";
426 comment += "Source file : ";
427 comment += *srcFile + "\n";
429 comment += "Executable : ";
430 comment += copyDest + "\n";
431 comment += "Run arguments : ";
434 comment += " Called from: " + this->Makefile->FormatListFileStack();
435 cmsys::SystemTools::ReplaceString(comment, "\n", "\n# ");
436 file << comment << "\n\n";
438 file << "set( " << this->RunResultVariable << " \n \""
439 << this->Makefile->GetSafeDefinition(this->RunResultVariable)
440 << "\"\n CACHE STRING \"Result from try_run\" FORCE)\n\n";
443 file << "set( " << internalRunOutputName << " \n \""
444 << this->Makefile->GetSafeDefinition(internalRunOutputName)
445 << "\"\n CACHE STRING \"Output from try_run\" FORCE)\n\n";
451 std::string errorMessage =
452 cmStrCat("try_run() invoked in cross-compiling mode, "
453 "please set the following cache variables "
455 this->RunResultVariable, " (advanced)\n");
457 errorMessage += " " + internalRunOutputName + " (advanced)\n";
459 errorMessage += detailsString;
460 cmSystemTools::Error(errorMessage);
464 if (stdOut || stdErr) {
466 (*stdOut) = *this->Makefile->GetDefinition(internalRunOutputStdOutName);
469 (*stdErr) = *this->Makefile->GetDefinition(internalRunOutputStdErrName);
472 (*out) = *this->Makefile->GetDefinition(internalRunOutputName);
477 bool cmTryRunCommand(std::vector<std::string> const& args,
478 cmExecutionStatus& status)
480 cmMakefile& mf = status.GetMakefile();
482 if (args.size() < 4) {
483 mf.IssueMessage(MessageType::FATAL_ERROR,
484 "The try_run() command requires at least 4 arguments.");
488 if (mf.GetCMakeInstance()->GetWorkingMode() == cmake::FIND_PACKAGE_MODE) {
490 MessageType::FATAL_ERROR,
491 "The try_run() command is not supported in --find-package mode.");
495 TryRunCommandImpl tr(&mf);
496 return tr.TryRunCode(args);