/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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& 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& Languages; cmGeneratorTarget const* HeadTarget; std::set 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 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 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 $ or $ " "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& tgts, std::set& 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::GetLinkInterfaceClosure(std::string const& config, cmGeneratorTarget const* headTarget, UseTo usage) const { cmGlobalGenerator* gg = this->GetLocalGenerator()->GetGlobalGenerator(); std::vector tgts; std::set 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& 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 empty; return empty; } LinkImplClosure& tgts = (usage == UseTo::Compile ? this->LinkImplClosureForUsageMap[config] : this->LinkImplClosureForLinkMap[config]); if (!tgts.Done) { tgts.Done = true; std::set 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(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 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(), "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, "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 cmGeneratorTarget::LookupLinkItem( std::string const& n, cmListFileBacktrace const& bt, std::string const& linkFeature, LookupLinkItemScope* scope, LookupSelf lookupSelf) const { cm::optional 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(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 $ 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 const& entry : entries) { cmGeneratorExpression ge(*this->LocalGenerator->GetCMakeInstance(), entry.Backtrace); std::unique_ptr 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 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 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(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> 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 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 std::vector computeImplicitLanguageTargets( std::string const& lang, std::string const& config, cmGeneratorTarget const* currentTarget) { cmListFileBacktrace bt; std::vector 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(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(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(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 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 Emitted; std::set Excluded; std::unordered_set 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 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 $ 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 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(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 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 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(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 const& name, std::string const& linkFeature) const { return this->ResolveLinkItem(name, this->LocalGenerator, linkFeature); } cmLinkItem cmGeneratorTarget::ResolveLinkItem( BT 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); }