cmTryRunCommand.cxx 12.6 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
Ken Martin's avatar
Ken Martin committed
3
#include "cmTryRunCommand.h"
Brad King's avatar
Brad King committed
4

Ken Martin's avatar
Ken Martin committed
5
#include "cmTryCompileCommand.h"
6
#include <cmsys/FStream.hxx>
Ken Martin's avatar
Ken Martin committed
7

Alexander Neundorf's avatar
 
Alexander Neundorf committed
8
// cmTryRunCommand
9 10
bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
                                  cmExecutionStatus&)
Ken Martin's avatar
Ken Martin committed
11
{
12
  if (argv.size() < 4) {
Ken Martin's avatar
Ken Martin committed
13
    return false;
14
  }
Ken Martin's avatar
Ken Martin committed
15

16 17 18 19 20
  if (this->Makefile->GetCMakeInstance()->GetWorkingMode() ==
      cmake::FIND_PACKAGE_MODE) {
    this->Makefile->IssueMessage(
      cmake::FATAL_ERROR,
      "The TRY_RUN() command is not supported in --find-package mode.");
21
    return false;
22
  }
23

24
  // build an arg list for TryCompile and extract the runArgs,
Ken Martin's avatar
Ken Martin committed
25
  std::vector<std::string> tryCompile;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
26 27 28 29 30 31 32

  this->CompileResultVariable = "";
  this->RunResultVariable = "";
  this->OutputVariable = "";
  this->RunOutputVariable = "";
  this->CompileOutputVariable = "";

Ken Martin's avatar
Ken Martin committed
33
  std::string runArgs;
Ken Martin's avatar
Ken Martin committed
34
  unsigned int i;
35 36
  for (i = 1; i < argv.size(); ++i) {
    if (argv[i] == "ARGS") {
Ken Martin's avatar
Ken Martin committed
37 38
      ++i;
      while (i < argv.size() && argv[i] != "COMPILE_DEFINITIONS" &&
39
             argv[i] != "CMAKE_FLAGS" && argv[i] != "LINK_LIBRARIES") {
Ken Martin's avatar
Ken Martin committed
40 41 42
        runArgs += " ";
        runArgs += argv[i];
        ++i;
43 44
      }
      if (i < argv.size()) {
Ken Martin's avatar
Ken Martin committed
45
        tryCompile.push_back(argv[i]);
46
      }
47 48 49
    } else {
      if (argv[i] == "OUTPUT_VARIABLE") {
        if (argv.size() <= (i + 1)) {
50
          cmSystemTools::Error(
Bill Hoffman's avatar
Bill Hoffman committed
51
            "OUTPUT_VARIABLE specified but there is no variable");
52
          return false;
53
        }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
54 55
        i++;
        this->OutputVariable = argv[i];
56 57
      } else if (argv[i] == "RUN_OUTPUT_VARIABLE") {
        if (argv.size() <= (i + 1)) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
58 59 60
          cmSystemTools::Error(
            "RUN_OUTPUT_VARIABLE specified but there is no variable");
          return false;
61
        }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
62 63
        i++;
        this->RunOutputVariable = argv[i];
64 65
      } else if (argv[i] == "COMPILE_OUTPUT_VARIABLE") {
        if (argv.size() <= (i + 1)) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
66 67 68
          cmSystemTools::Error(
            "COMPILE_OUTPUT_VARIABLE specified but there is no variable");
          return false;
69
        }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
70 71
        i++;
        this->CompileOutputVariable = argv[i];
72
      } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
73
        tryCompile.push_back(argv[i]);
Ken Martin's avatar
Ken Martin committed
74 75
      }
    }
76
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
77 78 79

  // although they could be used together, don't allow it, because
  // using OUTPUT_VARIABLE makes crosscompiling harder
80 81
  if (this->OutputVariable.size() && (!this->RunOutputVariable.empty() ||
                                      !this->CompileOutputVariable.empty())) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
82 83 84 85 86
    cmSystemTools::Error(
      "You cannot use OUTPUT_VARIABLE together with COMPILE_OUTPUT_VARIABLE "
      "or RUN_OUTPUT_VARIABLE. Please use only COMPILE_OUTPUT_VARIABLE and/or "
      "RUN_OUTPUT_VARIABLE.");
    return false;
87
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
88 89

  bool captureRunOutput = false;
90
  if (!this->OutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
91 92 93
    captureRunOutput = true;
    tryCompile.push_back("OUTPUT_VARIABLE");
    tryCompile.push_back(this->OutputVariable);
94 95
  }
  if (!this->CompileOutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
96 97
    tryCompile.push_back("OUTPUT_VARIABLE");
    tryCompile.push_back(this->CompileOutputVariable);
98 99
  }
  if (!this->RunOutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
100
    captureRunOutput = true;
101
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
102 103 104 105

  this->RunResultVariable = argv[0];
  this->CompileResultVariable = argv[1];

Ken Martin's avatar
Ken Martin committed
106
  // do the try compile
107
  int res = this->TryCompileCode(tryCompile, true);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
108

Ken Martin's avatar
Ken Martin committed
109
  // now try running the command if it compiled
110 111
  if (!res) {
    if (this->OutputFile.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
112
      cmSystemTools::Error(this->FindErrorMessage.c_str());
113
    } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
114 115
      // "run" it and capture the output
      std::string runOutputContents;
116
      if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") &&
117
          !this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
118 119 120
        this->DoNotRunExecutable(runArgs, argv[3], captureRunOutput
                                   ? &runOutputContents
                                   : CM_NULLPTR);
121
      } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
122
        this->RunExecutable(runArgs, &runOutputContents);
123
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
124 125

      // now put the output into the variables
126
      if (!this->RunOutputVariable.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
127
        this->Makefile->AddDefinition(this->RunOutputVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
128
                                      runOutputContents.c_str());
129
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
130

131
      if (!this->OutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
132 133
        // if the TryCompileCore saved output in this outputVariable then
        // prepend that output to this output
134 135 136
        const char* compileOutput =
          this->Makefile->GetDefinition(this->OutputVariable);
        if (compileOutput) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
137
          runOutputContents = std::string(compileOutput) + runOutputContents;
138
        }
Stephen Kelly's avatar
Stephen Kelly committed
139
        this->Makefile->AddDefinition(this->OutputVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
140
                                      runOutputContents.c_str());
Ken Martin's avatar
Ken Martin committed
141
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
142
    }
143
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
144

Alexander Neundorf's avatar
 
Alexander Neundorf committed
145
  // if we created a directory etc, then cleanup after ourselves
146
  if (!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
147
    this->CleanupFiles(this->BinaryDirectory.c_str());
148
  }
Ken Martin's avatar
Ken Martin committed
149 150
  return true;
}
Alexander Neundorf's avatar
 
Alexander Neundorf committed
151 152 153 154 155

void cmTryRunCommand::RunExecutable(const std::string& runArgs,
                                    std::string* out)
{
  int retVal = -1;
156 157 158

  std::string finalCommand;
  const std::string emulator =
159 160
    this->Makefile->GetSafeDefinition("CMAKE_CROSSCOMPILING_EMULATOR");
  if (!emulator.empty()) {
161 162
    std::vector<std::string> emulatorWithArgs;
    cmSystemTools::ExpandListArgument(emulator, emulatorWithArgs);
163 164
    finalCommand +=
      cmSystemTools::ConvertToRunCommandPath(emulatorWithArgs[0].c_str());
165 166
    finalCommand += " ";
    for (std::vector<std::string>::const_iterator ei =
167 168
           emulatorWithArgs.begin() + 1;
         ei != emulatorWithArgs.end(); ++ei) {
169 170 171 172 173
      finalCommand += "\"";
      finalCommand += *ei;
      finalCommand += "\"";
      finalCommand += " ";
    }
174 175 176 177
  }
  finalCommand +=
    cmSystemTools::ConvertToRunCommandPath(this->OutputFile.c_str());
  if (!runArgs.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
178
    finalCommand += runArgs;
179
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
180
  int timeout = 0;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
181 182 183
  bool worked = cmSystemTools::RunSingleCommand(
    finalCommand.c_str(), out, out, &retVal, CM_NULLPTR,
    cmSystemTools::OUTPUT_NONE, timeout);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
184 185
  // set the run var
  char retChar[1000];
186
  if (worked) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
187
    sprintf(retChar, "%i", retVal);
188
  } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
189
    strcpy(retChar, "FAILED_TO_RUN");
190
  }
Stephen Kelly's avatar
Stephen Kelly committed
191
  this->Makefile->AddCacheDefinition(this->RunResultVariable, retChar,
192 193
                                     "Result of TRY_RUN",
                                     cmStateEnums::INTERNAL);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
194 195 196 197
}

/* This is only used when cross compiling. Instead of running the
 executable, two cache variables are created which will hold the results
198
 the executable would have produced.
Alexander Neundorf's avatar
 
Alexander Neundorf committed
199
*/
200
void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
201 202
                                         const std::string& srcFile,
                                         std::string* out)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
203
{
Alexander Neundorf's avatar
 
Alexander Neundorf committed
204 205 206
  // copy the executable out of the CMakeFiles/ directory, so it is not
  // removed at the end of TRY_RUN and the user can run it manually
  // on the target platform.
207
  std::string copyDest = this->Makefile->GetHomeOutputDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
208
  copyDest += cmake::GetCMakeFilesDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
209
  copyDest += "/";
210
  copyDest += cmSystemTools::GetFilenameWithoutExtension(this->OutputFile);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
211 212
  copyDest += "-";
  copyDest += this->RunResultVariable;
Stephen Kelly's avatar
Stephen Kelly committed
213
  copyDest += cmSystemTools::GetFilenameExtension(this->OutputFile);
214
  cmSystemTools::CopyFileAlways(this->OutputFile, copyDest);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
215

216
  std::string resultFileName = this->Makefile->GetHomeOutputDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
217 218 219 220
  resultFileName += "/TryRunResults.cmake";

  std::string detailsString = "For details see ";
  detailsString += resultFileName;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
221

222 223
  std::string internalRunOutputName =
    this->RunResultVariable + "__TRYRUN_OUTPUT";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
224 225
  bool error = false;

Daniel Pfeifer's avatar
Daniel Pfeifer committed
226
  if (this->Makefile->GetDefinition(this->RunResultVariable) == CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
227 228 229
    // if the variables doesn't exist, create it with a helpful error text
    // and mark it as advanced
    std::string comment;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
230 231 232
    comment += "Run result of TRY_RUN(), indicates whether the executable "
               "would have been able to run on its target platform.\n";
    comment += detailsString;
Stephen Kelly's avatar
Stephen Kelly committed
233
    this->Makefile->AddCacheDefinition(this->RunResultVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
234
                                       "PLEASE_FILL_OUT-FAILED_TO_RUN",
235
                                       comment.c_str(), cmStateEnums::STRING);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
236

Stephen Kelly's avatar
Stephen Kelly committed
237
    cmState* state = this->Makefile->GetState();
238 239 240
    const char* existingValue =
      state->GetCacheEntryValue(this->RunResultVariable);
    if (existingValue) {
Stephen Kelly's avatar
Stephen Kelly committed
241
      state->SetCacheEntryProperty(this->RunResultVariable, "ADVANCED", "1");
242
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
243 244

    error = true;
245
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
246

Alexander Neundorf's avatar
 
Alexander Neundorf committed
247
  // is the output from the executable used ?
Daniel Pfeifer's avatar
Daniel Pfeifer committed
248 249
  if (out != CM_NULLPTR) {
    if (this->Makefile->GetDefinition(internalRunOutputName) == CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
250 251 252
      // if the variables doesn't exist, create it with a helpful error text
      // and mark it as advanced
      std::string comment;
253 254 255
      comment +=
        "Output of TRY_RUN(), contains the text, which the executable "
        "would have printed on stdout and stderr on its target platform.\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
256
      comment += detailsString;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
257

258 259 260
      this->Makefile->AddCacheDefinition(
        internalRunOutputName, "PLEASE_FILL_OUT-NOTFOUND", comment.c_str(),
        cmStateEnums::STRING);
Stephen Kelly's avatar
Stephen Kelly committed
261
      cmState* state = this->Makefile->GetState();
262 263 264 265
      const char* existing = state->GetCacheEntryValue(internalRunOutputName);
      if (existing) {
        state->SetCacheEntryProperty(internalRunOutputName, "ADVANCED", "1");
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
266 267 268

      error = true;
    }
269
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
270

271
  if (error) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
272
    static bool firstTryRun = true;
273
    cmsys::ofstream file(resultFileName.c_str(),
274 275 276
                         firstTryRun ? std::ios::out : std::ios::app);
    if (file) {
      if (firstTryRun) {
277
        /* clang-format off */
Alexander Neundorf's avatar
 
Alexander Neundorf committed
278 279 280 281 282 283 284
        file << "# This file was generated by CMake because it detected "
                "TRY_RUN() commands\n"
                "# in crosscompiling mode. It will be overwritten by the next "
                "CMake run.\n"
                "# Copy it to a safe location, set the variables to "
                "appropriate values\n"
                "# and use it then to preset the CMake cache (using -C).\n\n";
285
        /* clang-format on */
286
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
287

288
      std::string comment = "\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
289
      comment += this->RunResultVariable;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
290
      comment += "\n   indicates whether the executable would have been able "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
291 292
                 "to run on its\n"
                 "   target platform. If so, set ";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
293 294
      comment += this->RunResultVariable;
      comment += " to\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
295
                 "   the exit code (in many cases 0 for success), otherwise "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
296
                 "enter \"FAILED_TO_RUN\".\n";
Daniel Pfeifer's avatar
Daniel Pfeifer committed
297
      if (out != CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
298
        comment += internalRunOutputName;
299 300 301 302
        comment +=
          "\n   contains the text the executable "
          "would have printed on stdout and stderr.\n"
          "   If the executable would not have been able to run, set ";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
303 304
        comment += internalRunOutputName;
        comment += " empty.\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
305
                   "   Otherwise check if the output is evaluated by the "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
306
                   "calling CMake code. If so,\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
307 308
                   "   check what the source file would have printed when "
                   "called with the given arguments.\n";
309
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
310 311 312 313 314 315 316 317 318 319
      comment += "The ";
      comment += this->CompileResultVariable;
      comment += " variable holds the build result for this TRY_RUN().\n\n"
                 "Source file   : ";
      comment += srcFile + "\n";
      comment += "Executable    : ";
      comment += copyDest + "\n";
      comment += "Run arguments : ";
      comment += runArgs;
      comment += "\n";
320
      comment += "   Called from: " + this->Makefile->FormatListFileStack();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
321 322 323
      cmsys::SystemTools::ReplaceString(comment, "\n", "\n# ");
      file << comment << "\n\n";

324
      file << "set( " << this->RunResultVariable << " \n     \""
Stephen Kelly's avatar
Stephen Kelly committed
325
           << this->Makefile->GetDefinition(this->RunResultVariable)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
326
           << "\"\n     CACHE STRING \"Result from TRY_RUN\" FORCE)\n\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
327

Daniel Pfeifer's avatar
Daniel Pfeifer committed
328
      if (out != CM_NULLPTR) {
329
        file << "set( " << internalRunOutputName << " \n     \""
Stephen Kelly's avatar
Stephen Kelly committed
330
             << this->Makefile->GetDefinition(internalRunOutputName)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
331
             << "\"\n     CACHE STRING \"Output from TRY_RUN\" FORCE)\n\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
332
      }
333 334
      file.close();
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
335
    firstTryRun = false;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
336

Alexander Neundorf's avatar
 
Alexander Neundorf committed
337 338
    std::string errorMessage = "TRY_RUN() invoked in cross-compiling mode, "
                               "please set the following cache variables "
339
                               "appropriately:\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
340
    errorMessage += "   " + this->RunResultVariable + " (advanced)\n";
Daniel Pfeifer's avatar
Daniel Pfeifer committed
341
    if (out != CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
342
      errorMessage += "   " + internalRunOutputName + " (advanced)\n";
343
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
344
    errorMessage += detailsString;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
345 346
    cmSystemTools::Error(errorMessage.c_str());
    return;
347
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
348

Daniel Pfeifer's avatar
Daniel Pfeifer committed
349
  if (out != CM_NULLPTR) {
Stephen Kelly's avatar
Stephen Kelly committed
350
    (*out) = this->Makefile->GetDefinition(internalRunOutputName);
351
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
352
}