cmGlobalXCodeGenerator.cxx 130 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"
4

5
#include "cmsys/RegularExpression.hxx"
6 7 8 9 10 11
#include <assert.h>
#include <iomanip>
#include <sstream>
#include <stdio.h>
#include <string.h>

12 13
#include "cm_memory.hxx"

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

39
struct cmLinkImplementation;
40

41
#if !defined(CMAKE_BOOTSTRAP) && defined(__APPLE__)
42 43
#  define HAVE_APPLICATION_SERVICES
#  include <ApplicationServices/ApplicationServices.h>
44 45
#endif

46
#if !defined(CMAKE_BOOTSTRAP)
47
#  include "cmXMLParser.h"
48 49 50 51 52 53

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

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

public:
92 93
  BuildObjectListOrString(cmGlobalXCodeGenerator* gen, bool buildObjectList)
    : Generator(gen)
94
    , Group(nullptr)
95 96 97
    , Empty(true)
  {
    if (buildObjectList) {
98 99
      this->Group = this->Generator->CreateObject(cmXCodeObject::OBJECT_LIST);
    }
100
  }
101 102 103

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

104
  void Add(const std::string& newString)
105
  {
106 107
    this->Empty = false;

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

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

118 119 120
  cmXCodeObject* CreateList()
  {
    if (this->Group) {
121 122
      return this->Group;
    }
123
    return this->Generator->CreateString(this->String);
124
  }
125 126
};

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

133
  void GetDocumentation(cmDocumentationEntry& entry) const override
134 135 136
  {
    cmGlobalXCodeGenerator::GetDocumentation(entry);
  }
137

138
  std::vector<std::string> GetGeneratorNames() const override
139
  {
140
    std::vector<std::string> names;
141
    names.push_back(cmGlobalXCodeGenerator::GetActualName());
142 143 144 145 146 147
    return names;
  }

  std::vector<std::string> GetGeneratorNamesWithPlatform() const override
  {
    return std::vector<std::string>();
148
  }
149

150 151
  bool SupportsToolset() const override { return true; }
  bool SupportsPlatform() const override { return false; }
152 153 154 155 156

  std::vector<std::string> GetKnownPlatforms() const override
  {
    return std::vector<std::string>();
  }
157 158

  std::string GetDefaultPlatformName() const override { return std::string(); }
159 160
};

161 162
cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(
  cmake* cm, std::string const& version_string, unsigned int version_number)
163
  : cmGlobalGenerator(cm)
Bill Hoffman's avatar
Bill Hoffman committed
164
{
165 166
  this->VersionString = version_string;
  this->XcodeVersion = version_number;
167

168 169 170 171
  this->RootObject = nullptr;
  this->MainGroupChildren = nullptr;
  this->CurrentMakefile = nullptr;
  this->CurrentLocalGenerator = nullptr;
172
  this->XcodeBuildCommandInitialized = false;
173

174
  this->ObjectDirArchDefault = "$(CURRENT_ARCH)";
175
  this->ObjectDirArch = this->ObjectDirArchDefault;
176

177
  cm->GetState()->SetIsGeneratorMultiConfig(true);
178 179
}

180 181 182 183 184
cmGlobalGeneratorFactory* cmGlobalXCodeGenerator::NewFactory()
{
  return new Factory;
}

185 186
cmGlobalGenerator* cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(
  const std::string& name, cmake* cm) const
187
{
188
  if (name != GetActualName()) {
189
    return nullptr;
190
  }
191
#if !defined(CMAKE_BOOTSTRAP)
192
  cmXcodeVersionParser parser;
193 194
  std::string versionFile;
  {
195
    std::string out;
196 197 198 199 200 201 202 203
    bool commandResult = cmSystemTools::RunSingleCommand(
      "xcode-select --print-path", &out, nullptr, nullptr, nullptr,
      cmSystemTools::OUTPUT_NONE);
    if (commandResult) {
      std::string::size_type pos = out.find(".app/");
      if (pos != std::string::npos) {
        versionFile = out.substr(0, pos + 5) + "Contents/version.plist";
      }
204 205
    }
  }
206
  if (!versionFile.empty() && cmSystemTools::FileExists(versionFile)) {
207
    parser.ParseFile(versionFile.c_str());
208 209 210 211 212 213 214
  } 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");
  }
215 216 217 218 219 220 221
  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];

222
  if (version_number < 50) {
223
    cm->IssueMessage(MessageType::FATAL_ERROR,
224
                     "Xcode " + version_string + " not supported.");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
225
    return nullptr;
226 227
  }

228 229
  auto gg = cm::make_unique<cmGlobalXCodeGenerator>(cm, version_string,
                                                    version_number);
230
  return gg.release();
231
#else
232
  std::cerr << "CMake should be built with cmake to use Xcode, "
233
               "default to Xcode 1.5\n";
234
  return new cmGlobalXCodeGenerator(cm);
235
#endif
Bill Hoffman's avatar
Bill Hoffman committed
236 237
}

238
bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf)
239 240 241 242
{
  // 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.
243
  if (cmSystemTools::IsOff(mf->GetDefinition("CMAKE_MAKE_PROGRAM"))) {
244
    mf->AddDefinition("CMAKE_MAKE_PROGRAM", this->GetXcodeBuildCommand());
245
  }
246
  return true;
247 248
}

249 250
std::string const& cmGlobalXCodeGenerator::GetXcodeBuildCommand()
{
251
  if (!this->XcodeBuildCommandInitialized) {
252 253
    this->XcodeBuildCommandInitialized = true;
    this->XcodeBuildCommand = this->FindXcodeBuildCommand();
254
  }
255 256 257 258 259
  return this->XcodeBuildCommand;
}

std::string cmGlobalXCodeGenerator::FindXcodeBuildCommand()
{
260 261 262
  std::string makeProgram = cmSystemTools::FindProgram("xcodebuild");
  if (makeProgram.empty()) {
    makeProgram = "xcodebuild";
263
  }
264
  return makeProgram;
265 266
}

267 268
bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts,
                                                 cmMakefile* mf)
269
{
270
  if (ts.find_first_of(",=") != std::string::npos) {
271 272 273 274 275 276 277 278 279
    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 */
280
    mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
281 282 283 284
    return false;
  }
  this->GeneratorToolset = ts;
  if (!this->GeneratorToolset.empty()) {
285
    mf->AddDefinition("CMAKE_XCODE_PLATFORM_TOOLSET", this->GeneratorToolset);
286
  }
287
  return true;
288 289
}

290 291
void cmGlobalXCodeGenerator::EnableLanguage(
  std::vector<std::string> const& lang, cmMakefile* mf, bool optional)
292
{
293
  mf->AddDefinition("XCODE", "1");
294
  mf->AddDefinition("XCODE_VERSION", this->VersionString);
295 296 297 298 299 300 301
  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);
302
  }
Bill Hoffman's avatar
Bill Hoffman committed
303
  mf->AddDefinition("CMAKE_GENERATOR_NO_COMPILER_ENV", "1");
304
  this->cmGlobalGenerator::EnableLanguage(lang, mf, optional);
305
  this->ComputeArchitectures(mf);
Bill Hoffman's avatar
Bill Hoffman committed
306 307
}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
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;
}

337 338 339 340 341 342 343
std::vector<cmGlobalGenerator::GeneratedMakeCommand>
cmGlobalXCodeGenerator::GenerateBuildCommand(
  const std::string& makeProgram, const std::string& projectName,
  const std::string& /*projectDir*/,
  std::vector<std::string> const& targetNames, const std::string& config,
  bool /*fast*/, int jobs, bool /*verbose*/,
  std::vector<std::string> const& makeOptions)
Bill Hoffman's avatar
Bill Hoffman committed
344
{
345
  GeneratedMakeCommand makeCommand;
346
  // now build the test
347
  makeCommand.Add(
348
    this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand()));
349

350
  if (!projectName.empty()) {
351
    makeCommand.Add("-project");
352 353 354
    std::string projectArg = projectName;
    projectArg += ".xcode";
    projectArg += "proj";
355
    makeCommand.Add(projectArg);
356
  }
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
  if (std::find(targetNames.begin(), targetNames.end(), "clean") !=
      targetNames.end()) {
    makeCommand.Add("clean");
    makeCommand.Add("-target", "ALL_BUILD");
  } else {
    makeCommand.Add("build");
    if (targetNames.empty() ||
        ((targetNames.size() == 1) && targetNames.front().empty())) {
      makeCommand.Add("-target", "ALL_BUILD");
    } else {
      for (const auto& tname : targetNames) {
        if (!tname.empty()) {
          makeCommand.Add("-target", tname);
        }
      }
    }
373
  }
374

375
  makeCommand.Add("-configuration", (config.empty() ? "Debug" : config));
376 377

  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
378
    makeCommand.Add("-jobs");
379
    if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
380
      makeCommand.Add(std::to_string(jobs));
381 382 383
    }
  }

384
  if (this->XcodeVersion >= 70) {
385
    makeCommand.Add("-hideShellScriptEnvironment");
386
  }
387
  makeCommand.Add(makeOptions.begin(), makeOptions.end());
388
  return { std::move(makeCommand) };
Bill Hoffman's avatar
Bill Hoffman committed
389 390
}

391
//! Create a local generator appropriate to this Global Generator
392
cmLocalGenerator* cmGlobalXCodeGenerator::CreateLocalGenerator(cmMakefile* mf)
Bill Hoffman's avatar
Bill Hoffman committed
393
{
394
  return new cmLocalXCodeGenerator(this, mf);
Bill Hoffman's avatar
Bill Hoffman committed
395 396
}

397 398
void cmGlobalXCodeGenerator::AddExtraIDETargets()
{
399 400
  // make sure extra targets are added before calling
  // the parent generate which will call trace depends
401 402
  for (auto keyVal : this->ProjectMap) {
    cmLocalGenerator* root = keyVal.second[0];
403
    this->SetGenerationRoot(root);
404
    // add ALL_BUILD, INSTALL, etc
405
    this->AddExtraTargets(root, keyVal.second);
406
  }
407 408
}

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
void cmGlobalXCodeGenerator::ComputeTargetOrder()
{
  size_t index = 0;
  auto const& lgens = this->GetLocalGenerators();
  for (cmLocalGenerator* lgen : lgens) {
    auto const& targets = lgen->GetGeneratorTargets();
    for (cmGeneratorTarget const* gt : targets) {
      this->ComputeTargetOrder(gt, index);
    }
  }
  assert(index == this->TargetOrderIndex.size());
}

void cmGlobalXCodeGenerator::ComputeTargetOrder(cmGeneratorTarget const* gt,
                                                size_t& index)
{
  std::map<cmGeneratorTarget const*, size_t>::value_type value(gt, 0);
  auto insertion = this->TargetOrderIndex.insert(value);
  if (!insertion.second) {
    return;
  }
  auto entry = insertion.first;

  auto& deps = this->GetTargetDirectDepends(gt);
  for (auto& d : deps) {
    this->ComputeTargetOrder(d, index);
  }

  entry->second = index++;
}

440 441
void cmGlobalXCodeGenerator::Generate()
{
442
  this->cmGlobalGenerator::Generate();
443
  if (cmSystemTools::GetErrorOccuredFlag()) {
444
    return;
445
  }
446 447 448

  this->ComputeTargetOrder();

449 450
  for (auto keyVal : this->ProjectMap) {
    cmLocalGenerator* root = keyVal.second[0];
451 452 453 454 455 456 457 458 459 460 461

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

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

462
    this->SetGenerationRoot(root);
463
    // now create the project
464
    this->OutputXCodeProject(root, keyVal.second);
465
  }
466
}
467

468 469
void cmGlobalXCodeGenerator::SetGenerationRoot(cmLocalGenerator* root)
{
470
  this->CurrentProject = root->GetProjectName();
471
  this->SetCurrentLocalGenerator(root);
472
  cmSystemTools::SplitPath(
473 474
    this->CurrentLocalGenerator->GetCurrentSourceDirectory(),
    this->ProjectSourceDirectoryComponents);
475
  cmSystemTools::SplitPath(
476 477
    this->CurrentLocalGenerator->GetCurrentBinaryDirectory(),
    this->ProjectOutputDirectoryComponents);
478

479
  this->CurrentXCodeHackMakefile = root->GetCurrentBinaryDirectory();
480
  this->CurrentXCodeHackMakefile += "/CMakeScripts";
481
  cmSystemTools::MakeDirectory(this->CurrentXCodeHackMakefile);
482 483 484
  this->CurrentXCodeHackMakefile += "/XCODE_DEPEND_HELPER.make";
}

485 486
std::string cmGlobalXCodeGenerator::PostBuildMakeTarget(
  std::string const& tName, std::string const& configName)
487
{
488
  std::string target = tName;
489
  std::replace(target.begin(), target.end(), ' ', '_');
490
  std::string out = "PostBuild." + target;
491
  out += "." + configName;
492 493 494
  return out;
}

495
#define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK"
496
#define OBJECT_LIBRARY_ARTIFACT_DIR std::string()
497

498 499
void cmGlobalXCodeGenerator::AddExtraTargets(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens)
500 501
{
  cmMakefile* mf = root->GetMakefile();
502

503
  // Add ALL_BUILD
504
  const char* no_working_directory = nullptr;
505
  std::vector<std::string> no_depends;
506 507 508
  cmTarget* allbuild = mf->AddUtilityCommand(
    "ALL_BUILD", cmMakefile::TargetOrigin::Generator, true, no_depends,
    no_working_directory, "echo", "Build all projects");
509

510
  cmGeneratorTarget* allBuildGt = new cmGeneratorTarget(allbuild, root);
511
  root->AddGeneratorTarget(allBuildGt);
512

513
  // Add XCODE depend helper
514
  std::string dir = root->GetCurrentBinaryDirectory();
515
  cmCustomCommandLine makeHelper;
516 517 518 519 520
  makeHelper.push_back("make");
  makeHelper.push_back("-C");
  makeHelper.push_back(dir);
  makeHelper.push_back("-f");
  makeHelper.push_back(this->CurrentXCodeHackMakefile);
521
  makeHelper.push_back("OBJDIR=$(OBJDIR)");
522
  makeHelper.push_back(""); // placeholder, see below
523

524
  // Add ZERO_CHECK
525
  bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION");
526 527 528 529 530
  bool generateTopLevelProjectOnly =
    mf->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY");
  bool isTopLevel =
    !root->GetStateSnapshot().GetBuildsystemDirectoryParent().IsValid();
  if (regenerate && (isTopLevel || !generateTopLevelProjectOnly)) {
531
    this->CreateReRunCMakeFile(root, gens);
532
    std::string file =
533
      this->ConvertToRelativeForMake(this->CurrentReRunCMakeMakefile);
534
    cmSystemTools::ReplaceString(file, "\\ ", " ");
535 536 537
    cmTarget* check = mf->AddUtilityCommand(
      CMAKE_CHECK_BUILD_SYSTEM_TARGET, cmMakefile::TargetOrigin::Generator,
      true, no_depends, no_working_directory, "make", "-f", file.c_str());
538

539
    cmGeneratorTarget* checkGt = new cmGeneratorTarget(check, root);
540
    root->AddGeneratorTarget(checkGt);
541
  }
542

543 544
  // now make the allbuild depend on all the non-utility targets
  // in the project
545
  for (auto& gen : gens) {
546
    for (auto target : gen->GetGeneratorTargets()) {
547
      if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
548
        continue;
549
      }
550

551
      std::string targetName = target->GetName();
552

553
      if (regenerate && (targetName != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) {
554
        target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET);
555
      }
556

557
      // make all exe, shared libs and modules
558 559 560
      // 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
561
      if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
562
        makeHelper.back() = // fill placeholder
563
          this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)");
564
        cmCustomCommandLines commandLines;
565
        commandLines.push_back(makeHelper);
566
        std::vector<std::string> no_byproducts;
567
        gen->GetMakefile()->AddCustomCommandToTarget(
568
          target->GetName(), no_byproducts, no_depends, commandLines,
569
          cmTarget::POST_BUILD, "Depend check for xcode", dir.c_str(), true,
570
          false, "", "", false, cmMakefile::AcceptObjectLibraryCommands);
571
      }
572

573
      if (!this->IsExcluded(target)) {
574
        allbuild->AddUtility(target->GetName());
575
      }
Bill Hoffman's avatar
Bill Hoffman committed
576
    }
577
  }
Bill Hoffman's avatar
Bill Hoffman committed
578 579
}

580 581
void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*> const& gens)
582
{
583
  std::vector<std::string> lfiles;
584
  for (auto gen : gens) {
585
    cmAppend(lfiles, gen->GetMakefile()->GetListFiles());
586
  }
587

588
  // sort the array
589 590
  std::sort(lfiles.begin(), lfiles.end(), std::less<std::string>());
  std::vector<std::string>::iterator new_end =
591 592
    std::unique(lfiles.begin(), lfiles.end());
  lfiles.erase(new_end, lfiles.end());
593 594 595 596 597 598

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

599
  this->CurrentReRunCMakeMakefile = root->GetCurrentBinaryDirectory();
600
  this->CurrentReRunCMakeMakefile += "/CMakeScripts";
601
  cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile);
602
  this->CurrentReRunCMakeMakefile += "/ReRunCMake.make";
603
  cmGeneratedFileStream makefileStream(this->CurrentReRunCMakeMakefile);
604
  makefileStream.SetCopyIfDifferent(true);
605 606
  makefileStream << "# Generated by CMake, DO NOT EDIT\n\n";

607
  makefileStream << "TARGETS:= \n";
608 609 610 611
  makefileStream << "empty:= \n";
  makefileStream << "space:= $(empty) $(empty)\n";
  makefileStream << "spaceplus:= $(empty)\\ $(empty)\n\n";

612
  for (const auto& lfile : lfiles) {
613
    makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
614
                   << this->ConvertToRelativeForMake(lfile) << "))\n";
615
  }
616
  makefileStream << "\n";
617 618 619

  std::string checkCache = root->GetBinaryDirectory();
  checkCache += "/";
620
  checkCache += "CMakeFiles/";
621 622
  checkCache += "cmake.check_cache";

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
  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)
638
                 << ": $(TARGETS)\n";
639 640
  makefileStream << "\t"
                 << this->ConvertToRelativeForMake(
641
                      cmSystemTools::GetCMakeCommand())
642 643 644 645 646
                 << " -H"
                 << this->ConvertToRelativeForMake(root->GetSourceDirectory())
                 << " -B"
                 << this->ConvertToRelativeForMake(root->GetBinaryDirectory())
                 << "\n";
647 648
}

649 650 651 652 653 654 655 656 657 658 659
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
660 661
void cmGlobalXCodeGenerator::ClearXCodeObjects()
{
662
  this->TargetDoneSet.clear();
663 664
  for (auto& obj : this->XCodeObjects) {
    delete obj;
665
  }
666
  this->XCodeObjects.clear();
667
  this->XCodeObjectIDs.clear();
668
  this->XCodeObjectMap.clear();
669 670 671
  this->GroupMap.clear();
  this->GroupNameMap.clear();
  this->TargetGroup.clear();
672
  this->FileRefs.clear();
Bill Hoffman's avatar
Bill Hoffman committed
673 674
}

675
void cmGlobalXCodeGenerator::addObject(cmXCodeObject* obj)
676
{
677
  if (obj->GetType() == cmXCodeObject::OBJECT) {
678
    const std::string& id = obj->GetId();
679 680 681

    // If this is a duplicate id, it's an error:
    //
682
    if (this->XCodeObjectIDs.count(id)) {
683 684
      cmSystemTools::Error(
        "Xcode generator: duplicate object ids not allowed");
685
    }
686 687

    this->XCodeObjectIDs.insert(id);
688
  }
689 690 691 692

  this->XCodeObjects.push_back(obj);
}

693 694
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(
  cmXCodeObject::PBXType ptype)
Bill Hoffman's avatar
Bill Hoffman committed
695
{
696
  cmXCodeObject* obj = new cmXCode21Object(ptype, cmXCodeObject::OBJECT);
697
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
698 699 700
  return obj;
}

701
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(cmXCodeObject::Type type)
702
{
Bill Hoffman's avatar
Bill Hoffman committed
703
  cmXCodeObject* obj = new cmXCodeObject(cmXCodeObject::None, type);
704
  this->addObject(obj);
Bill Hoffman's avatar
Bill Hoffman committed
705 706 707
  return obj;
}

708
cmXCodeObject* cmGlobalXCodeGenerator::CreateString(const std::string& s)
Bill Hoffman's avatar
Bill Hoffman committed
709 710
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING);
711 712 713
  obj->SetString(s);
  return obj;
}
714

715 716
cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference(
  cmXCodeObject* ref)
Bill Hoffman's avatar
Bill Hoffman committed
717 718 719 720 721
{
  cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF);
  obj->SetObject(ref);
  return obj;
}
722

723
cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig)
724 725 726 727 728 729
{
  cmXCodeObject* obj = this->CreateObject(orig->GetType());
  obj->CopyAttributes(orig);
  return obj;
}

730 731
std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target,
                                   const std::string& fullpath)
732
{
733
  std::string key(target->GetName());
734
  key += "-";
735
  key += fullpath;
736 737 738
  return key;
}

739 740 741
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFileFromPath(
  const std::string& fullpath, cmGeneratorTarget* target,
  const std::string& lang, cmSourceFile* sf)
742 743 744 745 746
{
  // Using a map and the full path guarantees that we will always get the same
  // fileRef object for any given full path.
  //
  cmXCodeObject* fileRef =
747
    this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
748 749 750 751 752 753 754 755

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

  return buildFile;
}

756 757 758 759 760 761
class XCodeGeneratorExpressionInterpreter
  : public cmGeneratorExpressionInterpreter
{
public:
  XCodeGeneratorExpressionInterpreter(cmSourceFile* sourceFile,
                                      cmLocalGenerator* localGenerator,
762
                                      cmGeneratorTarget* headTarget,
763
                                      const std::string& lang)
764 765
    : cmGeneratorExpressionInterpreter(
        localGenerator, "NO-PER-CONFIG-SUPPORT-IN-XCODE", headTarget, lang)
766 767 768 769
    , SourceFile(sourceFile)
  {
  }

wahikihiki's avatar
wahikihiki committed
770 771 772 773 774
  XCodeGeneratorExpressionInterpreter(
    XCodeGeneratorExpressionInterpreter const&) = delete;
  XCodeGeneratorExpressionInterpreter& operator=(
    XCodeGeneratorExpressionInterpreter const&) = delete;

775 776
  using cmGeneratorExpressionInterpreter::Evaluate;

777 778
  const std::string& Evaluate(const char* expression,
                              const std::string& property)
779
  {
780
    const std::string& processed =
781
      this->cmGeneratorExpressionInterpreter::Evaluate(expression, property);
782
    if (this->CompiledGeneratorExpression->GetHadContextSensitiveCondition()) {
783 784 785 786 787 788 789 790
      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 */
791
      this->LocalGenerator->IssueMessage(MessageType::FATAL_ERROR, e.str());
792 793 794 795 796 797 798 799 800
    }

    return processed;
  }

private:
  cmSourceFile* SourceFile = nullptr;
};

801 802
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
  cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
Bill Hoffman's avatar
Bill Hoffman committed
803
{
804 805 806
  std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);

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

808
  // Add flags from target and source file properties.
809
  std::string flags;
810
  const char* srcfmt = sf->GetProperty("Fortran_FORMAT");
811
  switch (cmOutputConverter::GetFortranFormat(srcfmt)) {
812
    case cmOutputConverter::FortranFormatFixed:
813 814
      flags = "-fixed " + flags;
      break;
815
    case cmOutputConverter::FortranFormatFree:
816 817 818 819 820
      flags = "-free " + flags;
      break;
    default:
      break;
  }
821 822 823
  const std::string COMPILE_FLAGS("COMPILE_FLAGS");
  if (const char* cflags = sf->GetProperty(COMPILE_FLAGS)) {
    lg->AppendFlags(flags, genexInterpreter.Evaluate(cflags, COMPILE_FLAGS));
824
  }
825 826 827 828 829
  const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
  if (const char* coptions = sf->GetProperty(COMPILE_OPTIONS)) {
    lg->AppendCompileOptions(
      flags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
  }
830

831
  // Add per-source definitions.
832
  BuildObjectListOrString flagsBuild(this, false);
833 834
  const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
  if (const char* compile_defs = sf->GetProperty(COMPILE_DEFINITIONS)) {
835
    this->AppendDefines(
836 837
      flagsBuild,
      genexInterpreter.Evaluate(compile_defs, COMPILE_DEFINITIONS).c_str(),
838
      true);
839
  }
840 841
  if (!flagsBuild.IsEmpty()) {
    if (!flags.empty()) {
842 843
      flags += ' ';
    }
844 845
    flags += flagsBuild.GetString();
  }
846

847 848 849 850 851 852 853 854 855 856
  // 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));

857
  cmXCodeObject* buildFile =
858
    this->CreateXCodeSourceFileFromPath(sf->GetFullPath(), gtgt, lang, sf);
859

860
  cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
861 862
  settings->AddAttributeIfNotEmpty("COMPILER_FLAGS",
                                   this->CreateString(flags));
863

864
  cmGeneratorTarget::SourceFileFlags tsFlags =
865
    gtgt->GetTargetSourceFileFlags(sf);
866

867 868
  cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);

869 870 871
  // Is this a "private" or "public" framework header file?
  // Set the ATTRIBUTES attribute appropriately...
  //
872 873
  if (gtgt->IsFrameworkOnApple()) {
    if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) {
874
      attrs->AddObject(this->CreateString("Private"));
875
    } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) {
876 877
      attrs->AddObject(this->CreateString("Public"));
    }
878
  }
879

880 881 882 883 884
  // 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;
885
    cmExpandList(extraFileAttributes, attributes);
886 887

    // Store the attributes.
888 889
    for (const auto& attribute : attributes) {
      attrs->AddObject(this->CreateString(attribute));
890 891 892
    }
  }

893 894 895
  settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);

  buildFile->AddAttributeIfNotEmpty("settings", settings);
896 897 898
  return buildFile;
}

899 900 901 902 903 904 905 906 907 908 909 910 911
void cmGlobalXCodeGenerator::AddXCodeProjBuildRule(
  cmGeneratorTarget* target, std::vector<cmSourceFile*>& sources) const
{
  std::string listfile =
    target->GetLocalGenerator()->GetCurrentSourceDirectory();
  listfile += "/CMakeLists.txt";
  cmSourceFile* srcCMakeLists = target->Makefile->GetOrCreateSource(listfile);
  if (std::find(sources.begin(), sources.end(), srcCMakeLists) ==
      sources.end()) {
    sources.push_back(srcCMakeLists);
  }
}

912 913 914
std::string GetSourcecodeValueFromFileExtension(const std::string& _ext,
                                                const std::string& lang,
                                                bool& keepLastKnownFileType)
915
{
916
  std::string ext = cmSystemTools::LowerCase(_ext);
Bill Hoffman's avatar
Bill Hoffman committed
917
  std::string sourcecode = "sourcecode";
918

919
  if (ext == "o") {
920
    sourcecode = "compiled.mach-o.objfile";
921
  } else if (ext == "xctest") {
922
    sourcecode = "wrapper.cfbundle";
923
  } else if (ext == "xib") {
924
    keepLastKnownFileType = true;
925
    sourcecode = "file.xib";
926
  } else if (ext == "storyboard") {
927
    keepLastKnownFileType = true;
928
    sourcecode = "file.storyboard";
929
  } else if (ext == "mm") {
930
    sourcecode += ".cpp.objcpp";
931
  } else if (ext == "m") {
932
    sourcecode += ".c.objc";
933
  } else if (ext == "swift") {
934
    sourcecode += ".swift";
935
  } else if (ext == "plist") {
936
    sourcecode += ".text.plist";
937
  } else if (ext == "h") {
938
    sourcecode += ".c.h";
939 940
  } else if (ext == "hxx" || ext == "hpp" || ext == "txx" || ext == "pch" ||
             ext == "hh") {
941
    sourcecode += ".cpp.h";
942
  } else if (ext == "png" || ext == "gif" || ext == "jpg") {
943
    keepLastKnownFileType = true;
944
    sourcecode = "image";
945
  } else if (ext == "txt") {
946
    sourcecode += ".text";
947
  } else if (lang == "CXX") {
948
    sourcecode += ".cpp.cpp";
949
  } else if (lang == "C") {
950
    sourcecode += ".c.c";
951
  } else if (lang == "Fortran") {
952
    sourcecode += ".fortran.f90";
953
  } else if (lang == "ASM") {
954
    sourcecode += ".asm";
955
  } else if (ext == "metal") {
956
    sourcecode += ".metal";
957 958
  } else if (ext == "mig") {
    sourcecode += ".mig";
959 960
  }
  // else
961 962 963 964
  //  {
  //  // Already specialized above or we leave sourcecode == "sourcecode"
  //  // which is probably the most correct choice. Extensionless headers,
  //  // for example... Or file types unknown to Xcode that do not map to a
965
  //  // valid explicitFileType value.
966 967
  //  }

968 969 970
  return sourcecode;
}

971 972 973
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
  const std::string& fullpath, cmGeneratorTarget* target,
  const std::string& lang, cmSourceFile* sf)
974
{
975
  std::string key = GetGroupMapKeyFromPath(target, fullpath);
976
  cmXCodeObject* fileRef = this->FileRefs[key];
977
  if (!fileRef) {
978
    fileRef = this->CreateObject(cmXCodeObject::PBXFileReference);
979 980
    fileRef->SetComment(fullpath);
    this->FileRefs[key] = fileRef;
981
  }
982 983
  cmXCodeObject* group = this->GroupMap[key];
  cmXCodeObject* children = group->GetObject("children");
984
  if (!children->HasObject(fileRef)) {
985
    children->AddObject(fileRef);
986
  }
987 988
  fileRef->AddAttribute("fileEncoding", this->CreateString("4"));

989 990
  bool useLastKnownFileType = false;
  std::string fileType;
991 992
  if (sf) {
    if (const char* e = sf->GetProperty("XCODE_EXPLICIT_FILE_TYPE")) {
993
      fileType = e;
994
    } else if (const char* l = sf->GetProperty("XCODE_LAST_KNOWN_FILE_TYPE")) {
995 996 997
      useLastKnownFileType = true;
      fileType = l;
    }
998 999
  }
  if (fileType.empty()) {
1000 1001
    // Compute the extension without leading '.'.
    std::string ext = cmSystemTools::GetFilenameLastExtension(fullpath);
1002
    if (!ext.empty()) {
1003
      ext = ext.substr(1);
1004
    }
1005

1006 1007 1008 1009
    // If fullpath references a directory, then we need to specify
    // lastKnownFileType as folder in order for Xcode to be able to
    // open the contents of the folder.
    // (Xcode 4.6 does not like explicitFileType=folder).
1010
    if (cmSystemTools::FileIsDirectory(fullpath)) {
1011
      fileType = (ext == "xcassets" ? "folder.assetcatalog" : "folder");
1012
      useLastKnownFileType = true;
1013 1014 1015
    } else {
      fileType =
        GetSourcecodeValueFromFileExtension(ext, lang, useLastKnownFileType);
1016
    }
1017
  }
1018

1019 1020
  fileRef->AddAttribute(useLastKnownFileType ? "lastKnownFileType"
                                             : "explicitFileType",
1021 1022
                        this->CreateString(fileType));

1023
  // Store the file path relative to the top of the source tree.
1024
  std::string path = this->RelativeToSource(fullpath);
1025
  std::string name = cmSystemTools::GetFilenameName(path);
1026
  const char* sourceTree =
1027
    cmSystemTools::FileIsFullPath(path) ? "<absolute>" : "SOURCE_ROOT";
1028 1029
  fileRef->AddAttribute("name", this->CreateString(name));
  fileRef->AddAttribute("path", this->CreateString(path));
1030
  fileRef->AddAttribute("sourceTree", this->CreateString(sourceTree));
1031
  return fileRef;
Bill Hoffman's avatar
Bill Hoffman committed
1032 1033
}

1034 1035
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReference(
  cmSourceFile* sf, cmGeneratorTarget* target)
1036
{
1037
  std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);
1038

1039 1040
  return this->CreateXCodeFileReferenceFromPath(sf->GetFullPath(), target,
                                                lang, sf);
1041 1042
}

1043 1044
bool cmGlobalXCodeGenerator::SpecialTargetEmitted(std::string const& tname)
{
1045 1046 1047 1048
  if (tname == "ALL_BUILD" || tname == "XCODE_DEPEND_HELPER" ||
      tname == "install" || tname == "package" || tname == "RUN_TESTS" ||
      tname == CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
    if (this->TargetDoneSet.find(tname) != this->TargetDoneSet.end()) {
1049
      return true;
1050
    }
1051
    this->TargetDoneSet.insert(tname);
1052
    return false;
1053
  }
1054 1055 1056
  return false;
}

1057 1058
void cmGlobalXCodeGenerator::SetCurrentLocalGenerator(cmLocalGenerator* gen)
{
1059 1060
  this->CurrentLocalGenerator = gen;
  this->CurrentMakefile = gen->GetMakefile();