cmTryRunCommand.cxx 12.7 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

5 6 7 8 9
#include <cmsys/FStream.hxx>
#include <stdio.h>
#include <string.h>

#include "cmMakefile.h"
10
#include "cmState.h"
11
#include "cmStateTypes.h"
12
#include "cmSystemTools.h"
13
#include "cmake.h"
14

15
class cmExecutionStatus;
Ken Martin's avatar
Ken Martin committed
16

Alexander Neundorf's avatar
 
Alexander Neundorf committed
17
// cmTryRunCommand
18 19
bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
                                  cmExecutionStatus&)
Ken Martin's avatar
Ken Martin committed
20
{
21
  if (argv.size() < 4) {
Ken Martin's avatar
Ken Martin committed
22
    return false;
23
  }
Ken Martin's avatar
Ken Martin committed
24

25 26 27 28 29
  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.");
30
    return false;
31
  }
32

33
  // build an arg list for TryCompile and extract the runArgs,
Ken Martin's avatar
Ken Martin committed
34
  std::vector<std::string> tryCompile;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
35 36 37 38 39 40 41

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

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

  // although they could be used together, don't allow it, because
  // using OUTPUT_VARIABLE makes crosscompiling harder
89 90
  if (this->OutputVariable.size() && (!this->RunOutputVariable.empty() ||
                                      !this->CompileOutputVariable.empty())) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
91 92 93 94 95
    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;
96
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
97 98

  bool captureRunOutput = false;
99
  if (!this->OutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
100 101 102
    captureRunOutput = true;
    tryCompile.push_back("OUTPUT_VARIABLE");
    tryCompile.push_back(this->OutputVariable);
103 104
  }
  if (!this->CompileOutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
105 106
    tryCompile.push_back("OUTPUT_VARIABLE");
    tryCompile.push_back(this->CompileOutputVariable);
107 108
  }
  if (!this->RunOutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
109
    captureRunOutput = true;
110
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
111 112 113 114

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

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

Ken Martin's avatar
Ken Martin committed
118
  // now try running the command if it compiled
119 120
  if (!res) {
    if (this->OutputFile.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
121
      cmSystemTools::Error(this->FindErrorMessage.c_str());
122
    } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
123 124
      // "run" it and capture the output
      std::string runOutputContents;
125
      if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") &&
126
          !this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
127 128 129
        this->DoNotRunExecutable(runArgs, argv[3], captureRunOutput
                                   ? &runOutputContents
                                   : CM_NULLPTR);
130
      } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
131
        this->RunExecutable(runArgs, &runOutputContents);
132
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
133 134

      // now put the output into the variables
135
      if (!this->RunOutputVariable.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
136
        this->Makefile->AddDefinition(this->RunOutputVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
137
                                      runOutputContents.c_str());
138
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
139

140
      if (!this->OutputVariable.empty()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
141 142
        // if the TryCompileCore saved output in this outputVariable then
        // prepend that output to this output
143 144 145
        const char* compileOutput =
          this->Makefile->GetDefinition(this->OutputVariable);
        if (compileOutput) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
146
          runOutputContents = std::string(compileOutput) + runOutputContents;
147
        }
Stephen Kelly's avatar
Stephen Kelly committed
148
        this->Makefile->AddDefinition(this->OutputVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
149
                                      runOutputContents.c_str());
Ken Martin's avatar
Ken Martin committed
150
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
151
    }
152
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
153

Alexander Neundorf's avatar
 
Alexander Neundorf committed
154
  // if we created a directory etc, then cleanup after ourselves
155
  if (!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
156
    this->CleanupFiles(this->BinaryDirectory.c_str());
157
  }
Ken Martin's avatar
Ken Martin committed
158 159
  return true;
}
Alexander Neundorf's avatar
 
Alexander Neundorf committed
160 161 162 163 164

void cmTryRunCommand::RunExecutable(const std::string& runArgs,
                                    std::string* out)
{
  int retVal = -1;
165 166 167

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

/* This is only used when cross compiling. Instead of running the
 executable, two cache variables are created which will hold the results
207
 the executable would have produced.
Alexander Neundorf's avatar
 
Alexander Neundorf committed
208
*/
209
void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
210 211
                                         const std::string& srcFile,
                                         std::string* out)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
212
{
Alexander Neundorf's avatar
 
Alexander Neundorf committed
213 214 215
  // 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.
216
  std::string copyDest = this->Makefile->GetHomeOutputDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
217
  copyDest += cmake::GetCMakeFilesDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
218
  copyDest += "/";
219
  copyDest += cmSystemTools::GetFilenameWithoutExtension(this->OutputFile);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
220 221
  copyDest += "-";
  copyDest += this->RunResultVariable;
Stephen Kelly's avatar
Stephen Kelly committed
222
  copyDest += cmSystemTools::GetFilenameExtension(this->OutputFile);
223
  cmSystemTools::CopyFileAlways(this->OutputFile, copyDest);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
224

225
  std::string resultFileName = this->Makefile->GetHomeOutputDirectory();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
226 227 228 229
  resultFileName += "/TryRunResults.cmake";

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

231 232
  std::string internalRunOutputName =
    this->RunResultVariable + "__TRYRUN_OUTPUT";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
233 234
  bool error = false;

Daniel Pfeifer's avatar
Daniel Pfeifer committed
235
  if (this->Makefile->GetDefinition(this->RunResultVariable) == CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
236 237 238
    // 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
239 240 241
    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
242
    this->Makefile->AddCacheDefinition(this->RunResultVariable,
Alexander Neundorf's avatar
 
Alexander Neundorf committed
243
                                       "PLEASE_FILL_OUT-FAILED_TO_RUN",
244
                                       comment.c_str(), cmStateEnums::STRING);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
245

Stephen Kelly's avatar
Stephen Kelly committed
246
    cmState* state = this->Makefile->GetState();
247 248 249
    const char* existingValue =
      state->GetCacheEntryValue(this->RunResultVariable);
    if (existingValue) {
Stephen Kelly's avatar
Stephen Kelly committed
250
      state->SetCacheEntryProperty(this->RunResultVariable, "ADVANCED", "1");
251
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
252 253

    error = true;
254
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
255

Alexander Neundorf's avatar
 
Alexander Neundorf committed
256
  // is the output from the executable used ?
Daniel Pfeifer's avatar
Daniel Pfeifer committed
257 258
  if (out != CM_NULLPTR) {
    if (this->Makefile->GetDefinition(internalRunOutputName) == CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
259 260 261
      // if the variables doesn't exist, create it with a helpful error text
      // and mark it as advanced
      std::string comment;
262 263 264
      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
265
      comment += detailsString;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
266

267 268 269
      this->Makefile->AddCacheDefinition(
        internalRunOutputName, "PLEASE_FILL_OUT-NOTFOUND", comment.c_str(),
        cmStateEnums::STRING);
Stephen Kelly's avatar
Stephen Kelly committed
270
      cmState* state = this->Makefile->GetState();
271 272 273 274
      const char* existing = state->GetCacheEntryValue(internalRunOutputName);
      if (existing) {
        state->SetCacheEntryProperty(internalRunOutputName, "ADVANCED", "1");
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
275 276 277

      error = true;
    }
278
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
279

280
  if (error) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
281
    static bool firstTryRun = true;
282
    cmsys::ofstream file(resultFileName.c_str(),
283 284 285
                         firstTryRun ? std::ios::out : std::ios::app);
    if (file) {
      if (firstTryRun) {
286
        /* clang-format off */
Alexander Neundorf's avatar
 
Alexander Neundorf committed
287 288 289 290 291 292 293
        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";
294
        /* clang-format on */
295
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
296

297
      std::string comment = "\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
298
      comment += this->RunResultVariable;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
299
      comment += "\n   indicates whether the executable would have been able "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
300 301
                 "to run on its\n"
                 "   target platform. If so, set ";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
302 303
      comment += this->RunResultVariable;
      comment += " to\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
304
                 "   the exit code (in many cases 0 for success), otherwise "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
305
                 "enter \"FAILED_TO_RUN\".\n";
Daniel Pfeifer's avatar
Daniel Pfeifer committed
306
      if (out != CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
307
        comment += internalRunOutputName;
308 309 310 311
        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
312 313
        comment += internalRunOutputName;
        comment += " empty.\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
314
                   "   Otherwise check if the output is evaluated by the "
Alexander Neundorf's avatar
 
Alexander Neundorf committed
315
                   "calling CMake code. If so,\n"
Alexander Neundorf's avatar
 
Alexander Neundorf committed
316 317
                   "   check what the source file would have printed when "
                   "called with the given arguments.\n";
318
      }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
319 320 321 322 323 324 325 326 327 328
      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";
329
      comment += "   Called from: " + this->Makefile->FormatListFileStack();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
330 331 332
      cmsys::SystemTools::ReplaceString(comment, "\n", "\n# ");
      file << comment << "\n\n";

333
      file << "set( " << this->RunResultVariable << " \n     \""
Stephen Kelly's avatar
Stephen Kelly committed
334
           << this->Makefile->GetDefinition(this->RunResultVariable)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
335
           << "\"\n     CACHE STRING \"Result from TRY_RUN\" FORCE)\n\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
336

Daniel Pfeifer's avatar
Daniel Pfeifer committed
337
      if (out != CM_NULLPTR) {
338
        file << "set( " << internalRunOutputName << " \n     \""
Stephen Kelly's avatar
Stephen Kelly committed
339
             << this->Makefile->GetDefinition(internalRunOutputName)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
340
             << "\"\n     CACHE STRING \"Output from TRY_RUN\" FORCE)\n\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
341
      }
342 343
      file.close();
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
344
    firstTryRun = false;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
345

Alexander Neundorf's avatar
 
Alexander Neundorf committed
346 347
    std::string errorMessage = "TRY_RUN() invoked in cross-compiling mode, "
                               "please set the following cache variables "
348
                               "appropriately:\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
349
    errorMessage += "   " + this->RunResultVariable + " (advanced)\n";
Daniel Pfeifer's avatar
Daniel Pfeifer committed
350
    if (out != CM_NULLPTR) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
351
      errorMessage += "   " + internalRunOutputName + " (advanced)\n";
352
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
353
    errorMessage += detailsString;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
354 355
    cmSystemTools::Error(errorMessage.c_str());
    return;
356
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
357

Daniel Pfeifer's avatar
Daniel Pfeifer committed
358
  if (out != CM_NULLPTR) {
Stephen Kelly's avatar
Stephen Kelly committed
359
    (*out) = this->Makefile->GetDefinition(internalRunOutputName);
360
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
361
}