Bladeren bron

Merge topic 'xcode-link-phase-all'

525464ed2a Xcode: Use "Link Binary With Libraries" build phase in some cases
dc0898205c Xcode: Add special case for file type extension map for .xcassets
7b3d8411a2 Xcode: Refactor build setting append code and attribute getter naming

Acked-by: Kitware Robot <[email protected]>
Merge-request: !5036
Craig Scott 5 jaren geleden
bovenliggende
commit
3001e8b5d9

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -396,6 +396,7 @@ Properties on Targets
    /prop_tgt/XCODE_ATTRIBUTE_an-attribute
    /prop_tgt/XCODE_EXPLICIT_FILE_TYPE
    /prop_tgt/XCODE_GENERATE_SCHEME
+   /prop_tgt/XCODE_LINK_BUILD_PHASE_MODE
    /prop_tgt/XCODE_PRODUCT_TYPE
    /prop_tgt/XCODE_SCHEME_ADDRESS_SANITIZER
    /prop_tgt/XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -247,6 +247,7 @@ Variables that Change Behavior
    /variable/CMAKE_WARN_DEPRECATED
    /variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION
    /variable/CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY
+   /variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE
    /variable/CMAKE_XCODE_SCHEME_ADDRESS_SANITIZER
    /variable/CMAKE_XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN
    /variable/CMAKE_XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING

+ 52 - 0
Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst

@@ -0,0 +1,52 @@
+XCODE_LINK_BUILD_PHASE_MODE
+---------------------------
+
+When using the :generator:`Xcode` generator, libraries to be linked will be
+specified in the Xcode project file using either the "Link Binary With
+Libraries" build phase or directly as linker flags.  The former allows Xcode
+to manage build paths, which may be necessary when creating Xcode archives
+because it may use different build paths to a regular build.
+
+This property controls usage of "Link Binary With Libraries" build phase for
+a target that is an app bundle, executable, shared library, shared framework
+or a module library.
+
+Possible values are:
+
+* ``NONE``
+  The libraries will be linked by specifying the linker flags directly.
+
+* ``BUILT_ONLY``
+  The "Link Binary With Libraries" build phase will be used to link to another
+  target under the following conditions:
+
+  - The target to be linked to is a regular non-imported, non-interface library
+    target.
+  - The output directory of the target being built has not been changed from
+    its default (see :prop_tgt:`RUNTIME_OUTPUT_DIRECTORY` and
+    :prop_tgt:`LIBRARY_OUTPUT_DIRECTORY`).
+
+* ``KNOWN_LOCATION``
+  The "Link Binary With Libraries" build phase will be used to link to another
+  target under the same conditions as with ``BUILT_ONLY`` and also:
+  - Imported library targets except those of type ``UNKNOWN``.
+  - Any non-target library specified directly with a path.
+
+For all other cases, the libraries will be linked by specifying the linker
+flags directly.
+
+.. warning::
+  Libraries linked using "Link Binary With Libraries" are linked after the
+  ones linked through regular linker flags.  This order should be taken into
+  account when different static libraries contain symbols with the same name,
+  as the former ones will take precedence over the latter.
+
+.. warning::
+  If two or more directories contain libraries with identical file names and
+  some libraries are linked from those directories, the library search path
+  lookup will end up linking libraries from the first directory.  This is a
+  known limitation of Xcode.
+
+This property is initialized by the value of the
+:variable:`CMAKE_XCODE_LINK_BUILD_PHASE_MODE` variable if it is set when a
+target is created.

+ 9 - 0
Help/release/dev/xcode-link-phase-all.rst

@@ -0,0 +1,9 @@
+xcode-link-phase-all
+--------------------
+
+* The Xcode generator gained support for linking libraries and frameworks
+  via the *Link Binaries With Libraries* build phase instead of always by
+  embedding linker flags directly.  This behavior is controlled by a new
+  :prop_tgt:`XCODE_LINK_BUILD_PHASE_MODE` target property, which is
+  initialized by a new :variable:`CMAKE_XCODE_LINK_BUILD_PHASE_MODE`
+  variable.

+ 7 - 0
Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst

@@ -0,0 +1,7 @@
+CMAKE_XCODE_LINK_BUILD_PHASE_MODE
+---------------------------------
+
+This variable is used to initialize the
+:prop_tgt:`XCODE_LINK_BUILD_PHASE_MODE` property on targets.
+It affects the methods that the :generator:`Xcode` generator uses to link
+different kinds of libraries.  Its default value is ``NONE``.

+ 282 - 110
Source/cmGlobalXCodeGenerator.cxx

@@ -678,6 +678,7 @@ void cmGlobalXCodeGenerator::ClearXCodeObjects()
   this->TargetGroup.clear();
   this->FileRefs.clear();
   this->ExternalLibRefs.clear();
+  this->FileRefToBuildFileMap.clear();
 }
 
 void cmGlobalXCodeGenerator::addObject(std::unique_ptr<cmXCodeObject> obj)
@@ -751,16 +752,23 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeBuildFileFromPath(
   const std::string& lang, cmSourceFile* sf)
 {
   // Using a map and the full path guarantees that we will always get the same
-  // fileRef object for any given full path.
-  //
+  // fileRef object for any given full path. Same goes for the buildFile
+  // object.
   cmXCodeObject* fileRef =
     this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
-
-  cmXCodeObject* buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
-  buildFile->SetComment(fileRef->GetComment());
-  buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef));
-
-  return buildFile;
+  if (fileRef) {
+    auto it = this->FileRefToBuildFileMap.find(fileRef);
+    if (it == this->FileRefToBuildFileMap.end()) {
+      cmXCodeObject* buildFile =
+        this->CreateObject(cmXCodeObject::PBXBuildFile);
+      buildFile->SetComment(fileRef->GetComment());
+      buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef));
+      this->FileRefToBuildFileMap[fileRef] = buildFile;
+      return buildFile;
+    }
+    return it->second;
+  }
+  return nullptr;
 }
 
 class XCodeGeneratorExpressionInterpreter
@@ -918,7 +926,9 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
 
   settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);
 
-  buildFile->AddAttributeIfNotEmpty("settings", settings);
+  if (buildFile) {
+    buildFile->AddAttributeIfNotEmpty("settings", settings);
+  }
   return buildFile;
 }
 
@@ -935,16 +945,21 @@ void cmGlobalXCodeGenerator::AddXCodeProjBuildRule(
   }
 }
 
-bool IsLibraryExtension(const std::string& fileExt)
+namespace {
+
+bool IsLinkPhaseLibraryExtension(const std::string& fileExt)
 {
+  // Empty file extension is a special case for paths to framework's
+  // internal binary which could be MyFw.framework/Versions/*/MyFw
   return (fileExt == ".framework" || fileExt == ".a" || fileExt == ".o" ||
-          fileExt == ".dylib" || fileExt == ".tbd");
+          fileExt == ".dylib" || fileExt == ".tbd" || fileExt.empty());
 }
 bool IsLibraryType(const std::string& fileType)
 {
   return (fileType == "wrapper.framework" || fileType == "archive.ar" ||
           fileType == "compiled.mach-o.objfile" ||
           fileType == "compiled.mach-o.dylib" ||
+          fileType == "compiled.mach-o.executable" ||
           fileType == "sourcecode.text-based-dylib-definition");
 }
 
@@ -1020,6 +1035,12 @@ std::string GetSourcecodeValueFromFileExtension(
   } else if (ext == "dylib") {
     keepLastKnownFileType = true;
     sourcecode = "compiled.mach-o.dylib";
+  } else if (ext == "framework") {
+    keepLastKnownFileType = true;
+    sourcecode = "wrapper.framework";
+  } else if (ext == "xcassets") {
+    keepLastKnownFileType = true;
+    sourcecode = "folder.assetcatalog";
   }
   // else
   //  {
@@ -1032,6 +1053,47 @@ std::string GetSourcecodeValueFromFileExtension(
   return sourcecode;
 }
 
+// If the file has no extension it's either a raw executable or might
+// be a direct reference to a binary within a framework (bad practice!).
+// This is where we change the path to point to the framework directory.
+// .tbd files also can be located in SDK frameworks (they are
+// placeholders for actual libraries shipped with the OS)
+std::string GetLibraryOrFrameworkPath(const std::string& path)
+{
+  auto ext = cmSystemTools::GetFilenameLastExtension(path);
+  if (ext.empty() || ext == ".tbd") {
+    auto name = cmSystemTools::GetFilenameWithoutExtension(path);
+    // Check for iOS framework structure:
+    //    FwName.framework/FwName (and also on macOS where FwName lib is a
+    //    symlink)
+    auto parentDir = cmSystemTools::GetParentDirectory(path);
+    auto parentName = cmSystemTools::GetFilenameWithoutExtension(parentDir);
+    ext = cmSystemTools::GetFilenameLastExtension(parentDir);
+    if (ext == ".framework" && name == parentName) {
+      return parentDir;
+    }
+    // Check for macOS framework structure:
+    //    FwName.framework/Versions/*/FwName
+    std::vector<std::string> components;
+    cmSystemTools::SplitPath(path, components);
+    if (components.size() > 3 &&
+        components[components.size() - 3] == "Versions") {
+      ext = cmSystemTools::GetFilenameLastExtension(
+        components[components.size() - 4]);
+      parentName = cmSystemTools::GetFilenameWithoutExtension(
+        components[components.size() - 4]);
+      if (ext == ".framework" && name == parentName) {
+        components.erase(components.begin() + components.size() - 3,
+                         components.end());
+        return cmSystemTools::JoinPath(components);
+      }
+    }
+  }
+  return path;
+}
+
+} // anonymous
+
 cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
   const std::string& fullpath, cmGeneratorTarget* target,
   const std::string& lang, cmSourceFile* sf)
@@ -1054,17 +1116,10 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
     ext = ext.substr(1);
   }
   if (fileType.empty()) {
-    // If file has no extension it's either a raw executable or might
-    // be a direct reference to binary within a framework (bad practice!)
-    // so this is where we change the path to the point to framework
-    // directory.
-    if (ext.empty()) {
-      auto parentDir = cmSystemTools::GetParentDirectory(path);
-      auto parentExt = cmSystemTools::GetFilenameLastExtension(parentDir);
-      if (parentExt == ".framework") {
-        path = parentDir;
-        ext = parentExt.substr(1);
-      }
+    path = GetLibraryOrFrameworkPath(path);
+    ext = cmSystemTools::GetFilenameLastExtension(path);
+    if (!ext.empty()) {
+      ext = ext.substr(1);
     }
     // If fullpath references a directory, then we need to specify
     // lastKnownFileType as folder in order for Xcode to be able to
@@ -1074,8 +1129,17 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
       fileType = GetDirectoryValueFromFileExtension(ext);
       useLastKnownFileType = true;
     } else {
-      fileType = GetSourcecodeValueFromFileExtension(
-        ext, lang, useLastKnownFileType, this->EnabledLangs);
+      if (ext.empty() && !sf) {
+        // Special case for executable or library without extension
+        // that is not a source file. We can't tell which without reading
+        // its Mach-O header, but the file might not exist yet, so we
+        // have to pick one here.
+        useLastKnownFileType = true;
+        fileType = "compiled.mach-o.executable";
+      } else {
+        fileType = GetSourcecodeValueFromFileExtension(
+          ext, lang, useLastKnownFileType, this->EnabledLangs);
+      }
     }
   }
 
@@ -1106,7 +1170,11 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
     group = this->FrameworkGroup;
     this->GroupMap[key] = group;
   }
-  cmXCodeObject* children = group->GetObject("children");
+  if (!group) {
+    cmSystemTools::Error("Could not find a PBX group for " + key);
+    return nullptr;
+  }
+  cmXCodeObject* children = group->GetAttribute("children");
   if (!children->HasObject(fileRef)) {
     children->AddObject(fileRef);
   }
@@ -1243,10 +1311,11 @@ bool cmGlobalXCodeGenerator::CreateXCodeTarget(
   for (auto sourceFile : commonSourceFiles) {
     cmXCodeObject* xsf = this->CreateXCodeSourceFile(
       this->CurrentLocalGenerator, sourceFile, gtgt);
-    cmXCodeObject* fr = xsf->GetObject("fileRef");
-    cmXCodeObject* filetype = fr->GetObject()->GetObject("explicitFileType");
+    cmXCodeObject* fr = xsf->GetAttribute("fileRef");
+    cmXCodeObject* filetype =
+      fr->GetObject()->GetAttribute("explicitFileType");
     if (!filetype) {
-      filetype = fr->GetObject()->GetObject("lastKnownFileType");
+      filetype = fr->GetObject()->GetAttribute("lastKnownFileType");
     }
 
     cmGeneratorTarget::SourceFileFlags tsFlags =
@@ -2321,7 +2390,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
     if (stdlib.size() > 8) {
       const auto cxxLibrary = stdlib.substr(8);
       if (language == "CXX" ||
-          !buildSettings->GetObject("CLANG_CXX_LIBRARY")) {
+          !buildSettings->GetAttribute("CLANG_CXX_LIBRARY")) {
         buildSettings->AddAttribute("CLANG_CXX_LIBRARY",
                                     this->CreateString(cxxLibrary));
       }
@@ -2343,7 +2412,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
     std::string flags = cflags[language] + " " + defFlags;
     if (language == "CXX" || language == "OBJCXX") {
       if (language == "CXX" ||
-          !buildSettings->GetObject("OTHER_CPLUSPLUSFLAGS")) {
+          !buildSettings->GetAttribute("OTHER_CPLUSPLUSFLAGS")) {
         buildSettings->AddAttribute("OTHER_CPLUSPLUSFLAGS",
                                     this->CreateString(flags));
       }
@@ -2351,7 +2420,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
       buildSettings->AddAttribute("IFORT_OTHER_FLAGS",
                                   this->CreateString(flags));
     } else if (language == "C" || language == "OBJC") {
-      if (language == "C" || !buildSettings->GetObject("OTHER_CFLAGS")) {
+      if (language == "C" || !buildSettings->GetAttribute("OTHER_CFLAGS")) {
         buildSettings->AddAttribute("OTHER_CFLAGS", this->CreateString(flags));
       }
     } else if (language == "Swift") {
@@ -2771,7 +2840,7 @@ void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target,
   targetdep->AddAttribute("targetProxy",
                           this->CreateObjectReference(container));
 
-  cmXCodeObject* depends = target->GetObject("dependencies");
+  cmXCodeObject* depends = target->GetAttribute("dependencies");
   if (!depends) {
     cmSystemTools::Error(
       "target does not have dependencies attribute error..");
@@ -2783,33 +2852,60 @@ void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target,
 
 void cmGlobalXCodeGenerator::AppendOrAddBuildSetting(cmXCodeObject* settings,
                                                      const char* attribute,
-                                                     const char* value)
+                                                     cmXCodeObject* value)
 {
   if (settings) {
-    cmXCodeObject* attr = settings->GetObject(attribute);
+    cmXCodeObject* attr = settings->GetAttribute(attribute);
     if (!attr) {
-      settings->AddAttribute(attribute, this->CreateString(value));
+      settings->AddAttribute(attribute, value);
     } else {
-      std::string oldValue = cmStrCat(attr->GetString(), ' ', value);
-      attr->SetString(oldValue);
+      if (value->GetType() != cmXCodeObject::OBJECT_LIST &&
+          value->GetType() != cmXCodeObject::STRING) {
+        cmSystemTools::Error("Unsupported value type for appending: " +
+                             std::string(attribute));
+        return;
+      }
+      if (attr->GetType() == cmXCodeObject::OBJECT_LIST) {
+        if (value->GetType() == cmXCodeObject::OBJECT_LIST) {
+          for (auto* obj : value->GetObjectList()) {
+            attr->AddObject(obj);
+          }
+        } else {
+          attr->AddObject(value);
+        }
+      } else if (attr->GetType() == cmXCodeObject::STRING) {
+        if (value->GetType() == cmXCodeObject::OBJECT_LIST) {
+          // Add old value as a list item to new object list
+          // and replace the attribute with the new list
+          value->PrependObject(attr);
+          settings->AddAttribute(attribute, value);
+        } else {
+          std::string newValue =
+            cmStrCat(attr->GetString(), ' ', value->GetString());
+          attr->SetString(newValue);
+        }
+      } else {
+        cmSystemTools::Error("Unsupported attribute type for appending: " +
+                             std::string(attribute));
+      }
     }
   }
 }
 
 void cmGlobalXCodeGenerator::AppendBuildSettingAttribute(
-  cmXCodeObject* target, const char* attribute, const char* value,
+  cmXCodeObject* target, const char* attribute, cmXCodeObject* value,
   const std::string& configName)
 {
   // There are multiple configurations.  Add the setting to the
   // buildSettings of the configuration name given.
   cmXCodeObject* configurationList =
-    target->GetObject("buildConfigurationList")->GetObject();
+    target->GetAttribute("buildConfigurationList")->GetObject();
   cmXCodeObject* buildConfigs =
-    configurationList->GetObject("buildConfigurations");
+    configurationList->GetAttribute("buildConfigurations");
   for (auto obj : buildConfigs->GetObjectList()) {
     if (configName.empty() ||
-        obj->GetObject("name")->GetString() == configName) {
-      cmXCodeObject* settings = obj->GetObject("buildSettings");
+        obj->GetAttribute("name")->GetString() == configName) {
+      cmXCodeObject* settings = obj->GetAttribute("buildSettings");
       this->AppendOrAddBuildSetting(settings, attribute, value);
     }
   }
@@ -2835,9 +2931,9 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
 
   // Separate libraries into ones that can be linked using "Link Binary With
   // Libraries" build phase and the ones that can't. Only targets that build
-  // Apple bundles (.app, .framework, .bundle) can use this feature and only
-  // targets that represent actual libraries (static or dynamic, local or
-  // imported) not objects and not executables will be used. These are
+  // Apple bundles (.app, .framework, .bundle), executables and dylibs can use
+  // this feature and only targets that represent actual libraries (object,
+  // static, dynamic or bundle, excluding executables) will be used. These are
   // limitations imposed by CMake use-cases - otherwise a lot of things break.
   // The rest will be linked using linker flags (OTHER_LDFLAGS setting in Xcode
   // project).
@@ -2860,55 +2956,92 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
     std::pair<std::string, cmComputeLinkInformation::Item const*>;
   std::map<std::string, std::vector<ConfigItemPair>> targetItemMap;
   std::map<std::string, std::vector<std::string>> targetProductNameMap;
+  bool useLinkPhase = false;
+  bool forceLinkPhase = false;
+  cmProp prop =
+    target->GetTarget()->GetProperty("XCODE_LINK_BUILD_PHASE_MODE");
+  if (prop) {
+    if (*prop == "BUILT_ONLY") {
+      useLinkPhase = true;
+    } else if (*prop == "KNOWN_LOCATION") {
+      useLinkPhase = true;
+      forceLinkPhase = true;
+    } else if (*prop != "NONE") {
+      cmSystemTools::Error("Invalid value for XCODE_LINK_BUILD_PHASE_MODE: " +
+                           *prop);
+      return;
+    }
+  }
   for (auto const& configName : this->CurrentConfigurationTypes) {
     cmComputeLinkInformation* cli = gt->GetLinkInformation(configName);
     if (!cli) {
       continue;
     }
     for (auto const& libItem : cli->GetItems()) {
-      if (gt->IsBundleOnApple() &&
+      // We want to put only static libraries, dynamic libraries, frameworks
+      // and bundles that are built from targets that are not imported in "Link
+      // Binary With Libraries" build phase. Except if the target property
+      // XCODE_LINK_BUILD_PHASE_MODE is KNOWN_LOCATION then all imported and
+      // non-target libraries will be added as well.
+      if (useLinkPhase &&
           (gt->GetType() == cmStateEnums::EXECUTABLE ||
            gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
-           gt->GetType() == cmStateEnums::MODULE_LIBRARY ||
-           gt->GetType() == cmStateEnums::UNKNOWN_LIBRARY) &&
+           gt->GetType() == cmStateEnums::MODULE_LIBRARY) &&
           ((libItem.Target &&
+            (!libItem.Target->IsImported() || forceLinkPhase) &&
             (libItem.Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
              libItem.Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
-             libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY)) ||
-           (!libItem.Target && libItem.IsPath))) {
-        // Add unique configuration name to target-config map for later
-        // checks
+             libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
+             libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) ||
+           (!libItem.Target && libItem.IsPath && forceLinkPhase))) {
         std::string libName;
+        bool canUseLinkPhase = true;
         if (libItem.Target) {
+          if (libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY) {
+            canUseLinkPhase = canUseLinkPhase && forceLinkPhase;
+          } else {
+            // If a library target uses custom build output directory Xcode
+            // won't pick it up so we have to resort back to linker flags, but
+            // that's OK as long as the custom output dir is absolute path.
+            for (auto const& libConfigName : this->CurrentConfigurationTypes) {
+              canUseLinkPhase = canUseLinkPhase &&
+                libItem.Target->UsesDefaultOutputDir(
+                  libConfigName, cmStateEnums::RuntimeBinaryArtifact);
+            }
+          }
           libName = libItem.Target->GetName();
         } else {
           libName = cmSystemTools::GetFilenameName(libItem.Value.Value);
+          // We don't want all the possible files here, just standard libraries
           const auto libExt = cmSystemTools::GetFilenameExtension(libName);
-          if (!IsLibraryExtension(libExt)) {
-            // Add this library item to a regular linker flag list
-            addToLinkerArguments(configName, &libItem);
-            continue;
+          if (!IsLinkPhaseLibraryExtension(libExt)) {
+            canUseLinkPhase = false;
           }
         }
-        auto& configVector = targetConfigMap[libName];
-        if (std::find(configVector.begin(), configVector.end(), configName) ==
-            configVector.end()) {
-          configVector.push_back(configName);
-        }
-        // Add a pair of config and item to target-item map
-        auto& itemVector = targetItemMap[libName];
-        itemVector.emplace_back(ConfigItemPair(configName, &libItem));
-        // Add product file-name to a lib-product map
-        auto productName = cmSystemTools::GetFilenameName(libItem.Value.Value);
-        auto& productVector = targetProductNameMap[libName];
-        if (std::find(productVector.begin(), productVector.end(),
-                      productName) == productVector.end()) {
-          productVector.push_back(productName);
+        if (canUseLinkPhase) {
+          // Add unique configuration name to target-config map for later
+          // checks
+          auto& configVector = targetConfigMap[libName];
+          if (std::find(configVector.begin(), configVector.end(),
+                        configName) == configVector.end()) {
+            configVector.push_back(configName);
+          }
+          // Add a pair of config and item to target-item map
+          auto& itemVector = targetItemMap[libName];
+          itemVector.emplace_back(ConfigItemPair(configName, &libItem));
+          // Add product file-name to a lib-product map
+          auto productName =
+            cmSystemTools::GetFilenameName(libItem.Value.Value);
+          auto& productVector = targetProductNameMap[libName];
+          if (std::find(productVector.begin(), productVector.end(),
+                        productName) == productVector.end()) {
+            productVector.push_back(productName);
+          }
+          continue;
         }
-      } else {
-        // Add this library item to a regular linker flag list
-        addToLinkerArguments(configName, &libItem);
       }
+      // Add this library item to a regular linker flag list
+      addToLinkerArguments(configName, &libItem);
     }
   }
 
@@ -2938,18 +3071,28 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
   // in this build phase so we don't have to do this for each configuration
   // separately.
   std::vector<std::string> linkSearchPaths;
+  std::vector<std::string> frameworkSearchPaths;
   for (auto const& libItem : linkPhaseTargetVector) {
     // Add target output directory as a library search path
     std::string linkDir;
     if (libItem->Target) {
-      linkDir = cmSystemTools::GetParentDirectory(
-        libItem->Target->GetLocationForBuild());
+      linkDir = libItem->Target->GetLocationForBuild();
     } else {
-      linkDir = cmSystemTools::GetParentDirectory(libItem->Value.Value);
+      linkDir = libItem->Value.Value;
     }
-    if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
-        linkSearchPaths.end()) {
-      linkSearchPaths.push_back(linkDir);
+    linkDir = GetLibraryOrFrameworkPath(linkDir);
+    bool isFramework = cmSystemTools::IsPathToFramework(linkDir);
+    linkDir = cmSystemTools::GetParentDirectory(linkDir);
+    if (isFramework) {
+      if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(),
+                    linkDir) == frameworkSearchPaths.end()) {
+        frameworkSearchPaths.push_back(linkDir);
+      }
+    } else {
+      if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
+          linkSearchPaths.end()) {
+        linkSearchPaths.push_back(linkDir);
+      }
     }
     // Add target dependency
     if (libItem->Target && !libItem->Target->IsImported()) {
@@ -2967,6 +3110,13 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
         if (it == this->ExternalLibRefs.end()) {
           buildFile = CreateXCodeBuildFileFromPath(libItem->Value.Value, gt,
                                                    "", nullptr);
+          if (!buildFile) {
+            // Add this library item back to a regular linker flag list
+            for (const auto& conf : configItemMap) {
+              addToLinkerArguments(conf.first, libItem);
+            }
+            continue;
+          }
           this->ExternalLibRefs.emplace(libItem->Value.Value, buildFile);
         } else {
           buildFile = it->second;
@@ -2981,7 +3131,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
     } else {
       // Add the target output file as a build reference for other targets
       // to link against
-      auto* fileRefObject = libTarget->GetObject("productReference");
+      auto* fileRefObject = libTarget->GetAttribute("productReference");
       if (!fileRefObject) {
         // Add this library item back to a regular linker flag list
         for (const auto& conf : configItemMap) {
@@ -2999,20 +3149,23 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
       }
     }
     // Add this reference to current target
-    auto* buildPhases = target->GetObject("buildPhases");
+    auto* buildPhases = target->GetAttribute("buildPhases");
     if (!buildPhases) {
+      cmSystemTools::Error("Missing buildPhase of target");
       continue;
     }
     auto* frameworkBuildPhase =
       buildPhases->GetObject(cmXCodeObject::PBXFrameworksBuildPhase);
     if (!frameworkBuildPhase) {
+      cmSystemTools::Error("Missing PBXFrameworksBuildPhase of buildPhase");
       continue;
     }
-    auto* buildFiles = frameworkBuildPhase->GetObject("files");
+    auto* buildFiles = frameworkBuildPhase->GetAttribute("files");
     if (!buildFiles) {
+      cmSystemTools::Error("Missing files of PBXFrameworksBuildPhase");
       continue;
     }
-    if (!buildFiles->HasObject(buildFile)) {
+    if (buildFile && !buildFiles->HasObject(buildFile)) {
       buildFiles->AddObject(buildFile);
     }
   }
@@ -3021,20 +3174,18 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
   for (auto const& configName : this->CurrentConfigurationTypes) {
     {
       // Add object library contents as link flags.
-      std::string linkObjs;
-      const char* sep = "";
+      BuildObjectListOrString libSearchPaths(this, true);
       std::vector<cmSourceFile const*> objs;
       gt->GetExternalObjects(objs, configName);
       for (auto sourceFile : objs) {
         if (sourceFile->GetObjectLibrary().empty()) {
           continue;
         }
-        linkObjs += sep;
-        sep = " ";
-        linkObjs += this->XCodeEscapePath(sourceFile->GetFullPath());
+        libSearchPaths.Add(this->XCodeEscapePath(sourceFile->GetFullPath()));
       }
       this->AppendBuildSettingAttribute(
-        target, this->GetTargetLinkFlagsVar(gt), linkObjs.c_str(), configName);
+        target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(),
+        configName);
     }
 
     // Skip link information for object libraries.
@@ -3056,49 +3207,70 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
 
     // add the library search paths
     {
+      BuildObjectListOrString libSearchPaths(this, true);
       std::string linkDirs;
       for (auto const& libDir : cli->GetDirectories()) {
         if (!libDir.empty() && libDir != "/usr/lib") {
-          // Now add the same one but append
-          // $(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) to it:
-          linkDirs += " ";
-          linkDirs += this->XCodeEscapePath(
-            libDir + "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)");
-          linkDirs += " ";
-          linkDirs += this->XCodeEscapePath(libDir);
+          libSearchPaths.Add(this->XCodeEscapePath(
+            libDir + "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"));
+          libSearchPaths.Add(this->XCodeEscapePath(libDir));
         }
       }
       // Add previously collected paths where to look for libraries
       // that were added to "Link Binary With Libraries"
-      for (auto& linkDir : linkSearchPaths) {
-        linkDirs += " ";
-        linkDirs += this->XCodeEscapePath(linkDir);
+      for (auto& libDir : linkSearchPaths) {
+        libSearchPaths.Add(this->XCodeEscapePath(libDir));
       }
       this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS",
-                                        linkDirs.c_str(), configName);
+                                        libSearchPaths.CreateList(),
+                                        configName);
+    }
+
+    // add framework search paths
+    {
+      BuildObjectListOrString fwSearchPaths(this, true);
+      // Add previously collected paths where to look for frameworks
+      // that were added to "Link Binary With Libraries"
+      for (auto& fwDir : frameworkSearchPaths) {
+        fwSearchPaths.Add(this->XCodeEscapePath(fwDir));
+      }
+      this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
+                                        fwSearchPaths.CreateList(),
+                                        configName);
     }
 
     // now add the left-over link libraries
     {
-      std::string linkLibs;
-      const char* sep = "";
+      BuildObjectListOrString libPaths(this, true);
       for (auto const& libItem : configItemMap[configName]) {
         auto const& libName = *libItem;
-        linkLibs += sep;
-        sep = " ";
         if (libName.IsPath) {
-          linkLibs += this->XCodeEscapePath(libName.Value.Value);
+          libPaths.Add(this->XCodeEscapePath(libName.Value.Value));
+          const auto libPath = GetLibraryOrFrameworkPath(libName.Value.Value);
+          if ((!libName.Target || libName.Target->IsImported()) &&
+              IsLinkPhaseLibraryExtension(libPath)) {
+            // Create file reference for embedding
+            auto it = this->ExternalLibRefs.find(libName.Value.Value);
+            if (it == this->ExternalLibRefs.end()) {
+              auto* buildFile = this->CreateXCodeBuildFileFromPath(
+                libName.Value.Value, gt, "", nullptr);
+              if (buildFile) {
+                this->ExternalLibRefs.emplace(libName.Value.Value, buildFile);
+              }
+            }
+          }
         } else if (!libName.Target ||
                    libName.Target->GetType() !=
                      cmStateEnums::INTERFACE_LIBRARY) {
-          linkLibs += libName.Value.Value;
+          libPaths.Add(libName.Value.Value);
         }
         if (libName.Target && !libName.Target->IsImported()) {
           target->AddDependTarget(configName, libName.Target->GetName());
         }
       }
-      this->AppendBuildSettingAttribute(
-        target, this->GetTargetLinkFlagsVar(gt), linkLibs.c_str(), configName);
+      this->AppendBuildSettingAttribute(target,
+                                        this->GetTargetLinkFlagsVar(gt),
+                                        libPaths.CreateList(), configName);
     }
   }
 }
@@ -3166,7 +3338,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreatePBXGroup(cmXCodeObject* parent,
 {
   cmXCodeObject* parentChildren = nullptr;
   if (parent) {
-    parentChildren = parent->GetObject("children");
+    parentChildren = parent->GetAttribute("children");
   }
   cmXCodeObject* group = this->CreateObject(cmXCodeObject::PBXGroup);
   cmXCodeObject* groupChildren =
@@ -3465,7 +3637,7 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
   cmXCodeObject* allTargets = this->CreateObject(cmXCodeObject::OBJECT_LIST);
   for (auto t : targets) {
     allTargets->AddObject(t);
-    cmXCodeObject* productRef = t->GetObject("productReference");
+    cmXCodeObject* productRef = t->GetAttribute("productReference");
     if (productRef) {
       productGroupChildren->AddObject(productRef->GetObject());
     }

+ 2 - 2
Source/cmGlobalXCodeGenerator.h

@@ -168,9 +168,9 @@ private:
   std::string AddConfigurations(cmXCodeObject* target,
                                 cmGeneratorTarget* gtgt);
   void AppendOrAddBuildSetting(cmXCodeObject* settings, const char* attr,
-                               const char* value);
+                               cmXCodeObject* value);
   void AppendBuildSettingAttribute(cmXCodeObject* target, const char* attr,
-                                   const char* value,
+                                   cmXCodeObject* value,
                                    const std::string& configName);
   cmXCodeObject* CreateUtilityTarget(cmGeneratorTarget* gtgt);
   void AddDependAndLinkInformation(cmXCodeObject* target);

+ 1 - 0
Source/cmTarget.cxx

@@ -399,6 +399,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
       initProp("XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE");
       initProp("XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS");
       initProp("XCODE_SCHEME_ENVIRONMENT");
+      initPropValue("XCODE_LINK_BUILD_PHASE_MODE", "NONE");
     }
 #endif
   }

+ 1 - 1
Source/cmXCode21Object.cxx

@@ -16,7 +16,7 @@ cmXCode21Object::cmXCode21Object(PBXType ptype, Type type)
 void cmXCode21Object::PrintComment(std::ostream& out)
 {
   if (this->Comment.empty()) {
-    cmXCodeObject* n = this->GetObject("name");
+    cmXCodeObject* n = this->GetAttribute("name");
     if (n) {
       this->Comment = n->GetString();
       cmSystemTools::ReplaceString(this->Comment, "\"", "");

+ 5 - 1
Source/cmXCodeObject.h

@@ -82,6 +82,10 @@ public:
   void SetObject(cmXCodeObject* value) { this->Object = value; }
   cmXCodeObject* GetObject() { return this->Object; }
   void AddObject(cmXCodeObject* value) { this->List.push_back(value); }
+  void PrependObject(cmXCodeObject* value)
+  {
+    this->List.insert(this->List.begin(), value);
+  }
   bool HasObject(cmXCodeObject* o) const
   {
     return cm::contains(this->List, o);
@@ -107,7 +111,7 @@ public:
   void SetTarget(cmGeneratorTarget* t) { this->Target = t; }
   const std::string& GetComment() const { return this->Comment; }
   bool HasComment() const { return (!this->Comment.empty()); }
-  cmXCodeObject* GetObject(const char* name) const
+  cmXCodeObject* GetAttribute(const char* name) const
   {
     auto const i = this->ObjectAttributes.find(name);
     if (i != this->ObjectAttributes.end()) {

+ 87 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake

@@ -0,0 +1,87 @@
+enable_language(C)
+
+set(prototypes [[
+#include <stdio.h>
+#include <zlib.h>
+#include <resolv.h>
+int func1();
+int func2();
+int func3();
+int func4();
+int func5();
+]])
+set(impl [[
+{
+  printf("%p %p\n", compress, res_close);
+  return func1() + func2() + func3() + func4() + func5();
+}
+]])
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/mainOuter.c
+  "${prototypes}\nint main(int argc, char** argv) ${impl}"
+)
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/funcOuter.c
+  "${prototypes}\nint funcOuter() ${impl}"
+)
+
+foreach(i RANGE 1 5)
+  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/func${i}.c
+    "int func${i}() { return 32 + ${i}; }\n"
+  )
+endforeach()
+
+add_executable(app1 mainOuter.c)
+add_library(static1 STATIC funcOuter.c)
+add_library(shared1 SHARED funcOuter.c)
+add_library(module1 MODULE funcOuter.c)
+add_library(obj1    OBJECT funcOuter.c)
+add_library(staticFramework1 STATIC funcOuter.c)
+add_library(sharedFramework1 SHARED funcOuter.c)
+set_target_properties(staticFramework1 PROPERTIES FRAMEWORK TRUE)
+set_target_properties(sharedFramework1 PROPERTIES FRAMEWORK TRUE)
+
+add_library(static2 STATIC func1.c)
+add_library(shared2 SHARED func2.c)
+add_library(obj2    OBJECT func3.c)
+add_library(staticFramework2 STATIC func4.c)
+add_library(sharedFramework2 SHARED func5.c)
+set_target_properties(staticFramework2 PROPERTIES FRAMEWORK TRUE)
+set_target_properties(sharedFramework2 PROPERTIES FRAMEWORK TRUE)
+
+# Pick a couple of libraries that are always present in the Xcode SDK
+find_library(libz z REQUIRED)
+find_library(libresolv resolv REQUIRED)
+add_library(imported2 UNKNOWN IMPORTED)
+set_target_properties(imported2 PROPERTIES IMPORTED_LOCATION ${libz})
+
+# Save these for the check script to use
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/foundLibs.cmake "
+set(libz \"${libz}\")
+set(libresolv \"${libresolv}\")
+")
+
+set(mainTargets
+    app1
+    static1
+    shared1
+    module1
+    obj1
+    staticFramework1
+    sharedFramework1
+)
+set(linkToThings
+    static2
+    shared2
+    obj2
+    staticFramework2
+    sharedFramework2
+    imported2
+    ${libresolv}
+)
+
+foreach(mainTarget IN LISTS mainTargets)
+  foreach(linkTo IN LISTS linkToThings)
+    target_link_libraries(${mainTarget} PRIVATE ${linkTo})
+  endforeach()
+endforeach()

+ 19 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake

@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2    --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+  checkFlags(OTHER_LDFLAGS ${mainTarget}
+    "obj2;${libz};${libresolv}"
+    "static2;shared2;staticFramework2;sharedFramework2"
+  )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+  checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+    "obj2"
+    "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+  )
+endforeach()

+ 1 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

+ 55 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake

@@ -0,0 +1,55 @@
+macro(returnOnError errorMsg)
+  if(NOT "${errorMsg}" STREQUAL "")
+    set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}\n${errorMsg}" PARENT_SCOPE)
+    return()
+  endif()
+endmacro()
+
+function(getTargetFlags mainTarget projFlagsVar flagsVar errorVar)
+  # The flags variables in the project file might span over multiple lines
+  # so we can't easily read the flags directly from there. Instead, we use
+  # the xcodebuild -showBuildSettings option to report it on a single line.
+  execute_process(
+    COMMAND ${CMAKE_COMMAND}
+            --build ${RunCMake_TEST_BINARY_DIR}
+            --target ${mainTarget}
+            --config Debug
+            --
+            -showBuildSettings
+    COMMAND grep ${projFlagsVar}
+    OUTPUT_VARIABLE flagsContents
+    RESULT_VARIABLE result
+  )
+
+  if(result)
+    set(${errorVar} "Failed to get flags for ${mainTarget}: ${result}" PARENT_SCOPE)
+  else()
+    unset(${errorVar} PARENT_SCOPE)
+  endif()
+  set(${flagsVar} "${flagsContents}" PARENT_SCOPE)
+endfunction()
+
+function(checkFlags projFlagsVar mainTarget present absent)
+  getTargetFlags(${mainTarget} ${projFlagsVar} flags errorMsg)
+  returnOnError("${errorMsg}")
+
+  foreach(linkTo IN LISTS present)
+    string(REGEX MATCH "${linkTo}" result "${flags}")
+    if("${result}" STREQUAL "")
+      string(APPEND RunCMake_TEST_FAILED
+        "\n${mainTarget} ${projFlagsVar} is missing ${linkTo}"
+      )
+    endif()
+  endforeach()
+
+  foreach(linkTo IN LISTS absent)
+    string(REGEX MATCH "${linkTo}" result "${flags}")
+    if(NOT "${result}" STREQUAL "")
+      string(APPEND RunCMake_TEST_FAILED
+        "\n${mainTarget} ${projFlagsVar} unexpectedly contains ${linkTo}"
+      )
+    endif()
+  endforeach()
+
+  set(RunCMake_TEST_FAILED ${RunCMake_TEST_FAILED} PARENT_SCOPE)
+endfunction()

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

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

+ 1 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt

@@ -0,0 +1 @@
+CMake Error: Invalid value for XCODE_LINK_BUILD_PHASE_MODE: INVALID

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

@@ -0,0 +1,4 @@
+enable_language(CXX)
+
+add_executable(app main.cpp)
+set_target_properties(app PROPERTIES XCODE_LINK_BUILD_PHASE_MODE INVALID)

+ 19 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake

@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2    --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+  checkFlags(OTHER_LDFLAGS ${mainTarget}
+    "obj2"
+    "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+  )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+  checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+    "obj2"
+    "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+  )
+endforeach()

+ 1 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

+ 19 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake

@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2    --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+  checkFlags(OTHER_LDFLAGS ${mainTarget}
+    "static2;shared2;staticFramework2;sharedFramework2;obj2;${libz};${libresolv}"
+    ""
+  )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+  checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+    "obj2"
+    "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+  )
+endforeach()

+ 1 - 0
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

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

@@ -19,6 +19,19 @@ endfunction()
 
 XcodeGenerateTopLevelProjectOnlyWithObjectLibrary()
 
+function(LinkBinariesBuildPhase mode)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LinkBinariesBuildPhase_${mode}-build)
+  set(RunCMake_TEST_OPTIONS "-DCMAKE_XCODE_LINK_BUILD_PHASE_MODE=${mode}")
+  run_cmake(LinkBinariesBuildPhase_${mode})
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(LinkBinariesBuildPhase_${mode}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+LinkBinariesBuildPhase(NONE)
+LinkBinariesBuildPhase(BUILT_ONLY)
+LinkBinariesBuildPhase(KNOWN_LOCATION)
+run_cmake(LinkBinariesBuildPhase_INVALID)
+
 run_cmake(XcodeObjectNeedsEscape)
 run_cmake(XcodeObjectNeedsQuote)
 run_cmake(XcodeOptimizationFlags)