You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1510 lines
52 KiB
1510 lines
52 KiB
6 months ago
|
/* 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 ∅
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
UseTo usage)
|
||
|
{
|
||
|
if (item.Target && emitted.insert(item.Target).second) {
|
||
|
tgts.push_back(item.Target);
|
||
|
if (cmLinkInterfaceLibraries const* iface =
|
||
|
item.Target->GetLinkInterfaceLibraries(config, headTarget, usage)) {
|
||
|
for (cmLinkItem const& lib : iface->Libraries) {
|
||
|
processILibs(config, headTarget, lib, gg, tgts, emitted, usage);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::vector<cmGeneratorTarget const*>
|
||
|
cmGeneratorTarget::GetLinkInterfaceClosure(std::string const& config,
|
||
|
cmGeneratorTarget const* headTarget,
|
||
|
UseTo usage) const
|
||
|
{
|
||
|
cmGlobalGenerator* gg = this->GetLocalGenerator()->GetGlobalGenerator();
|
||
|
std::vector<cmGeneratorTarget const*> tgts;
|
||
|
std::set<cmGeneratorTarget const*> emitted;
|
||
|
if (cmLinkInterfaceLibraries const* iface =
|
||
|
this->GetLinkInterfaceLibraries(config, headTarget, usage)) {
|
||
|
for (cmLinkItem const& lib : iface->Libraries) {
|
||
|
processILibs(config, headTarget, lib, gg, tgts, emitted, usage);
|
||
|
}
|
||
|
}
|
||
|
return tgts;
|
||
|
}
|
||
|
|
||
|
const std::vector<const cmGeneratorTarget*>&
|
||
|
cmGeneratorTarget::GetLinkImplementationClosure(const std::string& config,
|
||
|
UseTo usage) 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 =
|
||
|
(usage == UseTo::Compile ? this->LinkImplClosureForUsageMap[config]
|
||
|
: this->LinkImplClosureForLinkMap[config]);
|
||
|
if (!tgts.Done) {
|
||
|
tgts.Done = true;
|
||
|
std::set<cmGeneratorTarget const*> emitted;
|
||
|
|
||
|
cmLinkImplementationLibraries const* impl =
|
||
|
this->GetLinkImplementationLibraries(config, usage);
|
||
|
assert(impl);
|
||
|
|
||
|
for (cmLinkImplItem const& lib : impl->Libraries) {
|
||
|
processILibs(config, this, lib,
|
||
|
this->LocalGenerator->GetGlobalGenerator(), tgts, emitted,
|
||
|
usage);
|
||
|
}
|
||
|
}
|
||
|
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, config);
|
||
|
// 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, config);
|
||
|
// 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);
|
||
|
}
|