cmGlobalXCodeGenerator.cxx 128 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.  */
Bill Hoffman's avatar
Bill Hoffman committed
3
#include "cmGlobalXCodeGenerator.h"
Brad King's avatar
Brad King committed
4

5
#include "cmsys/RegularExpression.hxx"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
6
7
#include <assert.h>
#include <iomanip>
8
#include <memory> // IWYU pragma: keep
Daniel Pfeifer's avatar
Daniel Pfeifer committed
9
10
11
12
#include <sstream>
#include <stdio.h>
#include <string.h>

13
#include "cmAlgorithms.h"
14
#include "cmComputeLinkInformation.h"
15
#include "cmCustomCommandGenerator.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
16
#include "cmDocumentationEntry.h"
17
#include "cmGeneratedFileStream.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
18
#include "cmGeneratorExpression.h"
19
#include "cmGeneratorTarget.h"
20
#include "cmGlobalGeneratorFactory.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
21
#include "cmLocalGenerator.h"
22
23
#include "cmLocalXCodeGenerator.h"
#include "cmMakefile.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
24
#include "cmOutputConverter.h"
25
#include "cmSourceFile.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
26
#include "cmSourceGroup.h"
27
#include "cmState.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
28
29
30
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
31
32
#include "cmXCode21Object.h"
#include "cmXCodeObject.h"
33
#include "cmXCodeScheme.h"
34
#include "cmake.h"
35

Daniel Pfeifer's avatar
Daniel Pfeifer committed
36
struct cmLinkImplementation;
37

38
39
40
41
42
#if defined(CMAKE_BUILD_WITH_CMAKE) && defined(__APPLE__)
#define HAVE_APPLICATION_SERVICES
#include <ApplicationServices/ApplicationServices.h>
#endif

43
44
#if defined(CMAKE_BUILD_WITH_CMAKE)
#include "cmXMLParser.h"
45
46
47
48
49
50

// parse the xml file storing the installed version of Xcode on
// the machine
class cmXcodeVersionParser : public cmXMLParser
{
public:
51
52
53
54
  cmXcodeVersionParser()
    : Version("1.5")
  {
  }
55
56
57
58
59
  void StartElement(const std::string&, const char**) override
  {
    this->Data = "";
  }
  void EndElement(const std::string& name) override
60
61
62
63
64
65
66
  {
    if (name == "key") {
      this->Key = this->Data;
    } else if (name == "string") {
      if (this->Key == "CFBundleShortVersionString") {
        this->Version = this->Data;
      }
67
    }
68
  }
69
  void CharacterDataHandler(const char* data, int length) override
70
71
72
  {
    this->Data.append(data, length);
  }
73
  std::string Version;
74
75
  std::string Key;
  std::string Data;
76
};
77
#endif
78

79
80
81
82
// Builds either an object list or a space-separated string from the
// given inputs.
class cmGlobalXCodeGenerator::BuildObjectListOrString
{
83
84
  cmGlobalXCodeGenerator* Generator;
  cmXCodeObject* Group;
85
86
87
88
  bool Empty;
  std::string String;

public:
89
90
  BuildObjectListOrString(cmGlobalXCodeGenerator* gen, bool buildObjectList)
    : Generator(gen)
Matthias Männich's avatar
Matthias Männich committed
91
    , Group(nullptr)
92
93
94
    , Empty(true)
  {
    if (buildObjectList) {
95
96
      this->Group = this->Generator->CreateObject(cmXCodeObject::OBJECT_LIST);
    }
97
  }
98
99
100

  bool IsEmpty() const { return this->Empty; }

101
  void Add(const std::string& newString)
102
  {
103
104
    this->Empty = false;

105
    if (this->Group) {
106
      this->Group->AddObject(this->Generator->CreateString(newString));
107
    } else {
108
109
110
      this->String += newString;
      this->String += ' ';
    }
111
  }
112

113
  const std::string& GetString() const { return this->String; }
114

115
116
117
  cmXCodeObject* CreateList()
  {
    if (this->Group) {
118
119
      return this->Group;
    }
120
    return this->Generator->CreateString(this->String);
121
  }
122
123
};

124
125
126
class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory
{
public:
127
  cmGlobalGenerator* CreateGlobalGenerator(const std::string& name,
128
                                           cmake* cm) const override;
129

130
  void GetDocumentation(cmDocumentationEntry& entry) const override
131
132
133
  {
    cmGlobalXCodeGenerator::GetDocumentation(entry);
  }
134

135
  void GetGenerators(std::vector<std::string>& names) const override
136
137
138
  {
    names.push_back(cmGlobalXCodeGenerator::GetActualName());
  }
139

140
141
  bool SupportsToolset() const override { return true; }
  bool SupportsPlatform() const override { return false; }
142
143
};

144
145
cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(
  cmake* cm, std::string const& version_string, unsigned int version_number)
146
  : cmGlobalGenerator(cm)
Bill Hoffman's avatar
Bill Hoffman committed
147
{
148
149
  this->VersionString = version_string;
  this->XcodeVersion = version_number;
150

Matthias Männich's avatar
Matthias Männich committed
151
152
153
154
  this->RootObject = nullptr;
  this->MainGroupChildren = nullptr;
  this->CurrentMakefile = nullptr;
  this->CurrentLocalGenerator = nullptr;
155
  this->XcodeBuildCommandInitialized = false;
156

157
  this->ObjectDirArchDefault = "$(CURRENT_ARCH)";
158
  this->ObjectDirArch = this->ObjectDirArchDefault;
159

160
  cm->GetState()->SetIsGeneratorMultiConfig(true);
161
162
}

163
164
165
166
167
cmGlobalGeneratorFactory* cmGlobalXCodeGenerator::NewFactory()
{
  return new Factory;
}

168
169
cmGlobalGenerator* cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(
  const std::string& name, cmake* cm) const
170
{
171
  if (name != GetActualName()) {
Matthias Männich's avatar
Matthias Männich committed
172
    return nullptr;
173
  }
174
#if defined(CMAKE_BUILD_WITH_CMAKE)
175
  cmXcodeVersionParser parser;
176
177
  std::string versionFile;
  {
178
    std::string out;
179
    std::string::size_type pos = 0;
Matthias Männich's avatar
Matthias Männich committed
180
181
182
    if (cmSystemTools::RunSingleCommand("xcode-select --print-path", &out,
                                        nullptr, nullptr, nullptr,
                                        cmSystemTools::OUTPUT_NONE) &&
183
        (pos = out.find(".app/"), pos != std::string::npos)) {
184
      versionFile = out.substr(0, pos + 5) + "Contents/version.plist";
185
186
    }
  }
187
  if (!versionFile.empty() && cmSystemTools::FileExists(versionFile.c_str())) {
188
    parser.ParseFile(versionFile.c_str());
189
190
191
192
193
194
195
  } else if (cmSystemTools::FileExists(
               "/Applications/Xcode.app/Contents/version.plist")) {
    parser.ParseFile("/Applications/Xcode.app/Contents/version.plist");
  } else {
    parser.ParseFile(
      "/Developer/Applications/Xcode.app/Contents/version.plist");
  }
196
197
198
199
200
201
202
  std::string const& version_string = parser.Version;

  // Compute an integer form of the version number.
  unsigned int v[2] = { 0, 0 };
  sscanf(version_string.c_str(), "%u.%u", &v[0], &v[1]);
  unsigned int version_number = 10 * v[0] + v[1];

203
204
205
  if (version_number < 30) {
    cm->IssueMessage(cmake::FATAL_ERROR,
                     "Xcode " + version_string + " not supported.");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
206
    return nullptr;
207
208
  }

209
210
  auto gg = cm::make_unique<cmGlobalXCodeGenerator>(cm, version_string,
                                                    version_number);
211
  return gg.release();
212
#else
213
  std::cerr << "CMake should be built with cmake to use Xcode, "
214
               "default to Xcode 1.5\n";
215
  return new cmGlobalXCodeGenerator(cm);
216
#endif
Bill Hoffman's avatar
Bill Hoffman committed
217
218
}

219
bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf)
220
221
222
223
{
  // The Xcode generator knows how to lookup its build tool
  // directly instead of needing a helper module to do it, so we
  // do not actually need to put CMAKE_MAKE_PROGRAM into the cache.
224
  if (cmSystemTools::IsOff(mf->GetDefinition("CMAKE_MAKE_PROGRAM"))) {
225
226
    mf->AddDefinition("CMAKE_MAKE_PROGRAM",
                      this->GetXcodeBuildCommand().c_str());
227
  }
228
  return true;
229
230
}

231
232
std::string const& cmGlobalXCodeGenerator::GetXcodeBuildCommand()
{
233
  if (!this->XcodeBuildCommandInitialized) {
234
235
    this->XcodeBuildCommandInitialized = true;
    this->XcodeBuildCommand = this->FindXcodeBuildCommand();
236
  }
237
238
239
240
241
  return this->XcodeBuildCommand;
}

std::string cmGlobalXCodeGenerator::FindXcodeBuildCommand()
{
242
  if (this->XcodeVersion >= 40) {
243
    std::string makeProgram = cmSystemTools::FindProgram("xcodebuild");
244
    if (makeProgram.empty()) {
245
246
      makeProgram = "xcodebuild";
    }
247
248
    return makeProgram;
  }
249
250
  // Use cmakexbuild wrapper to suppress environment dump from output.
  return cmSystemTools::GetCMakeCommand() + "xbuild";
251
252
}

253
254
bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts,
                                                 cmMakefile* mf)
255
{
256
  if (ts.find_first_of(",=") != std::string::npos) {
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
    std::ostringstream e;
    /* clang-format off */
    e <<
      "Generator\n"
      "  " << this->GetName() << "\n"
      "does not recognize the toolset\n"
      "  " << ts << "\n"
      "that was specified.";
    /* clang-format on */
    mf->IssueMessage(cmake::FATAL_ERROR, e.str());
    return false;
  }
  this->GeneratorToolset = ts;
  if (!this->GeneratorToolset.empty()) {
    mf->AddDefinition("CMAKE_XCODE_PLATFORM_TOOLSET",
                      this->GeneratorToolset.c_str());
273
  }
274
  return true;
275
276
}

277
278
void cmGlobalXCodeGenerator::EnableLanguage(
  std::vector<std::string> const& lang, cmMakefile* mf, bool optional)
279
{
280
  mf->AddDefinition("XCODE", "1");
281
  mf->AddDefinition("XCODE_VERSION", this->VersionString.c_str());
282
283
284
285
286
287
288
  if (!mf->GetDefinition("CMAKE_CONFIGURATION_TYPES")) {
    mf->AddCacheDefinition(
      "CMAKE_CONFIGURATION_TYPES", "Debug;Release;MinSizeRel;RelWithDebInfo",
      "Semicolon separated list of supported configuration types, "
      "only supports Debug, Release, MinSizeRel, and RelWithDebInfo, "
      "anything else will be ignored.",
      cmStateEnums::STRING);
289
  }
Bill Hoffman's avatar
Bill Hoffman committed
290
  mf->AddDefinition("CMAKE_GENERATOR_NO_COMPILER_ENV", "1");
Alexander Neundorf's avatar
 
Alexander Neundorf committed
291
  this->cmGlobalGenerator::EnableLanguage(lang, mf, optional);
292
  this->ComputeArchitectures(mf);
Bill Hoffman's avatar
Bill Hoffman committed
293
294
}

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
bool cmGlobalXCodeGenerator::Open(const std::string& bindir,
                                  const std::string& projectName, bool dryRun)
{
  bool ret = false;

#ifdef HAVE_APPLICATION_SERVICES
  std::string url = bindir + "/" + projectName + ".xcodeproj";

  if (dryRun) {
    return cmSystemTools::FileExists(url, false);
  }

  CFStringRef cfStr = CFStringCreateWithCString(
    kCFAllocatorDefault, url.c_str(), kCFStringEncodingUTF8);
  if (cfStr) {
    CFURLRef cfUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStr,
                                                   kCFURLPOSIXPathStyle, true);
    if (cfUrl) {
      OSStatus err = LSOpenCFURLRef(cfUrl, nullptr);
      ret = err == noErr;
      CFRelease(cfUrl);
    }
    CFRelease(cfStr);
  }
#endif

  return ret;
}

324
325
326
327
328
void cmGlobalXCodeGenerator::GenerateBuildCommand(
  std::vector<std::string>& makeCommand, const std::string& makeProgram,
  const std::string& projectName, const std::string& /*projectDir*/,
  const std::string& targetName, const std::string& config, bool /*fast*/,
  bool /*verbose*/, std::vector<std::string> const& makeOptions)
Bill Hoffman's avatar
Bill Hoffman committed
329
{
330
  // now build the test
331
  makeCommand.push_back(
332
    this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand()));
333

334
335
336
  makeCommand.push_back("-project");
  std::string projectArg = projectName;
  projectArg += ".xcode";
337
  projectArg += "proj";
338
  makeCommand.push_back(projectArg);
339

340
  bool clean = false;
341
  std::string realTarget = targetName;
342
  if (realTarget == "clean") {
343
    clean = true;
344
    realTarget = "ALL_BUILD";
345
346
  }
  if (clean) {
347
    makeCommand.push_back("clean");
348
  } else {
349
    makeCommand.push_back("build");
350
  }
351
  makeCommand.push_back("-target");
352
  if (!realTarget.empty()) {
353
    makeCommand.push_back(realTarget);
354
  } else {
355
    makeCommand.push_back("ALL_BUILD");
356
  }
357
358
  makeCommand.push_back("-configuration");
  makeCommand.push_back(!config.empty() ? config : "Debug");
359
360
  makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                     makeOptions.end());
Bill Hoffman's avatar
Bill Hoffman committed
361
362
363
}

///! Create a local generator appropriate to this Global Generator
364
cmLocalGenerator* cmGlobalXCodeGenerator::CreateLocalGenerator(cmMakefile* mf)
Bill Hoffman's avatar
Bill Hoffman committed
365
{
366
  return new cmLocalXCodeGenerator(this, mf);
Bill Hoffman's avatar
Bill Hoffman committed
367
368
}

369
370
void cmGlobalXCodeGenerator::AddExtraIDETargets()
{
371
372
  // make sure extra targets are added before calling
  // the parent generate which will call trace depends
373
374
  for (auto keyVal : this->ProjectMap) {
    cmLocalGenerator* root = keyVal.second[0];
375
    this->SetGenerationRoot(root);
376
    // add ALL_BUILD, INSTALL, etc
377
    this->AddExtraTargets(root, keyVal.second);
378
  }
379
380
381
382
}

void cmGlobalXCodeGenerator::Generate()
{
383
  this->cmGlobalGenerator::Generate();
384
  if (cmSystemTools::GetErrorOccuredFlag()) {
385
    return;
386
  }
387
388
  for (auto keyVal : this->ProjectMap) {
    cmLocalGenerator* root = keyVal.second[0];
389
390
391
392
393
394
395
396
397
398
399

    bool generateTopLevelProjectOnly =
      root->GetMakefile()->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY");

    if (generateTopLevelProjectOnly) {
      cmStateSnapshot snp = root->GetStateSnapshot();
      if (snp.GetBuildsystemDirectoryParent().IsValid()) {
        continue;
      }
    }

400
    this->SetGenerationRoot(root);
401
    // now create the project
402
    this->OutputXCodeProject(root, keyVal.second);
403
  }
404
}
405

406
407
void cmGlobalXCodeGenerator::SetGenerationRoot(cmLocalGenerator* root)
{
408
  this->CurrentProject = root->GetProjectName();
409
  this->SetCurrentLocalGenerator(root);
410
  cmSystemTools::SplitPath(
411
412
    this->CurrentLocalGenerator->GetCurrentSourceDirectory(),
    this->ProjectSourceDirectoryComponents);
413
  cmSystemTools::SplitPath(
414
415
    this->CurrentLocalGenerator->GetCurrentBinaryDirectory(),
    this->ProjectOutputDirectoryComponents);
416

417
  this->CurrentXCodeHackMakefile = root->GetCurrentBinaryDirectory();
418
419
420
421
422
  this->CurrentXCodeHackMakefile += "/CMakeScripts";
  cmSystemTools::MakeDirectory(this->CurrentXCodeHackMakefile.c_str());
  this->CurrentXCodeHackMakefile += "/XCODE_DEPEND_HELPER.make";
}

423
424
std::string cmGlobalXCodeGenerator::PostBuildMakeTarget(
  std::string const& tName, std::string const& configName)
425
{
426
  std::string target = tName;
427
  std::replace(target.begin(), target.end(), ' ', '_');
428
  std::string out = "PostBuild." + target;
429
  out += "." + configName;
430
431
432
  return out;
}

433
434
#define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK"

435
436
void cmGlobalXCodeGenerator::AddExtraTargets(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens)
437
438
{
  cmMakefile* mf = root->GetMakefile();
439

440
  // Add ALL_BUILD
Matthias Männich's avatar
Matthias Männich committed
441
  const char* no_working_directory = nullptr;
442
  std::vector<std::string> no_depends;
443
444
445
  cmTarget* allbuild = mf->AddUtilityCommand(
    "ALL_BUILD", cmMakefile::TargetOrigin::Generator, true, no_depends,
    no_working_directory, "echo", "Build all projects");
446

447
  cmGeneratorTarget* allBuildGt = new cmGeneratorTarget(allbuild, root);
448
  root->AddGeneratorTarget(allBuildGt);
449

450
  // Add XCODE depend helper
451
  std::string dir = root->GetCurrentBinaryDirectory();
452
  cmCustomCommandLine makeHelper;
453
454
455
456
457
458
  makeHelper.push_back("make");
  makeHelper.push_back("-C");
  makeHelper.push_back(dir);
  makeHelper.push_back("-f");
  makeHelper.push_back(this->CurrentXCodeHackMakefile);
  makeHelper.push_back(""); // placeholder, see below
459

460
  // Add ZERO_CHECK
461
  bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION");
462
463
464
465
466
  bool generateTopLevelProjectOnly =
    mf->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY");
  bool isTopLevel =
    !root->GetStateSnapshot().GetBuildsystemDirectoryParent().IsValid();
  if (regenerate && (isTopLevel || !generateTopLevelProjectOnly)) {
467
    this->CreateReRunCMakeFile(root, gens);
468
    std::string file =
469
      this->ConvertToRelativeForMake(this->CurrentReRunCMakeMakefile);
470
    cmSystemTools::ReplaceString(file, "\\ ", " ");
471
472
473
    cmTarget* check = mf->AddUtilityCommand(
      CMAKE_CHECK_BUILD_SYSTEM_TARGET, cmMakefile::TargetOrigin::Generator,
      true, no_depends, no_working_directory, "make", "-f", file.c_str());
474

475
    cmGeneratorTarget* checkGt = new cmGeneratorTarget(check, root);
476
    root->AddGeneratorTarget(checkGt);
477
  }
478

479
480
  // now make the allbuild depend on all the non-utility targets
  // in the project
481
482
  for (auto& gen : gens) {
    if (this->IsExcluded(root, gen)) {
483
      continue;
484
    }
485

486
    for (auto target : gen->GetGeneratorTargets()) {
487
      if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
488
        continue;
489
      }
490

491
      std::string targetName = target->GetName();
Stephen Kelly's avatar
Stephen Kelly committed
492

493
      if (regenerate && (targetName != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) {
494
        target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET);
495
      }
496

497
      // make all exe, shared libs and modules
498
499
500
      // run the depend check makefile as a post build rule
      // this will make sure that when the next target is built
      // things are up-to-date
501
502
503
504
505
506
      if (target->GetType() == cmStateEnums::OBJECT_LIBRARY ||
          (this->XcodeVersion < 50 &&
           (target->GetType() == cmStateEnums::EXECUTABLE ||
            target->GetType() == cmStateEnums::STATIC_LIBRARY ||
            target->GetType() == cmStateEnums::SHARED_LIBRARY ||
            target->GetType() == cmStateEnums::MODULE_LIBRARY))) {
507
        makeHelper[makeHelper.size() - 1] = // fill placeholder
508
          this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)");
509
        cmCustomCommandLines commandLines;
510
        commandLines.push_back(makeHelper);
511
        std::vector<std::string> no_byproducts;
512
        gen->GetMakefile()->AddCustomCommandToTarget(
513
          target->GetName(), no_byproducts, no_depends, commandLines,
514
515
          cmTarget::POST_BUILD, "Depend check for xcode", dir.c_str(), true,
          false, "", false, cmMakefile::AcceptObjectLibraryCommands);
516
      }
517

518
      if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
519
          !target->GetPropertyAsBool("EXCLUDE_FROM_ALL")) {
520
        allbuild->AddUtility(target->GetName());
521
      }
Bill Hoffman's avatar
Bill Hoffman committed
522
    }
523
  }
Bill Hoffman's avatar
Bill Hoffman committed
524
525
}

526
527
void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*> const& gens)
528
{
529
  std::vector<std::string> lfiles;
530
531
  for (auto gen : gens) {
    std::vector<std::string> const& lf = gen->GetMakefile()->GetListFiles();
532
    lfiles.insert(lfiles.end(), lf.begin(), lf.end());
533
  }
534

535
  // sort the array
536
537
  std::sort(lfiles.begin(), lfiles.end(), std::less<std::string>());
  std::vector<std::string>::iterator new_end =
538
539
    std::unique(lfiles.begin(), lfiles.end());
  lfiles.erase(new_end, lfiles.end());
540
541
542
543
544
545

  cmake* cm = this->GetCMakeInstance();
  if (cm->DoWriteGlobVerifyTarget()) {
    lfiles.emplace_back(cm->GetGlobVerifyStamp());
  }

546
  this->CurrentReRunCMakeMakefile = root->GetCurrentBinaryDirectory();
Ken Martin's avatar
Ken Martin committed
547
548
549
  this->CurrentReRunCMakeMakefile += "/CMakeScripts";
  cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile.c_str());
  this->CurrentReRunCMakeMakefile += "/ReRunCMake.make";
550
551
  cmGeneratedFileStream makefileStream(
    this->CurrentReRunCMakeMakefile.c_str());
552
  makefileStream.SetCopyIfDifferent(true);
553
554
  makefileStream << "# Generated by CMake, DO NOT EDIT\n\n";

555
  makefileStream << "TARGETS:= \n";
556
557
558
559
  makefileStream << "empty:= \n";
  makefileStream << "space:= $(empty) $(empty)\n";
  makefileStream << "spaceplus:= $(empty)\\ $(empty)\n\n";

560
  for (const auto& lfile : lfiles) {
561
    makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
562
                   << this->ConvertToRelativeForMake(lfile) << "))\n";
563
  }
564
  makefileStream << "\n";
565
566
567
568
569
570

  std::string checkCache = root->GetBinaryDirectory();
  checkCache += "/";
  checkCache += cmake::GetCMakeFilesDirectoryPostSlash();
  checkCache += "cmake.check_cache";

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
  if (cm->DoWriteGlobVerifyTarget()) {
    makefileStream << ".NOTPARALLEL:\n\n";
    makefileStream << ".PHONY: all VERIFY_GLOBS\n\n";
    makefileStream << "all: VERIFY_GLOBS "
                   << this->ConvertToRelativeForMake(checkCache) << "\n\n";
    makefileStream << "VERIFY_GLOBS:\n";
    makefileStream << "\t"
                   << this->ConvertToRelativeForMake(
                        cmSystemTools::GetCMakeCommand())
                   << " -P "
                   << this->ConvertToRelativeForMake(cm->GetGlobVerifyScript())
                   << "\n\n";
  }

  makefileStream << this->ConvertToRelativeForMake(checkCache)
586
                 << ": $(TARGETS)\n";
587
588
  makefileStream << "\t"
                 << this->ConvertToRelativeForMake(
589
                      cmSystemTools::GetCMakeCommand())
590
591
592
593
594
                 << " -H"
                 << this->ConvertToRelativeForMake(root->GetSourceDirectory())
                 << " -B"
                 << this->ConvertToRelativeForMake(root->GetBinaryDirectory())
                 << "\n";
595
596
}

Gregor Jasny's avatar
Gregor Jasny committed
597
598
599
600
601
602
603
604
605
606
607
static bool objectIdLessThan(cmXCodeObject* l, cmXCodeObject* r)
{
  return l->GetId() < r->GetId();
}

void cmGlobalXCodeGenerator::SortXCodeObjects()
{
  std::sort(this->XCodeObjects.begin(), this->XCodeObjects.end(),
            objectIdLessThan);
}

Bill Hoffman's avatar
Bill Hoffman committed
608
609
void cmGlobalXCodeGenerator::ClearXCodeObjects()
{
Ken Martin's avatar
Ken Martin committed
610
  this->TargetDoneSet.clear();
611
612
  for (auto& obj : this->XCodeObjects) {
    delete obj;
613
  }
Ken Martin's avatar
Ken Martin committed
614
  this->XCodeObjects.clear();
615
  this->XCodeObjectIDs.clear();
616
  this->XCodeObjectMap.clear();
Ken Martin's avatar
Ken Martin committed
617
618
619
  this->GroupMap.clear();
  this->GroupNameMap.clear();
  this->TargetGroup.clear();
620
  this->FileRefs.clear();
Bill Hoffman's avatar
Bill Hoffman committed
621
622
}

623
void cmGlobalXCodeGenerator::addObject(cmXCodeObject* obj)
624
{
625
  if (obj->GetType() == cmXCodeObject::OBJECT) {
626
    const std::string& id = obj->GetId();
627
628
629

    // If this is a duplicate id, it's an error:
    //
630
    if (this->XCodeObjectIDs.count(id)) {
631
632
      cmSystemTools::Error(
        "Xcode generator: duplicate object ids not allowed");
633
    }
634
635

    this->XCodeObjectIDs.insert(id);
636
  }
637
638
639
640

  this->XCodeObjects.push_back(obj);
}

641
642
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(
  cmXCodeObject::PBXType ptype)
Bill Hoffman's avatar
Bill Hoffman committed
643
{
644
  cmXCodeObject* obj = new cmXCode21Object(ptype, cmXCodeObject::OBJECT);
645
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
646
647
648
  return obj;
}

649
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(cmXCodeObject::Type type)
650
{
Bill Hoffman's avatar
Bill Hoffman committed
651
  cmXCodeObject* obj = new cmXCodeObject(cmXCodeObject::None, type);
652
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
653
654
655
  return obj;
}

656
cmXCodeObject* cmGlobalXCodeGenerator::CreateString(const std::string& s)
Bill Hoffman's avatar
Bill Hoffman committed
657
658
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING);
659
660
661
  obj->SetString(s);
  return obj;
}
662

663
664
cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference(
  cmXCodeObject* ref)
Bill Hoffman's avatar
Bill Hoffman committed
665
666
667
668
669
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF);
  obj->SetObject(ref);
  return obj;
}
670

671
cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig)
672
673
674
675
676
677
{
  cmXCodeObject* obj = this->CreateObject(orig->GetType());
  obj->CopyAttributes(orig);
  return obj;
}

678
679
std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target,
                                   const std::string& fullpath)
680
{
681
  std::string key(target->GetName());
682
  key += "-";
683
  key += fullpath;
684
685
686
  return key;
}

687
688
689
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFileFromPath(
  const std::string& fullpath, cmGeneratorTarget* target,
  const std::string& lang, cmSourceFile* sf)
690
691
692
693
694
{
  // Using a map and the full path guarantees that we will always get the same
  // fileRef object for any given full path.
  //
  cmXCodeObject* fileRef =
695
    this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
696
697
698
699
700
701
702
703

  cmXCodeObject* buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
  buildFile->SetComment(fileRef->GetComment());
  buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef));

  return buildFile;
}

704
705
706
707
708
709
710
711
class XCodeGeneratorExpressionInterpreter
  : public cmGeneratorExpressionInterpreter
{
  CM_DISABLE_COPY(XCodeGeneratorExpressionInterpreter)

public:
  XCodeGeneratorExpressionInterpreter(cmSourceFile* sourceFile,
                                      cmLocalGenerator* localGenerator,
712
713
                                      cmGeneratorTarget* generatorTarget,
                                      const std::string& lang)
714
    : cmGeneratorExpressionInterpreter(localGenerator, generatorTarget,
715
716
                                       "NO-PER-CONFIG-SUPPORT-IN-XCODE",
                                       generatorTarget->GetName(), lang)
717
718
719
720
721
722
    , SourceFile(sourceFile)
  {
  }

  using cmGeneratorExpressionInterpreter::Evaluate;

723
  const char* Evaluate(const char* expression, const std::string& property)
724
  {
725
726
    const char* processed =
      this->cmGeneratorExpressionInterpreter::Evaluate(expression, property);
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
    if (this->GetCompiledGeneratorExpression()
          .GetHadContextSensitiveCondition()) {
      std::ostringstream e;
      /* clang-format off */
      e <<
          "Xcode does not support per-config per-source " << property << ":\n"
          "  " << expression << "\n"
          "specified for source:\n"
          "  " << this->SourceFile->GetFullPath() << "\n";
      /* clang-format on */
      this->GetLocalGenerator()->IssueMessage(cmake::FATAL_ERROR, e.str());
    }

    return processed;
  }

private:
  cmSourceFile* SourceFile = nullptr;
};

747
748
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
  cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
Bill Hoffman's avatar
Bill Hoffman committed
749
{
750
751
752
  std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);

  XCodeGeneratorExpressionInterpreter genexInterpreter(sf, lg, gtgt, lang);
753

754
  // Add flags from target and source file properties.
Bill Hoffman's avatar
Bill Hoffman committed
755
  std::string flags;
756
  const char* srcfmt = sf->GetProperty("Fortran_FORMAT");
757
  switch (cmOutputConverter::GetFortranFormat(srcfmt)) {
758
    case cmOutputConverter::FortranFormatFixed:
759
760
      flags = "-fixed " + flags;
      break;
761
    case cmOutputConverter::FortranFormatFree:
762
763
764
765
766
      flags = "-free " + flags;
      break;
    default:
      break;
  }
767
768
769
  const std::string COMPILE_FLAGS("COMPILE_FLAGS");
  if (const char* cflags = sf->GetProperty(COMPILE_FLAGS)) {
    lg->AppendFlags(flags, genexInterpreter.Evaluate(cflags, COMPILE_FLAGS));
770
  }
771
772
773
774
775
  const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
  if (const char* coptions = sf->GetProperty(COMPILE_OPTIONS)) {
    lg->AppendCompileOptions(
      flags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
  }
776

777
  // Add per-source definitions.
778
  BuildObjectListOrString flagsBuild(this, false);
779
780
  const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
  if (const char* compile_defs = sf->GetProperty(COMPILE_DEFINITIONS)) {
781
    this->AppendDefines(
782
783
      flagsBuild, genexInterpreter.Evaluate(compile_defs, COMPILE_DEFINITIONS),
      true);
784
  }
785
786
  if (!flagsBuild.IsEmpty()) {
    if (!flags.empty()) {
787
788
      flags += ' ';
    }
789
790
    flags += flagsBuild.GetString();
  }
791

792
793
794
795
796
797
798
799
800
801
  // Add per-source include directories.
  std::vector<std::string> includes;
  const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
  if (const char* cincludes = sf->GetProperty(INCLUDE_DIRECTORIES)) {
    lg->AppendIncludeDirectories(
      includes, genexInterpreter.Evaluate(cincludes, INCLUDE_DIRECTORIES),
      *sf);
  }
  lg->AppendFlags(flags, lg->GetIncludeFlags(includes, gtgt, lang, true));

802
  cmXCodeObject* buildFile =
803
    this->CreateXCodeSourceFileFromPath(sf->GetFullPath(), gtgt, lang, sf);
804

805
  cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
806
807
  settings->AddAttributeIfNotEmpty("COMPILER_FLAGS",
                                   this->CreateString(flags));
808

809
  cmGeneratorTarget::SourceFileFlags tsFlags =
810
    gtgt->GetTargetSourceFileFlags(sf);
811

812
813
  cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);

814
815
816
  // Is this a "private" or "public" framework header file?
  // Set the ATTRIBUTES attribute appropriately...
  //
817
818
  if (gtgt->IsFrameworkOnApple()) {
    if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) {
819
      attrs->AddObject(this->CreateString("Private"));
820
    } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) {
821
822
      attrs->AddObject(this->CreateString("Public"));
    }
823
  }
824

825
826
827
828
829
830
831
832