Browse Source

Xcode: Switch to the "new build system" for Xcode 12 and above

Provide an option to switch back to the original build system via
`-T buildsystem=1`.

Fixes: #18088
Brad King 5 years ago
parent
commit
8d5f4c4db9
26 changed files with 423 additions and 31 deletions
  1. 3 0
      Help/generator/Xcode.rst
  2. 7 0
      Help/release/dev/xcode-12-new-build-system.rst
  3. 6 1
      Help/variable/CMAKE_XCODE_BUILD_SYSTEM.rst
  4. 302 27
      Source/cmGlobalXCodeGenerator.cxx
  5. 16 0
      Source/cmGlobalXCodeGenerator.h
  6. 1 1
      Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem-stderr.txt
  7. 1 0
      Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12-result.txt
  8. 10 0
      Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12-stderr.txt
  9. 1 0
      Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12.cmake
  10. 13 2
      Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake
  11. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-result.txt
  12. 4 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-stderr.txt
  13. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-stdout.txt
  14. 8 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1.cmake
  15. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-result.txt
  16. 4 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-stderr.txt
  17. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-stdout.txt
  18. 8 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12.cmake
  19. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-result.txt
  20. 4 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-stderr.txt
  21. 1 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-stdout.txt
  22. 8 0
      Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12.cmake
  23. 4 0
      Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
  24. 1 0
      Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-result.txt
  25. 13 0
      Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-stderr.txt
  26. 3 0
      Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand.cmake

+ 3 - 0
Help/generator/Xcode.rst

@@ -30,3 +30,6 @@ Supported pairs are:
 ``buildsystem=<variant>``
   Specify the buildsystem variant to use.
   See the :variable:`CMAKE_XCODE_BUILD_SYSTEM` variable for allowed values.
+
+  For example, to select the original build system under Xcode 12,
+  run :manual:`cmake(1)` with the option ``-T buildsystem=1``.

+ 7 - 0
Help/release/dev/xcode-12-new-build-system.rst

@@ -0,0 +1,7 @@
+xcode-12-new-build-system
+-------------------------
+
+* The :generator:`Xcode` generator now uses the Xcode "new build system"
+  when generating for Xcode 12.0 or higher.
+  See the :variable:`CMAKE_XCODE_BUILD_SYSTEM` variable.
+  One may use ``-T buildsystem=1`` to switch to the legacy build system.

+ 6 - 1
Help/variable/CMAKE_XCODE_BUILD_SYSTEM.rst

@@ -10,7 +10,12 @@ mature enough for use by CMake.  The possible values are:
 
 ``1``
   The original Xcode build system.
-  This is the default.
+  This is the default when using Xcode 11.x or below.
+
+``12``
+  The Xcode "new build system" introduced by Xcode 10.
+  It became mature enough for use by CMake in Xcode 12.
+  This is the default when using Xcode 12.x or above.
 
 The ``CMAKE_XCODE_BUILD_SYSTEM`` variable is informational and should not
 be modified by project code.  See the :ref:`Xcode Build System Selection`

+ 302 - 27
Source/cmGlobalXCodeGenerator.cxx

@@ -171,6 +171,11 @@ cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(
 {
   this->VersionString = version_string;
   this->XcodeVersion = version_number;
+  if (this->XcodeVersion >= 120) {
+    this->XcodeBuildSystem = BuildSystem::Twelve;
+  } else {
+    this->XcodeBuildSystem = BuildSystem::One;
+  }
 
   this->RootObject = nullptr;
   this->MainGroupChildren = nullptr;
@@ -287,6 +292,8 @@ cm::string_view cmXcodeBuildSystemString(cmGlobalXCodeGenerator::BuildSystem b)
   switch (b) {
     case cmGlobalXCodeGenerator::BuildSystem::One:
       return "1"_s;
+    case cmGlobalXCodeGenerator::BuildSystem::Twelve:
+      return "12"_s;
   }
   return {};
 }
@@ -371,6 +378,8 @@ bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField(
   if (key == "buildsystem") {
     if (value == "1"_s) {
       this->XcodeBuildSystem = BuildSystem::One;
+    } else if (value == "12"_s) {
+      this->XcodeBuildSystem = BuildSystem::Twelve;
     } else {
       /* clang-format off */
       std::string const& e = cmStrCat(
@@ -378,7 +387,21 @@ bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField(
         "  ",  this->GetName(), "\n"
         "toolset specification field\n"
         "  buildsystem=", value, "\n"
-        "value is unkonwn.  It must be '1'."
+        "value is unkonwn.  It must be '1' or '12'."
+        );
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e);
+      return false;
+    }
+    if (this->XcodeBuildSystem == BuildSystem::Twelve &&
+        this->XcodeVersion < 120) {
+      /* clang-format off */
+      std::string const& e = cmStrCat(
+        "Generator\n"
+        "  ",  this->GetName(), "\n"
+        "toolset specification field\n"
+        "  buildsystem=", value, "\n"
+        "is not allowed with Xcode ", this->VersionString, '.'
         );
       /* clang-format on */
       mf->IssueMessage(MessageType::FATAL_ERROR, e);
@@ -479,6 +502,9 @@ cmGlobalXCodeGenerator::GenerateBuildCommand(
     }
   }
 
+  if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
+    makeCommand.Add("-parallelizeTargets");
+  }
   makeCommand.Add("-configuration", (config.empty() ? "Debug" : config));
 
   if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
@@ -499,8 +525,15 @@ cmGlobalXCodeGenerator::GenerateBuildCommand(
 std::unique_ptr<cmLocalGenerator> cmGlobalXCodeGenerator::CreateLocalGenerator(
   cmMakefile* mf)
 {
-  return std::unique_ptr<cmLocalGenerator>(
+  std::unique_ptr<cmLocalGenerator> lg(
     cm::make_unique<cmLocalXCodeGenerator>(this, mf));
+  if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
+    // For this build system variant we generate custom commands as
+    // shell scripts directly rather than inside Makefiles.
+    // FIXME: Rename or refactor this option for clarity.
+    lg->SetLinkScriptShell(true);
+  }
+  return lg;
 }
 
 void cmGlobalXCodeGenerator::AddExtraIDETargets()
@@ -1692,30 +1725,46 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
   cmXCodeObject* preLinkPhase = nullptr;
   cmXCodeObject* postBuildPhase = nullptr;
 
-  std::vector<cmSourceFile*> classes;
-  if (!gtgt->GetConfigCommonSourceFiles(classes)) {
-    return;
-  }
-  // add all the sources
-  std::vector<cmCustomCommand> commands;
-  auto& visited = this->CommandsVisited[gtgt];
-  for (auto sourceFile : classes) {
-    if (sourceFile->GetCustomCommand() && visited.insert(sourceFile).second) {
-      commands.push_back(*sourceFile->GetCustomCommand());
+  if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
+    // create prebuild phase
+    preBuildPhase =
+      this->CreateRunScriptBuildPhase("CMake PreBuild Rules", prebuild);
+    // create prelink phase
+    preLinkPhase =
+      this->CreateRunScriptBuildPhase("CMake PreLink Rules", prelink);
+    // create postbuild phase
+    postBuildPhase =
+      this->CreateRunScriptBuildPhase("CMake PostBuild Rules", postbuild);
+  } else {
+    std::vector<cmSourceFile*> classes;
+    if (!gtgt->GetConfigCommonSourceFiles(classes)) {
+      return;
+    }
+    // add all the sources
+    std::vector<cmCustomCommand> commands;
+    auto& visited = this->CommandsVisited[gtgt];
+    for (auto sourceFile : classes) {
+      if (sourceFile->GetCustomCommand() &&
+          visited.insert(sourceFile).second) {
+        commands.push_back(*sourceFile->GetCustomCommand());
+        if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
+          this->CustomCommandRoots[sourceFile].insert(gtgt);
+        }
+      }
     }
+    // create custom commands phase
+    legacyCustomCommandsBuildPhase = this->CreateLegacyRunScriptBuildPhase(
+      "CMake Rules", "cmakeRulesBuildPhase", gtgt, commands);
+    // create prebuild phase
+    preBuildPhase = this->CreateLegacyRunScriptBuildPhase(
+      "CMake PreBuild Rules", "preBuildCommands", gtgt, prebuild);
+    // create prelink phase
+    preLinkPhase = this->CreateLegacyRunScriptBuildPhase(
+      "CMake PreLink Rules", "preLinkCommands", gtgt, prelink);
+    // create postbuild phase
+    postBuildPhase = this->CreateLegacyRunScriptBuildPhase(
+      "CMake PostBuild Rules", "postBuildPhase", gtgt, postbuild);
   }
-  // create custom commands phase
-  legacyCustomCommandsBuildPhase = this->CreateLegacyRunScriptBuildPhase(
-    "CMake Rules", "cmakeRulesBuildPhase", gtgt, commands);
-  // create prebuild phase
-  preBuildPhase = this->CreateLegacyRunScriptBuildPhase(
-    "CMake PreBuild Rules", "preBuildCommands", gtgt, prebuild);
-  // create prelink phase
-  preLinkPhase = this->CreateLegacyRunScriptBuildPhase(
-    "CMake PreLink Rules", "preLinkCommands", gtgt, prelink);
-  // create postbuild phase
-  postBuildPhase = this->CreateLegacyRunScriptBuildPhase(
-    "CMake PostBuild Rules", "postBuildPhase", gtgt, postbuild);
 
   // The order here is the order they will be built in.
   // The order "headers, resources, sources" mimics a native project generated
@@ -1727,6 +1776,9 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
   if (legacyCustomCommandsBuildPhase) {
     buildPhases->AddObject(legacyCustomCommandsBuildPhase);
   }
+  if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
+    this->CreateRunScriptBuildPhases(buildPhases, gtgt);
+  }
   if (headerBuildPhase) {
     buildPhases->AddObject(headerBuildPhase);
   }
@@ -1750,6 +1802,199 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
   }
 }
 
+void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases(
+  cmXCodeObject* buildPhases, cmGeneratorTarget const* gt)
+{
+  std::vector<cmSourceFile*> sources;
+  if (!gt->GetConfigCommonSourceFiles(sources)) {
+    return;
+  }
+  auto& visited = this->CommandsVisited[gt];
+  for (auto sf : sources) {
+    this->CreateRunScriptBuildPhases(buildPhases, sf, gt, visited);
+  }
+}
+
+void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases(
+  cmXCodeObject* buildPhases, cmSourceFile const* sf,
+  cmGeneratorTarget const* gt, std::set<cmSourceFile const*>& visited)
+{
+  cmCustomCommand const* cc = sf->GetCustomCommand();
+  if (cc && visited.insert(sf).second) {
+    this->CustomCommandRoots[sf].insert(gt);
+    if (std::vector<cmSourceFile*> const* depends = gt->GetSourceDepends(sf)) {
+      for (cmSourceFile const* di : *depends) {
+        this->CreateRunScriptBuildPhases(buildPhases, di, gt, visited);
+      }
+    }
+    cmXCodeObject* buildPhase = this->CreateRunScriptBuildPhase(sf, gt, *cc);
+    buildPhases->AddObject(buildPhase);
+  }
+}
+
+cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
+  cmSourceFile const* sf, cmGeneratorTarget const* gt,
+  cmCustomCommand const& cc)
+{
+  std::set<std::string> allConfigInputs;
+  std::set<std::string> allConfigOutputs;
+
+  std::string shellScript = "set -e\n";
+  for (std::string const& configName : this->CurrentConfigurationTypes) {
+    cmCustomCommandGenerator ccg(cc, configName, this->CurrentLocalGenerator);
+    std::vector<std::string> realDepends;
+    realDepends.reserve(ccg.GetDepends().size());
+    for (auto const& d : ccg.GetDepends()) {
+      std::string dep;
+      if (this->CurrentLocalGenerator->GetRealDependency(d, configName, dep)) {
+        realDepends.emplace_back(std::move(dep));
+      }
+    }
+
+    allConfigInputs.insert(realDepends.begin(), realDepends.end());
+    allConfigOutputs.insert(ccg.GetByproducts().begin(),
+                            ccg.GetByproducts().end());
+    allConfigOutputs.insert(ccg.GetOutputs().begin(), ccg.GetOutputs().end());
+
+    shellScript =
+      cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")", configName,
+               "\"; then :\n", this->ConstructScript(ccg), "fi\n");
+  }
+
+  cmXCodeObject* buildPhase =
+    this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase);
+  buildPhase->AddAttribute("buildActionMask",
+                           this->CreateString("2147483647"));
+  cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
+  buildPhase->AddAttribute("files", buildFiles);
+  {
+    std::string name;
+    if (!allConfigOutputs.empty()) {
+      name = cmStrCat("Generate ",
+                      this->RelativeToBinary(*allConfigOutputs.begin()));
+    } else {
+      name = sf->GetLocation().GetName();
+    }
+    buildPhase->AddAttribute("name", this->CreateString(name));
+  }
+  buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
+                           this->CreateString("0"));
+  buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh"));
+  buildPhase->AddAttribute("shellScript", this->CreateString(shellScript));
+  buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
+
+  bool symbolic = false;
+  {
+    cmXCodeObject* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST);
+    for (std::string const& i : allConfigInputs) {
+      inputPaths->AddUniqueObject(this->CreateString(i));
+      if (!symbolic) {
+        if (cmSourceFile* isf =
+              gt->GetLocalGenerator()->GetMakefile()->GetSource(
+                i, cmSourceFileLocationKind::Known)) {
+          symbolic = isf->GetPropertyAsBool("SYMBOLIC");
+        }
+      }
+    }
+    buildPhase->AddAttribute("inputPaths", inputPaths);
+  }
+  {
+    cmXCodeObject* outputPaths =
+      this->CreateObject(cmXCodeObject::OBJECT_LIST);
+    for (std::string const& o : allConfigOutputs) {
+      outputPaths->AddUniqueObject(this->CreateString(o));
+      if (!symbolic) {
+        if (cmSourceFile* osf =
+              gt->GetLocalGenerator()->GetMakefile()->GetSource(
+                o, cmSourceFileLocationKind::Known)) {
+          symbolic = osf->GetPropertyAsBool("SYMBOLIC");
+        }
+      }
+    }
+    buildPhase->AddAttribute("outputPaths", outputPaths);
+  }
+  if (symbolic) {
+    buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1"));
+  }
+
+  return buildPhase;
+}
+
+cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
+  std::string const& name, std::vector<cmCustomCommand> const& commands)
+{
+  if (commands.empty()) {
+    return nullptr;
+  }
+
+  std::set<std::string> allConfigOutputs;
+
+  std::string shellScript = "set -e\n";
+  for (std::string const& configName : this->CurrentConfigurationTypes) {
+    shellScript = cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")",
+                           configName, "\"; then :\n");
+    for (cmCustomCommand const& cc : commands) {
+      cmCustomCommandGenerator ccg(cc, configName,
+                                   this->CurrentLocalGenerator);
+      shellScript = cmStrCat(shellScript, this->ConstructScript(ccg));
+      allConfigOutputs.insert(ccg.GetByproducts().begin(),
+                              ccg.GetByproducts().end());
+    }
+    shellScript = cmStrCat(shellScript, "fi\n");
+  }
+
+  cmXCodeObject* buildPhase =
+    this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase);
+  buildPhase->AddAttribute("buildActionMask",
+                           this->CreateString("2147483647"));
+  cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
+  buildPhase->AddAttribute("files", buildFiles);
+  buildPhase->AddAttribute("name", this->CreateString(name));
+  buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
+                           this->CreateString("0"));
+  buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh"));
+  buildPhase->AddAttribute("shellScript", this->CreateString(shellScript));
+  buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
+  {
+    cmXCodeObject* outputPaths =
+      this->CreateObject(cmXCodeObject::OBJECT_LIST);
+    for (std::string const& o : allConfigOutputs) {
+      outputPaths->AddUniqueObject(this->CreateString(o));
+    }
+    buildPhase->AddAttribute("outputPaths", outputPaths);
+  }
+  buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1"));
+
+  return buildPhase;
+}
+
+std::string cmGlobalXCodeGenerator::ConstructScript(
+  cmCustomCommandGenerator const& ccg)
+{
+  std::string script;
+  cmLocalGenerator* lg = this->CurrentLocalGenerator;
+  std::string wd = ccg.GetWorkingDirectory();
+  if (wd.empty()) {
+    wd = lg->GetCurrentBinaryDirectory();
+  }
+  wd = lg->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
+  script = cmStrCat(script, "  cd ", wd, "\n");
+  for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) {
+    std::string cmd = ccg.GetCommand(c);
+    if (cmd.empty()) {
+      continue;
+    }
+    cmSystemTools::ReplaceString(cmd, "/./", "/");
+    cmd = lg->ConvertToOutputFormat(cmd, cmOutputConverter::SHELL);
+    ccg.AppendArguments(c, cmd);
+    cmSystemTools::ReplaceString(cmd, "$(CONFIGURATION)", "$CONFIGURATION");
+    cmSystemTools::ReplaceString(cmd, "$(EFFECTIVE_PLATFORM_NAME)",
+                                 "$EFFECTIVE_PLATFORM_NAME");
+    script = cmStrCat(script, "  ", cmd, '\n');
+  }
+  return script;
+}
+
 // This function removes each occurrence of the flag and returns the last one
 // (i.e., the dominant flag in GCC)
 std::string cmGlobalXCodeGenerator::ExtractFlag(const char* flag,
@@ -3713,6 +3958,29 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
     if (!this->CreateXCodeTargets(generator, targets)) {
       return false;
     }
+    for (auto const& ccRoot : this->CustomCommandRoots) {
+      if (ccRoot.second.size() > 1) {
+        std::string e = "The custom command ";
+        std::vector<std::string> const& outputs =
+          ccRoot.first->GetCustomCommand()->GetOutputs();
+        if (!outputs.empty()) {
+          e = cmStrCat(e, "generating\n  ", outputs[0]);
+        } else {
+          e = cmStrCat(e, "driven by\n  ", ccRoot.first->GetFullPath());
+        }
+        e = cmStrCat(e, "\nis attached to multiple targets:");
+        for (cmGeneratorTarget const* gt : ccRoot.second) {
+          e = cmStrCat(e, "\n  ", gt->GetName());
+        }
+        e = cmStrCat(
+          e,
+          "\nbut none of these is a common dependency of the other(s).  "
+          "This is not allowed by the Xcode \"new build system\".");
+        generator->IssueMessage(MessageType::FATAL_ERROR, e);
+        return false;
+      }
+    }
+    this->CustomCommandRoots.clear();
   }
   // loop over all targets and add link and depend info
   for (auto t : targets) {
@@ -4007,9 +4275,16 @@ void cmGlobalXCodeGenerator::OutputXCodeWorkspaceSettings(
   xout.StartElement("dict");
   if (this->XcodeVersion >= 100) {
     xout.Element("key", "BuildSystemType");
-    xout.Element("string", "Original");
-    xout.Element("key", "DisableBuildSystemDeprecationWarning");
-    xout.Element("true");
+    switch (this->XcodeBuildSystem) {
+      case BuildSystem::One:
+        xout.Element("string", "Original");
+        xout.Element("key", "DisableBuildSystemDeprecationWarning");
+        xout.Element("true");
+        break;
+      case BuildSystem::Twelve:
+        xout.Element("string", "Latest");
+        break;
+    }
   }
   if (hasGeneratedSchemes) {
     xout.Element("key",

+ 16 - 0
Source/cmGlobalXCodeGenerator.h

@@ -15,6 +15,7 @@
 #include "cmXCodeObject.h"
 
 class cmCustomCommand;
+class cmCustomCommandGenerator;
 class cmGeneratorTarget;
 class cmGlobalGeneratorFactory;
 class cmLocalGenerator;
@@ -116,6 +117,7 @@ public:
   enum class BuildSystem
   {
     One = 1,
+    Twelve = 12,
   };
 
 protected:
@@ -233,6 +235,18 @@ private:
   cmXCodeObject* CreateLegacyRunScriptBuildPhase(
     const char* name, const char* name2, cmGeneratorTarget* target,
     const std::vector<cmCustomCommand>&);
+  void CreateRunScriptBuildPhases(cmXCodeObject* buildPhases,
+                                  cmGeneratorTarget const* gt);
+  void CreateRunScriptBuildPhases(cmXCodeObject* buildPhases,
+                                  cmSourceFile const* sf,
+                                  cmGeneratorTarget const* gt,
+                                  std::set<cmSourceFile const*>& visited);
+  cmXCodeObject* CreateRunScriptBuildPhase(cmSourceFile const* sf,
+                                           cmGeneratorTarget const* gt,
+                                           cmCustomCommand const& cc);
+  cmXCodeObject* CreateRunScriptBuildPhase(
+    std::string const& name, std::vector<cmCustomCommand> const& commands);
+  std::string ConstructScript(cmCustomCommandGenerator const& ccg);
   void CreateReRunCMakeFile(cmLocalGenerator* root,
                             std::vector<cmLocalGenerator*> const& gens);
 
@@ -315,4 +329,6 @@ private:
   std::vector<std::string> EnabledLangs;
   std::map<cmGeneratorTarget const*, std::set<cmSourceFile const*>>
     CommandsVisited;
+  std::map<cmSourceFile const*, std::set<cmGeneratorTarget const*>>
+    CustomCommandRoots;
 };

+ 1 - 1
Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem-stderr.txt

@@ -7,4 +7,4 @@ CMake Error at CMakeLists.txt:[0-9]+ \(project\):
 
     buildsystem=bad
 
-  value is unkonwn.  It must be '1'\.$
+  value is unkonwn.  It must be '1' or '12'\.$

+ 1 - 0
Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12-result.txt

@@ -0,0 +1 @@
+1

+ 10 - 0
Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12-stderr.txt

@@ -0,0 +1,10 @@
+CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Xcode
+
+  toolset specification field
+
+    buildsystem=12
+
+  is not allowed with Xcode [0-9.]+\.$

+ 1 - 0
Tests/RunCMake/GeneratorToolset/BadToolsetXcodeBuildSystem12.cmake

@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")

+ 13 - 2
Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake

@@ -56,8 +56,19 @@ elseif("${RunCMake_GENERATOR}" STREQUAL "Xcode")
   run_cmake(BadToolsetHostArchXcode)
   set(RunCMake_GENERATOR_TOOLSET "buildsystem=bad")
   run_cmake(BadToolsetXcodeBuildSystem)
-  set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
-  run_cmake(TestToolsetXcodeBuildSystemDefault1)
+  if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
+    set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
+    run_cmake(TestToolsetXcodeBuildSystemDefault12)
+    set(RunCMake_GENERATOR_TOOLSET "Test Toolset,buildsystem=1")
+    run_cmake(TestToolsetXcodeBuildSystem1)
+    set(RunCMake_GENERATOR_TOOLSET "Test Toolset,buildsystem=12")
+    run_cmake(TestToolsetXcodeBuildSystem12)
+  else()
+    set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
+    run_cmake(TestToolsetXcodeBuildSystemDefault1)
+    set(RunCMake_GENERATOR_TOOLSET "buildsystem=12")
+    run_cmake(BadToolsetXcodeBuildSystem12)
+  endif()
 else()
   set(RunCMake_GENERATOR_TOOLSET "Bad Toolset")
   run_cmake(BadToolset)

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TestToolsetXcodeBuildSystem1.cmake:[0-9]+ \(message\):
+  CMAKE_GENERATOR_TOOLSET is "Test Toolset,buildsystem=1" as expected.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1-stdout.txt

@@ -0,0 +1 @@
+CMAKE_XCODE_BUILD_SYSTEM='1'

+ 8 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem1.cmake

@@ -0,0 +1,8 @@
+message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
+if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset,buildsystem=1")
+  message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset,buildsystem=1\" as expected.")
+else()
+  message(FATAL_ERROR
+    "CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
+    "but should be \"Test Toolset,buildsystem=1\"!")
+endif()

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TestToolsetXcodeBuildSystem12.cmake:[0-9]+ \(message\):
+  CMAKE_GENERATOR_TOOLSET is "Test Toolset,buildsystem=12" as expected.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12-stdout.txt

@@ -0,0 +1 @@
+CMAKE_XCODE_BUILD_SYSTEM='12'

+ 8 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystem12.cmake

@@ -0,0 +1,8 @@
+message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
+if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset,buildsystem=12")
+  message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset,buildsystem=12\" as expected.")
+else()
+  message(FATAL_ERROR
+    "CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
+    "but should be \"Test Toolset,buildsystem=12\"!")
+endif()

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TestToolsetXcodeBuildSystemDefault12.cmake:[0-9]+ \(message\):
+  CMAKE_GENERATOR_TOOLSET is "Test Toolset" as expected.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12-stdout.txt

@@ -0,0 +1 @@
+CMAKE_XCODE_BUILD_SYSTEM='12'

+ 8 - 0
Tests/RunCMake/GeneratorToolset/TestToolsetXcodeBuildSystemDefault12.cmake

@@ -0,0 +1,8 @@
+message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
+if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset")
+  message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset\" as expected.")
+else()
+  message(FATAL_ERROR
+    "CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
+    "but should be \"Test Toolset\"!")
+endif()

+ 4 - 0
Tests/RunCMake/XcodeProject/RunCMakeTest.cmake

@@ -10,6 +10,10 @@ run_cmake(XcodeAttributeGenex)
 run_cmake(XcodeAttributeGenexError)
 run_cmake(XcodeGenerateTopLevelProjectOnly)
 
+if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
+  run_cmake(XcodeDuplicateCustomCommand)
+endif()
+
 function(XcodeGenerateTopLevelProjectOnlyWithObjectLibrary)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeGenerateTopLevelProjectOnlyWithObjectLibrary-build)
   run_cmake(XcodeGenerateTopLevelProjectOnlyWithObjectLibrary)

+ 1 - 0
Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-result.txt

@@ -0,0 +1 @@
+1

+ 13 - 0
Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-stderr.txt

@@ -0,0 +1,13 @@
+^CMake Error in CMakeLists.txt:
+  The custom command generating
+
+    [^
+]*/Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-build/out.txt
+
+  is attached to multiple targets:
+
+    drive[0-9]
+    drive[0-9]
+
+  but none of these is a common dependency of the other\(s\).  This is not
+  allowed by the Xcode "new build system".

+ 3 - 0
Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand.cmake

@@ -0,0 +1,3 @@
+add_custom_command(OUTPUT out.txt COMMAND false)
+add_custom_target(drive1 DEPENDS out.txt)
+add_custom_target(drive2 DEPENDS out.txt)