Merge topic 'LINK_LIBRARY-framework'

a2cfa2da4f GenEx/LINK_LIBRARY: Add features for framework support on Apple
40178f3c90 cmGlobalGenerator: Add helper to split framework path

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !6967
diff --git a/Help/release/dev/Apple-link-framework.rst b/Help/release/dev/Apple-link-framework.rst
new file mode 100644
index 0000000..e194c15
--- /dev/null
+++ b/Help/release/dev/Apple-link-framework.rst
@@ -0,0 +1,11 @@
+Apple-link-framework
+--------------------
+
+* The :genex:`LINK_LIBRARY` generator expression gained the ability to link
+  frameworks in various ways when targeting ``Apple`` platforms. The following
+  new features were added:
+
+  * ``FRAMEWORK``
+  * ``NEEDED_FRAMEWORK``
+  * ``REEXPORT_FRAMEWORK``
+  * ``WEAK_FRAMEWORK``
diff --git a/Help/variable/LINK_LIBRARY_PREDEFINED_FEATURES.txt b/Help/variable/LINK_LIBRARY_PREDEFINED_FEATURES.txt
index dd22e14..d4fdf76 100644
--- a/Help/variable/LINK_LIBRARY_PREDEFINED_FEATURES.txt
+++ b/Help/variable/LINK_LIBRARY_PREDEFINED_FEATURES.txt
@@ -3,3 +3,43 @@
 * ``DEFAULT``: This feature enables default link expression. This is mainly
   useful with :prop_tgt:`LINK_LIBRARY_OVERRIDE` and
   :prop_tgt:`LINK_LIBRARY_OVERRIDE_<LIBRARY>` target properties.
+
+**Features available in Apple environments**
+
+It is assumed that the linker used is the one provided by `XCode` or is
+compatible with it.
+
+* ``FRAMEWORK``: This option tells the linker to search for the specified
+  framework (use linker option ``-framework``).
+* ``NEEDED_FRAMEWORK``: This is the same as the ``FRAMEWORK`` feature but means
+  to really link with the framework even if no symbols are used from it (use
+  linker option ``-needed_framework``).
+* ``REEXPORT_FRAMEWORK``: This is the same as the ``FRAMEWORK`` feature but
+  also specifies that all symbols in that framework should be available to
+  clients linking to the library being created (use linker option
+  ``-reexport_framework``).
+* ``WEAK_FRAMEWORK``: This is the same as the ``FRAMEWORK`` feature but forces
+  the framework and all references to it to be marked as weak imports (use
+  linker option ``-weak_framework``).
+
+Features for framework linking have a special handling in ``CMake``: the
+framework can be specified as a ``CMake`` framework target or file path. In
+the later case, if the path includes a directory part, this one will be
+specified as framework search path at link step.
+
+.. code-block:: cmake
+
+  add_library(lib SHARED ...)
+  target_link_libraries(lib PRIVATE "$<LINK_LIBRARY:NEEDED_FRAMEWORK,/path/to/my_framework>")
+
+  # at link step we will have:
+  # -F/path/to -needed_framework my_framework
+
+.. note::
+
+   The expected formats for the file path, with optional parts specified as
+   ``()?``, are:
+
+   * (/path/to/)?FwName(.framework)?
+   * (/path/to/)?FwName.framework/FwName
+   * (/path/to/)?FwName.framework/Versions/\*/FwName
diff --git a/Modules/Compiler/Clang-CUDA.cmake b/Modules/Compiler/Clang-CUDA.cmake
index 219897e..44eb35f 100644
--- a/Modules/Compiler/Clang-CUDA.cmake
+++ b/Modules/Compiler/Clang-CUDA.cmake
@@ -38,3 +38,18 @@
 if(UNIX)
   list(APPEND CMAKE_CUDA_RUNTIME_LIBRARY_LINK_OPTIONS_STATIC "rt" "pthread" "dl")
 endif()
+
+if(APPLE)
+  # Defines host link features for frameworks
+  set(CMAKE_CUDA_LINK_USING_FRAMEWORK "LINKER:-framework,<LIBRARY>")
+  set(CMAKE_CUDA_LINK_USING_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_CUDA_LINK_USING_NEEDED_FRAMEWORK "LINKER:-needed_framework,<LIBRARY>")
+  set(CMAKE_CUDA_LINK_USING_NEEDED_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_CUDA_LINK_USING_REEXPORT_FRAMEWORK "LINKER:-reexport_framework,<LIBRARY>")
+  set(CMAKE_CUDA_LINK_USING_REEXPORT_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_CUDA_LINK_USING_WEAK_FRAMEWORK "LINKER:-weak_framework,<LIBRARY>")
+  set(CMAKE_CUDA_LINK_USING_WEAK_FRAMEWORK_SUPPORTED TRUE)
+endif()
diff --git a/Modules/Platform/Apple-Apple-Swift.cmake b/Modules/Platform/Apple-Apple-Swift.cmake
index 7ca3e36..255411e 100644
--- a/Modules/Platform/Apple-Apple-Swift.cmake
+++ b/Modules/Platform/Apple-Apple-Swift.cmake
@@ -1 +1,15 @@
 set(CMAKE_Swift_SYSROOT_FLAG "-sdk")
+
+
+# Defines host link features for frameworks
+set(CMAKE_Swift_LINK_USING_FRAMEWORK "LINKER:-framework,<LIBRARY>")
+set(CMAKE_Swift_LINK_USING_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_Swift_LINK_USING_NEEDED_FRAMEWORK "LINKER:-needed_framework,<LIBRARY>")
+set(CMAKE_Swift_LINK_USING_NEEDED_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_Swift_LINK_USING_REEXPORT_FRAMEWORK "LINKER:-reexport_framework,<LIBRARY>")
+set(CMAKE_Swift_LINK_USING_REEXPORT_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_Swift_LINK_USING_WEAK_FRAMEWORK "LINKER:-weak_framework,<LIBRARY>")
+set(CMAKE_Swift_LINK_USING_WEAK_FRAMEWORK_SUPPORTED TRUE)
diff --git a/Modules/Platform/Apple-NVIDIA-CUDA.cmake b/Modules/Platform/Apple-NVIDIA-CUDA.cmake
index 35e759a..ba669e1 100644
--- a/Modules/Platform/Apple-NVIDIA-CUDA.cmake
+++ b/Modules/Platform/Apple-NVIDIA-CUDA.cmake
@@ -17,3 +17,17 @@
 
 set(CMAKE_CUDA_CREATE_SHARED_LIBRARY "<CMAKE_CUDA_HOST_LINK_LAUNCHER> <CMAKE_SHARED_LIBRARY_CUDA_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CUDA_FLAGS> -o <TARGET> <SONAME_FLAG> <TARGET_INSTALLNAME_DIR><TARGET_SONAME> <OBJECTS> <LINK_LIBRARIES>${__IMPLICIT_LINKS}")
 set(CMAKE_CUDA_CREATE_SHARED_MODULE "<CMAKE_CUDA_HOST_LINK_LAUNCHER> <CMAKE_SHARED_LIBRARY_CUDA_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CUDA_FLAGS> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>${__IMPLICIT_LINKS}")
+
+
+# Defines host link features for frameworks
+set(CMAKE_CUDA_LINK_USING_FRAMEWORK "LINKER:-framework,<LIBRARY>")
+set(CMAKE_CUDA_LINK_USING_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_CUDA_LINK_USING_NEEDED_FRAMEWORK "LINKER:-needed_framework,<LIBRARY>")
+set(CMAKE_CUDA_LINK_USING_NEEDED_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_CUDA_LINK_USING_REEXPORT_FRAMEWORK "LINKER:-reexport_framework,<LIBRARY>")
+set(CMAKE_CUDA_LINK_USING_REEXPORT_FRAMEWORK_SUPPORTED TRUE)
+
+set(CMAKE_CUDA_LINK_USING_WEAK_FRAMEWORK "LINKER:-weak_framework,<LIBRARY>")
+set(CMAKE_CUDA_LINK_USING_WEAK_FRAMEWORK_SUPPORTED TRUE)
diff --git a/Modules/Platform/Darwin.cmake b/Modules/Platform/Darwin.cmake
index 839dc81..d7b615c 100644
--- a/Modules/Platform/Darwin.cmake
+++ b/Modules/Platform/Darwin.cmake
@@ -106,6 +106,19 @@
   # Set default framework search path flag for languages known to use a
   # preprocessor that may find headers in frameworks.
   set(CMAKE_${lang}_FRAMEWORK_SEARCH_FLAG -F)
+
+  # Defines link features for frameworks
+  set(CMAKE_${lang}_LINK_USING_FRAMEWORK "LINKER:-framework,<LIBRARY>")
+  set(CMAKE_${lang}_LINK_USING_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_${lang}_LINK_USING_NEEDED_FRAMEWORK "LINKER:-needed_framework,<LIBRARY>")
+  set(CMAKE_${lang}_LINK_USING_NEEDED_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_${lang}_LINK_USING_REEXPORT_FRAMEWORK "LINKER:-reexport_framework,<LIBRARY>")
+  set(CMAKE_${lang}_LINK_USING_REEXPORT_FRAMEWORK_SUPPORTED TRUE)
+
+  set(CMAKE_${lang}_LINK_USING_WEAK_FRAMEWORK "LINKER:-weak_framework,<LIBRARY>")
+  set(CMAKE_${lang}_LINK_USING_WEAK_FRAMEWORK_SUPPORTED TRUE)
 endforeach()
 
 # default to searching for frameworks first
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index 15e9d60..cf8c9a1 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -8,7 +8,9 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cmext/algorithm>
+#include <cmext/string_view>
 
 #include "cmComputeLinkDepends.h"
 #include "cmGeneratorTarget.h"
@@ -18,7 +20,6 @@
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmOrderDirectories.h"
-#include "cmOutputConverter.h"
 #include "cmPlaceholderExpander.h"
 #include "cmPolicies.h"
 #include "cmState.h"
@@ -1044,12 +1045,14 @@
     }
   } else {
     // This is not a CMake target.  Use the name given.
-    if (cmSystemTools::FileIsFullPath(item.Value)) {
-      if (cmSystemTools::IsPathToFramework(item.Value) &&
-          this->Makefile->IsOn("APPLE")) {
-        // This is a framework.
-        this->AddFrameworkItem(entry);
-      } else if (cmSystemTools::FileIsDirectory(item.Value)) {
+    if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) ||
+        (entry.Feature == DEFAULT &&
+         cmSystemTools::IsPathToFramework(item.Value) &&
+         this->Makefile->IsOn("APPLE"))) {
+      // This is a framework.
+      this->AddFrameworkItem(entry);
+    } else if (cmSystemTools::FileIsFullPath(item.Value)) {
+      if (cmSystemTools::FileIsDirectory(item.Value)) {
         // This is a directory.
         this->DropDirectoryItem(item);
       } else {
@@ -1424,11 +1427,41 @@
     this->OldLinkDirItems.push_back(item.Value);
   }
 
-  // Now add the full path to the library.
-  this->Items.emplace_back(item, ItemIsPath::Yes, target,
-                           this->FindLibraryFeature(entry.Feature == DEFAULT
-                                                      ? "__CMAKE_LINK_LIBRARY"
-                                                      : entry.Feature));
+  if (target->IsFrameworkOnApple() && this->GlobalGenerator->IsXcode() &&
+      entry.Feature == DEFAULT) {
+    // ensure FRAMEWORK feature is loaded
+    this->AddLibraryFeature("FRAMEWORK");
+  }
+
+  if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) &&
+      target->IsFrameworkOnApple() && !this->GlobalGenerator->IsXcode()) {
+    // Add the framework directory and the framework item itself
+    auto fwItems = this->GlobalGenerator->SplitFrameworkPath(item.Value, true);
+    if (!fwItems) {
+      this->CMakeInstance->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Could not parse framework path \"", item.Value,
+                 "\" linked by target ", this->Target->GetName(), '.'),
+        item.Backtrace);
+      return;
+    }
+    if (!fwItems->first.empty()) {
+      // Add the directory portion to the framework search path.
+      this->AddFrameworkPath(fwItems->first);
+    }
+    this->Items.emplace_back(fwItems->second, ItemIsPath::Yes, target,
+                             this->FindLibraryFeature(entry.Feature));
+  } else {
+    // Now add the full path to the library.
+    this->Items.emplace_back(
+      item, ItemIsPath::Yes, target,
+      this->FindLibraryFeature(
+        entry.Feature == DEFAULT
+          ? (target->IsFrameworkOnApple() && this->GlobalGenerator->IsXcode()
+               ? "FRAMEWORK"
+               : "__CMAKE_LINK_LIBRARY")
+          : entry.Feature));
+  }
 }
 
 void cmComputeLinkInformation::AddFullItem(LinkEntry const& entry)
@@ -1679,7 +1712,9 @@
   std::string const& item = entry.Item.Value;
 
   // Try to separate the framework name and path.
-  if (!this->SplitFramework.find(item)) {
+  auto fwItems =
+    this->GlobalGenerator->SplitFrameworkPath(item, entry.Feature != DEFAULT);
+  if (!fwItems) {
     std::ostringstream e;
     e << "Could not parse framework path \"" << item << "\" "
       << "linked by target " << this->Target->GetName() << ".";
@@ -1687,26 +1722,36 @@
     return;
   }
 
-  std::string fw_path = this->SplitFramework.match(1);
-  std::string fw = this->SplitFramework.match(2);
-  std::string full_fw = cmStrCat(fw_path, '/', fw, ".framework/", fw);
+  std::string fw_path = std::move(fwItems->first);
+  std::string fw = std::move(fwItems->second);
+  std::string full_fw = cmStrCat(fw, ".framework/", fw);
 
-  // Add the directory portion to the framework search path.
-  this->AddFrameworkPath(fw_path);
+  if (!fw_path.empty()) {
+    full_fw = cmStrCat(fw_path, '/', full_fw);
+    // Add the directory portion to the framework search path.
+    this->AddFrameworkPath(fw_path);
+  }
 
   // add runtime information
   this->AddLibraryRuntimeInfo(full_fw);
 
+  if (entry.Feature == DEFAULT) {
+    // ensure FRAMEWORK feature is loaded
+    this->AddLibraryFeature("FRAMEWORK");
+  }
+
   if (this->GlobalGenerator->IsXcode()) {
     // Add framework path - it will be handled by Xcode after it's added to
     // "Link Binary With Libraries" build phase
-    this->Items.emplace_back(item, ItemIsPath::Yes);
+    this->Items.emplace_back(item, ItemIsPath::Yes, nullptr,
+                             this->FindLibraryFeature(entry.Feature == DEFAULT
+                                                        ? "FRAMEWORK"
+                                                        : entry.Feature));
   } else {
-    // Add the item using the -framework option.
-    this->Items.emplace_back(std::string("-framework"), ItemIsPath::No);
-    cmOutputConverter converter(this->Makefile->GetStateSnapshot());
-    fw = converter.EscapeForShell(fw);
-    this->Items.emplace_back(fw, ItemIsPath::No);
+    this->Items.emplace_back(fw, ItemIsPath::Yes, nullptr,
+                             this->FindLibraryFeature(entry.Feature == DEFAULT
+                                                        ? "FRAMEWORK"
+                                                        : entry.Feature));
   }
 }
 
@@ -1739,9 +1784,6 @@
 
   this->FrameworkPathsEmitted.insert(implicitDirVec.begin(),
                                      implicitDirVec.end());
-
-  // Regular expression to extract a framework path and name.
-  this->SplitFramework.compile("(.*)/(.*)\\.framework$");
 }
 
 void cmComputeLinkInformation::AddFrameworkPath(std::string const& p)
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index ce9f393..4b7fb1a 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/Source/cmComputeLinkInformation.h
@@ -63,6 +63,11 @@
     cmGeneratorTarget const* Target = nullptr;
 
     bool HasFeature() const { return this->Feature != nullptr; }
+    const std::string& GetFeatureName() const
+    {
+      return HasFeature() ? this->Feature->Name
+                          : cmComputeLinkDepends::LinkEntry::DEFAULT;
+    }
 
     BT<std::string> GetFormattedItem(std::string const& path) const
     {
@@ -205,7 +210,6 @@
   void ComputeFrameworkInfo();
   void AddFrameworkPath(std::string const& p);
   std::set<std::string> FrameworkPathsEmitted;
-  cmsys::RegularExpression SplitFramework;
 
   // Linker search path computation.
   std::unique_ptr<cmOrderDirectories> OrderLinkerSearchPath;
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 156ecce..baa54e5 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -19,6 +19,7 @@
 
 #include "cmsys/Directory.hxx"
 #include "cmsys/FStream.hxx"
+#include "cmsys/RegularExpression.hxx"
 
 #if defined(_WIN32) && !defined(__CYGWIN__)
 #  include <windows.h>
@@ -2522,6 +2523,47 @@
   return false;
 }
 
+// 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)
+cm::optional<std::pair<std::string, std::string>>
+cmGlobalGenerator::SplitFrameworkPath(const std::string& path,
+                                      bool extendedFormat) const
+{
+  // Check for framework structure:
+  //    (/path/to/)?FwName.framework
+  // or (/path/to/)?FwName.framework/FwName(.tbd)?
+  // or (/path/to/)?FwName.framework/Versions/*/FwName(.tbd)?
+  static cmsys::RegularExpression frameworkPath(
+    "((.+)/)?(.+)\\.framework(/Versions/[^/]+)?(/(.+))?$");
+
+  auto ext = cmSystemTools::GetFilenameLastExtension(path);
+  if ((ext.empty() || ext == ".tbd" || ext == ".framework") &&
+      frameworkPath.find(path)) {
+    auto name = frameworkPath.match(3);
+    auto libname =
+      cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(6));
+    if (!libname.empty() && name != libname) {
+      return cm::nullopt;
+    }
+    return std::pair<std::string, std::string>{ frameworkPath.match(2), name };
+  }
+
+  if (extendedFormat) {
+    // path format can be more flexible: (/path/to/)?fwName(.framework)?
+    auto fwDir = cmSystemTools::GetParentDirectory(path);
+    auto name = cmSystemTools::GetFilenameLastExtension(path) == ".framework"
+      ? cmSystemTools::GetFilenameWithoutExtension(path)
+      : cmSystemTools::GetFilenameName(path);
+
+    return std::pair<std::string, std::string>{ fwDir, name };
+  }
+
+  return cm::nullopt;
+}
+
 bool cmGlobalGenerator::CheckCMP0037(std::string const& targetName,
                                      std::string const& reason) const
 {
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index a43d4a6..a4b2ae3 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -367,6 +367,13 @@
   /** Determine if a name resolves to a framework on disk or a built target
       that is a framework. */
   bool NameResolvesToFramework(const std::string& libname) const;
+  /** Split a framework path to the directory and name of the framework
+   * returns std::nullopt if the path does not match with framework format
+   * when extendedFormat is true, required format is relaxed (i.e. extension
+   * `.framework' is optional). Used when FRAMEWORK link feature is
+   * specified */
+  cm::optional<std::pair<std::string, std::string>> SplitFrameworkPath(
+    const std::string& path, bool extendedFormat = false) const;
 
   cmMakefile* FindMakefile(const std::string& start_dir) const;
   cmLocalGenerator* FindLocalGenerator(cmDirectoryId const& id) const;
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 489c7fb..b752c41 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -1154,47 +1154,25 @@
   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)
+} // anonymous
+
+// Extracts the framework directory, if path matches the framework syntax
+// otherwise returns the path untouched
+std::string cmGlobalXCodeGenerator::GetLibraryOrFrameworkPath(
+  const std::string& path) const
 {
-  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);
-      }
+  auto fwItems = this->SplitFrameworkPath(path);
+  if (fwItems) {
+    if (fwItems->first.empty()) {
+      return cmStrCat(fwItems->second, ".framework");
+    } else {
+      return cmStrCat(fwItems->first, '/', fwItems->second, ".framework");
     }
   }
+
   return path;
 }
 
-} // anonymous
-
 cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
   const std::string& fullpath, cmGeneratorTarget* target,
   const std::string& lang, cmSourceFile* sf)
@@ -1217,7 +1195,7 @@
     ext = ext.substr(1);
   }
   if (fileType.empty()) {
-    path = GetLibraryOrFrameworkPath(path);
+    path = this->GetLibraryOrFrameworkPath(path);
     ext = cmSystemTools::GetFilenameLastExtension(path);
     if (!ext.empty()) {
       ext = ext.substr(1);
@@ -3541,13 +3519,14 @@
     } else {
       linkDir = libItem->Value.Value;
     }
-    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);
+    if (cmHasSuffix(libItem->GetFeatureName(), "FRAMEWORK"_s)) {
+      auto fwItems = this->SplitFrameworkPath(linkDir, true);
+      if (fwItems && !fwItems->first.empty()) {
+        linkDir = std::move(fwItems->first);
+        if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(),
+                      linkDir) == frameworkSearchPaths.end()) {
+          frameworkSearchPaths.push_back(linkDir);
+        }
       }
     } else {
       if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
@@ -3555,7 +3534,7 @@
         linkSearchPaths.push_back(linkDir);
       }
     }
-    // Add target dependency
+
     if (libItem->Target && !libItem->Target->IsImported()) {
       for (auto const& configName : this->CurrentConfigurationTypes) {
         target->AddDependTarget(configName, libItem->Target->GetName());
@@ -3729,24 +3708,27 @@
           if (cmSystemTools::FileIsFullPath(cleanPath)) {
             cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
           }
-          const auto libPath = GetLibraryOrFrameworkPath(cleanPath);
-          if (cmSystemTools::StringEndsWith(libPath.c_str(), ".framework")) {
-            const auto fwName =
-              cmSystemTools::GetFilenameWithoutExtension(libPath);
-            const auto fwDir = cmSystemTools::GetParentDirectory(libPath);
-            if (emitted.insert(fwDir).second) {
-              // This is a search path we had not added before and it isn't an
-              // implicit search path, so we need it
-              libPaths.Add("-F " + this->XCodeEscapePath(fwDir));
+          bool isFramework =
+            cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s);
+          if (isFramework) {
+            const auto fwItems =
+              this->SplitFrameworkPath(cleanPath, isFramework);
+            if (!fwItems->first.empty() &&
+                emitted.insert(fwItems->first).second) {
+              // This is a search path we had not added before and it isn't
+              // an implicit search path, so we need it
+              libPaths.Add("-F " + this->XCodeEscapePath(fwItems->first));
             }
-            libPaths.Add("-framework " + this->XCodeEscapePath(fwName));
+            libPaths.Add(
+              libName.GetFormattedItem(this->XCodeEscapePath(fwItems->second))
+                .Value);
           } else {
             libPaths.Add(
               libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
                 .Value);
           }
           if ((!libName.Target || libName.Target->IsImported()) &&
-              IsLinkPhaseLibraryExtension(libPath)) {
+              (isFramework || IsLinkPhaseLibraryExtension(cleanPath))) {
             // Create file reference for embedding
             auto it = this->ExternalLibRefs.find(cleanPath);
             if (it == this->ExternalLibRefs.end()) {
@@ -3914,8 +3896,8 @@
 {
   static const auto dstSubfolderSpec = "10";
 
-  // Despite the name, by default Xcode uses "Embed Frameworks" build phase for
-  // both frameworks and dynamic libraries
+  // Despite the name, by default Xcode uses "Embed Frameworks" build phase
+  // for both frameworks and dynamic libraries
   this->AddEmbeddedObjects(target, "Embed Frameworks",
                            "XCODE_EMBED_FRAMEWORKS", dstSubfolderSpec,
                            NoActionOnCopyByDefault);
diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h
index ff6ffe8..98cebef 100644
--- a/Source/cmGlobalXCodeGenerator.h
+++ b/Source/cmGlobalXCodeGenerator.h
@@ -330,6 +330,8 @@
   {
   }
 
+  std::string GetLibraryOrFrameworkPath(const std::string& path) const;
+
   std::string GetObjectsDirectory(const std::string& projName,
                                   const std::string& configName,
                                   const cmGeneratorTarget* t,
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 652ea5c..3673ac9 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -659,6 +659,7 @@
                                                      -DMSYS=${MSYS}
                                                      -DCYGWIN=${CYGWIN}
                                                      -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
+                                                     -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION}
                                                      -DMSVC_VERSION=${MSVC_VERSION}
                                                      -DCMAKE_SHARED_LIBRARY_PREFIX=${CMAKE_SHARED_LIBRARY_PREFIX}
                                                      -DCMAKE_SHARED_LIBRARY_SUFFIX=${CMAKE_SHARED_LIBRARY_SUFFIX}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/RunCMakeTest.cmake b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/RunCMakeTest.cmake
index 9ebbdb7..9582e11 100644
--- a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/RunCMakeTest.cmake
+++ b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/RunCMakeTest.cmake
@@ -79,3 +79,21 @@
   unset(RunCMake_TEST_OUTPUT_MERGE)
 
 endif()
+
+# Apple framework features
+if(APPLE AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang"))
+  run_cmake(apple_framework)
+  run_cmake_target(apple_framework framework main-framework)
+  run_cmake_target(apple_framework reexport_framework main-reexport_framework)
+  run_cmake_target(apple_framework weak_framework main-weak_framework)
+
+  run_cmake_target(apple_framework target-framework main-target-framework)
+  run_cmake_target(apple_framework target-reexport_framework main-target-reexport_framework)
+  run_cmake_target(apple_framework target-weak_framework main-target-weak_framework)
+endif()
+
+if (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION GREATER_EQUAL "12")
+  run_cmake_target(apple_framework needed_framework main-needed_framework)
+
+  run_cmake_target(apple_framework target-needed_framework main-target-needed_framework)
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/apple_framework.cmake b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/apple_framework.cmake
new file mode 100644
index 0000000..e9a93e9
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/apple_framework.cmake
@@ -0,0 +1,61 @@
+
+enable_language(OBJCXX)
+
+
+# feature FRAMEWORK
+add_library(foo-framework SHARED foo.mm)
+target_link_libraries(foo-framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>")
+
+add_executable(main-framework main.mm)
+target_link_libraries(main-framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" foo-framework)
+
+
+# feature NEEDED_FRAMEWORK
+add_library(foo-needed_framework SHARED foo.mm)
+target_link_libraries(foo-needed_framework PRIVATE "$<LINK_LIBRARY:NEEDED_FRAMEWORK,Foundation>")
+
+add_executable(main-needed_framework main.mm)
+target_link_libraries(main-needed_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" foo-needed_framework)
+
+
+# feature REEXPORT_FRAMEWORK
+add_library(foo-reexport_framework SHARED foo.mm)
+target_link_libraries(foo-reexport_framework PRIVATE "$<LINK_LIBRARY:REEXPORT_FRAMEWORK,Foundation>")
+
+add_executable(main-reexport_framework main.mm)
+target_link_libraries(main-reexport_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" foo-reexport_framework)
+
+
+# feature WEAK_FRAMEWORK
+add_library(foo-weak_framework SHARED foo.mm)
+target_link_libraries(foo-weak_framework PRIVATE "$<LINK_LIBRARY:WEAK_FRAMEWORK,Foundation>")
+
+add_executable(main-weak_framework main.mm)
+target_link_libraries(main-weak_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" foo-weak_framework)
+
+
+##
+## Consumption of target specified as FRAMEWORK
+add_library(target-framework SHARED foo.mm)
+set_target_properties(target-framework PROPERTIES FRAMEWORK TRUE)
+target_link_libraries(target-framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>")
+
+
+# feature FRAMEWORK
+add_executable(main-target-framework main.mm)
+target_link_libraries(main-target-framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" "$<LINK_LIBRARY:FRAMEWORK,target-framework>")
+
+
+# feature NEEDED_FRAMEWORK
+add_executable(main-target-needed_framework main.mm)
+target_link_libraries(main-target-needed_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" "$<LINK_LIBRARY:NEEDED_FRAMEWORK,target-framework>")
+
+
+# feature REEXPORT_FRAMEWORK
+add_executable(main-target-reexport_framework main.mm)
+target_link_libraries(main-target-reexport_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" "$<LINK_LIBRARY:REEXPORT_FRAMEWORK,target-framework>")
+
+
+# feature WEAK_FRAMEWORK
+add_executable(main-target-weak_framework main.mm)
+target_link_libraries(main-target-weak_framework PRIVATE "$<LINK_LIBRARY:FRAMEWORK,Foundation>" "$<LINK_LIBRARY:REEXPORT_FRAMEWORK,target-framework>")
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.h b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.h
new file mode 100644
index 0000000..b3fb084
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.h
@@ -0,0 +1,9 @@
+#import <Foundation/Foundation.h>
+
+@interface Foo : NSObject {
+  NSNumber* age;
+}
+
+@property (nonatomic, retain) NSNumber* age;
+
+@end
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.mm b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.mm
new file mode 100644
index 0000000..2d452a8
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/foo.mm
@@ -0,0 +1,7 @@
+#import "foo.h"
+
+@implementation Foo
+
+@synthesize age;
+
+@end
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/main.mm b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/main.mm
new file mode 100644
index 0000000..7c85551
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_LIBRARY/main.mm
@@ -0,0 +1,14 @@
+#import <Foundation/Foundation.h>
+#import "foo.h"
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+  Foo *theFoo = [[Foo alloc] init];
+  theFoo.age = [NSNumber numberWithInt:argc];
+  NSLog(@"%d\n",[theFoo.age intValue]);
+  std::cout << [theFoo.age intValue] << std::endl;
+  [pool release];
+  return 0;
+}