diff options
Diffstat (limited to 'Source/cmComputeLinkDepends.cxx')
-rw-r--r-- | Source/cmComputeLinkDepends.cxx | 643 |
1 files changed, 553 insertions, 90 deletions
diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx index 370ddff..8cbdcaa 100644 --- a/Source/cmComputeLinkDepends.cxx +++ b/Source/cmComputeLinkDepends.cxx @@ -11,13 +11,18 @@ #include <utility> #include <cm/memory> +#include <cm/string_view> +#include <cmext/string_view> #include "cmComputeComponentGraph.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorExpressionDAGChecker.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmRange.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" @@ -174,8 +179,62 @@ items that we know the linker will re-use automatically (shared libs). */ +namespace { +// LINK_LIBRARY helpers +const auto LL_BEGIN = "<LINK_LIBRARY:"_s; +const auto LL_END = "</LINK_LIBRARY:"_s; + +inline std::string ExtractFeature(std::string const& item) +{ + return item.substr(LL_BEGIN.length(), + item.find('>', LL_BEGIN.length()) - LL_BEGIN.length()); +} + +bool IsFeatureSupported(cmMakefile* makefile, std::string const& linkLanguage, + std::string const& feature) +{ + auto featureSupported = cmStrCat( + "CMAKE_", linkLanguage, "_LINK_LIBRARY_USING_", feature, "_SUPPORTED"); + if (makefile->GetDefinition(featureSupported).IsOn()) { + return true; + } + + featureSupported = + cmStrCat("CMAKE_LINK_LIBRARY_USING_", feature, "_SUPPORTED"); + return makefile->GetDefinition(featureSupported).IsOn(); +} + +// LINK_GROUP helpers +const auto LG_BEGIN = "<LINK_GROUP:"_s; +const auto LG_END = "</LINK_GROUP:"_s; + +inline std::string ExtractGroupFeature(std::string const& item) +{ + return item.substr(LG_BEGIN.length(), + item.find(':', LG_BEGIN.length()) - LG_BEGIN.length()); +} + +bool IsGroupFeatureSupported(cmMakefile* makefile, + std::string const& linkLanguage, + std::string const& feature) +{ + auto featureSupported = cmStrCat( + "CMAKE_", linkLanguage, "_LINK_GROUP_USING_", feature, "_SUPPORTED"); + if (makefile->GetDefinition(featureSupported).IsOn()) { + return true; + } + + featureSupported = + cmStrCat("CMAKE_LINK_GROUP_USING_", feature, "_SUPPORTED"); + return makefile->GetDefinition(featureSupported).IsOn(); +} +} + +const std::string cmComputeLinkDepends::LinkEntry::DEFAULT = "DEFAULT"; + cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target, - const std::string& config) + const std::string& config, + const std::string& linkLanguage) { // Store context information. this->Target = target; @@ -183,6 +242,50 @@ cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target, this->GlobalGenerator = this->Target->GetLocalGenerator()->GetGlobalGenerator(); this->CMakeInstance = this->GlobalGenerator->GetCMakeInstance(); + this->LinkLanguage = linkLanguage; + + // target oriented feature override property takes precedence over + // global override property + cm::string_view lloPrefix = "LINK_LIBRARY_OVERRIDE_"_s; + auto const& keys = this->Target->GetPropertyKeys(); + std::for_each( + keys.cbegin(), keys.cend(), + [this, &lloPrefix, &config, &linkLanguage](std::string const& key) { + if (cmHasPrefix(key, lloPrefix)) { + if (cmValue feature = this->Target->GetProperty(key)) { + if (!feature->empty() && key.length() > lloPrefix.length()) { + auto item = key.substr(lloPrefix.length()); + cmGeneratorExpressionDAGChecker dag{ this->Target->GetBacktrace(), + this->Target, + "LINK_LIBRARY_OVERRIDE", + nullptr, nullptr }; + auto overrideFeature = cmGeneratorExpression::Evaluate( + feature, this->Target->GetLocalGenerator(), config, this->Target, + &dag, this->Target, linkLanguage); + this->LinkLibraryOverride.emplace(item, overrideFeature); + } + } + } + }); + // global override property + if (cmValue linkLibraryOverride = + this->Target->GetProperty("LINK_LIBRARY_OVERRIDE")) { + cmGeneratorExpressionDAGChecker dag{ target->GetBacktrace(), target, + "LINK_LIBRARY_OVERRIDE", nullptr, + nullptr }; + auto overrideValue = cmGeneratorExpression::Evaluate( + linkLibraryOverride, target->GetLocalGenerator(), config, target, &dag, + target, linkLanguage); + + auto overrideList = cmTokenize(overrideValue, ","_s); + if (overrideList.size() >= 2) { + auto const& feature = overrideList.front(); + for_each(overrideList.cbegin() + 1, overrideList.cend(), + [this, &feature](std::string const& item) { + this->LinkLibraryOverride.emplace(item, feature); + }); + } + } // The configuration being linked. this->HasConfig = !config.empty(); @@ -234,6 +337,11 @@ cmComputeLinkDepends::Compute() // Infer dependencies of targets for which they were not known. this->InferDependencies(); + // finalize groups dependencies + // All dependencies which are raw items must be replaced by the group + // it belongs to, if any. + this->UpdateGroupDependencies(); + // Cleanup the constraint graph. this->CleanConstraintGraph(); @@ -248,8 +356,21 @@ cmComputeLinkDepends::Compute() this->DisplayConstraintGraph(); } + // Compute the DAG of strongly connected components. The algorithm + // used by cmComputeComponentGraph should identify the components in + // the same order in which the items were originally discovered in + // the BFS. This should preserve the original order when no + // constraints disallow it. + this->CCG = + cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph); + this->CCG->Compute(); + + if (!this->CheckCircularDependencies()) { + return this->FinalLinkEntries; + } + // Compute the final ordering. - this->OrderLinkEntires(); + this->OrderLinkEntries(); // Compute the final set of link entries. // Iterate in reverse order so we can keep only the last occurrence @@ -273,6 +394,29 @@ cmComputeLinkDepends::Compute() // Reverse the resulting order since we iterated in reverse. std::reverse(this->FinalLinkEntries.begin(), this->FinalLinkEntries.end()); + // Expand group items + if (!this->GroupItems.empty()) { + for (const auto& group : this->GroupItems) { + const LinkEntry& groupEntry = this->EntryList[group.first]; + auto it = this->FinalLinkEntries.begin(); + while (true) { + it = std::find_if(it, this->FinalLinkEntries.end(), + [&groupEntry](const LinkEntry& entry) -> bool { + return groupEntry.Item == entry.Item; + }); + if (it == this->FinalLinkEntries.end()) { + break; + } + it->Item.Value = "</LINK_GROUP>"; + for (auto i = group.second.rbegin(); i != group.second.rend(); ++i) { + it = this->FinalLinkEntries.insert(it, this->EntryList[*i]); + } + it = this->FinalLinkEntries.insert(it, groupEntry); + it->Item.Value = "<LINK_GROUP>"; + } + } + } + // Display the final set. if (this->DebugMode) { this->DisplayFinalEntries(); @@ -281,76 +425,91 @@ cmComputeLinkDepends::Compute() return this->FinalLinkEntries; } -std::map<cmLinkItem, int>::iterator cmComputeLinkDepends::AllocateLinkEntry( - cmLinkItem const& item) +std::string const& cmComputeLinkDepends::GetCurrentFeature( + std::string const& item, std::string const& defaultFeature) const +{ + auto it = this->LinkLibraryOverride.find(item); + return it == this->LinkLibraryOverride.end() ? defaultFeature : it->second; +} + +std::pair<std::map<cmLinkItem, int>::iterator, bool> +cmComputeLinkDepends::AllocateLinkEntry(cmLinkItem const& item) { std::map<cmLinkItem, int>::value_type index_entry( item, static_cast<int>(this->EntryList.size())); - auto lei = this->LinkEntryIndex.insert(index_entry).first; - this->EntryList.emplace_back(); - this->InferredDependSets.emplace_back(); - this->EntryConstraintGraph.emplace_back(); + auto lei = this->LinkEntryIndex.insert(index_entry); + if (lei.second) { + this->EntryList.emplace_back(); + this->InferredDependSets.emplace_back(); + this->EntryConstraintGraph.emplace_back(); + } return lei; } -int cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item) +std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item, + int groupIndex) { + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(item); + // Check if the item entry has already been added. - auto lei = this->LinkEntryIndex.find(item); - if (lei != this->LinkEntryIndex.end()) { + if (!lei.second) { // Yes. We do not need to follow the item's dependencies again. - return lei->second; + return { lei.first->second, false }; } - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(item); - // Initialize the item entry. - int index = lei->second; + int index = lei.first->second; LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(item.AsStr(), item.Backtrace); entry.Target = item.Target; - entry.IsFlag = (!entry.Target && entry.Item.Value[0] == '-' && - entry.Item.Value[1] != 'l' && - entry.Item.Value.substr(0, 10) != "-framework"); + if (!entry.Target && entry.Item.Value[0] == '-' && + entry.Item.Value[1] != 'l' && + entry.Item.Value.substr(0, 10) != "-framework") { + entry.Kind = LinkEntry::Flag; + } else if (cmHasPrefix(entry.Item.Value, LG_BEGIN) && + cmHasSuffix(entry.Item.Value, '>')) { + entry.Kind = LinkEntry::Group; + } - // If the item has dependencies queue it to follow them. - if (entry.Target) { - // Target dependencies are always known. Follow them. - BFSEntry qe = { index, nullptr }; - this->BFSQueue.push(qe); - } else { - // Look for an old-style <item>_LIB_DEPENDS variable. - std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS"); - if (cmValue val = this->Makefile->GetDefinition(var)) { - // The item dependencies are known. Follow them. - BFSEntry qe = { index, val->c_str() }; + if (entry.Kind != LinkEntry::Group) { + // If the item has dependencies queue it to follow them. + if (entry.Target) { + // Target dependencies are always known. Follow them. + BFSEntry qe = { index, groupIndex, nullptr }; this->BFSQueue.push(qe); - } else if (!entry.IsFlag) { - // The item dependencies are not known. We need to infer them. - this->InferredDependSets[index].Initialized = true; + } else { + // Look for an old-style <item>_LIB_DEPENDS variable. + std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS"); + if (cmValue val = this->Makefile->GetDefinition(var)) { + // The item dependencies are known. Follow them. + BFSEntry qe = { index, groupIndex, val->c_str() }; + this->BFSQueue.push(qe); + } else if (entry.Kind != LinkEntry::Flag) { + // The item dependencies are not known. We need to infer them. + this->InferredDependSets[index].Initialized = true; + } } } - return index; + return { index, true }; } void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item) { + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(item); + // Check if the item entry has already been added. - auto lei = this->LinkEntryIndex.find(item); - if (lei != this->LinkEntryIndex.end()) { + if (!lei.second) { return; } - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(item); - // Initialize the item entry. - int index = lei->second; + int index = lei.first->second; LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(item.AsStr(), item.Backtrace); - entry.IsObject = true; + entry.Kind = LinkEntry::Object; // Record explicitly linked object files separately. this->ObjectEntries.emplace_back(index); @@ -359,8 +518,8 @@ void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item) void cmComputeLinkDepends::FollowLinkEntry(BFSEntry qe) { // Get this entry representation. - int depender_index = qe.Index; - LinkEntry const& entry = this->EntryList[depender_index]; + int depender_index = qe.GroupIndex == -1 ? qe.Index : qe.GroupIndex; + LinkEntry const& entry = this->EntryList[qe.Index]; // Follow the item's dependencies. if (entry.Target) { @@ -423,25 +582,24 @@ void cmComputeLinkDepends::QueueSharedDependencies( void cmComputeLinkDepends::HandleSharedDependency(SharedDepEntry const& dep) { - // Check if the target already has an entry. - auto lei = this->LinkEntryIndex.find(dep.Item); - if (lei == this->LinkEntryIndex.end()) { - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(dep.Item); + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(dep.Item); + int index = lei.first->second; + // Check if the target does not already has an entry. + if (lei.second) { // Initialize the item entry. - LinkEntry& entry = this->EntryList[lei->second]; + LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(dep.Item.AsStr(), dep.Item.Backtrace); entry.Target = dep.Item.Target; // This item was added specifically because it is a dependent // shared library. It may get special treatment // in cmComputeLinkInformation. - entry.IsSharedDep = true; + entry.Kind = LinkEntry::SharedDep; } // Get the link entry for this target. - int index = lei->second; LinkEntry& entry = this->EntryList[index]; // This shared library dependency must follow the item that listed @@ -519,8 +677,8 @@ void cmComputeLinkDepends::AddVarLinkEntries(int depender_index, void cmComputeLinkDepends::AddDirectLinkEntries() { // Add direct link dependencies in this configuration. - cmLinkImplementation const* impl = - this->Target->GetLinkImplementation(this->Config); + cmLinkImplementation const* impl = this->Target->GetLinkImplementation( + this->Config, cmGeneratorTarget::LinkInterfaceFor::Link); this->AddLinkEntries(-1, impl->Libraries); this->AddLinkObjects(impl->Objects); @@ -541,6 +699,11 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, { // Track inferred dependency sets implied by this list. std::map<int, DependSet> dependSets; + std::string feature = LinkEntry::DEFAULT; + + bool inGroup = false; + std::pair<int, bool> groupIndex{ -1, false }; + std::vector<int> groupItems; // Loop over the libraries linked directly by the depender. for (T const& l : libs) { @@ -551,35 +714,233 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, continue; } + if (cmHasPrefix(item.AsStr(), LL_BEGIN) && + cmHasSuffix(item.AsStr(), '>')) { + feature = ExtractFeature(item.AsStr()); + // emit a warning if an undefined feature is used as part of + // an imported target + if (depender_index >= 0) { + const auto& depender = this->EntryList[depender_index]; + if (depender.Target != nullptr && depender.Target->IsImported() && + !IsFeatureSupported(this->Makefile, this->LinkLanguage, feature)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(), + "' uses the generator-expression '$<LINK_LIBRARY>' with " + "the feature '", + feature, + "', which is undefined or unsupported.\nDid you miss to " + "define it by setting variables \"CMAKE_", + this->LinkLanguage, "_LINK_LIBRARY_USING_", feature, + "\" and \"CMAKE_", this->LinkLanguage, + "_LINK_LIBRARY_USING_", feature, "_SUPPORTED\"?"), + this->Target->GetBacktrace()); + } + } + continue; + } + if (cmHasPrefix(item.AsStr(), LL_END) && cmHasSuffix(item.AsStr(), '>')) { + feature = LinkEntry::DEFAULT; + continue; + } + + if (cmHasPrefix(item.AsStr(), LG_BEGIN) && + cmHasSuffix(item.AsStr(), '>')) { + groupIndex = this->AddLinkEntry(item); + if (groupIndex.second) { + LinkEntry& entry = this->EntryList[groupIndex.first]; + entry.Feature = ExtractGroupFeature(item.AsStr()); + } + inGroup = true; + if (depender_index >= 0) { + this->EntryConstraintGraph[depender_index].emplace_back( + groupIndex.first, false, false, cmListFileBacktrace()); + } else { + // This is a direct dependency of the target being linked. + this->OriginalEntries.push_back(groupIndex.first); + } + continue; + } + + int dependee_index; + + if (cmHasPrefix(item.AsStr(), LG_END) && cmHasSuffix(item.AsStr(), '>')) { + dependee_index = groupIndex.first; + if (groupIndex.second) { + this->GroupItems.emplace(groupIndex.first, groupItems); + } + inGroup = false; + groupIndex = std::make_pair(-1, false); + groupItems.clear(); + continue; + } + + if (depender_index >= 0 && inGroup) { + const auto& depender = this->EntryList[depender_index]; + const auto& groupFeature = this->EntryList[groupIndex.first].Feature; + if (depender.Target != nullptr && depender.Target->IsImported() && + !IsGroupFeatureSupported(this->Makefile, this->LinkLanguage, + groupFeature)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(), + "' uses the generator-expression '$<LINK_GROUP>' with " + "the feature '", + groupFeature, + "', which is undefined or unsupported.\nDid you miss to " + "define it by setting variables \"CMAKE_", + this->LinkLanguage, "_LINK_GROUP_USING_", groupFeature, + "\" and \"CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_", + groupFeature, "_SUPPORTED\"?"), + this->Target->GetBacktrace()); + } + } + // Add a link entry for this item. - int dependee_index = this->AddLinkEntry(l); + auto ale = this->AddLinkEntry(item, groupIndex.first); + dependee_index = ale.first; + LinkEntry& entry = this->EntryList[dependee_index]; + auto const& itemFeature = + this->GetCurrentFeature(entry.Item.Value, feature); + if (inGroup && ale.second && entry.Target != nullptr && + (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY || + entry.Target->GetType() == + cmStateEnums::TargetType::INTERFACE_LIBRARY)) { + const auto& groupFeature = this->EntryList[groupIndex.first].Feature; + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat( + "The feature '", groupFeature, + "', specified as part of a generator-expression " + "'$", + LG_BEGIN, groupFeature, ">', will not be applied to the ", + (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY + ? "OBJECT" + : "INTERFACE"), + " library '", entry.Item.Value, "'."), + this->Target->GetBacktrace()); + } + if (itemFeature != LinkEntry::DEFAULT) { + if (ale.second) { + // current item not yet defined + if (entry.Target != nullptr && + (entry.Target->GetType() == + cmStateEnums::TargetType::OBJECT_LIBRARY || + entry.Target->GetType() == + cmStateEnums::TargetType::INTERFACE_LIBRARY)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat("The feature '", feature, + "', specified as part of a generator-expression " + "'$", + LL_BEGIN, feature, ">', will not be applied to the ", + (entry.Target->GetType() == + cmStateEnums::TargetType::OBJECT_LIBRARY + ? "OBJECT" + : "INTERFACE"), + " library '", entry.Item.Value, "'."), + this->Target->GetBacktrace()); + } else { + entry.Feature = itemFeature; + } + } + } - // The dependee must come after the depender. - if (depender_index >= 0) { - this->EntryConstraintGraph[depender_index].emplace_back( - dependee_index, false, false, cmListFileBacktrace()); - } else { - // This is a direct dependency of the target being linked. - this->OriginalEntries.push_back(dependee_index); - } - - // Update the inferred dependencies for earlier items. - for (auto& dependSet : dependSets) { - // Add this item to the inferred dependencies of other items. - // Target items are never inferred dependees because unknown - // items are outside libraries that should not be depending on - // targets. - if (!this->EntryList[dependee_index].Target && - !this->EntryList[dependee_index].IsFlag && - dependee_index != dependSet.first) { - dependSet.second.insert(dependee_index); + bool supportedItem = entry.Target == nullptr || + (entry.Target->GetType() != cmStateEnums::TargetType::OBJECT_LIBRARY && + entry.Target->GetType() != cmStateEnums::TargetType::INTERFACE_LIBRARY); + + if (supportedItem) { + if (inGroup) { + const auto& currentFeature = this->EntryList[groupIndex.first].Feature; + for (const auto& g : this->GroupItems) { + const auto& groupFeature = this->EntryList[g.first].Feature; + if (groupFeature == currentFeature) { + continue; + } + if (std::find(g.second.cbegin(), g.second.cend(), dependee_index) != + g.second.cend()) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Impossible to link target '", this->Target->GetName(), + "' because the link item '", entry.Item.Value, + "', specified with the group feature '", currentFeature, + '\'', ", has already occurred with the feature '", + groupFeature, '\'', ", which is not allowed."), + this->Target->GetBacktrace()); + continue; + } + } + } + if (entry.Feature != itemFeature) { + // incompatibles features occurred + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Impossible to link target '", this->Target->GetName(), + "' because the link item '", entry.Item.Value, + "', specified ", + (itemFeature == LinkEntry::DEFAULT + ? "without any feature or 'DEFAULT' feature" + : cmStrCat("with the feature '", itemFeature, '\'')), + ", has already occurred ", + (entry.Feature == LinkEntry::DEFAULT + ? "without any feature or 'DEFAULT' feature" + : cmStrCat("with the feature '", entry.Feature, '\'')), + ", which is not allowed."), + this->Target->GetBacktrace()); } } - // If this item needs to have dependencies inferred, do so. - if (this->InferredDependSets[dependee_index].Initialized) { - // Make sure an entry exists to hold the set for the item. - dependSets[dependee_index]; + if (inGroup) { + // store item index for dependencies handling + groupItems.push_back(dependee_index); + } else { + std::vector<int> indexes; + bool entryHandled = false; + // search any occurrence of the library in already defined groups + for (const auto& group : this->GroupItems) { + for (auto index : group.second) { + if (entry.Item.Value == this->EntryList[index].Item.Value) { + indexes.push_back(group.first); + entryHandled = true; + break; + } + } + } + if (!entryHandled) { + indexes.push_back(dependee_index); + } + + for (auto index : indexes) { + // The dependee must come after the depender. + if (depender_index >= 0) { + this->EntryConstraintGraph[depender_index].emplace_back( + index, false, false, cmListFileBacktrace()); + } else { + // This is a direct dependency of the target being linked. + this->OriginalEntries.push_back(index); + } + + // Update the inferred dependencies for earlier items. + for (auto& dependSet : dependSets) { + // Add this item to the inferred dependencies of other items. + // Target items are never inferred dependees because unknown + // items are outside libraries that should not be depending on + // targets. + if (!this->EntryList[index].Target && + this->EntryList[index].Kind != LinkEntry::Flag && + this->EntryList[index].Kind != LinkEntry::Group && + dependee_index != dependSet.first) { + dependSet.second.insert(index); + } + } + + // If this item needs to have dependencies inferred, do so. + if (this->InferredDependSets[index].Initialized) { + // Make sure an entry exists to hold the set for the item. + dependSets[index]; + } + } } } @@ -642,6 +1003,36 @@ void cmComputeLinkDepends::InferDependencies() } } +void cmComputeLinkDepends::UpdateGroupDependencies() +{ + if (this->GroupItems.empty()) { + return; + } + + // Walks through all entries of the constraint graph to replace dependencies + // over raw items by the group it belongs to, if any. + for (auto& edgeList : this->EntryConstraintGraph) { + for (auto& edge : edgeList) { + int index = edge; + if (this->EntryList[index].Kind == LinkEntry::Group || + this->EntryList[index].Kind == LinkEntry::Flag || + this->EntryList[index].Kind == LinkEntry::Object) { + continue; + } + // search the item in the defined groups + for (const auto& groupItems : this->GroupItems) { + auto pos = std::find(groupItems.second.cbegin(), + groupItems.second.cend(), index); + if (pos != groupItems.second.cend()) { + // replace lib dependency by the group it belongs to + edge = cmGraphEdge{ groupItems.first, false, false, + cmListFileBacktrace() }; + } + } + } + } +} + void cmComputeLinkDepends::CleanConstraintGraph() { for (cmGraphEdgeList& edgeList : this->EntryConstraintGraph) { @@ -655,6 +1046,76 @@ void cmComputeLinkDepends::CleanConstraintGraph() } } +bool cmComputeLinkDepends::CheckCircularDependencies() const +{ + std::vector<NodeList> const& components = this->CCG->GetComponents(); + int nc = static_cast<int>(components.size()); + for (int c = 0; c < nc; ++c) { + // Get the current component. + NodeList const& nl = components[c]; + + // Skip trivial components. + if (nl.size() < 2) { + continue; + } + + // no group must be evolved + bool cycleDetected = false; + for (int ni : nl) { + if (this->EntryList[ni].Kind == LinkEntry::Group) { + cycleDetected = true; + break; + } + } + if (!cycleDetected) { + continue; + } + + // Construct the error message. + auto formatItem = [](LinkEntry const& entry) -> std::string { + if (entry.Kind == LinkEntry::Group) { + auto items = + entry.Item.Value.substr(entry.Item.Value.find(':', 12) + 1); + items.pop_back(); + std::replace(items.begin(), items.end(), '|', ','); + return cmStrCat("group \"", ExtractGroupFeature(entry.Item.Value), + ":{", items, "}\""); + } + return cmStrCat('"', entry.Item.Value, '"'); + }; + + std::ostringstream e; + e << "The inter-target dependency graph, for the target \"" + << this->Target->GetName() + << "\", contains the following strongly connected component " + "(cycle):\n"; + std::vector<int> const& cmap = this->CCG->GetComponentMap(); + for (int i : nl) { + // Get the depender. + LinkEntry const& depender = this->EntryList[i]; + + // Describe the depender. + e << " " << formatItem(depender) << "\n"; + + // List its dependencies that are inside the component. + EdgeList const& el = this->EntryConstraintGraph[i]; + for (cmGraphEdge const& ni : el) { + int j = ni; + if (cmap[j] == c) { + LinkEntry const& dependee = this->EntryList[j]; + e << " depends on " << formatItem(dependee) << "\n"; + } + } + } + this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(), + this->Target->GetBacktrace()); + + return false; + } + + return true; +} + void cmComputeLinkDepends::DisplayConstraintGraph() { // Display the graph nodes and their edges. @@ -667,17 +1128,8 @@ void cmComputeLinkDepends::DisplayConstraintGraph() fprintf(stderr, "%s\n", e.str().c_str()); } -void cmComputeLinkDepends::OrderLinkEntires() +void cmComputeLinkDepends::OrderLinkEntries() { - // Compute the DAG of strongly connected components. The algorithm - // used by cmComputeComponentGraph should identify the components in - // the same order in which the items were originally discovered in - // the BFS. This should preserve the original order when no - // constraints disallow it. - this->CCG = - cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph); - this->CCG->Compute(); - // The component graph is guaranteed to be acyclic. Start a DFS // from every entry to compute a topological order for the // components. @@ -867,12 +1319,23 @@ int cmComputeLinkDepends::ComputeComponentCount(NodeList const& nl) void cmComputeLinkDepends::DisplayFinalEntries() { fprintf(stderr, "target [%s] links to:\n", this->Target->GetName().c_str()); + char space[] = " "; + int count = 2; for (LinkEntry const& lei : this->FinalLinkEntries) { - if (lei.Target) { - fprintf(stderr, " target [%s]\n", lei.Target->GetName().c_str()); + if (lei.Kind == LinkEntry::Group) { + fprintf(stderr, " %s group", + lei.Item.Value == "<LINK_GROUP>" ? "start" : "end"); + count = lei.Item.Value == "<LINK_GROUP>" ? 4 : 2; + } else if (lei.Target) { + fprintf(stderr, "%*starget [%s]", count, space, + lei.Target->GetName().c_str()); } else { - fprintf(stderr, " item [%s]\n", lei.Item.Value.c_str()); + fprintf(stderr, "%*sitem [%s]", count, space, lei.Item.Value.c_str()); + } + if (lei.Feature != LinkEntry::DEFAULT) { + fprintf(stderr, ", feature [%s]", lei.Feature.c_str()); } + fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } |