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

5
#include "cmsys/FStream.hxx"
6 7
#include <stdio.h>

8
#include "cmDuration.h"
9
#include "cmMakefile.h"
10
#include "cmMessageType.h"
11
#include "cmRange.h"
12
#include "cmState.h"
13
#include "cmStateTypes.h"
14
#include "cmSystemTools.h"
15
#include "cmake.h"
16

17
class cmExecutionStatus;
Ken Martin's avatar
Ken Martin committed
18

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

27 28 29
  if (this->Makefile->GetCMakeInstance()->GetWorkingMode() ==
      cmake::FIND_PACKAGE_MODE) {
    this->Makefile->IssueMessage(
30
      MessageType::FATAL_ERROR,
31
      "The TRY_RUN() command is not supported in --find-package mode.");
32
    return false;
33
  }
34

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

38 39 40 41 42
  this->CompileResultVariable.clear();
  this->RunResultVariable.clear();
  this->OutputVariable.clear();
  this->RunOutputVariable.clear();
  this->CompileOutputVariable.clear();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
43

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

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

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

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

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

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

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

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

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

void cmTryRunCommand::RunExecutable(const std::string& runArgs,
                                    std::string* out)
{
  int retVal = -1;
168 169 170

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

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

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

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

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

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

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

    error = true;
256
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
257

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

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

      error = true;
    }
280
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
281

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

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

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

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

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

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