cmGlobalXCodeGenerator.cxx 126 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

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

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

Daniel Pfeifer's avatar
Daniel Pfeifer committed
33
struct cmLinkImplementation;
34

35
36
#if defined(CMAKE_BUILD_WITH_CMAKE)
#include "cmXMLParser.h"
37
38
39
40
41
42

// parse the xml file storing the installed version of Xcode on
// the machine
class cmXcodeVersionParser : public cmXMLParser
{
public:
43
44
45
46
47
  cmXcodeVersionParser()
    : Version("1.5")
  {
  }
  void StartElement(const std::string&, const char**) { this->Data = ""; }
48
  void EndElement(const std::string& name)
49
50
51
52
53
54
55
  {
    if (name == "key") {
      this->Key = this->Data;
    } else if (name == "string") {
      if (this->Key == "CFBundleShortVersionString") {
        this->Version = this->Data;
      }
56
    }
57
  }
58
  void CharacterDataHandler(const char* data, int length)
59
60
61
  {
    this->Data.append(data, length);
  }
62
  std::string Version;
63
64
  std::string Key;
  std::string Data;
65
};
66
#endif
67

68
69
70
71
// Builds either an object list or a space-separated string from the
// given inputs.
class cmGlobalXCodeGenerator::BuildObjectListOrString
{
72
73
  cmGlobalXCodeGenerator* Generator;
  cmXCodeObject* Group;
74
75
76
77
  bool Empty;
  std::string String;

public:
78
79
80
81
82
83
  BuildObjectListOrString(cmGlobalXCodeGenerator* gen, bool buildObjectList)
    : Generator(gen)
    , Group(0)
    , Empty(true)
  {
    if (buildObjectList) {
84
85
      this->Group = this->Generator->CreateObject(cmXCodeObject::OBJECT_LIST);
    }
86
  }
87
88
89

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

90
  void Add(const std::string& newString)
91
  {
92
93
    this->Empty = false;

94
    if (this->Group) {
95
      this->Group->AddObject(this->Generator->CreateString(newString));
96
    } else {
97
98
99
      this->String += newString;
      this->String += ' ';
    }
100
  }
101

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

104
105
106
  cmXCodeObject* CreateList()
  {
    if (this->Group) {
107
      return this->Group;
108
    } else {
109
      return this->Generator->CreateString(this->String);
110
    }
111
  }
112
113
};

114
115
116
class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory
{
public:
117
118
  cmGlobalGenerator* CreateGlobalGenerator(const std::string& name,
                                           cmake* cm) const CM_OVERRIDE;
119

120
  void GetDocumentation(cmDocumentationEntry& entry) const CM_OVERRIDE
121
122
123
  {
    cmGlobalXCodeGenerator::GetDocumentation(entry);
  }
124

125
  void GetGenerators(std::vector<std::string>& names) const CM_OVERRIDE
126
127
128
  {
    names.push_back(cmGlobalXCodeGenerator::GetActualName());
  }
129

130
  bool SupportsToolset() const CM_OVERRIDE { return true; }
131
  bool SupportsPlatform() const CM_OVERRIDE { return false; }
132
133
};

134
135
136
cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(cmake* cm,
                                               std::string const& version)
  : cmGlobalGenerator(cm)
Bill Hoffman's avatar
Bill Hoffman committed
137
{
138
139
140
  this->VersionString = version;

  // Compute an integer form of the version number.
141
  unsigned int v[2] = { 0, 0 };
142
  sscanf(this->VersionString.c_str(), "%u.%u", &v[0], &v[1]);
143
  this->XcodeVersion = 10 * v[0] + v[1];
144

Ken Martin's avatar
Ken Martin committed
145
146
147
  this->RootObject = 0;
  this->MainGroupChildren = 0;
  this->SourcesGroupChildren = 0;
148
  this->ResourcesGroupChildren = 0;
Ken Martin's avatar
Ken Martin committed
149
150
  this->CurrentMakefile = 0;
  this->CurrentLocalGenerator = 0;
151
  this->XcodeBuildCommandInitialized = false;
152
153
}

154
155
156
157
158
cmGlobalGeneratorFactory* cmGlobalXCodeGenerator::NewFactory()
{
  return new Factory;
}

159
160
cmGlobalGenerator* cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(
  const std::string& name, cmake* cm) const
161
{
162
  if (name != GetActualName())
163
    return 0;
164
#if defined(CMAKE_BUILD_WITH_CMAKE)
165
  cmXcodeVersionParser parser;
166
167
  std::string versionFile;
  {
168
169
170
171
172
173
    std::string out;
    std::string::size_type pos;
    if (cmSystemTools::RunSingleCommand("xcode-select --print-path", &out, 0,
                                        0, 0, cmSystemTools::OUTPUT_NONE) &&
        (pos = out.find(".app/"), pos != out.npos)) {
      versionFile = out.substr(0, pos + 5) + "Contents/version.plist";
174
175
    }
  }
176
  if (!versionFile.empty() && cmSystemTools::FileExists(versionFile.c_str())) {
177
    parser.ParseFile(versionFile.c_str());
178
179
180
181
182
183
184
  } 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");
  }
185
  CM_AUTO_PTR<cmGlobalXCodeGenerator> gg(
186
187
    new cmGlobalXCodeGenerator(cm, parser.Version));
  if (gg->XcodeVersion == 20) {
188
189
    cmSystemTools::Message("Xcode 2.0 not really supported by cmake, "
                           "using Xcode 15 generator\n");
190
    gg->XcodeVersion = 15;
191
  }
192
  return gg.release();
193
#else
194
  std::cerr << "CMake should be built with cmake to use Xcode, "
195
               "default to Xcode 1.5\n";
196
  return new cmGlobalXCodeGenerator(cm);
197
#endif
Bill Hoffman's avatar
Bill Hoffman committed
198
199
}

200
bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf)
201
202
203
204
{
  // 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.
205
  if (cmSystemTools::IsOff(mf->GetDefinition("CMAKE_MAKE_PROGRAM"))) {
206
207
    mf->AddDefinition("CMAKE_MAKE_PROGRAM",
                      this->GetXcodeBuildCommand().c_str());
208
  }
209
  return true;
210
211
}

212
213
std::string const& cmGlobalXCodeGenerator::GetXcodeBuildCommand()
{
214
  if (!this->XcodeBuildCommandInitialized) {
215
216
    this->XcodeBuildCommandInitialized = true;
    this->XcodeBuildCommand = this->FindXcodeBuildCommand();
217
  }
218
219
220
221
222
  return this->XcodeBuildCommand;
}

std::string cmGlobalXCodeGenerator::FindXcodeBuildCommand()
{
223
  if (this->XcodeVersion >= 40) {
224
    std::string makeProgram = cmSystemTools::FindProgram("xcodebuild");
225
    if (makeProgram.empty()) {
226
227
      makeProgram = "xcodebuild";
    }
228
229
    return makeProgram;
  } else {
230
231
    // Use cmakexbuild wrapper to suppress environment dump from output.
    return cmSystemTools::GetCMakeCommand() + "xbuild";
232
  }
233
234
}

235
236
bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts,
                                                 cmMakefile* mf)
237
{
238
  if (this->XcodeVersion >= 30) {
239
240
241
242
243
244
245
246
247
248
249
250
251
    if (ts.find_first_of(",=") != ts.npos) {
      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;
    }
252
    this->GeneratorToolset = ts;
253
    if (!this->GeneratorToolset.empty()) {
254
255
      mf->AddDefinition("CMAKE_XCODE_PLATFORM_TOOLSET",
                        this->GeneratorToolset.c_str());
256
    }
257
258
    return true;
  } else {
259
    return cmGlobalGenerator::SetGeneratorToolset(ts, mf);
260
  }
261
262
}

263
264
void cmGlobalXCodeGenerator::EnableLanguage(
  std::vector<std::string> const& lang, cmMakefile* mf, bool optional)
265
{
266
  mf->AddDefinition("XCODE", "1");
267
  mf->AddDefinition("XCODE_VERSION", this->VersionString.c_str());
268
269
270
  if (this->XcodeVersion == 15) {
  } else {
    if (!mf->GetDefinition("CMAKE_CONFIGURATION_TYPES")) {
271
      mf->AddCacheDefinition(
272
        "CMAKE_CONFIGURATION_TYPES", "Debug;Release;MinSizeRel;RelWithDebInfo",
273
274
275
        "Semicolon separated list of supported configuration types, "
        "only supports Debug, Release, MinSizeRel, and RelWithDebInfo, "
        "anything else will be ignored.",
276
        cmStateEnums::STRING);
277
    }
278
  }
Bill Hoffman's avatar
Bill Hoffman committed
279
  mf->AddDefinition("CMAKE_GENERATOR_NO_COMPILER_ENV", "1");
Alexander Neundorf's avatar
   
Alexander Neundorf committed
280
  this->cmGlobalGenerator::EnableLanguage(lang, mf, optional);
281
282
283
  const char* osxArch = mf->GetDefinition("CMAKE_OSX_ARCHITECTURES");
  const char* sysroot = mf->GetDefinition("CMAKE_OSX_SYSROOT");
  if (osxArch && sysroot) {
284
285
286
    this->Architectures.clear();
    cmSystemTools::ExpandListArgument(std::string(osxArch),
                                      this->Architectures);
287
  }
Bill Hoffman's avatar
Bill Hoffman committed
288
289
}

290
291
292
293
294
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
295
{
296
  // now build the test
297
  makeCommand.push_back(
298
    this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand()));
299

300
301
302
  makeCommand.push_back("-project");
  std::string projectArg = projectName;
  projectArg += ".xcode";
303
  if (this->XcodeVersion > 20) {
304
    projectArg += "proj";
305
  }
306
  makeCommand.push_back(projectArg);
307

308
  bool clean = false;
309
  std::string realTarget = targetName;
310
  if (realTarget == "clean") {
311
    clean = true;
312
    realTarget = "ALL_BUILD";
313
314
  }
  if (clean) {
315
    makeCommand.push_back("clean");
316
  } else {
317
    makeCommand.push_back("build");
318
  }
319
  makeCommand.push_back("-target");
320
  if (!realTarget.empty()) {
321
    makeCommand.push_back(realTarget);
322
  } else {
323
    makeCommand.push_back("ALL_BUILD");
324
325
  }
  if (this->XcodeVersion == 15) {
326
327
    makeCommand.push_back("-buildstyle");
    makeCommand.push_back("Development");
328
  } else {
329
    makeCommand.push_back("-configuration");
330
331
332
333
    makeCommand.push_back(!config.empty() ? config : "Debug");
  }
  makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                     makeOptions.end());
Bill Hoffman's avatar
Bill Hoffman committed
334
335
336
}

///! Create a local generator appropriate to this Global Generator
337
cmLocalGenerator* cmGlobalXCodeGenerator::CreateLocalGenerator(cmMakefile* mf)
Bill Hoffman's avatar
Bill Hoffman committed
338
{
339
  return new cmLocalXCodeGenerator(this, mf);
Bill Hoffman's avatar
Bill Hoffman committed
340
341
}

342
343
void cmGlobalXCodeGenerator::AddExtraIDETargets()
{
344
  std::map<std::string, std::vector<cmLocalGenerator*> >::iterator it;
345
346
  // make sure extra targets are added before calling
  // the parent generate which will call trace depends
347
  for (it = this->ProjectMap.begin(); it != this->ProjectMap.end(); ++it) {
348
    cmLocalGenerator* root = it->second[0];
349
    this->SetGenerationRoot(root);
350
351
    // add ALL_BUILD, INSTALL, etc
    this->AddExtraTargets(root, it->second);
352
  }
353
354
355
356
}

void cmGlobalXCodeGenerator::Generate()
{
357
  this->cmGlobalGenerator::Generate();
358
  if (cmSystemTools::GetErrorOccuredFlag()) {
359
    return;
360
  }
361
  std::map<std::string, std::vector<cmLocalGenerator*> >::iterator it;
362
  for (it = this->ProjectMap.begin(); it != this->ProjectMap.end(); ++it) {
363
    cmLocalGenerator* root = it->second[0];
364
    this->SetGenerationRoot(root);
365
366
    // now create the project
    this->OutputXCodeProject(root, it->second);
367
  }
368
}
369

370
371
void cmGlobalXCodeGenerator::SetGenerationRoot(cmLocalGenerator* root)
{
372
  this->CurrentProject = root->GetProjectName();
373
  this->SetCurrentLocalGenerator(root);
374
  cmSystemTools::SplitPath(
375
376
    this->CurrentLocalGenerator->GetCurrentSourceDirectory(),
    this->ProjectSourceDirectoryComponents);
377
  cmSystemTools::SplitPath(
378
379
    this->CurrentLocalGenerator->GetCurrentBinaryDirectory(),
    this->ProjectOutputDirectoryComponents);
380

381
  this->CurrentXCodeHackMakefile = root->GetCurrentBinaryDirectory();
382
383
384
385
386
  this->CurrentXCodeHackMakefile += "/CMakeScripts";
  cmSystemTools::MakeDirectory(this->CurrentXCodeHackMakefile.c_str());
  this->CurrentXCodeHackMakefile += "/XCODE_DEPEND_HELPER.make";
}

387
388
std::string cmGlobalXCodeGenerator::PostBuildMakeTarget(
  std::string const& tName, std::string const& configName)
389
{
390
  std::string target = tName;
391
  std::replace(target.begin(), target.end(), ' ', '_');
392
  std::string out = "PostBuild." + target;
393
  if (this->XcodeVersion > 20) {
394
    out += "." + configName;
395
  }
396
397
398
  return out;
}

399
400
#define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK"

401
402
void cmGlobalXCodeGenerator::AddExtraTargets(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens)
403
404
{
  cmMakefile* mf = root->GetMakefile();
405

406
  // Add ALL_BUILD
407
  const char* no_working_directory = 0;
408
  std::vector<std::string> no_depends;
409
410
411
  cmTarget* allbuild =
    mf->AddUtilityCommand("ALL_BUILD", true, no_depends, no_working_directory,
                          "echo", "Build all projects");
412

413
  cmGeneratorTarget* allBuildGt = new cmGeneratorTarget(allbuild, root);
414
  root->AddGeneratorTarget(allBuildGt);
415

416
  // Refer to the main build configuration file for easy editing.
417
  std::string listfile = root->GetCurrentSourceDirectory();
418
419
  listfile += "/";
  listfile += "CMakeLists.txt";
420
  allBuildGt->AddSource(listfile);
421

422
  // Add XCODE depend helper
423
  std::string dir = root->GetCurrentBinaryDirectory();
424
  cmCustomCommandLine makeHelper;
425
  if (this->XcodeVersion < 50) {
426
427
    makeHelper.push_back("make");
    makeHelper.push_back("-C");
428
    makeHelper.push_back(dir);
429
    makeHelper.push_back("-f");
430
    makeHelper.push_back(this->CurrentXCodeHackMakefile);
431
    makeHelper.push_back(""); // placeholder, see below
432
  }
433

434
435
  // Add ZERO_CHECK
  bool regenerate = !mf->IsOn("CMAKE_SUPPRESS_REGENERATION");
436
  if (regenerate) {
437
    this->CreateReRunCMakeFile(root, gens);
438
439
    std::string file =
      this->ConvertToRelativeForMake(this->CurrentReRunCMakeMakefile.c_str());
440
    cmSystemTools::ReplaceString(file, "\\ ", " ");
441
442
443
    cmTarget* check =
      mf->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, true, no_depends,
                            no_working_directory, "make", "-f", file.c_str());
444

445
    cmGeneratorTarget* checkGt = new cmGeneratorTarget(check, root);
446
    root->AddGeneratorTarget(checkGt);
447
  }
448

449
450
  // now make the allbuild depend on all the non-utility targets
  // in the project
451
452
  for (std::vector<cmLocalGenerator*>::iterator i = gens.begin();
       i != gens.end(); ++i) {
453
    cmLocalGenerator* lg = *i;
454
    if (this->IsExcluded(root, *i)) {
455
      continue;
456
    }
457

Stephen Kelly's avatar
Stephen Kelly committed
458
    std::vector<cmGeneratorTarget*> tgts = lg->GetGeneratorTargets();
459
460
    for (std::vector<cmGeneratorTarget*>::iterator l = tgts.begin();
         l != tgts.end(); l++) {
461
      cmGeneratorTarget* target = *l;
462

463
      if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
464
        continue;
465
      }
466

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

469
      if (regenerate && (targetName != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) {
470
        target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET);
471
      }
472

473
      // make all exe, shared libs and modules
474
475
476
      // 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
477
      if (!makeHelper.empty() &&
478
          (target->GetType() == cmStateEnums::EXECUTABLE ||
479
           // Nope - no post-build for OBJECT_LIRBRARY
480
481
482
483
           //          target->GetType() == cmStateEnums::OBJECT_LIBRARY ||
           target->GetType() == cmStateEnums::STATIC_LIBRARY ||
           target->GetType() == cmStateEnums::SHARED_LIBRARY ||
           target->GetType() == cmStateEnums::MODULE_LIBRARY)) {
484
        makeHelper[makeHelper.size() - 1] = // fill placeholder
485
          this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)");
486
        cmCustomCommandLines commandLines;
487
        commandLines.push_back(makeHelper);
488
        std::vector<std::string> no_byproducts;
489
490
491
492
        lg->GetMakefile()->AddCustomCommandToTarget(
          target->GetName(), no_byproducts, no_depends, commandLines,
          cmTarget::POST_BUILD, "Depend check for xcode", dir.c_str());
      }
493

494
      if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
495
          !target->GetPropertyAsBool("EXCLUDE_FROM_ALL")) {
496
        allbuild->AddUtility(target->GetName());
497
      }
498
499

      // Refer to the build configuration file for easy editing.
500
      listfile = lg->GetCurrentSourceDirectory();
501
502
      listfile += "/";
      listfile += "CMakeLists.txt";
503
      target->AddSource(listfile);
Bill Hoffman's avatar
Bill Hoffman committed
504
    }
505
  }
Bill Hoffman's avatar
Bill Hoffman committed
506
507
}

508
509
void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*> const& gens)
510
{
511
  std::vector<std::string> lfiles;
512
513
  for (std::vector<cmLocalGenerator*>::const_iterator gi = gens.begin();
       gi != gens.end(); ++gi) {
514
515
    std::vector<std::string> const& lf = (*gi)->GetMakefile()->GetListFiles();
    lfiles.insert(lfiles.end(), lf.begin(), lf.end());
516
  }
517

518
  // sort the array
519
520
  std::sort(lfiles.begin(), lfiles.end(), std::less<std::string>());
  std::vector<std::string>::iterator new_end =
521
522
    std::unique(lfiles.begin(), lfiles.end());
  lfiles.erase(new_end, lfiles.end());
523
  this->CurrentReRunCMakeMakefile = root->GetCurrentBinaryDirectory();
Ken Martin's avatar
Ken Martin committed
524
525
526
  this->CurrentReRunCMakeMakefile += "/CMakeScripts";
  cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile.c_str());
  this->CurrentReRunCMakeMakefile += "/ReRunCMake.make";
527
528
  cmGeneratedFileStream makefileStream(
    this->CurrentReRunCMakeMakefile.c_str());
529
  makefileStream.SetCopyIfDifferent(true);
530
531
532
533
534
535
  makefileStream << "# Generated by CMake, DO NOT EDIT\n\n";

  makefileStream << "empty:= \n";
  makefileStream << "space:= $(empty) $(empty)\n";
  makefileStream << "spaceplus:= $(empty)\\ $(empty)\n\n";

536
537
  for (std::vector<std::string>::const_iterator i = lfiles.begin();
       i != lfiles.end(); ++i) {
538
    makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
539
540
                   << this->ConvertToRelativeForMake(i->c_str()) << "))\n";
  }
541
542
543
544
545
546

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

547
548
  makefileStream << "\n"
                 << this->ConvertToRelativeForMake(checkCache.c_str())
549
                 << ": $(TARGETS)\n";
550
551
552
553
554
555
556
557
  makefileStream << "\t"
                 << this->ConvertToRelativeForMake(
                      cmSystemTools::GetCMakeCommand().c_str())
                 << " -H"
                 << this->ConvertToRelativeForMake(root->GetSourceDirectory())
                 << " -B"
                 << this->ConvertToRelativeForMake(root->GetBinaryDirectory())
                 << "\n";
558
559
}

Gregor Jasny's avatar
Gregor Jasny committed
560
561
562
563
564
565
566
567
568
569
570
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
571
572
void cmGlobalXCodeGenerator::ClearXCodeObjects()
{
Ken Martin's avatar
Ken Martin committed
573
  this->TargetDoneSet.clear();
574
  for (unsigned int i = 0; i < this->XCodeObjects.size(); ++i) {
Ken Martin's avatar
Ken Martin committed
575
    delete this->XCodeObjects[i];
576
  }
Ken Martin's avatar
Ken Martin committed
577
  this->XCodeObjects.clear();
578
  this->XCodeObjectIDs.clear();
579
  this->XCodeObjectMap.clear();
Ken Martin's avatar
Ken Martin committed
580
581
582
  this->GroupMap.clear();
  this->GroupNameMap.clear();
  this->TargetGroup.clear();
583
  this->FileRefs.clear();
Bill Hoffman's avatar
Bill Hoffman committed
584
585
}

586
void cmGlobalXCodeGenerator::addObject(cmXCodeObject* obj)
587
{
588
  if (obj->GetType() == cmXCodeObject::OBJECT) {
589
    std::string id = obj->GetId();
590
591
592

    // If this is a duplicate id, it's an error:
    //
593
    if (this->XCodeObjectIDs.count(id)) {
594
595
      cmSystemTools::Error(
        "Xcode generator: duplicate object ids not allowed");
596
    }
597
598

    this->XCodeObjectIDs.insert(id);
599
  }
600
601
602
603

  this->XCodeObjects.push_back(obj);
}

604
605
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(
  cmXCodeObject::PBXType ptype)
Bill Hoffman's avatar
Bill Hoffman committed
606
{
607
  cmXCodeObject* obj;
608
  if (this->XcodeVersion == 15) {
609
    obj = new cmXCodeObject(ptype, cmXCodeObject::OBJECT);
610
  } else {
611
    obj = new cmXCode21Object(ptype, cmXCodeObject::OBJECT);
612
  }
613
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
614
615
616
  return obj;
}

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

624
cmXCodeObject* cmGlobalXCodeGenerator::CreateString(const std::string& s)
Bill Hoffman's avatar
Bill Hoffman committed
625
626
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING);
627
628
629
  obj->SetString(s);
  return obj;
}
630

631
632
cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference(
  cmXCodeObject* ref)
Bill Hoffman's avatar
Bill Hoffman committed
633
634
635
636
637
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF);
  obj->SetObject(ref);
  return obj;
}
638

639
cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig)
640
641
642
643
644
645
{
  cmXCodeObject* obj = this->CreateObject(orig->GetType());
  obj->CopyAttributes(orig);
  return obj;
}

646
647
std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target,
                                   const std::string& fullpath)
648
{
649
  std::string key(target->GetName());
650
  key += "-";
651
  key += fullpath;
652
653
654
  return key;
}

655
std::string GetGroupMapKey(cmGeneratorTarget* target, cmSourceFile* sf)
656
{
657
  return GetGroupMapKeyFromPath(target, sf->GetFullPath());
658
659
}

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

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

  return buildFile;
}

677
678
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
  cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
Bill Hoffman's avatar
Bill Hoffman committed
679
{
680
  // Add flags from target and source file properties.
Bill Hoffman's avatar
Bill Hoffman committed
681
  std::string flags;
682
  const char* srcfmt = sf->GetProperty("Fortran_FORMAT");
683
  switch (cmOutputConverter::GetFortranFormat(srcfmt)) {
684
    case cmOutputConverter::FortranFormatFixed:
685
686
      flags = "-fixed " + flags;
      break;
687
    case cmOutputConverter::FortranFormatFree:
688
689
690
691
692
      flags = "-free " + flags;
      break;
    default:
      break;
  }
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
  if (const char* cflags = sf->GetProperty("COMPILE_FLAGS")) {
    cmGeneratorExpression ge;
    std::string configName = "NO-PER-CONFIG-SUPPORT-IN-XCODE";
    CM_AUTO_PTR<cmCompiledGeneratorExpression> compiledExpr = ge.Parse(cflags);
    const char* processed = compiledExpr->Evaluate(lg, configName);
    if (compiledExpr->GetHadContextSensitiveCondition()) {
      std::ostringstream e;
      /* clang-format off */
      e <<
        "Xcode does not support per-config per-source COMPILE_FLAGS:\n"
        "  " << cflags << "\n"
        "specified for source:\n"
        "  " << sf->GetFullPath() << "\n";
      /* clang-format on */
      lg->IssueMessage(cmake::FATAL_ERROR, e.str());
    }
    lg->AppendFlags(flags, processed);
  }
711

712
  // Add per-source definitions.
713
  BuildObjectListOrString flagsBuild(this, false);
714
715
716
717
  this->AppendDefines(flagsBuild, sf->GetProperty("COMPILE_DEFINITIONS"),
                      true);
  if (!flagsBuild.IsEmpty()) {
    if (!flags.empty()) {
718
719
      flags += ' ';
    }
720
721
    flags += flagsBuild.GetString();
  }
722

723
  std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);
724

725
  cmXCodeObject* buildFile =
726
    this->CreateXCodeSourceFileFromPath(sf->GetFullPath(), gtgt, lang, sf);
727
  cmXCodeObject* fileRef = buildFile->GetObject("fileRef")->GetObject();
728

729
  cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
730
731
  settings->AddAttributeIfNotEmpty("COMPILER_FLAGS",
                                   this->CreateString(flags));
732
733
734

  // Is this a resource file in this target? Add it to the resources group...
  //
735

736
  cmGeneratorTarget::SourceFileFlags tsFlags =
737
    gtgt->GetTargetSourceFileFlags(sf);
738
  bool isResource = tsFlags.Type == cmGeneratorTarget::SourceFileTypeResource;
739

740
741
  cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);

742
743
744
  // Is this a "private" or "public" framework header file?
  // Set the ATTRIBUTES attribute appropriately...
  //
745
746
  if (gtgt->IsFrameworkOnApple()) {
    if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) {
747
748
      attrs->AddObject(this->CreateString("Private"));
      isResource = true;
749
    } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) {
750
751
752
      attrs->AddObject(this->CreateString("Public"));
      isResource = true;
    }
753
  }
754

755
756
757
758
759
760
761
762
763
764
765
766
767
768
  // 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.
    for (std::vector<std::string>::const_iterator ai = attributes.begin();
         ai != attributes.end(); ++ai) {
      attrs->AddObject(this->CreateString(*ai));
    }
  }

769
770
  settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);

771
772
773
  // Add the fileRef to the top level Resources group/folder if it is not
  // already there.
  //
774
775
  if (isResource && this->ResourcesGroupChildren &&
      !this->ResourcesGroupChildren->HasObject(fileRef)) {
776
    this->ResourcesGroupChildren->AddObject(fileRef);
777
  }
778

779
  buildFile->AddAttributeIfNotEmpty("settings", settings);
780
781
782
  return buildFile;
}

783
784
785
std::string GetSourcecodeValueFromFileExtension(const std::string& _ext,
                                                const std::string& lang,
                                                bool& keepLastKnownFileType)
786
{
787
  std::string ext = cmSystemTools::LowerCase(_ext);
Bill Hoffman's avatar
Bill Hoffman committed
788
  std::string sourcecode = "sourcecode";
789

790
  if (ext == "o") {
791
    sourcecode = "compiled.mach-o.objfile";
792
  } else if (ext == "xctest") {
793
    sourcecode = "wrapper.cfbundle";
794
  } else if (ext == "xib") {
Ruslan Baratov's avatar
Ruslan Baratov committed
795
    keepLastKnownFileType = true;
796
    sourcecode = "file.xib";
797
  } else if (ext == "storyboard") {
Ruslan Baratov's avatar
Ruslan Baratov committed
798
    keepLastKnownFileType = true;
799
    sourcecode = "file.storyboard";
800
  } else if (ext == "mm") {
801
    sourcecode += ".cpp.objcpp";
802
  } else if (ext == "m") {
803
    sourcecode += ".c.objc";
804
  } else if (ext == "swift") {
805
    sourcecode += ".swift";
806
  } else if (ext == "plist") {
807
    sourcecode += ".text.plist";
808
  } else if (ext == "h") {
809
    sourcecode += ".c.h";
810
811
  } else if (ext == "hxx" || ext == "hpp" || ext == "txx" || ext == "pch" ||
             ext == "hh") {
812
    sourcecode += ".cpp.h";
813
  } else if (ext == "png" || ext == "gif" || ext == "jpg") {
Ruslan Baratov's avatar
Ruslan Baratov committed