cmGlobalXCodeGenerator.cxx 127 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
461
  // Add ZERO_CHECK
  bool regenerate = !mf->IsOn("CMAKE_SUPPRESS_REGENERATION");
462
  if (regenerate) {
463
    this->CreateReRunCMakeFile(root, gens);
464
465
    std::string file =
      this->ConvertToRelativeForMake(this->CurrentReRunCMakeMakefile.c_str());
466
    cmSystemTools::ReplaceString(file, "\\ ", " ");
467
468
469
    cmTarget* check = mf->AddUtilityCommand(
      CMAKE_CHECK_BUILD_SYSTEM_TARGET, cmMakefile::TargetOrigin::Generator,
      true, no_depends, no_working_directory, "make", "-f", file.c_str());
470

471
    cmGeneratorTarget* checkGt = new cmGeneratorTarget(check, root);
472
    root->AddGeneratorTarget(checkGt);
473
  }
474

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

482
    for (auto target : gen->GetGeneratorTargets()) {
483
      if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
484
        continue;
485
      }
486

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

489
      if (regenerate && (targetName != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) {
490
        target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET);
491
      }
492

493
      // make all exe, shared libs and modules
494
495
496
      // 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
497
498
499
500
501
502
      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))) {
503
        makeHelper[makeHelper.size() - 1] = // fill placeholder
504
          this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)");
505
        cmCustomCommandLines commandLines;
506
        commandLines.push_back(makeHelper);
507
        std::vector<std::string> no_byproducts;
508
        gen->GetMakefile()->AddCustomCommandToTarget(
509
          target->GetName(), no_byproducts, no_depends, commandLines,
510
511
          cmTarget::POST_BUILD, "Depend check for xcode", dir.c_str(), true,
          false, "", false, cmMakefile::AcceptObjectLibraryCommands);
512
      }
513

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

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

531
  // sort the array
532
533
  std::sort(lfiles.begin(), lfiles.end(), std::less<std::string>());
  std::vector<std::string>::iterator new_end =
534
535
    std::unique(lfiles.begin(), lfiles.end());
  lfiles.erase(new_end, lfiles.end());
536
  this->CurrentReRunCMakeMakefile = root->GetCurrentBinaryDirectory();
Ken Martin's avatar
Ken Martin committed
537
538
539
  this->CurrentReRunCMakeMakefile += "/CMakeScripts";
  cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile.c_str());
  this->CurrentReRunCMakeMakefile += "/ReRunCMake.make";
540
541
  cmGeneratedFileStream makefileStream(
    this->CurrentReRunCMakeMakefile.c_str());
542
  makefileStream.SetCopyIfDifferent(true);
543
544
  makefileStream << "# Generated by CMake, DO NOT EDIT\n\n";

545
  makefileStream << "TARGETS:= \n";
546
547
548
549
  makefileStream << "empty:= \n";
  makefileStream << "space:= $(empty) $(empty)\n";
  makefileStream << "spaceplus:= $(empty)\\ $(empty)\n\n";

550
  for (const auto& lfile : lfiles) {
551
    makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
552
                   << this->ConvertToRelativeForMake(lfile.c_str()) << "))\n";
553
  }
554
555
556
557
558
559

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

560
561
  makefileStream << "\n"
                 << this->ConvertToRelativeForMake(checkCache.c_str())
562
                 << ": $(TARGETS)\n";
563
564
565
566
567
568
569
570
  makefileStream << "\t"
                 << this->ConvertToRelativeForMake(
                      cmSystemTools::GetCMakeCommand().c_str())
                 << " -H"
                 << this->ConvertToRelativeForMake(root->GetSourceDirectory())
                 << " -B"
                 << this->ConvertToRelativeForMake(root->GetBinaryDirectory())
                 << "\n";
571
572
}

Gregor Jasny's avatar
Gregor Jasny committed
573
574
575
576
577
578
579
580
581
582
583
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
584
585
void cmGlobalXCodeGenerator::ClearXCodeObjects()
{
Ken Martin's avatar
Ken Martin committed
586
  this->TargetDoneSet.clear();
587
588
  for (auto& obj : this->XCodeObjects) {
    delete obj;
589
  }
Ken Martin's avatar
Ken Martin committed
590
  this->XCodeObjects.clear();
591
  this->XCodeObjectIDs.clear();
592
  this->XCodeObjectMap.clear();
Ken Martin's avatar
Ken Martin committed
593
594
595
  this->GroupMap.clear();
  this->GroupNameMap.clear();
  this->TargetGroup.clear();
596
  this->FileRefs.clear();
Bill Hoffman's avatar
Bill Hoffman committed
597
598
}

599
void cmGlobalXCodeGenerator::addObject(cmXCodeObject* obj)
600
{
601
  if (obj->GetType() == cmXCodeObject::OBJECT) {
602
    const std::string& id = obj->GetId();
603
604
605

    // If this is a duplicate id, it's an error:
    //
606
    if (this->XCodeObjectIDs.count(id)) {
607
608
      cmSystemTools::Error(
        "Xcode generator: duplicate object ids not allowed");
609
    }
610
611

    this->XCodeObjectIDs.insert(id);
612
  }
613
614
615
616

  this->XCodeObjects.push_back(obj);
}

617
618
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(
  cmXCodeObject::PBXType ptype)
Bill Hoffman's avatar
Bill Hoffman committed
619
{
620
  cmXCodeObject* obj = new cmXCode21Object(ptype, cmXCodeObject::OBJECT);
621
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
622
623
624
  return obj;
}

625
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(cmXCodeObject::Type type)
626
{
Bill Hoffman's avatar
Bill Hoffman committed
627
  cmXCodeObject* obj = new cmXCodeObject(cmXCodeObject::None, type);
628
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
629
630
631
  return obj;
}

632
cmXCodeObject* cmGlobalXCodeGenerator::CreateString(const std::string& s)
Bill Hoffman's avatar
Bill Hoffman committed
633
634
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING);
635
636
637
  obj->SetString(s);
  return obj;
}
638

639
640
cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference(
  cmXCodeObject* ref)
Bill Hoffman's avatar
Bill Hoffman committed
641
642
643
644
645
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF);
  obj->SetObject(ref);
  return obj;
}
646

647
cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig)
648
649
650
651
652
653
{
  cmXCodeObject* obj = this->CreateObject(orig->GetType());
  obj->CopyAttributes(orig);
  return obj;
}

654
655
std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target,
                                   const std::string& fullpath)
656
{
657
  std::string key(target->GetName());
658
  key += "-";
659
  key += fullpath;
660
661
662
  return key;
}

663
664
665
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFileFromPath(
  const std::string& fullpath, cmGeneratorTarget* target,
  const std::string& lang, cmSourceFile* sf)
666
667
668
669
670
{
  // Using a map and the full path guarantees that we will always get the same
  // fileRef object for any given full path.
  //
  cmXCodeObject* fileRef =
671
    this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
672
673
674
675
676
677
678
679

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

  return buildFile;
}

680
681
682
683
684
685
686
687
class XCodeGeneratorExpressionInterpreter
  : public cmGeneratorExpressionInterpreter
{
  CM_DISABLE_COPY(XCodeGeneratorExpressionInterpreter)

public:
  XCodeGeneratorExpressionInterpreter(cmSourceFile* sourceFile,
                                      cmLocalGenerator* localGenerator,
688
689
                                      cmGeneratorTarget* generatorTarget,
                                      const std::string& lang)
690
    : cmGeneratorExpressionInterpreter(localGenerator, generatorTarget,
691
692
                                       "NO-PER-CONFIG-SUPPORT-IN-XCODE",
                                       generatorTarget->GetName(), lang)
693
694
695
696
697
698
    , SourceFile(sourceFile)
  {
  }

  using cmGeneratorExpressionInterpreter::Evaluate;

699
  const char* Evaluate(const char* expression, const std::string& property)
700
  {
701
702
    const char* processed =
      this->cmGeneratorExpressionInterpreter::Evaluate(expression, property);
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
    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;
};

723
724
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
  cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
Bill Hoffman's avatar
Bill Hoffman committed
725
{
726
727
728
  std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);

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

730
  // Add flags from target and source file properties.
Bill Hoffman's avatar
Bill Hoffman committed
731
  std::string flags;
732
  const char* srcfmt = sf->GetProperty("Fortran_FORMAT");
733
  switch (cmOutputConverter::GetFortranFormat(srcfmt)) {
734
    case cmOutputConverter::FortranFormatFixed:
735
736
      flags = "-fixed " + flags;
      break;
737
    case cmOutputConverter::FortranFormatFree:
738
739
740
741
742
      flags = "-free " + flags;
      break;
    default:
      break;
  }
743
744
745
  const std::string COMPILE_FLAGS("COMPILE_FLAGS");
  if (const char* cflags = sf->GetProperty(COMPILE_FLAGS)) {
    lg->AppendFlags(flags, genexInterpreter.Evaluate(cflags, COMPILE_FLAGS));
746
  }
747
748
749
750
751
  const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
  if (const char* coptions = sf->GetProperty(COMPILE_OPTIONS)) {
    lg->AppendCompileOptions(
      flags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
  }
752

753
  // Add per-source definitions.
754
  BuildObjectListOrString flagsBuild(this, false);
755
756
  const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
  if (const char* compile_defs = sf->GetProperty(COMPILE_DEFINITIONS)) {
757
    this->AppendDefines(
758
759
      flagsBuild, genexInterpreter.Evaluate(compile_defs, COMPILE_DEFINITIONS),
      true);
760
  }
761
762
  if (!flagsBuild.IsEmpty()) {
    if (!flags.empty()) {
763
764
      flags += ' ';
    }
765
766
    flags += flagsBuild.GetString();
  }
767

768
769
770
771
772
773
774
775
776
777
  // 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));

778
  cmXCodeObject* buildFile =
779
    this->CreateXCodeSourceFileFromPath(sf->GetFullPath(), gtgt, lang, sf);
780

781
  cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
782
783
  settings->AddAttributeIfNotEmpty("COMPILER_FLAGS",
                                   this->CreateString(flags));
784

785
  cmGeneratorTarget::SourceFileFlags tsFlags =
786
    gtgt->GetTargetSourceFileFlags(sf);
787

788
789
  cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);

790
791
792
  // Is this a "private" or "public" framework header file?
  // Set the ATTRIBUTES attribute appropriately...
  //
793
794
  if (gtgt->IsFrameworkOnApple()) {
    if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) {
795
      attrs->AddObject(this->CreateString("Private"));
796
    } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) {
797
798
      attrs->AddObject(this->CreateString("Public"));
    }
799
  }
800

801
802
803
804
805
806
807
808
  // Add user-specified file attributes.
  const char* extraFileAttributes = sf->GetProperty("XCODE_FILE_ATTRIBUTES");
  if (extraFileAttributes) {
    // Expand the list of attributes.
    std::vector<std::string> attributes;
    cmSystemTools::ExpandListArgument(extraFileAttributes, attributes);

    // Store the attributes.
809
810
    for (const auto& attribute : attributes) {
      attrs->AddObject(this->CreateString(attribute));
811
812
813
    }
  }

814
815
816
  settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);

  buildFile->AddAttributeIfNotEmpty("settings", settings);
817
818
819
  return buildFile;
}

820
821
822
std::string GetSourcecodeValueFromFileExtension(const std::string& _ext,
                                                const std::string& lang,
                                                bool& keepLastKnownFileType)
823
{
824
  std::string ext = cmSystemTools::LowerCase(_ext);
Bill Hoffman's avatar
Bill Hoffman committed
825
  std::string sourcecode = "sourcecode";
826

827
  if (ext == "o") {
828
    sourcecode = "compiled.mach-o.objfile";
829
  } else if (ext == "xctest") {
830
    sourcecode = "wrapper.cfbundle";
831
  } else if (ext == "xib") {
Ruslan Baratov's avatar
Ruslan Baratov committed
832
    keepLastKnownFileType = true;
833
    sourcecode = "file.xib";
834
  } else if (ext == "storyboard") {
Ruslan Baratov's avatar
Ruslan Baratov committed
835
    keepLastKnownFileType = true;
836
    sourcecode = "file.storyboard";
837
  } else if (ext == "mm") {
838
    sourcecode += ".cpp.objcpp";
839
  } else if (ext == "m") {
840
    sourcecode += ".c.objc";
841
  } else if (ext == "swift") {
842
    sourcecode += ".swift";
843
  } else if (ext == "plist") {
844
    sourcecode += ".text.plist";
845
  } else if (ext == "h") {
846
    sourcecode += ".c.h";
847
848
  } else if (ext == "hxx" || ext == "hpp" || ext == "txx" || ext == "pch" ||
             ext == "hh") {
849
    sourcecode += ".cpp.h";