cmCoreTryCompile.cxx 25.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.  */
Alexander Neundorf's avatar
 
Alexander Neundorf committed
3
#include "cmCoreTryCompile.h"
Brad King's avatar
Brad King committed
4

5
6
7
8
9
10
11
#include <cmConfigure.h>
#include <cmsys/Directory.hxx>
#include <set>
#include <sstream>
#include <stdio.h>
#include <string.h>

12
#include "cmAlgorithms.h"
13
#include "cmExportTryCompileFileGenerator.h"
14
#include "cmGlobalGenerator.h"
15
#include "cmMakefile.h"
16
#include "cmOutputConverter.h"
17
#include "cmPolicies.h"
18
#include "cmState.h"
19
20
21
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmVersion.h"
22
#include "cmake.h"
23

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static std::string const kCMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN =
  "CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN";
static std::string const kCMAKE_C_COMPILER_TARGET = "CMAKE_C_COMPILER_TARGET";
static std::string const kCMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN =
  "CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN";
static std::string const kCMAKE_CXX_COMPILER_TARGET =
  "CMAKE_CXX_COMPILER_TARGET";
static std::string const kCMAKE_ENABLE_EXPORTS = "CMAKE_ENABLE_EXPORTS";
static std::string const kCMAKE_LINK_SEARCH_END_STATIC =
  "CMAKE_LINK_SEARCH_END_STATIC";
static std::string const kCMAKE_LINK_SEARCH_START_STATIC =
  "CMAKE_LINK_SEARCH_START_STATIC";
static std::string const kCMAKE_OSX_ARCHITECTURES = "CMAKE_OSX_ARCHITECTURES";
static std::string const kCMAKE_OSX_DEPLOYMENT_TARGET =
  "CMAKE_OSX_DEPLOYMENT_TARGET";
static std::string const kCMAKE_OSX_SYSROOT = "CMAKE_OSX_SYSROOT";
static std::string const kCMAKE_POSITION_INDEPENDENT_CODE =
  "CMAKE_POSITION_INDEPENDENT_CODE";
static std::string const kCMAKE_SYSROOT = "CMAKE_SYSROOT";
static std::string const kCMAKE_TRY_COMPILE_OSX_ARCHITECTURES =
  "CMAKE_TRY_COMPILE_OSX_ARCHITECTURES";
45
46
static std::string const kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES =
  "CMAKE_TRY_COMPILE_PLATFORM_VARIABLES";
47

48
49
int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
                                     bool isTryRun)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
50
{
51
  this->BinaryDirectory = argv[1];
Alexander Neundorf's avatar
 
Alexander Neundorf committed
52
53
  this->OutputFile = "";
  // which signature were we called with ?
54
  this->SrcFileSignature = true;
55

56
  cmStateEnums::TargetType targetType = cmStateEnums::EXECUTABLE;
57
58
  const char* tt =
    this->Makefile->GetDefinition("CMAKE_TRY_COMPILE_TARGET_TYPE");
59
  if (!isTryRun && tt && *tt) {
60
61
62
    if (strcmp(tt, cmState::GetTargetTypeName(cmStateEnums::EXECUTABLE)) ==
        0) {
      targetType = cmStateEnums::EXECUTABLE;
63
    } else if (strcmp(tt, cmState::GetTargetTypeName(
64
65
                            cmStateEnums::STATIC_LIBRARY)) == 0) {
      targetType = cmStateEnums::STATIC_LIBRARY;
66
    } else {
67
      this->Makefile->IssueMessage(
68
69
70
71
        cmake::FATAL_ERROR, std::string("Invalid value '") + tt +
          "' for "
          "CMAKE_TRY_COMPILE_TARGET_TYPE.  Only "
          "'" +
72
73
74
          cmState::GetTargetTypeName(cmStateEnums::EXECUTABLE) + "' and "
                                                                 "'" +
          cmState::GetTargetTypeName(cmStateEnums::STATIC_LIBRARY) +
75
76
          "' "
          "are allowed.");
77
78
      return -1;
    }
79
  }
80

Alexander Neundorf's avatar
 
Alexander Neundorf committed
81
  const char* sourceDirectory = argv[2].c_str();
Daniel Pfeifer's avatar
Daniel Pfeifer committed
82
  const char* projectName = CM_NULLPTR;
83
  std::string targetName;
84
  std::vector<std::string> cmakeFlags(1, "CMAKE_FLAGS"); // fake argv[0]
85
  std::vector<std::string> compileDefs;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
86
  std::string outputVariable;
87
  std::string copyFile;
88
  std::string copyFileError;
89
  std::vector<std::string> targets;
90
91
92
93
94
  std::string libsToLink = " ";
  bool useOldLinkLibs = true;
  char targetNameBuf[64];
  bool didOutputVariable = false;
  bool didCopyFile = false;
95
  bool didCopyFileError = false;
96
97
  bool useSources = argv[2] == "SOURCES";
  std::vector<std::string> sources;
98

99
100
101
102
103
104
105
106
107
108
109
110
111
112
  enum Doing
  {
    DoingNone,
    DoingCMakeFlags,
    DoingCompileDefinitions,
    DoingLinkLibraries,
    DoingOutputVariable,
    DoingCopyFile,
    DoingCopyFileError,
    DoingSources
  };
  Doing doing = useSources ? DoingSources : DoingNone;
  for (size_t i = 3; i < argv.size(); ++i) {
    if (argv[i] == "CMAKE_FLAGS") {
113
      doing = DoingCMakeFlags;
114
    } else if (argv[i] == "COMPILE_DEFINITIONS") {
115
      doing = DoingCompileDefinitions;
116
    } else if (argv[i] == "LINK_LIBRARIES") {
117
      doing = DoingLinkLibraries;
118
      useOldLinkLibs = false;
119
    } else if (argv[i] == "OUTPUT_VARIABLE") {
120
121
      doing = DoingOutputVariable;
      didOutputVariable = true;
122
    } else if (argv[i] == "COPY_FILE") {
123
124
      doing = DoingCopyFile;
      didCopyFile = true;
125
    } else if (argv[i] == "COPY_FILE_ERROR") {
126
127
      doing = DoingCopyFileError;
      didCopyFileError = true;
128
    } else if (doing == DoingCMakeFlags) {
129
      cmakeFlags.push_back(argv[i]);
130
    } else if (doing == DoingCompileDefinitions) {
131
      compileDefs.push_back(argv[i]);
132
    } else if (doing == DoingLinkLibraries) {
133
      libsToLink += "\"" + cmSystemTools::TrimWhitespace(argv[i]) + "\" ";
134
135
      if (cmTarget* tgt = this->Makefile->FindTargetToUse(argv[i])) {
        switch (tgt->GetType()) {
136
137
138
139
          case cmStateEnums::SHARED_LIBRARY:
          case cmStateEnums::STATIC_LIBRARY:
          case cmStateEnums::INTERFACE_LIBRARY:
          case cmStateEnums::UNKNOWN_LIBRARY:
140
            break;
141
          case cmStateEnums::EXECUTABLE:
142
            if (tgt->IsExecutableWithExports()) {
143
              break;
144
            }
145
          default:
146
147
            this->Makefile->IssueMessage(
              cmake::FATAL_ERROR,
148
              "Only libraries may be used as try_compile or try_run IMPORTED "
149
150
151
152
              "LINK_LIBRARIES.  Got " +
                std::string(tgt->GetName()) + " of "
                                              "type " +
                cmState::GetTargetTypeName(tgt->GetType()) + ".");
153
            return -1;
154
155
        }
        if (tgt->IsImported()) {
156
          targets.push_back(argv[i]);
157
        }
158
      }
159
    } else if (doing == DoingOutputVariable) {
160
      outputVariable = argv[i];
161
      doing = DoingNone;
162
    } else if (doing == DoingCopyFile) {
163
      copyFile = argv[i];
164
      doing = DoingNone;
165
    } else if (doing == DoingCopyFileError) {
166
      copyFileError = argv[i];
167
      doing = DoingNone;
168
    } else if (doing == DoingSources) {
169
      sources.push_back(argv[i]);
170
    } else if (i == 3) {
171
172
      this->SrcFileSignature = false;
      projectName = argv[i].c_str();
173
    } else if (i == 4 && !this->SrcFileSignature) {
174
      targetName = argv[i];
175
    } else {
176
      std::ostringstream m;
177
178
      m << "try_compile given unknown argument \"" << argv[i] << "\".";
      this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, m.str());
179
    }
180
  }
181

182
  if (didCopyFile && copyFile.empty()) {
183
    this->Makefile->IssueMessage(cmake::FATAL_ERROR,
184
                                 "COPY_FILE must be followed by a file path");
185
    return -1;
186
  }
187

188
189
190
  if (didCopyFileError && copyFileError.empty()) {
    this->Makefile->IssueMessage(
      cmake::FATAL_ERROR,
191
192
      "COPY_FILE_ERROR must be followed by a variable name");
    return -1;
193
  }
194

195
196
197
  if (didCopyFileError && !didCopyFile) {
    this->Makefile->IssueMessage(
      cmake::FATAL_ERROR, "COPY_FILE_ERROR may be used only with COPY_FILE");
198
    return -1;
199
  }
200

201
202
203
  if (didOutputVariable && outputVariable.empty()) {
    this->Makefile->IssueMessage(
      cmake::FATAL_ERROR,
204
205
      "OUTPUT_VARIABLE must be followed by a variable name");
    return -1;
206
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
207

208
209
210
  if (useSources && sources.empty()) {
    this->Makefile->IssueMessage(
      cmake::FATAL_ERROR,
211
212
      "SOURCES must be followed by at least one source file");
    return -1;
213
  }
214

Alexander Neundorf's avatar
 
Alexander Neundorf committed
215
216
  // compute the binary dir when TRY_COMPILE is called with a src file
  // signature
217
  if (this->SrcFileSignature) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
218
219
    this->BinaryDirectory += cmake::GetCMakeFilesDirectory();
    this->BinaryDirectory += "/CMakeTmp";
220
  } else {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
221
    // only valid for srcfile signatures
222
223
224
    if (!compileDefs.empty()) {
      this->Makefile->IssueMessage(
        cmake::FATAL_ERROR,
225
        "COMPILE_DEFINITIONS specified on a srcdir type TRY_COMPILE");
Alexander Neundorf's avatar
 
Alexander Neundorf committed
226
      return -1;
227
228
229
230
    }
    if (!copyFile.empty()) {
      this->Makefile->IssueMessage(
        cmake::FATAL_ERROR,
231
        "COPY_FILE specified on a srcdir type TRY_COMPILE");
Alexander Neundorf's avatar
 
Alexander Neundorf committed
232
      return -1;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
233
    }
234
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
235
236
  // make sure the binary directory exists
  cmSystemTools::MakeDirectory(this->BinaryDirectory.c_str());
237

Alexander Neundorf's avatar
 
Alexander Neundorf committed
238
  // do not allow recursive try Compiles
239
  if (this->BinaryDirectory == this->Makefile->GetHomeOutputDirectory()) {
240
    std::ostringstream e;
241
242
243
    e << "Attempt at a recursive or nested TRY_COMPILE in directory\n"
      << "  " << this->BinaryDirectory << "\n";
    this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
Alexander Neundorf's avatar
 
Alexander Neundorf committed
244
    return -1;
245
  }
246

Alexander Neundorf's avatar
 
Alexander Neundorf committed
247
248
  std::string outFileName = this->BinaryDirectory + "/CMakeLists.txt";
  // which signature are we using? If we are using var srcfile bindir
249
  if (this->SrcFileSignature) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
250
251
    // remove any CMakeCache.txt files so we will have a clean test
    std::string ccFile = this->BinaryDirectory + "/CMakeCache.txt";
252
    cmSystemTools::RemoveFile(ccFile);
253

254
    // Choose sources.
255
    if (!useSources) {
256
      sources.push_back(argv[2]);
257
    }
258
259

    // Detect languages to enable.
260
    cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
261
    std::set<std::string> testLangs;
262
263
    for (std::vector<std::string>::iterator si = sources.begin();
         si != sources.end(); ++si) {
264
      std::string ext = cmSystemTools::GetFilenameLastExtension(*si);
265
      std::string lang = gg->GetLanguageFromExtension(ext.c_str());
266
      if (!lang.empty()) {
267
        testLangs.insert(lang);
268
      } else {
269
        std::ostringstream err;
270
271
272
        err << "Unknown extension \"" << ext << "\" for file\n"
            << "  " << *si << "\n"
            << "try_compile() works only for enabled languages.  "
Stephen Kelly's avatar
Stephen Kelly committed
273
            << "Currently these are:\n  ";
274
275
        std::vector<std::string> langs;
        gg->GetEnabledLanguages(langs);
Stephen Kelly's avatar
Stephen Kelly committed
276
        err << cmJoin(langs, " ");
277
278
279
280
        err << "\nSee project() command to enable other languages.";
        this->Makefile->IssueMessage(cmake::FATAL_ERROR, err.str());
        return -1;
      }
281
    }
282

283
284
285
    std::string const tcConfig =
      this->Makefile->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");

286
    // we need to create a directory and CMakeLists file etc...
Alexander Neundorf's avatar
 
Alexander Neundorf committed
287
288
289
    // first create the directories
    sourceDirectory = this->BinaryDirectory.c_str();

290
    // now create a CMakeLists.txt file in that directory
291
292
    FILE* fout = cmsys::SystemTools::Fopen(outFileName, "w");
    if (!fout) {
293
      std::ostringstream e;
294
      /* clang-format off */
295
      e << "Failed to open\n"
296
        << "  " << outFileName << "\n"
297
        << cmSystemTools::GetLastSystemError();
298
      /* clang-format on */
299
      this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
Alexander Neundorf's avatar
 
Alexander Neundorf committed
300
      return -1;
301
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
302
303

    const char* def = this->Makefile->GetDefinition("CMAKE_MODULE_PATH");
304
305
306
    fprintf(fout, "cmake_minimum_required(VERSION %u.%u.%u.%u)\n",
            cmVersion::GetMajorVersion(), cmVersion::GetMinorVersion(),
            cmVersion::GetPatchVersion(), cmVersion::GetTweakVersion());
307
    if (def) {
308
      fprintf(fout, "set(CMAKE_MODULE_PATH \"%s\")\n", def);
309
    }
310

311
    std::string projectLangs;
312
313
    for (std::set<std::string>::iterator li = testLangs.begin();
         li != testLangs.end(); ++li) {
314
315
316
      projectLangs += " " + *li;
      std::string rulesOverrideBase = "CMAKE_USER_MAKE_RULES_OVERRIDE";
      std::string rulesOverrideLang = rulesOverrideBase + "_" + *li;
317
318
319
320
321
322
323
324
      if (const char* rulesOverridePath =
            this->Makefile->GetDefinition(rulesOverrideLang)) {
        fprintf(fout, "set(%s \"%s\")\n", rulesOverrideLang.c_str(),
                rulesOverridePath);
      } else if (const char* rulesOverridePath2 =
                   this->Makefile->GetDefinition(rulesOverrideBase)) {
        fprintf(fout, "set(%s \"%s\")\n", rulesOverrideBase.c_str(),
                rulesOverridePath2);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
325
      }
326
    }
327
328
    fprintf(fout, "project(CMAKE_TRY_COMPILE%s)\n", projectLangs.c_str());
    fprintf(fout, "set(CMAKE_VERBOSE_MAKEFILE 1)\n");
329
330
    for (std::set<std::string>::iterator li = testLangs.begin();
         li != testLangs.end(); ++li) {
331
      std::string langFlags = "CMAKE_" + *li + "_FLAGS";
Stephen Kelly's avatar
Stephen Kelly committed
332
      const char* flags = this->Makefile->GetDefinition(langFlags);
333
      fprintf(fout, "set(CMAKE_%s_FLAGS %s)\n", li->c_str(),
334
              cmOutputConverter::EscapeForCMake(flags ? flags : "").c_str());
335
      fprintf(fout, "set(CMAKE_%s_FLAGS \"${CMAKE_%s_FLAGS}"
336
337
338
                    " ${COMPILE_DEFINITIONS}\")\n",
              li->c_str(), li->c_str());
    }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
    switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0066)) {
      case cmPolicies::WARN:
        if (this->Makefile->PolicyOptionalWarningEnabled(
              "CMAKE_POLICY_WARNING_CMP0066")) {
          std::ostringstream w;
          /* clang-format off */
          w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0066) << "\n"
            "For compatibility with older versions of CMake, try_compile "
            "is not honoring caller config-specific compiler flags "
            "(e.g. CMAKE_C_FLAGS_DEBUG) in the test project."
            ;
          /* clang-format on */
          this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, w.str());
        }
      case cmPolicies::OLD:
        // OLD behavior is to do nothing.
        break;
      case cmPolicies::REQUIRED_IF_USED:
      case cmPolicies::REQUIRED_ALWAYS:
        this->Makefile->IssueMessage(
          cmake::FATAL_ERROR,
          cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0066));
      case cmPolicies::NEW: {
        // NEW behavior is to pass config-specific compiler flags.
        static std::string const cfgDefault = "DEBUG";
        std::string const cfg =
          !tcConfig.empty() ? cmSystemTools::UpperCase(tcConfig) : cfgDefault;
        for (std::set<std::string>::iterator li = testLangs.begin();
             li != testLangs.end(); ++li) {
          std::string const langFlagsCfg = "CMAKE_" + *li + "_FLAGS_" + cfg;
          const char* flagsCfg = this->Makefile->GetDefinition(langFlagsCfg);
          fprintf(fout, "set(%s %s)\n", langFlagsCfg.c_str(),
                  cmOutputConverter::EscapeForCMake(flagsCfg ? flagsCfg : "")
                    .c_str());
        }
      } break;
    }
376
    switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0056)) {
377
      case cmPolicies::WARN:
378
379
        if (this->Makefile->PolicyOptionalWarningEnabled(
              "CMAKE_POLICY_WARNING_CMP0056")) {
380
          std::ostringstream w;
381
          /* clang-format off */
Stephen Kelly's avatar
Stephen Kelly committed
382
          w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0056) << "\n"
383
384
385
386
            "For compatibility with older versions of CMake, try_compile "
            "is not honoring caller link flags (e.g. CMAKE_EXE_LINKER_FLAGS) "
            "in the test project."
            ;
387
          /* clang-format on */
388
          this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, w.str());
389
        }
390
391
392
393
394
395
396
      case cmPolicies::OLD:
        // OLD behavior is to do nothing.
        break;
      case cmPolicies::REQUIRED_IF_USED:
      case cmPolicies::REQUIRED_ALWAYS:
        this->Makefile->IssueMessage(
          cmake::FATAL_ERROR,
397
          cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0056));
398
399
400
      case cmPolicies::NEW:
        // NEW behavior is to pass linker flags.
        {
401
402
403
404
405
406
407
408
409
          const char* exeLinkFlags =
            this->Makefile->GetDefinition("CMAKE_EXE_LINKER_FLAGS");
          fprintf(
            fout, "set(CMAKE_EXE_LINKER_FLAGS %s)\n",
            cmOutputConverter::EscapeForCMake(exeLinkFlags ? exeLinkFlags : "")
              .c_str());
        }
        break;
    }
410
    fprintf(fout, "set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS}"
411
                  " ${EXE_LINKER_FLAGS}\")\n");
412
413
414
    fprintf(fout, "include_directories(${INCLUDE_DIRECTORIES})\n");
    fprintf(fout, "set(CMAKE_SUPPRESS_REGENERATION 1)\n");
    fprintf(fout, "link_directories(${LINK_DIRECTORIES})\n");
Alexander Neundorf's avatar
 
Alexander Neundorf committed
415
    // handle any compile flags we need to pass on
416
    if (!compileDefs.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
417
      fprintf(fout, "add_definitions(%s)\n", cmJoin(compileDefs, " ").c_str());
418
    }
419

420
421
    /* Use a random file name to avoid rapid creation and deletion
       of the same executable name (some filesystems fail on that).  */
422
    sprintf(targetNameBuf, "cmTC_%05x", cmSystemTools::RandomSeed() & 0xFFFFF);
423
424
    targetName = targetNameBuf;

425
    if (!targets.empty()) {
426
      std::string fname = "/" + std::string(targetName) + "Targets.cmake";
427
      cmExportTryCompileFileGenerator tcfg(gg, targets, this->Makefile);
428
      tcfg.SetExportFile((this->BinaryDirectory + fname).c_str());
429
      tcfg.SetConfig(tcConfig);
430

431
      if (!tcfg.GenerateImportFile()) {
432
433
        this->Makefile->IssueMessage(cmake::FATAL_ERROR,
                                     "could not write export file.");
434
        fclose(fout);
435
436
        return -1;
      }
437
438
439
      fprintf(fout, "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n\n",
              fname.c_str());
    }
440

441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
    // Forward a set of variables to the inner project cache.
    {
      std::set<std::string> vars;
      vars.insert(kCMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN);
      vars.insert(kCMAKE_C_COMPILER_TARGET);
      vars.insert(kCMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN);
      vars.insert(kCMAKE_CXX_COMPILER_TARGET);
      vars.insert(kCMAKE_ENABLE_EXPORTS);
      vars.insert(kCMAKE_LINK_SEARCH_END_STATIC);
      vars.insert(kCMAKE_LINK_SEARCH_START_STATIC);
      vars.insert(kCMAKE_OSX_ARCHITECTURES);
      vars.insert(kCMAKE_OSX_DEPLOYMENT_TARGET);
      vars.insert(kCMAKE_OSX_SYSROOT);
      vars.insert(kCMAKE_POSITION_INDEPENDENT_CODE);
      vars.insert(kCMAKE_SYSROOT);

457
458
459
460
461
462
463
      if (const char* varListStr = this->Makefile->GetDefinition(
            kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES)) {
        std::vector<std::string> varList;
        cmSystemTools::ExpandListArgument(varListStr, varList);
        vars.insert(varList.begin(), varList.end());
      }

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
      /* for the TRY_COMPILEs we want to be able to specify the architecture.
         So the user can set CMAKE_OSX_ARCHITECTURES to i386;ppc and then set
         CMAKE_TRY_COMPILE_OSX_ARCHITECTURES first to i386 and then to ppc to
         have the tests run for each specific architecture. Since
         cmLocalGenerator doesn't allow building for "the other"
         architecture only via CMAKE_OSX_ARCHITECTURES.
         */
      if (const char* tcArchs = this->Makefile->GetDefinition(
            kCMAKE_TRY_COMPILE_OSX_ARCHITECTURES)) {
        vars.erase(kCMAKE_OSX_ARCHITECTURES);
        std::string flag = "-DCMAKE_OSX_ARCHITECTURES=" + std::string(tcArchs);
        cmakeFlags.push_back(flag);
      }

      for (std::set<std::string>::iterator vi = vars.begin(); vi != vars.end();
           ++vi) {
        std::string const& var = *vi;
        if (const char* val = this->Makefile->GetDefinition(var)) {
          std::string flag = "-D" + var + "=" + val;
          cmakeFlags.push_back(flag);
        }
      }
486
    }
487

488
489
    /* Set the appropriate policy information for ENABLE_EXPORTS */
    fprintf(fout, "cmake_policy(SET CMP0065 %s)\n",
490
491
492
493
            this->Makefile->GetPolicyStatus(cmPolicies::CMP0065) ==
                cmPolicies::NEW
              ? "NEW"
              : "OLD");
494

495
    if (targetType == cmStateEnums::EXECUTABLE) {
496
497
498
499
500
      /* Put the executable at a known location (for COPY_FILE).  */
      fprintf(fout, "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"%s\")\n",
              this->BinaryDirectory.c_str());
      /* Create the actual executable.  */
      fprintf(fout, "add_executable(%s", targetName.c_str());
501
    } else // if (targetType == cmStateEnums::STATIC_LIBRARY)
502
    {
503
504
505
506
507
      /* Put the static library at a known location (for COPY_FILE).  */
      fprintf(fout, "set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"%s\")\n",
              this->BinaryDirectory.c_str());
      /* Create the actual static library.  */
      fprintf(fout, "add_library(%s STATIC", targetName.c_str());
508
509
510
    }
    for (std::vector<std::string>::iterator si = sources.begin();
         si != sources.end(); ++si) {
511
512
513
      fprintf(fout, " \"%s\"", si->c_str());

      // Add dependencies on any non-temporary sources.
514
      if (si->find("CMakeTmp") == si->npos) {
515
        this->Makefile->AddCMakeDependFile(*si);
516
      }
517
    }
518
    fprintf(fout, ")\n");
519
520
    if (useOldLinkLibs) {
      fprintf(fout, "target_link_libraries(%s ${LINK_LIBRARIES})\n",
521
              targetName.c_str());
522
523
    } else {
      fprintf(fout, "target_link_libraries(%s %s)\n", targetName.c_str(),
524
              libsToLink.c_str());
525
    }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
526
527
    fclose(fout);
    projectName = "CMAKE_TRY_COMPILE";
528
  }
529

Alexander Neundorf's avatar
 
Alexander Neundorf committed
530
531
532
533
  bool erroroc = cmSystemTools::GetErrorOccuredFlag();
  cmSystemTools::ResetErrorOccuredFlag();
  std::string output;
  // actually do the try compile now that everything is setup
534
535
536
537
  int res = this->Makefile->TryCompile(
    sourceDirectory, this->BinaryDirectory, projectName, targetName,
    this->SrcFileSignature, &cmakeFlags, output);
  if (erroroc) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
538
    cmSystemTools::SetErrorOccured();
539
  }
540

Alexander Neundorf's avatar
 
Alexander Neundorf committed
541
  // set the result var to the return value to indicate success or failure
542
  this->Makefile->AddCacheDefinition(argv[0], (res == 0 ? "TRUE" : "FALSE"),
Alexander Neundorf's avatar
 
Alexander Neundorf committed
543
                                     "Result of TRY_COMPILE",
544
                                     cmStateEnums::INTERNAL);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
545

546
  if (!outputVariable.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
547
    this->Makefile->AddDefinition(outputVariable, output.c_str());
548
  }
549

550
  if (this->SrcFileSignature) {
551
    std::string copyFileErrorMessage;
552
    this->FindOutputFile(targetName, targetType);
553

554
555
556
    if ((res == 0) && !copyFile.empty()) {
      if (this->OutputFile.empty() ||
          !cmSystemTools::CopyFileAlways(this->OutputFile, copyFile)) {
557
        std::ostringstream emsg;
558
        /* clang-format off */
559
        emsg << "Cannot copy output executable\n"
560
             << "  '" << this->OutputFile << "'\n"
561
             << "to destination specified by COPY_FILE:\n"
562
             << "  '" << copyFile << "'\n";
563
        /* clang-format on */
564
        if (!this->FindErrorMessage.empty()) {
565
          emsg << this->FindErrorMessage.c_str();
566
567
        }
        if (copyFileError.empty()) {
568
569
          this->Makefile->IssueMessage(cmake::FATAL_ERROR, emsg.str());
          return -1;
Alexander Neundorf's avatar
 
Alexander Neundorf committed
570
        }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
571
        copyFileErrorMessage = emsg.str();
Alexander Neundorf's avatar
 
Alexander Neundorf committed
572
      }
573
    }
574

575
    if (!copyFileError.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
576
      this->Makefile->AddDefinition(copyFileError,
577
                                    copyFileErrorMessage.c_str());
Alexander Neundorf's avatar
 
Alexander Neundorf committed
578
    }
579
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
580
581
582
583
584
  return res;
}

void cmCoreTryCompile::CleanupFiles(const char* binDir)
{
585
  if (!binDir) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
586
    return;
587
  }
588

Alexander Neundorf's avatar
 
Alexander Neundorf committed
589
  std::string bdir = binDir;
590
  if (bdir.find("CMakeTmp") == std::string::npos) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
591
592
    cmSystemTools::Error(
      "TRY_COMPILE attempt to remove -rf directory that does not contain "
593
594
      "CMakeTmp:",
      binDir);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
595
    return;
596
  }
597

Alexander Neundorf's avatar
 
Alexander Neundorf committed
598
599
600
  cmsys::Directory dir;
  dir.Load(binDir);
  size_t fileNum;
601
  std::set<std::string> deletedFiles;
602
603
604
  for (fileNum = 0; fileNum < dir.GetNumberOfFiles(); ++fileNum) {
    if (strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)), ".") &&
        strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)), "..")) {
605

606
607
      if (deletedFiles.find(dir.GetFile(
            static_cast<unsigned long>(fileNum))) == deletedFiles.end()) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
608
609
610
611
        deletedFiles.insert(dir.GetFile(static_cast<unsigned long>(fileNum)));
        std::string fullPath = binDir;
        fullPath += "/";
        fullPath += dir.GetFile(static_cast<unsigned long>(fileNum));
612
        if (cmSystemTools::FileIsDirectory(fullPath)) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
613
          this->CleanupFiles(fullPath.c_str());
614
          cmSystemTools::RemoveADirectory(fullPath);
615
        } else {
616
#ifdef _WIN32
617
618
          // Sometimes anti-virus software hangs on to new files so we
          // cannot delete them immediately.  Try a few times.
619
620
          cmSystemTools::WindowsFileRetry retry =
            cmSystemTools::GetWindowsFileRetry();
621
622
623
          while (!cmSystemTools::RemoveFile(fullPath.c_str()) &&
                 --retry.Count &&
                 cmSystemTools::FileExists(fullPath.c_str())) {
624
            cmSystemTools::Delay(retry.Delay);
625
626
          }
          if (retry.Count == 0)
627
#else
628
          if (!cmSystemTools::RemoveFile(fullPath))
629
#endif
630
          {
631
632
            std::string m = "Remove failed on file: " + fullPath;
            cmSystemTools::ReportLastSystemError(m.c_str());
Alexander Neundorf's avatar
 
Alexander Neundorf committed
633
634
635
636
          }
        }
      }
    }
637
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
638
639
}

640
void cmCoreTryCompile::FindOutputFile(const std::string& targetName,
641
                                      cmStateEnums::TargetType targetType)
Alexander Neundorf's avatar
 
Alexander Neundorf committed
642
643
644
645
{
  this->FindErrorMessage = "";
  this->OutputFile = "";
  std::string tmpOutputFile = "/";
646
  if (targetType == cmStateEnums::EXECUTABLE) {
647
648
649
    tmpOutputFile += targetName;
    tmpOutputFile +=
      this->Makefile->GetSafeDefinition("CMAKE_EXECUTABLE_SUFFIX");
650
  } else // if (targetType == cmStateEnums::STATIC_LIBRARY)
651
  {
652
653
654
655
656
    tmpOutputFile +=
      this->Makefile->GetSafeDefinition("CMAKE_STATIC_LIBRARY_PREFIX");
    tmpOutputFile += targetName;
    tmpOutputFile +=
      this->Makefile->GetSafeDefinition("CMAKE_STATIC_LIBRARY_SUFFIX");
657
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
658
659
660
661
662
663

  // a list of directories where to search for the compilation result
  // at first directly in the binary dir
  std::vector<std::string> searchDirs;
  searchDirs.push_back("");

664
665
  const char* config =
    this->Makefile->GetDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");
Alexander Neundorf's avatar
 
Alexander Neundorf committed
666
  // if a config was specified try that first
667
  if (config && config[0]) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
668
669
670
    std::string tmp = "/";
    tmp += config;
    searchDirs.push_back(tmp);
671
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
672
  searchDirs.push_back("/Debug");
673
674
675
676
#if defined(__APPLE__)
  std::string app = "/Debug/" + targetName + ".app";
  searchDirs.push_back(app);
#endif
Alexander Neundorf's avatar
 
Alexander Neundorf committed
677
678
  searchDirs.push_back("/Development");

679
680
  for (std::vector<std::string>::const_iterator it = searchDirs.begin();
       it != searchDirs.end(); ++it) {
Alexander Neundorf's avatar
 
Alexander Neundorf committed
681
682
683
    std::string command = this->BinaryDirectory;
    command += *it;
    command += tmpOutputFile;
684
    if (cmSystemTools::FileExists(command.c_str())) {
685
      this->OutputFile = cmSystemTools::CollapseFullPath(command);
Alexander Neundorf's avatar
 
Alexander Neundorf committed
686
687
      return;
    }
688
  }
Alexander Neundorf's avatar
 
Alexander Neundorf committed
689

690
  std::ostringstream emsg;
691
  emsg << "Unable to find the executable at any of:\n";
692
693
  emsg << cmWrap("  " + this->BinaryDirectory, searchDirs, tmpOutputFile, "\n")
       << "\n";
Alexander Neundorf's avatar
 
Alexander Neundorf committed
694
695
696
  this->FindErrorMessage = emsg.str();
  return;
}