Просмотр исходного кода

Xcode: Use "Link Binary With Libraries" build phase in some cases

OBJECT and STATIC libraries (framework or non-framework) do not use
this build phase. Not all items to be linked use this build phase either.

Co-Authored-By: Craig Scott <[email protected]>
Gusts Kaksis 5 лет назад
Родитель
Сommit
525464ed2a

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

@@ -395,6 +395,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``.

+ 216 - 68
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,9 @@ 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";
@@ -1035,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)
@@ -1057,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
@@ -1077,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);
+      }
     }
   }
 
@@ -1109,6 +1170,10 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
     group = this->FrameworkGroup;
     this->GroupMap[key] = group;
   }
+  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);
@@ -2866,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).
@@ -2891,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);
     }
   }
 
@@ -2969,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()) {
@@ -2998,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;
@@ -3032,18 +3151,21 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
     // Add this reference to current target
     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->GetAttribute("files");
     if (!buildFiles) {
+      cmSystemTools::Error("Missing files of PBXFrameworksBuildPhase");
       continue;
     }
-    if (!buildFiles->HasObject(buildFile)) {
+    if (buildFile && !buildFiles->HasObject(buildFile)) {
       buildFiles->AddObject(buildFile);
     }
   }
@@ -3104,25 +3226,51 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
                                         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
     {
-      BuildObjectListOrString libSearchPaths(this, true);
+      BuildObjectListOrString libPaths(this, true);
       for (auto const& libItem : configItemMap[configName]) {
         auto const& libName = *libItem;
         if (libName.IsPath) {
-          libSearchPaths.Add(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) {
-          libSearchPaths.Add(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), libSearchPaths.CreateList(),
-        configName);
+      this->AppendBuildSettingAttribute(target,
+                                        this->GetTargetLinkFlagsVar(gt),
+                                        libPaths.CreateList(), configName);
     }
   }
 }

+ 1 - 0
Source/cmTarget.cxx

@@ -398,6 +398,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
   }

+ 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)