Browse Source

cmGeneratorTarget: Factor link interface/impl methods into own source

Brad King 1 year ago
parent
commit
feaca40931
5 changed files with 1493 additions and 1003 deletions
  1. 1 0
      Source/CMakeLists.txt
  2. 1 1002
      Source/cmGeneratorTarget.cxx
  3. 1 1
      Source/cmGeneratorTarget.h
  4. 1489 0
      Source/cmGeneratorTarget_Link.cxx
  5. 1 0
      bootstrap

+ 1 - 0
Source/CMakeLists.txt

@@ -287,6 +287,7 @@ add_library(
   cmGeneratorTarget.h
   cmGeneratorTarget_CompatibleInterface.cxx
   cmGeneratorTarget_IncludeDirectories.cxx
+  cmGeneratorTarget_Link.cxx
   cmGeneratorTarget_LinkDirectories.cxx
   cmGeneratorTarget_Options.cxx
   cmGeneratorTarget_Sources.cxx

File diff suppressed because it is too large
+ 1 - 1002
Source/cmGeneratorTarget.cxx


+ 1 - 1
Source/cmGeneratorTarget.h

@@ -18,7 +18,6 @@
 #include <cm/string_view>
 
 #include "cmAlgorithms.h"
-#include "cmComputeLinkInformation.h"
 #include "cmLinkItem.h"
 #include "cmListFileCache.h"
 #include "cmPolicies.h"
@@ -29,6 +28,7 @@
 class cmake;
 enum class cmBuildStep;
 class cmCompiledGeneratorExpression;
+class cmComputeLinkInformation;
 class cmCustomCommand;
 class cmFileSet;
 class cmGlobalGenerator;

+ 1489 - 0
Source/cmGeneratorTarget_Link.cxx

@@ -0,0 +1,1489 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+/* clang-format off */
+#include "cmGeneratorTarget.h"
+/* clang-format on */
+
+#include <algorithm>
+#include <cassert>
+#include <cstdio>
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <cm/memory>
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/algorithm>
+#include <cmext/string_view>
+
+#include "cmAlgorithms.h"
+#include "cmComputeLinkInformation.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorExpressionDAGChecker.h"
+#include "cmGlobalGenerator.h"
+#include "cmLinkItem.h"
+#include "cmList.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmPolicies.h"
+#include "cmRange.h"
+#include "cmSourceFile.h"
+#include "cmSourceFileLocationKind.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTargetLinkLibraryType.h"
+#include "cmValue.h"
+#include "cmake.h"
+
+namespace {
+using UseTo = cmGeneratorTarget::UseTo;
+
+const std::string kINTERFACE_LINK_LIBRARIES = "INTERFACE_LINK_LIBRARIES";
+const std::string kINTERFACE_LINK_LIBRARIES_DIRECT =
+  "INTERFACE_LINK_LIBRARIES_DIRECT";
+const std::string kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE =
+  "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE";
+}
+
+class cmTargetCollectLinkLanguages
+{
+public:
+  cmTargetCollectLinkLanguages(cmGeneratorTarget const* target,
+                               std::string config,
+                               std::unordered_set<std::string>& languages,
+                               cmGeneratorTarget const* head, bool secondPass)
+    : Config(std::move(config))
+    , Languages(languages)
+    , HeadTarget(head)
+    , SecondPass(secondPass)
+  {
+    this->Visited.insert(target);
+  }
+
+  void Visit(cmLinkItem const& item)
+  {
+    if (!item.Target) {
+      return;
+    }
+    if (!this->Visited.insert(item.Target).second) {
+      return;
+    }
+    cmLinkInterface const* iface = item.Target->GetLinkInterface(
+      this->Config, this->HeadTarget, this->SecondPass);
+    if (!iface) {
+      return;
+    }
+    if (iface->HadLinkLanguageSensitiveCondition) {
+      this->HadLinkLanguageSensitiveCondition = true;
+    }
+
+    for (std::string const& language : iface->Languages) {
+      this->Languages.insert(language);
+    }
+
+    for (cmLinkItem const& lib : iface->Libraries) {
+      this->Visit(lib);
+    }
+  }
+
+  bool GetHadLinkLanguageSensitiveCondition() const
+  {
+    return this->HadLinkLanguageSensitiveCondition;
+  }
+
+private:
+  std::string Config;
+  std::unordered_set<std::string>& Languages;
+  cmGeneratorTarget const* HeadTarget;
+  std::set<cmGeneratorTarget const*> Visited;
+  bool SecondPass;
+  bool HadLinkLanguageSensitiveCondition = false;
+};
+
+cmGeneratorTarget::LinkClosure const* cmGeneratorTarget::GetLinkClosure(
+  const std::string& config) const
+{
+  // There is no link implementation for targets that cannot compile sources.
+  if (!this->CanCompileSources()) {
+    static LinkClosure const empty = { {}, {} };
+    return &empty;
+  }
+
+  std::string key(cmSystemTools::UpperCase(config));
+  auto i = this->LinkClosureMap.find(key);
+  if (i == this->LinkClosureMap.end()) {
+    LinkClosure lc;
+    this->ComputeLinkClosure(config, lc);
+    LinkClosureMapType::value_type entry(key, lc);
+    i = this->LinkClosureMap.insert(entry).first;
+  }
+  return &i->second;
+}
+
+class cmTargetSelectLinker
+{
+  int Preference = 0;
+  cmGeneratorTarget const* Target;
+  cmGlobalGenerator* GG;
+  std::set<std::string> Preferred;
+
+public:
+  cmTargetSelectLinker(cmGeneratorTarget const* target)
+    : Target(target)
+  {
+    this->GG = this->Target->GetLocalGenerator()->GetGlobalGenerator();
+  }
+  void Consider(const std::string& lang)
+  {
+    int preference = this->GG->GetLinkerPreference(lang);
+    if (preference > this->Preference) {
+      this->Preference = preference;
+      this->Preferred.clear();
+    }
+    if (preference == this->Preference) {
+      this->Preferred.insert(lang);
+    }
+  }
+  std::string Choose()
+  {
+    if (this->Preferred.empty()) {
+      return "";
+    }
+    if (this->Preferred.size() > 1) {
+      std::ostringstream e;
+      e << "Target " << this->Target->GetName()
+        << " contains multiple languages with the highest linker preference"
+        << " (" << this->Preference << "):\n";
+      for (std::string const& li : this->Preferred) {
+        e << "  " << li << "\n";
+      }
+      e << "Set the LINKER_LANGUAGE property for this target.";
+      cmake* cm = this->Target->GetLocalGenerator()->GetCMakeInstance();
+      cm->IssueMessage(MessageType::FATAL_ERROR, e.str(),
+                       this->Target->GetBacktrace());
+    }
+    return *this->Preferred.begin();
+  }
+};
+
+bool cmGeneratorTarget::ComputeLinkClosure(const std::string& config,
+                                           LinkClosure& lc,
+                                           bool secondPass) const
+{
+  // Get languages built in this target.
+  std::unordered_set<std::string> languages;
+  cmLinkImplementation const* impl =
+    this->GetLinkImplementation(config, UseTo::Link, secondPass);
+  assert(impl);
+  languages.insert(impl->Languages.cbegin(), impl->Languages.cend());
+
+  // Add interface languages from linked targets.
+  // cmTargetCollectLinkLanguages cll(this, config, languages, this,
+  // secondPass);
+  cmTargetCollectLinkLanguages cll(this, config, languages, this, secondPass);
+  for (cmLinkImplItem const& lib : impl->Libraries) {
+    cll.Visit(lib);
+  }
+
+  // Store the transitive closure of languages.
+  cm::append(lc.Languages, languages);
+
+  // Choose the language whose linker should be used.
+  if (secondPass || lc.LinkerLanguage.empty()) {
+    // Find the language with the highest preference value.
+    cmTargetSelectLinker tsl(this);
+
+    // First select from the languages compiled directly in this target.
+    for (std::string const& l : impl->Languages) {
+      tsl.Consider(l);
+    }
+
+    // Now consider languages that propagate from linked targets.
+    for (std::string const& lang : languages) {
+      std::string propagates =
+        "CMAKE_" + lang + "_LINKER_PREFERENCE_PROPAGATES";
+      if (this->Makefile->IsOn(propagates)) {
+        tsl.Consider(lang);
+      }
+    }
+
+    lc.LinkerLanguage = tsl.Choose();
+  }
+
+  return impl->HadLinkLanguageSensitiveCondition ||
+    cll.GetHadLinkLanguageSensitiveCondition();
+}
+
+void cmGeneratorTarget::ComputeLinkClosure(const std::string& config,
+                                           LinkClosure& lc) const
+{
+  bool secondPass = false;
+
+  {
+    LinkClosure linkClosure;
+    linkClosure.LinkerLanguage = this->LinkerLanguage;
+
+    bool hasHardCodedLinkerLanguage = this->Target->GetProperty("HAS_CXX") ||
+      !this->Target->GetSafeProperty("LINKER_LANGUAGE").empty();
+
+    // Get languages built in this target.
+    secondPass = this->ComputeLinkClosure(config, linkClosure, false) &&
+      !hasHardCodedLinkerLanguage;
+    this->LinkerLanguage = linkClosure.LinkerLanguage;
+    if (!secondPass) {
+      lc = std::move(linkClosure);
+    }
+  }
+
+  if (secondPass) {
+    LinkClosure linkClosure;
+
+    this->ComputeLinkClosure(config, linkClosure, secondPass);
+    lc = std::move(linkClosure);
+
+    // linker language must not be changed between the two passes
+    if (this->LinkerLanguage != lc.LinkerLanguage) {
+      std::ostringstream e;
+      e << "Evaluation of $<LINK_LANGUAGE:...> or $<LINK_LAND_AND_ID:...> "
+           "changes\nthe linker language for target \""
+        << this->GetName() << "\" (from '" << this->LinkerLanguage << "' to '"
+        << lc.LinkerLanguage << "') which is invalid.";
+      cmSystemTools::Error(e.str());
+    }
+  }
+}
+
+static void processILibs(const std::string& config,
+                         cmGeneratorTarget const* headTarget,
+                         cmLinkItem const& item, cmGlobalGenerator* gg,
+                         std::vector<cmGeneratorTarget const*>& tgts,
+                         std::set<cmGeneratorTarget const*>& emitted)
+{
+  if (item.Target && emitted.insert(item.Target).second) {
+    tgts.push_back(item.Target);
+    if (cmLinkInterfaceLibraries const* iface =
+          item.Target->GetLinkInterfaceLibraries(config, headTarget,
+                                                 UseTo::Compile)) {
+      for (cmLinkItem const& lib : iface->Libraries) {
+        processILibs(config, headTarget, lib, gg, tgts, emitted);
+      }
+    }
+  }
+}
+
+const std::vector<const cmGeneratorTarget*>&
+cmGeneratorTarget::GetLinkImplementationClosure(
+  const std::string& config) const
+{
+  // There is no link implementation for targets that cannot compile sources.
+  if (!this->CanCompileSources()) {
+    static std::vector<const cmGeneratorTarget*> const empty;
+    return empty;
+  }
+
+  LinkImplClosure& tgts = this->LinkImplClosureMap[config];
+  if (!tgts.Done) {
+    tgts.Done = true;
+    std::set<cmGeneratorTarget const*> emitted;
+
+    cmLinkImplementationLibraries const* impl =
+      this->GetLinkImplementationLibraries(config, UseTo::Compile);
+    assert(impl);
+
+    for (cmLinkImplItem const& lib : impl->Libraries) {
+      processILibs(config, this, lib,
+                   this->LocalGenerator->GetGlobalGenerator(), tgts, emitted);
+    }
+  }
+  return tgts;
+}
+
+cmComputeLinkInformation* cmGeneratorTarget::GetLinkInformation(
+  const std::string& config) const
+{
+  // Lookup any existing information for this configuration.
+  std::string key(cmSystemTools::UpperCase(config));
+  auto i = this->LinkInformation.find(key);
+  if (i == this->LinkInformation.end()) {
+    // Compute information for this configuration.
+    auto info = cm::make_unique<cmComputeLinkInformation>(this, config);
+    if (info && !info->Compute()) {
+      info.reset();
+    }
+
+    // Store the information for this configuration.
+    i = this->LinkInformation.emplace(key, std::move(info)).first;
+
+    if (i->second) {
+      this->CheckPropertyCompatibility(*i->second, config);
+    }
+  }
+  return i->second.get();
+}
+
+void cmGeneratorTarget::CheckLinkLibraries() const
+{
+  bool linkLibrariesOnlyTargets =
+    this->GetPropertyAsBool("LINK_LIBRARIES_ONLY_TARGETS");
+
+  // Evaluate the link interface of this target if needed for extra checks.
+  if (linkLibrariesOnlyTargets) {
+    std::vector<std::string> const& configs =
+      this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+    for (std::string const& config : configs) {
+      this->GetLinkInterfaceLibraries(config, this, UseTo::Link);
+    }
+  }
+
+  // Check link the implementation for each generated configuration.
+  for (auto const& hmp : this->LinkImplMap) {
+    HeadToLinkImplementationMap const& hm = hmp.second;
+    // There could be several entries used when computing the pre-CMP0022
+    // default link interface.  Check only the entry for our own link impl.
+    auto const hmi = hm.find(this);
+    if (hmi == hm.end() || !hmi->second.LibrariesDone) {
+      continue;
+    }
+    for (cmLinkImplItem const& item : hmi->second.Libraries) {
+      if (!this->VerifyLinkItemColons(LinkItemRole::Implementation, item)) {
+        return;
+      }
+      if (linkLibrariesOnlyTargets &&
+          !this->VerifyLinkItemIsTarget(LinkItemRole::Implementation, item)) {
+        return;
+      }
+    }
+  }
+
+  // Check link the interface for each generated combination of
+  // configuration and consuming head target.  We should not need to
+  // consider LinkInterfaceUsageRequirementsOnlyMap because its entries
+  // should be a subset of LinkInterfaceMap (with LINK_ONLY left out).
+  for (auto const& hmp : this->LinkInterfaceMap) {
+    for (auto const& hmi : hmp.second) {
+      if (!hmi.second.LibrariesDone) {
+        continue;
+      }
+      for (cmLinkItem const& item : hmi.second.Libraries) {
+        if (!this->VerifyLinkItemColons(LinkItemRole::Interface, item)) {
+          return;
+        }
+        if (linkLibrariesOnlyTargets &&
+            !this->VerifyLinkItemIsTarget(LinkItemRole::Interface, item)) {
+          return;
+        }
+      }
+    }
+  }
+}
+
+namespace {
+cm::string_view missingTargetPossibleReasons =
+  "Possible reasons include:\n"
+  "  * There is a typo in the target name.\n"
+  "  * A find_package call is missing for an IMPORTED target.\n"
+  "  * An ALIAS target is missing.\n"_s;
+}
+
+bool cmGeneratorTarget::VerifyLinkItemColons(LinkItemRole role,
+                                             cmLinkItem const& item) const
+{
+  if (item.Target || cmHasPrefix(item.AsStr(), "<LINK_GROUP:"_s) ||
+      item.AsStr().find("::") == std::string::npos) {
+    return true;
+  }
+  MessageType messageType = MessageType::FATAL_ERROR;
+  std::string e;
+  switch (this->GetLocalGenerator()->GetPolicyStatus(cmPolicies::CMP0028)) {
+    case cmPolicies::WARN: {
+      e = cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0028), "\n");
+      messageType = MessageType::AUTHOR_WARNING;
+    } break;
+    case cmPolicies::OLD:
+      return true;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+    case cmPolicies::NEW:
+      // Issue the fatal message.
+      break;
+  }
+
+  if (role == LinkItemRole::Implementation) {
+    e = cmStrCat(e, "Target \"", this->GetName(), "\" links to");
+  } else {
+    e = cmStrCat(e, "The link interface of target \"", this->GetName(),
+                 "\" contains");
+  }
+  e =
+    cmStrCat(e, ":\n  ", item.AsStr(), "\n", "but the target was not found.  ",
+             missingTargetPossibleReasons);
+  cmListFileBacktrace backtrace = item.Backtrace;
+  if (backtrace.Empty()) {
+    backtrace = this->GetBacktrace();
+  }
+  this->GetLocalGenerator()->GetCMakeInstance()->IssueMessage(messageType, e,
+                                                              backtrace);
+  return false;
+}
+
+bool cmGeneratorTarget::VerifyLinkItemIsTarget(LinkItemRole role,
+                                               cmLinkItem const& item) const
+{
+  if (item.Target) {
+    return true;
+  }
+  std::string const& str = item.AsStr();
+  if (!str.empty() &&
+      (str[0] == '-' || str[0] == '$' || str[0] == '`' ||
+       str.find_first_of("/\\") != std::string::npos ||
+       cmHasPrefix(str, "<LINK_LIBRARY:"_s) ||
+       cmHasPrefix(str, "<LINK_GROUP:"_s))) {
+    return true;
+  }
+
+  std::string e = cmStrCat("Target \"", this->GetName(),
+                           "\" has LINK_LIBRARIES_ONLY_TARGETS enabled, but ",
+                           role == LinkItemRole::Implementation
+                             ? "it links to"
+                             : "its link interface contains",
+                           ":\n  ", item.AsStr(), "\nwhich is not a target.  ",
+                           missingTargetPossibleReasons);
+  cmListFileBacktrace backtrace = item.Backtrace;
+  if (backtrace.Empty()) {
+    backtrace = this->GetBacktrace();
+  }
+  this->LocalGenerator->GetCMakeInstance()->IssueMessage(
+    MessageType::FATAL_ERROR, e, backtrace);
+  return false;
+}
+
+bool cmGeneratorTarget::IsLinkLookupScope(std::string const& n,
+                                          cmLocalGenerator const*& lg) const
+{
+  if (cmHasLiteralPrefix(n, CMAKE_DIRECTORY_ID_SEP)) {
+    cmDirectoryId const dirId = n.substr(cmStrLen(CMAKE_DIRECTORY_ID_SEP));
+    if (dirId.String.empty()) {
+      lg = this->LocalGenerator;
+      return true;
+    }
+    if (cmLocalGenerator const* otherLG =
+          this->GlobalGenerator->FindLocalGenerator(dirId)) {
+      lg = otherLG;
+      return true;
+    }
+  }
+  return false;
+}
+
+cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem(
+  std::string const& n, cmListFileBacktrace const& bt,
+  std::string const& linkFeature, LookupLinkItemScope* scope,
+  LookupSelf lookupSelf) const
+{
+  cm::optional<cmLinkItem> maybeItem;
+  if (this->IsLinkLookupScope(n, scope->LG)) {
+    return maybeItem;
+  }
+
+  std::string name = this->CheckCMP0004(n);
+  if (name.empty() ||
+      (lookupSelf == LookupSelf::No && name == this->GetName())) {
+    return maybeItem;
+  }
+  maybeItem =
+    this->ResolveLinkItem(BT<std::string>(name, bt), scope->LG, linkFeature);
+  return maybeItem;
+}
+
+void cmGeneratorTarget::ExpandLinkItems(std::string const& prop,
+                                        cmBTStringRange entries,
+                                        std::string const& config,
+                                        cmGeneratorTarget const* headTarget,
+                                        UseTo usage, LinkInterfaceField field,
+                                        cmLinkInterface& iface) const
+{
+  if (entries.empty()) {
+    return;
+  }
+  // Keep this logic in sync with ComputeLinkImplementationLibraries.
+  cmGeneratorExpressionDAGChecker dagChecker(this, prop, nullptr, nullptr,
+                                             this->LocalGenerator);
+  // The $<LINK_ONLY> expression may be in a link interface to specify
+  // private link dependencies that are otherwise excluded from usage
+  // requirements.
+  if (usage == UseTo::Compile) {
+    dagChecker.SetTransitivePropertiesOnly();
+    dagChecker.SetTransitivePropertiesOnlyCMP0131();
+  }
+  cmMakefile const* mf = this->LocalGenerator->GetMakefile();
+  LookupLinkItemScope scope{ this->LocalGenerator };
+  for (BT<std::string> const& entry : entries) {
+    cmGeneratorExpression ge(*this->LocalGenerator->GetCMakeInstance(),
+                             entry.Backtrace);
+    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(entry.Value);
+    cge->SetEvaluateForBuildsystem(true);
+    cmList libs{ cge->Evaluate(this->LocalGenerator, config, headTarget,
+                               &dagChecker, this,
+                               headTarget->LinkerLanguage) };
+
+    auto linkFeature = cmLinkItem::DEFAULT;
+    for (auto const& lib : libs) {
+      if (auto maybeLinkFeature = ParseLinkFeature(lib)) {
+        linkFeature = std::move(*maybeLinkFeature);
+        continue;
+      }
+
+      if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
+            lib, cge->GetBacktrace(), linkFeature, &scope,
+            field == LinkInterfaceField::Libraries ? LookupSelf::No
+                                                   : LookupSelf::Yes)) {
+        cmLinkItem item = std::move(*maybeItem);
+
+        if (field == LinkInterfaceField::HeadInclude) {
+          iface.HeadInclude.emplace_back(std::move(item));
+          continue;
+        }
+        if (field == LinkInterfaceField::HeadExclude) {
+          iface.HeadExclude.emplace_back(std::move(item));
+          continue;
+        }
+        if (!item.Target) {
+          // Report explicitly linked object files separately.
+          std::string const& maybeObj = item.AsStr();
+          if (cmSystemTools::FileIsFullPath(maybeObj)) {
+            cmSourceFile const* sf =
+              mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
+            if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
+              item.ObjectSource = sf;
+              iface.Objects.emplace_back(std::move(item));
+              continue;
+            }
+          }
+        }
+
+        iface.Libraries.emplace_back(std::move(item));
+      }
+    }
+    if (cge->GetHadHeadSensitiveCondition()) {
+      iface.HadHeadSensitiveCondition = true;
+    }
+    if (cge->GetHadContextSensitiveCondition()) {
+      iface.HadContextSensitiveCondition = true;
+    }
+    if (cge->GetHadLinkLanguageSensitiveCondition()) {
+      iface.HadLinkLanguageSensitiveCondition = true;
+    }
+  }
+}
+
+cmLinkInterface const* cmGeneratorTarget::GetLinkInterface(
+  const std::string& config, cmGeneratorTarget const* head) const
+{
+  return this->GetLinkInterface(config, head, false);
+}
+
+cmLinkInterface const* cmGeneratorTarget::GetLinkInterface(
+  const std::string& config, cmGeneratorTarget const* head,
+  bool secondPass) const
+{
+  // Imported targets have their own link interface.
+  if (this->IsImported()) {
+    return this->GetImportLinkInterface(config, head, UseTo::Link, secondPass);
+  }
+
+  // Link interfaces are not supported for executables that do not
+  // export symbols.
+  if (this->GetType() == cmStateEnums::EXECUTABLE &&
+      !this->IsExecutableWithExports()) {
+    return nullptr;
+  }
+
+  // Lookup any existing link interface for this configuration.
+  cmHeadToLinkInterfaceMap& hm = this->GetHeadToLinkInterfaceMap(config);
+
+  // If the link interface does not depend on the head target
+  // then reuse the one from the head we computed first.
+  if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
+    head = hm.begin()->first;
+  }
+
+  cmOptionalLinkInterface& iface = hm[head];
+  if (secondPass) {
+    iface = cmOptionalLinkInterface();
+  }
+  if (!iface.LibrariesDone) {
+    iface.LibrariesDone = true;
+    this->ComputeLinkInterfaceLibraries(config, iface, head, UseTo::Link);
+  }
+  if (!iface.AllDone) {
+    iface.AllDone = true;
+    if (iface.Exists) {
+      this->ComputeLinkInterface(config, iface, head, secondPass);
+      this->ComputeLinkInterfaceRuntimeLibraries(config, iface);
+    }
+  }
+
+  return iface.Exists ? &iface : nullptr;
+}
+
+void cmGeneratorTarget::ComputeLinkInterface(
+  const std::string& config, cmOptionalLinkInterface& iface,
+  cmGeneratorTarget const* headTarget) const
+{
+  this->ComputeLinkInterface(config, iface, headTarget, false);
+}
+
+void cmGeneratorTarget::ComputeLinkInterface(
+  const std::string& config, cmOptionalLinkInterface& iface,
+  cmGeneratorTarget const* headTarget, bool secondPass) const
+{
+  if (iface.Explicit) {
+    if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
+        this->GetType() == cmStateEnums::STATIC_LIBRARY ||
+        this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+      // Shared libraries may have runtime implementation dependencies
+      // on other shared libraries that are not in the interface.
+      std::set<cmLinkItem> emitted;
+      for (cmLinkItem const& lib : iface.Libraries) {
+        emitted.insert(lib);
+      }
+      if (this->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
+        cmLinkImplementation const* impl =
+          this->GetLinkImplementation(config, UseTo::Link, secondPass);
+        for (cmLinkImplItem const& lib : impl->Libraries) {
+          if (emitted.insert(lib).second) {
+            if (lib.Target) {
+              // This is a runtime dependency on another shared library.
+              if (lib.Target->GetType() == cmStateEnums::SHARED_LIBRARY) {
+                iface.SharedDeps.push_back(lib);
+              }
+            } else {
+              // TODO: Recognize shared library file names.  Perhaps this
+              // should be moved to cmComputeLinkInformation, but that
+              // creates a chicken-and-egg problem since this list is needed
+              // for its construction.
+            }
+          }
+        }
+      }
+    }
+  } else if (this->GetPolicyStatusCMP0022() == cmPolicies::WARN ||
+             this->GetPolicyStatusCMP0022() == cmPolicies::OLD) {
+    // The link implementation is the default link interface.
+    cmLinkImplementationLibraries const* impl =
+      this->GetLinkImplementationLibrariesInternal(config, headTarget,
+                                                   UseTo::Link);
+    iface.ImplementationIsInterface = true;
+    iface.WrongConfigLibraries = impl->WrongConfigLibraries;
+  }
+
+  if (this->LinkLanguagePropagatesToDependents()) {
+    // Targets using this archive need its language runtime libraries.
+    if (cmLinkImplementation const* impl =
+          this->GetLinkImplementation(config, UseTo::Link, secondPass)) {
+      iface.Languages = impl->Languages;
+    }
+  }
+
+  if (this->GetType() == cmStateEnums::STATIC_LIBRARY) {
+    // Construct the property name suffix for this configuration.
+    std::string suffix = "_";
+    if (!config.empty()) {
+      suffix += cmSystemTools::UpperCase(config);
+    } else {
+      suffix += "NOCONFIG";
+    }
+
+    // How many repetitions are needed if this library has cyclic
+    // dependencies?
+    std::string propName = cmStrCat("LINK_INTERFACE_MULTIPLICITY", suffix);
+    if (cmValue config_reps = this->GetProperty(propName)) {
+      sscanf(config_reps->c_str(), "%u", &iface.Multiplicity);
+    } else if (cmValue reps =
+                 this->GetProperty("LINK_INTERFACE_MULTIPLICITY")) {
+      sscanf(reps->c_str(), "%u", &iface.Multiplicity);
+    }
+  }
+}
+
+const cmLinkInterfaceLibraries* cmGeneratorTarget::GetLinkInterfaceLibraries(
+  const std::string& config, cmGeneratorTarget const* head, UseTo usage) const
+{
+  // Imported targets have their own link interface.
+  if (this->IsImported()) {
+    return this->GetImportLinkInterface(config, head, usage);
+  }
+
+  // Link interfaces are not supported for executables that do not
+  // export symbols.
+  if (this->GetType() == cmStateEnums::EXECUTABLE &&
+      !this->IsExecutableWithExports()) {
+    return nullptr;
+  }
+
+  // Lookup any existing link interface for this configuration.
+  cmHeadToLinkInterfaceMap& hm =
+    (usage == UseTo::Compile
+       ? this->GetHeadToLinkInterfaceUsageRequirementsMap(config)
+       : this->GetHeadToLinkInterfaceMap(config));
+
+  // If the link interface does not depend on the head target
+  // then reuse the one from the head we computed first.
+  if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
+    head = hm.begin()->first;
+  }
+
+  cmOptionalLinkInterface& iface = hm[head];
+  if (!iface.LibrariesDone) {
+    iface.LibrariesDone = true;
+    this->ComputeLinkInterfaceLibraries(config, iface, head, usage);
+  }
+
+  return iface.Exists ? &iface : nullptr;
+}
+
+void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
+  const std::string& config, cmOptionalLinkInterface& iface,
+  cmGeneratorTarget const* headTarget, UseTo usage) const
+{
+  // Construct the property name suffix for this configuration.
+  std::string suffix = "_";
+  if (!config.empty()) {
+    suffix += cmSystemTools::UpperCase(config);
+  } else {
+    suffix += "NOCONFIG";
+  }
+
+  // An explicit list of interface libraries may be set for shared
+  // libraries and executables that export symbols.
+  bool haveExplicitLibraries = false;
+  cmValue explicitLibrariesCMP0022OLD;
+  std::string linkIfacePropCMP0022OLD;
+  bool const cmp0022NEW = (this->GetPolicyStatusCMP0022() != cmPolicies::OLD &&
+                           this->GetPolicyStatusCMP0022() != cmPolicies::WARN);
+  if (cmp0022NEW) {
+    // CMP0022 NEW behavior is to use INTERFACE_LINK_LIBRARIES.
+    haveExplicitLibraries = !this->Target->GetLinkInterfaceEntries().empty() ||
+      !this->Target->GetLinkInterfaceDirectEntries().empty() ||
+      !this->Target->GetLinkInterfaceDirectExcludeEntries().empty();
+  } else {
+    // CMP0022 OLD behavior is to use LINK_INTERFACE_LIBRARIES if set on a
+    // shared lib or executable.
+    if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
+        this->IsExecutableWithExports()) {
+      // Lookup the per-configuration property.
+      linkIfacePropCMP0022OLD = cmStrCat("LINK_INTERFACE_LIBRARIES", suffix);
+      explicitLibrariesCMP0022OLD = this->GetProperty(linkIfacePropCMP0022OLD);
+
+      // If not set, try the generic property.
+      if (!explicitLibrariesCMP0022OLD) {
+        linkIfacePropCMP0022OLD = "LINK_INTERFACE_LIBRARIES";
+        explicitLibrariesCMP0022OLD =
+          this->GetProperty(linkIfacePropCMP0022OLD);
+      }
+    }
+
+    if (explicitLibrariesCMP0022OLD &&
+        this->GetPolicyStatusCMP0022() == cmPolicies::WARN &&
+        !this->PolicyWarnedCMP0022) {
+      // Compare the explicitly set old link interface properties to the
+      // preferred new link interface property one and warn if different.
+      cmValue newExplicitLibraries =
+        this->GetProperty("INTERFACE_LINK_LIBRARIES");
+      if (newExplicitLibraries &&
+          (*newExplicitLibraries != *explicitLibrariesCMP0022OLD)) {
+        std::ostringstream w;
+        /* clang-format off */
+        w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0022) << "\n"
+          "Target \"" << this->GetName() << "\" has an "
+          "INTERFACE_LINK_LIBRARIES property which differs from its " <<
+          linkIfacePropCMP0022OLD << " properties."
+          "\n"
+          "INTERFACE_LINK_LIBRARIES:\n"
+          "  " << *newExplicitLibraries << "\n" <<
+          linkIfacePropCMP0022OLD << ":\n"
+          "  " << *explicitLibrariesCMP0022OLD << "\n";
+        /* clang-format on */
+        this->LocalGenerator->IssueMessage(MessageType::AUTHOR_WARNING,
+                                           w.str());
+        this->PolicyWarnedCMP0022 = true;
+      }
+    }
+
+    haveExplicitLibraries = static_cast<bool>(explicitLibrariesCMP0022OLD);
+  }
+
+  // There is no implicit link interface for executables or modules
+  // so if none was explicitly set then there is no link interface.
+  if (!haveExplicitLibraries &&
+      (this->GetType() == cmStateEnums::EXECUTABLE ||
+       (this->GetType() == cmStateEnums::MODULE_LIBRARY))) {
+    return;
+  }
+  iface.Exists = true;
+
+  // If CMP0022 is NEW then the plain tll signature sets the
+  // INTERFACE_LINK_LIBRARIES property.  Even if the project
+  // clears it, the link interface is still explicit.
+  iface.Explicit = cmp0022NEW || explicitLibrariesCMP0022OLD;
+
+  if (cmp0022NEW) {
+    // The interface libraries are specified by INTERFACE_LINK_LIBRARIES.
+    // Use its special representation directly to get backtraces.
+    this->ExpandLinkItems(
+      kINTERFACE_LINK_LIBRARIES, this->Target->GetLinkInterfaceEntries(),
+      config, headTarget, usage, LinkInterfaceField::Libraries, iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT,
+                          this->Target->GetLinkInterfaceDirectEntries(),
+                          config, headTarget, usage,
+                          LinkInterfaceField::HeadInclude, iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
+                          this->Target->GetLinkInterfaceDirectExcludeEntries(),
+                          config, headTarget, usage,
+                          LinkInterfaceField::HeadExclude, iface);
+  } else if (explicitLibrariesCMP0022OLD) {
+    // The interface libraries have been explicitly set in pre-CMP0022 style.
+    std::vector<BT<std::string>> entries;
+    entries.emplace_back(*explicitLibrariesCMP0022OLD);
+    this->ExpandLinkItems(linkIfacePropCMP0022OLD, cmMakeRange(entries),
+                          config, headTarget, usage,
+                          LinkInterfaceField::Libraries, iface);
+  }
+
+  // If the link interface is explicit, do not fall back to the link impl.
+  if (iface.Explicit) {
+    return;
+  }
+
+  // The link implementation is the default link interface.
+  if (cmLinkImplementationLibraries const* impl =
+        this->GetLinkImplementationLibrariesInternal(config, headTarget,
+                                                     usage)) {
+    iface.Libraries.insert(iface.Libraries.end(), impl->Libraries.begin(),
+                           impl->Libraries.end());
+    if (this->GetPolicyStatusCMP0022() == cmPolicies::WARN &&
+        !this->PolicyWarnedCMP0022 && usage == UseTo::Link) {
+      // Compare the link implementation fallback link interface to the
+      // preferred new link interface property and warn if different.
+      cmLinkInterface ifaceNew;
+      this->ExpandLinkItems(
+        kINTERFACE_LINK_LIBRARIES, this->Target->GetLinkInterfaceEntries(),
+        config, headTarget, usage, LinkInterfaceField::Libraries, ifaceNew);
+      if (ifaceNew.Libraries != iface.Libraries) {
+        std::string oldLibraries = cmJoin(impl->Libraries, ";");
+        std::string newLibraries = cmJoin(ifaceNew.Libraries, ";");
+        if (oldLibraries.empty()) {
+          oldLibraries = "(empty)";
+        }
+        if (newLibraries.empty()) {
+          newLibraries = "(empty)";
+        }
+
+        std::ostringstream w;
+        /* clang-format off */
+        w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0022) << "\n"
+          "Target \"" << this->GetName() << "\" has an "
+          "INTERFACE_LINK_LIBRARIES property.  "
+          "This should be preferred as the source of the link interface "
+          "for this library but because CMP0022 is not set CMake is "
+          "ignoring the property and using the link implementation "
+          "as the link interface instead."
+          "\n"
+          "INTERFACE_LINK_LIBRARIES:\n"
+          "  " << newLibraries << "\n"
+          "Link implementation:\n"
+          "  " << oldLibraries << "\n";
+        /* clang-format on */
+        this->LocalGenerator->IssueMessage(MessageType::AUTHOR_WARNING,
+                                           w.str());
+        this->PolicyWarnedCMP0022 = true;
+      }
+    }
+  }
+}
+
+namespace {
+
+template <typename ReturnType>
+ReturnType constructItem(cmGeneratorTarget* target,
+                         cmListFileBacktrace const& bt);
+
+template <>
+inline cmLinkImplItem constructItem(cmGeneratorTarget* target,
+                                    cmListFileBacktrace const& bt)
+{
+  return cmLinkImplItem(cmLinkItem(target, false, bt), false);
+}
+
+template <>
+inline cmLinkItem constructItem(cmGeneratorTarget* target,
+                                cmListFileBacktrace const& bt)
+{
+  return cmLinkItem(target, false, bt);
+}
+
+template <typename ValueType>
+std::vector<ValueType> computeImplicitLanguageTargets(
+  std::string const& lang, std::string const& config,
+  cmGeneratorTarget const* currentTarget)
+{
+  cmListFileBacktrace bt;
+  std::vector<ValueType> result;
+  cmLocalGenerator* lg = currentTarget->GetLocalGenerator();
+
+  std::string const& runtimeLibrary =
+    currentTarget->GetRuntimeLinkLibrary(lang, config);
+  if (cmValue runtimeLinkOptions = currentTarget->Makefile->GetDefinition(
+        "CMAKE_" + lang + "_RUNTIME_LIBRARIES_" + runtimeLibrary)) {
+    cmList libsList{ *runtimeLinkOptions };
+    result.reserve(libsList.size());
+
+    for (auto const& i : libsList) {
+      cmGeneratorTarget::TargetOrString resolved =
+        currentTarget->ResolveTargetReference(i, lg);
+      if (resolved.Target) {
+        result.emplace_back(constructItem<ValueType>(resolved.Target, bt));
+      }
+    }
+  }
+
+  return result;
+}
+}
+
+void cmGeneratorTarget::ComputeLinkInterfaceRuntimeLibraries(
+  const std::string& config, cmOptionalLinkInterface& iface) const
+{
+  for (std::string const& lang : iface.Languages) {
+    if ((lang == "CUDA" || lang == "HIP") &&
+        iface.LanguageRuntimeLibraries.find(lang) ==
+          iface.LanguageRuntimeLibraries.end()) {
+      auto implicitTargets =
+        computeImplicitLanguageTargets<cmLinkItem>(lang, config, this);
+      iface.LanguageRuntimeLibraries[lang] = std::move(implicitTargets);
+    }
+  }
+}
+
+void cmGeneratorTarget::ComputeLinkImplementationRuntimeLibraries(
+  const std::string& config, cmOptionalLinkImplementation& impl) const
+{
+  for (std::string const& lang : impl.Languages) {
+    if ((lang == "CUDA" || lang == "HIP") &&
+        impl.LanguageRuntimeLibraries.find(lang) ==
+          impl.LanguageRuntimeLibraries.end()) {
+      auto implicitTargets =
+        computeImplicitLanguageTargets<cmLinkImplItem>(lang, config, this);
+      impl.LanguageRuntimeLibraries[lang] = std::move(implicitTargets);
+    }
+  }
+}
+
+const cmLinkInterface* cmGeneratorTarget::GetImportLinkInterface(
+  const std::string& config, cmGeneratorTarget const* headTarget, UseTo usage,
+  bool secondPass) const
+{
+  cmGeneratorTarget::ImportInfo const* info = this->GetImportInfo(config);
+  if (!info) {
+    return nullptr;
+  }
+
+  cmHeadToLinkInterfaceMap& hm =
+    (usage == UseTo::Compile
+       ? this->GetHeadToLinkInterfaceUsageRequirementsMap(config)
+       : this->GetHeadToLinkInterfaceMap(config));
+
+  // If the link interface does not depend on the head target
+  // then reuse the one from the head we computed first.
+  if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
+    headTarget = hm.begin()->first;
+  }
+
+  cmOptionalLinkInterface& iface = hm[headTarget];
+  if (secondPass) {
+    iface = cmOptionalLinkInterface();
+  }
+  if (!iface.AllDone) {
+    iface.AllDone = true;
+    iface.LibrariesDone = true;
+    iface.Multiplicity = info->Multiplicity;
+    cmExpandList(info->Languages, iface.Languages);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT,
+                          cmMakeRange(info->LibrariesHeadInclude), config,
+                          headTarget, usage, LinkInterfaceField::HeadInclude,
+                          iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
+                          cmMakeRange(info->LibrariesHeadExclude), config,
+                          headTarget, usage, LinkInterfaceField::HeadExclude,
+                          iface);
+    this->ExpandLinkItems(info->LibrariesProp, cmMakeRange(info->Libraries),
+                          config, headTarget, usage,
+                          LinkInterfaceField::Libraries, iface);
+    cmList deps{ info->SharedDeps };
+    LookupLinkItemScope scope{ this->LocalGenerator };
+
+    auto linkFeature = cmLinkItem::DEFAULT;
+    for (auto const& dep : deps) {
+      if (auto maybeLinkFeature = ParseLinkFeature(dep)) {
+        linkFeature = std::move(*maybeLinkFeature);
+        continue;
+      }
+
+      if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
+            dep, cmListFileBacktrace(), linkFeature, &scope, LookupSelf::No)) {
+        iface.SharedDeps.emplace_back(std::move(*maybeItem));
+      }
+    }
+  }
+
+  return &iface;
+}
+
+cmHeadToLinkInterfaceMap& cmGeneratorTarget::GetHeadToLinkInterfaceMap(
+  const std::string& config) const
+{
+  return this->LinkInterfaceMap[cmSystemTools::UpperCase(config)];
+}
+
+cmHeadToLinkInterfaceMap&
+cmGeneratorTarget::GetHeadToLinkInterfaceUsageRequirementsMap(
+  const std::string& config) const
+{
+  return this
+    ->LinkInterfaceUsageRequirementsOnlyMap[cmSystemTools::UpperCase(config)];
+}
+
+const cmLinkImplementation* cmGeneratorTarget::GetLinkImplementation(
+  const std::string& config, UseTo usage) const
+{
+  return this->GetLinkImplementation(config, usage, false);
+}
+
+const cmLinkImplementation* cmGeneratorTarget::GetLinkImplementation(
+  const std::string& config, UseTo usage, bool secondPass) const
+{
+  // There is no link implementation for targets that cannot compile sources.
+  if (!this->CanCompileSources()) {
+    return nullptr;
+  }
+
+  HeadToLinkImplementationMap& hm =
+    (usage == UseTo::Compile
+       ? this->GetHeadToLinkImplementationUsageRequirementsMap(config)
+       : this->GetHeadToLinkImplementationMap(config));
+  cmOptionalLinkImplementation& impl = hm[this];
+  if (secondPass) {
+    impl = cmOptionalLinkImplementation();
+  }
+  if (!impl.LibrariesDone) {
+    impl.LibrariesDone = true;
+    this->ComputeLinkImplementationLibraries(config, impl, this, usage);
+  }
+  if (!impl.LanguagesDone) {
+    impl.LanguagesDone = true;
+    this->ComputeLinkImplementationLanguages(config, impl);
+    this->ComputeLinkImplementationRuntimeLibraries(config, impl);
+  }
+  return &impl;
+}
+
+cmGeneratorTarget::HeadToLinkImplementationMap&
+cmGeneratorTarget::GetHeadToLinkImplementationMap(
+  std::string const& config) const
+{
+  return this->LinkImplMap[cmSystemTools::UpperCase(config)];
+}
+
+cmGeneratorTarget::HeadToLinkImplementationMap&
+cmGeneratorTarget::GetHeadToLinkImplementationUsageRequirementsMap(
+  std::string const& config) const
+{
+  return this
+    ->LinkImplUsageRequirementsOnlyMap[cmSystemTools::UpperCase(config)];
+}
+
+cmLinkImplementationLibraries const*
+cmGeneratorTarget::GetLinkImplementationLibraries(const std::string& config,
+                                                  UseTo usage) const
+{
+  return this->GetLinkImplementationLibrariesInternal(config, this, usage);
+}
+
+cmLinkImplementationLibraries const*
+cmGeneratorTarget::GetLinkImplementationLibrariesInternal(
+  const std::string& config, cmGeneratorTarget const* head, UseTo usage) const
+{
+  // There is no link implementation for targets that cannot compile sources.
+  if (!this->CanCompileSources()) {
+    return nullptr;
+  }
+
+  // Populate the link implementation libraries for this configuration.
+  HeadToLinkImplementationMap& hm =
+    (usage == UseTo::Compile
+       ? this->GetHeadToLinkImplementationUsageRequirementsMap(config)
+       : this->GetHeadToLinkImplementationMap(config));
+
+  // If the link implementation does not depend on the head target
+  // then reuse the one from the head we computed first.
+  if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
+    head = hm.begin()->first;
+  }
+
+  cmOptionalLinkImplementation& impl = hm[head];
+  if (!impl.LibrariesDone) {
+    impl.LibrariesDone = true;
+    this->ComputeLinkImplementationLibraries(config, impl, head, usage);
+  }
+  return &impl;
+}
+
+namespace {
+class TransitiveLinkImpl
+{
+  cmGeneratorTarget const* Self;
+  std::string const& Config;
+  UseTo ImplFor;
+  cmLinkImplementation& Impl;
+
+  std::set<cmLinkItem> Emitted;
+  std::set<cmLinkItem> Excluded;
+  std::unordered_set<cmGeneratorTarget const*> Followed;
+
+  void Follow(cmGeneratorTarget const* target);
+
+public:
+  TransitiveLinkImpl(cmGeneratorTarget const* self, std::string const& config,
+                     UseTo usage, cmLinkImplementation& impl)
+    : Self(self)
+    , Config(config)
+    , ImplFor(usage)
+    , Impl(impl)
+  {
+  }
+
+  void Compute();
+};
+
+void TransitiveLinkImpl::Follow(cmGeneratorTarget const* target)
+{
+  if (!target || !this->Followed.insert(target).second ||
+      target->GetPolicyStatusCMP0022() == cmPolicies::OLD ||
+      target->GetPolicyStatusCMP0022() == cmPolicies::WARN) {
+    return;
+  }
+
+  // Get this target's usage requirements.
+  cmLinkInterfaceLibraries const* iface =
+    target->GetLinkInterfaceLibraries(this->Config, this->Self, this->ImplFor);
+  if (!iface) {
+    return;
+  }
+  if (iface->HadContextSensitiveCondition) {
+    this->Impl.HadContextSensitiveCondition = true;
+  }
+
+  // Process 'INTERFACE_LINK_LIBRARIES_DIRECT' usage requirements.
+  for (cmLinkItem const& item : iface->HeadInclude) {
+    // Inject direct dependencies from the item's usage requirements
+    // before the item itself.
+    this->Follow(item.Target);
+
+    // Add the item itself, but at most once.
+    if (this->Emitted.insert(item).second) {
+      this->Impl.Libraries.emplace_back(item, /* checkCMP0027= */ false);
+    }
+  }
+
+  // Follow transitive dependencies.
+  for (cmLinkItem const& item : iface->Libraries) {
+    this->Follow(item.Target);
+  }
+
+  // Record exclusions from 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE'
+  // usage requirements.
+  for (cmLinkItem const& item : iface->HeadExclude) {
+    this->Excluded.insert(item);
+  }
+}
+
+void TransitiveLinkImpl::Compute()
+{
+  // Save the original items and start with an empty list.
+  std::vector<cmLinkImplItem> original = std::move(this->Impl.Libraries);
+
+  // Avoid injecting any original items as usage requirements.
+  // This gives LINK_LIBRARIES final control over the order
+  // if it explicitly lists everything.
+  this->Emitted.insert(original.cbegin(), original.cend());
+
+  // Process each original item.
+  for (cmLinkImplItem& item : original) {
+    // Inject direct dependencies listed in 'INTERFACE_LINK_LIBRARIES_DIRECT'
+    // usage requirements before the item itself.
+    this->Follow(item.Target);
+
+    // Add the item itself.
+    this->Impl.Libraries.emplace_back(std::move(item));
+  }
+
+  // Remove items listed in 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE'
+  // usage requirements found through any dependency above.
+  this->Impl.Libraries.erase(
+    std::remove_if(this->Impl.Libraries.begin(), this->Impl.Libraries.end(),
+                   [this](cmLinkImplItem const& item) {
+                     return this->Excluded.find(item) != this->Excluded.end();
+                   }),
+    this->Impl.Libraries.end());
+}
+
+void ComputeLinkImplTransitive(cmGeneratorTarget const* self,
+                               std::string const& config, UseTo usage,
+                               cmLinkImplementation& impl)
+{
+  TransitiveLinkImpl transitiveLinkImpl(self, config, usage, impl);
+  transitiveLinkImpl.Compute();
+}
+}
+
+void cmGeneratorTarget::ComputeLinkImplementationLibraries(
+  const std::string& config, cmOptionalLinkImplementation& impl,
+  cmGeneratorTarget const* head, UseTo usage) const
+{
+  cmLocalGenerator const* lg = this->LocalGenerator;
+  cmMakefile const* mf = lg->GetMakefile();
+  cmBTStringRange entryRange = this->Target->GetLinkImplementationEntries();
+  auto const& synthTargetsForConfig = this->Configs[config].SyntheticDeps;
+  // Collect libraries directly linked in this configuration.
+  for (auto const& entry : entryRange) {
+    // Keep this logic in sync with ExpandLinkItems.
+    cmGeneratorExpressionDAGChecker dagChecker(this, "LINK_LIBRARIES", nullptr,
+                                               nullptr, this->LocalGenerator);
+    // The $<LINK_ONLY> expression may be used to specify link dependencies
+    // that are otherwise excluded from usage requirements.
+    if (usage == UseTo::Compile) {
+      dagChecker.SetTransitivePropertiesOnly();
+      switch (this->GetPolicyStatusCMP0131()) {
+        case cmPolicies::WARN:
+        case cmPolicies::OLD:
+          break;
+        case cmPolicies::REQUIRED_IF_USED:
+        case cmPolicies::REQUIRED_ALWAYS:
+        case cmPolicies::NEW:
+          dagChecker.SetTransitivePropertiesOnlyCMP0131();
+          break;
+      }
+    }
+    cmGeneratorExpression ge(*this->LocalGenerator->GetCMakeInstance(),
+                             entry.Backtrace);
+    std::unique_ptr<cmCompiledGeneratorExpression> const cge =
+      ge.Parse(entry.Value);
+    cge->SetEvaluateForBuildsystem(true);
+    std::string const& evaluated =
+      cge->Evaluate(this->LocalGenerator, config, head, &dagChecker, nullptr,
+                    this->LinkerLanguage);
+    bool const checkCMP0027 = evaluated != entry.Value;
+    cmList llibs(evaluated);
+    if (cge->GetHadHeadSensitiveCondition()) {
+      impl.HadHeadSensitiveCondition = true;
+    }
+    if (cge->GetHadContextSensitiveCondition()) {
+      impl.HadContextSensitiveCondition = true;
+    }
+    if (cge->GetHadLinkLanguageSensitiveCondition()) {
+      impl.HadLinkLanguageSensitiveCondition = true;
+    }
+
+    auto linkFeature = cmLinkItem::DEFAULT;
+    for (auto const& lib : llibs) {
+      if (auto maybeLinkFeature = ParseLinkFeature(lib)) {
+        linkFeature = std::move(*maybeLinkFeature);
+        continue;
+      }
+
+      if (this->IsLinkLookupScope(lib, lg)) {
+        continue;
+      }
+
+      // Skip entries that resolve to the target itself or are empty.
+      std::string name = this->CheckCMP0004(lib);
+      if (this->GetPolicyStatusCMP0108() == cmPolicies::NEW) {
+        // resolve alias name
+        auto* target = this->Makefile->FindTargetToUse(name);
+        if (target) {
+          name = target->GetName();
+        }
+      }
+      if (name == this->GetName() || name.empty()) {
+        if (name == this->GetName()) {
+          bool noMessage = false;
+          MessageType messageType = MessageType::FATAL_ERROR;
+          std::ostringstream e;
+          switch (this->GetPolicyStatusCMP0038()) {
+            case cmPolicies::WARN: {
+              e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0038) << "\n";
+              messageType = MessageType::AUTHOR_WARNING;
+            } break;
+            case cmPolicies::OLD:
+              noMessage = true;
+              break;
+            case cmPolicies::REQUIRED_IF_USED:
+            case cmPolicies::REQUIRED_ALWAYS:
+            case cmPolicies::NEW:
+              // Issue the fatal message.
+              break;
+          }
+
+          if (!noMessage) {
+            e << "Target \"" << this->GetName() << "\" links to itself.";
+            this->LocalGenerator->GetCMakeInstance()->IssueMessage(
+              messageType, e.str(), this->GetBacktrace());
+            if (messageType == MessageType::FATAL_ERROR) {
+              return;
+            }
+          }
+        }
+        continue;
+      }
+
+      // The entry is meant for this configuration.
+      cmLinkItem item = this->ResolveLinkItem(
+        BT<std::string>(name, entry.Backtrace), lg, linkFeature);
+      if (item.Target) {
+        auto depsForTarget = synthTargetsForConfig.find(item.Target);
+        if (depsForTarget != synthTargetsForConfig.end()) {
+          for (auto const* depForTarget : depsForTarget->second) {
+            cmLinkItem synthItem(depForTarget, item.Cross, item.Backtrace);
+            impl.Libraries.emplace_back(std::move(synthItem), false);
+          }
+        }
+      } else {
+        // Report explicitly linked object files separately.
+        std::string const& maybeObj = item.AsStr();
+        if (cmSystemTools::FileIsFullPath(maybeObj)) {
+          cmSourceFile const* sf =
+            mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
+          if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
+            item.ObjectSource = sf;
+            impl.Objects.emplace_back(std::move(item));
+            continue;
+          }
+        }
+      }
+
+      impl.Libraries.emplace_back(std::move(item), checkCMP0027);
+    }
+
+    std::set<std::string> const& seenProps = cge->GetSeenTargetProperties();
+    for (std::string const& sp : seenProps) {
+      if (!this->GetProperty(sp)) {
+        this->LinkImplicitNullProperties.insert(sp);
+      }
+    }
+    cge->GetMaxLanguageStandard(this, this->MaxLanguageStandards);
+  }
+
+  // Update the list of direct link dependencies from usage requirements.
+  if (head == this) {
+    ComputeLinkImplTransitive(this, config, usage, impl);
+  }
+
+  // Get the list of configurations considered to be DEBUG.
+  std::vector<std::string> debugConfigs =
+    this->Makefile->GetCMakeInstance()->GetDebugConfigs();
+
+  cmTargetLinkLibraryType linkType =
+    CMP0003_ComputeLinkType(config, debugConfigs);
+  cmTarget::LinkLibraryVectorType const& oldllibs =
+    this->Target->GetOriginalLinkLibraries();
+
+  auto linkFeature = cmLinkItem::DEFAULT;
+  for (cmTarget::LibraryID const& oldllib : oldllibs) {
+    if (auto maybeLinkFeature = ParseLinkFeature(oldllib.first)) {
+      linkFeature = std::move(*maybeLinkFeature);
+      continue;
+    }
+
+    if (oldllib.second != GENERAL_LibraryType && oldllib.second != linkType) {
+      std::string name = this->CheckCMP0004(oldllib.first);
+      if (name == this->GetName() || name.empty()) {
+        continue;
+      }
+      // Support OLD behavior for CMP0003.
+      impl.WrongConfigLibraries.push_back(
+        this->ResolveLinkItem(BT<std::string>(name), linkFeature));
+    }
+  }
+}
+
+cmGeneratorTarget::TargetOrString cmGeneratorTarget::ResolveTargetReference(
+  std::string const& name) const
+{
+  return this->ResolveTargetReference(name, this->LocalGenerator);
+}
+
+cmGeneratorTarget::TargetOrString cmGeneratorTarget::ResolveTargetReference(
+  std::string const& name, cmLocalGenerator const* lg) const
+{
+  TargetOrString resolved;
+
+  if (cmGeneratorTarget* tgt = lg->FindGeneratorTargetToUse(name)) {
+    resolved.Target = tgt;
+  } else {
+    resolved.String = name;
+  }
+
+  return resolved;
+}
+
+cmLinkItem cmGeneratorTarget::ResolveLinkItem(
+  BT<std::string> const& name, std::string const& linkFeature) const
+{
+  return this->ResolveLinkItem(name, this->LocalGenerator, linkFeature);
+}
+
+cmLinkItem cmGeneratorTarget::ResolveLinkItem(
+  BT<std::string> const& name, cmLocalGenerator const* lg,
+  std::string const& linkFeature) const
+{
+  auto bt = name.Backtrace;
+  TargetOrString resolved = this->ResolveTargetReference(name.Value, lg);
+
+  if (!resolved.Target) {
+    return cmLinkItem(resolved.String, false, bt, linkFeature);
+  }
+
+  // Check deprecation, issue message with `bt` backtrace.
+  if (resolved.Target->IsDeprecated()) {
+    std::ostringstream w;
+    /* clang-format off */
+    w <<
+      "The library that is being linked to, "  << resolved.Target->GetName() <<
+      ", is marked as being deprecated by the owner.  The message provided by "
+      "the developer is: \n" << resolved.Target->GetDeprecation() << "\n";
+    /* clang-format on */
+    this->LocalGenerator->GetCMakeInstance()->IssueMessage(
+      MessageType::AUTHOR_WARNING, w.str(), bt);
+  }
+
+  // Skip targets that will not really be linked.  This is probably a
+  // name conflict between an external library and an executable
+  // within the project.
+  if (resolved.Target->GetType() == cmStateEnums::EXECUTABLE &&
+      !resolved.Target->IsExecutableWithExports()) {
+    return cmLinkItem(resolved.Target->GetName(), false, bt, linkFeature);
+  }
+
+  return cmLinkItem(resolved.Target, false, bt, linkFeature);
+}

+ 1 - 0
bootstrap

@@ -383,6 +383,7 @@ CMAKE_CXX_SOURCES="\
   cmGeneratorTarget \
   cmGeneratorTarget_CompatibleInterface \
   cmGeneratorTarget_IncludeDirectories \
+  cmGeneratorTarget_Link \
   cmGeneratorTarget_LinkDirectories \
   cmGeneratorTarget_Options \
   cmGeneratorTarget_Sources \

Some files were not shown because too many files changed in this diff