diff options
author | Marc Chevrier <marc.chevrier@gmail.com> | 2022-01-31 15:43:41 (GMT) |
---|---|---|
committer | Marc Chevrier <marc.chevrier@gmail.com> | 2022-02-28 09:26:26 (GMT) |
commit | 0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de (patch) | |
tree | 8491b1ae05b5c252b1165244c6976d81c87397ee /Source | |
parent | a9928eb4a54431780d589f70460e5715258f1d27 (diff) | |
download | CMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.zip CMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.tar.gz CMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.tar.bz2 |
Genex-LINK_GROUP: Add possibility to group libraries at link step
Fixes: #23121
Diffstat (limited to 'Source')
-rw-r--r-- | Source/Checks/cm_cxx_filesystem.cxx | 2 | ||||
-rw-r--r-- | Source/cmComputeLinkDepends.cxx | 433 | ||||
-rw-r--r-- | Source/cmComputeLinkDepends.h | 16 | ||||
-rw-r--r-- | Source/cmComputeLinkInformation.cxx | 284 | ||||
-rw-r--r-- | Source/cmComputeLinkInformation.h | 37 | ||||
-rw-r--r-- | Source/cmGeneratorExpressionNode.cxx | 85 | ||||
-rw-r--r-- | Source/cmGeneratorTarget.cxx | 6 | ||||
-rw-r--r-- | Source/cmMakefile.cxx | 18 | ||||
-rw-r--r-- | Source/cmTarget.cxx | 17 |
9 files changed, 718 insertions, 180 deletions
diff --git a/Source/Checks/cm_cxx_filesystem.cxx b/Source/Checks/cm_cxx_filesystem.cxx index ae8acc5..b7d5be5 100644 --- a/Source/Checks/cm_cxx_filesystem.cxx +++ b/Source/Checks/cm_cxx_filesystem.cxx @@ -3,6 +3,8 @@ int main() { + return 1; + std::filesystem::path p0(L"/a/b/c"); std::filesystem::path p1("/a/b/c"); diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx index 7c8de9e..a4dc01b 100644 --- a/Source/cmComputeLinkDepends.cxx +++ b/Source/cmComputeLinkDepends.cxx @@ -180,6 +180,7 @@ 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; @@ -202,6 +203,31 @@ bool IsFeatureSupported(cmMakefile* makefile, std::string const& linkLanguage, 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"; @@ -311,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(); @@ -325,6 +356,19 @@ 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->OrderLinkEntries(); @@ -350,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(); @@ -379,7 +446,8 @@ cmComputeLinkDepends::AllocateLinkEntry(cmLinkItem const& item) return lei; } -std::pair<int, bool> 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); @@ -399,23 +467,28 @@ std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item) 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.Kind != LinkEntry::Flag) { - // 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; + } } } @@ -445,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) { @@ -628,6 +701,10 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, 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) { // Skip entries that will resolve to the target getting linked or @@ -636,6 +713,7 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, if (item.AsStr() == this->Target->GetName() || item.AsStr().empty()) { continue; } + if (cmHasPrefix(item.AsStr(), LL_BEGIN) && cmHasSuffix(item.AsStr(), '>')) { feature = ExtractFeature(item.AsStr()); @@ -666,12 +744,82 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, 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. - auto ale = this->AddLinkEntry(item); - int dependee_index = ale.first; + 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 @@ -702,50 +850,97 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, (entry.Target->GetType() != cmStateEnums::TargetType::OBJECT_LIBRARY && entry.Target->GetType() != cmStateEnums::TargetType::INTERFACE_LIBRARY); - if (supportedItem && 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 (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()); + } } - // The dependee must come after the depender. - if (depender_index >= 0) { - this->EntryConstraintGraph[depender_index].emplace_back( - dependee_index, false, false, cmListFileBacktrace()); + if (inGroup) { + // store item index for dependencies handling + groupItems.push_back(dependee_index); } 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].Kind != LinkEntry::Flag && - dependee_index != dependSet.first) { - dependSet.second.insert(dependee_index); + 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[dependee_index].Initialized) { - // Make sure an entry exists to hold the set for the item. - dependSets[dependee_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]; + } + } } } @@ -808,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) { @@ -821,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. @@ -835,15 +1130,6 @@ void cmComputeLinkDepends::DisplayConstraintGraph() 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. @@ -1033,11 +1319,18 @@ 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]", 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]", 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()); diff --git a/Source/cmComputeLinkDepends.h b/Source/cmComputeLinkDepends.h index 3c83f5a..8cc916a 100644 --- a/Source/cmComputeLinkDepends.h +++ b/Source/cmComputeLinkDepends.h @@ -54,7 +54,10 @@ public: Library, Object, SharedDep, - Flag + Flag, + // The following member is for the management of items specified + // through genex $<LINK_GROUP:...> + Group }; BT<std::string> Item; @@ -90,7 +93,8 @@ private: std::pair<std::map<cmLinkItem, int>::iterator, bool> AllocateLinkEntry( cmLinkItem const& item); - std::pair<int, bool> AddLinkEntry(cmLinkItem const& item); + std::pair<int, bool> AddLinkEntry(cmLinkItem const& item, + int groupIndex = -1); void AddLinkObject(cmLinkItem const& item); void AddVarLinkEntries(int depender_index, const char* value); void AddDirectLinkEntries(); @@ -103,10 +107,14 @@ private: std::vector<LinkEntry> EntryList; std::map<cmLinkItem, int> LinkEntryIndex; + // map storing, for each group, the list of items + std::map<int, std::vector<int>> GroupItems; + // BFS of initial dependencies. struct BFSEntry { int Index; + int GroupIndex; const char* LibDepends; }; std::queue<BFSEntry> BFSQueue; @@ -139,12 +147,16 @@ private: std::vector<DependSetList> InferredDependSets; void InferDependencies(); + // To finalize dependencies over groups in place of raw items + void UpdateGroupDependencies(); + // Ordering constraint graph adjacency list. using NodeList = cmGraphNodeList; using EdgeList = cmGraphEdgeList; using Graph = cmGraphAdjacencyList; Graph EntryConstraintGraph; void CleanConstraintGraph(); + bool CheckCircularDependencies() const; void DisplayConstraintGraph(); // Ordering algorithm. diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx index fe4491d..67214f1 100644 --- a/Source/cmComputeLinkInformation.cxx +++ b/Source/cmComputeLinkInformation.cxx @@ -350,21 +350,23 @@ cmComputeLinkInformation::cmComputeLinkInformation( if (!this->GetLibLinkFileFlag().empty()) { this->LibraryFeatureDescriptors.emplace( "__CMAKE_LINK_LIBRARY", - FeatureDescriptor{ "__CMAKE_LINK_LIBRARY", - cmStrCat(this->GetLibLinkFileFlag(), "<LIBRARY>") }); + LibraryFeatureDescriptor{ + "__CMAKE_LINK_LIBRARY", + cmStrCat(this->GetLibLinkFileFlag(), "<LIBRARY>") }); } if (!this->GetObjLinkFileFlag().empty()) { this->LibraryFeatureDescriptors.emplace( "__CMAKE_LINK_OBJECT", - FeatureDescriptor{ "__CMAKE_LINK_OBJECT", - cmStrCat(this->GetObjLinkFileFlag(), "<LIBRARY>") }); + LibraryFeatureDescriptor{ + "__CMAKE_LINK_OBJECT", + cmStrCat(this->GetObjLinkFileFlag(), "<LIBRARY>") }); } if (!this->LoaderFlag->empty()) { // Define a Feature descriptor for the link of an executable with exports this->LibraryFeatureDescriptors.emplace( "__CMAKE_LINK_EXECUTABLE", - FeatureDescriptor{ "__CMAKE_LINK_EXECUTABLE", - cmStrCat(this->LoaderFlag, "<LIBRARY>") }); + LibraryFeatureDescriptor{ "__CMAKE_LINK_EXECUTABLE", + cmStrCat(this->LoaderFlag, "<LIBRARY>") }); } // Check the platform policy for missing soname case. @@ -544,6 +546,19 @@ bool cmComputeLinkInformation::Compute() // Add the link line items. for (cmComputeLinkDepends::LinkEntry const& linkEntry : linkEntries) { + if (linkEntry.Kind == cmComputeLinkDepends::LinkEntry::Group) { + const auto& groupFeature = this->GetGroupFeature(linkEntry.Feature); + if (groupFeature.Supported) { + this->Items.emplace_back( + BT<std::string>{ linkEntry.Item.Value == "<LINK_GROUP>" + ? groupFeature.Prefix + : groupFeature.Suffix, + linkEntry.Item.Backtrace }, + ItemIsPath::No); + } + continue; + } + if (currentFeature != nullptr && linkEntry.Feature != currentFeature->Name) { // emit feature suffix, if any @@ -664,6 +679,117 @@ bool IsValidFeatureFormat(const std::string& format) format.find("<LIB_ITEM>") != std::string::npos || format.find("<LINK_ITEM>") != std::string::npos; } + +class FeaturePlaceHolderExpander : public cmPlaceholderExpander +{ +public: + FeaturePlaceHolderExpander(const std::string* library, + const std::string* libItem = nullptr, + const std::string* linkItem = nullptr) + : Library(library) + , LibItem(libItem) + , LinkItem(linkItem) + { + } + +private: + std::string ExpandVariable(std::string const& variable) override + { + if (this->Library != nullptr && variable == "LIBRARY") { + return *this->Library; + } + if (this->LibItem != nullptr && variable == "LIB_ITEM") { + return *this->LibItem; + } + if (this->LinkItem != nullptr && variable == "LINK_ITEM") { + return *this->LinkItem; + } + + return variable; + } + + const std::string* Library = nullptr; + const std::string* LibItem = nullptr; + const std::string* LinkItem = nullptr; +}; +} + +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string itemFormat) + : Name(std::move(name)) + , Supported(true) + , ItemPathFormat(std::move(itemFormat)) + , ItemNameFormat(this->ItemPathFormat) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string itemPathFormat, std::string itemNameFormat) + : Name(std::move(name)) + , Supported(true) + , ItemPathFormat(std::move(itemPathFormat)) + , ItemNameFormat(std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string prefix, std::string itemPathFormat, + std::string itemNameFormat, std::string suffix) + : Name(std::move(name)) + , Supported(true) + , Prefix(std::move(prefix)) + , Suffix(std::move(suffix)) + , ItemPathFormat(std::move(itemPathFormat)) + , ItemNameFormat(std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string prefix, std::string suffix, bool) + : Name(std::move(name)) + , Supported(true) + , Prefix(std::move(prefix)) + , Suffix(std::move(suffix)) +{ +} + +std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( + std::string const& library, ItemIsPath isPath) const +{ + auto format = + isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + + // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns with library path + FeaturePlaceHolderExpander expander(&library, &library, &library); + return expander.ExpandVariables(format); +} +std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( + std::string const& library, std::string const& libItem, + std::string const& linkItem, ItemIsPath isPath) const +{ + auto format = + isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + + // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns + FeaturePlaceHolderExpander expander(&library, &libItem, &linkItem); + return expander.ExpandVariables(format); +} + +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string itemFormat) + : FeatureDescriptor(std::move(name), std::move(itemFormat)) +{ +} +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string itemPathFormat, std::string itemNameFormat) + : FeatureDescriptor(std::move(name), std::move(itemPathFormat), + std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string prefix, std::string itemPathFormat, + std::string itemNameFormat, std::string suffix) + : FeatureDescriptor(std::move(name), std::move(prefix), + std::move(itemPathFormat), std::move(itemNameFormat), + std::move(suffix)) +{ } bool cmComputeLinkInformation::AddLibraryFeature(std::string const& feature) @@ -792,12 +918,13 @@ bool cmComputeLinkInformation::AddLibraryFeature(std::string const& feature) if (items.size() == 2) { this->LibraryFeatureDescriptors.emplace( - feature, FeatureDescriptor{ feature, items[0].Value, items[1].Value }); + feature, + LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value }); } else { this->LibraryFeatureDescriptors.emplace( feature, - FeatureDescriptor{ feature, items[0].Value, items[1].Value, - items[2].Value, items[3].Value }); + LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value, + items[2].Value, items[3].Value }); } return true; @@ -819,89 +946,80 @@ cmComputeLinkInformation::FindLibraryFeature(std::string const& feature) const return &it->second; } -namespace { -class FeaturePlaceHolderExpander : public cmPlaceholderExpander +cmComputeLinkInformation::GroupFeatureDescriptor::GroupFeatureDescriptor( + std::string name, std::string prefix, std::string suffix) + : FeatureDescriptor(std::move(name), std::move(prefix), std::move(suffix), + true) { -public: - FeaturePlaceHolderExpander(const std::string* library, - const std::string* libItem = nullptr, - const std::string* linkItem = nullptr) - : Library(library) - , LibItem(libItem) - , LinkItem(linkItem) - { - } +} -private: - std::string ExpandVariable(std::string const& variable) override - { - if (this->Library != nullptr && variable == "LIBRARY") { - return *this->Library; - } - if (this->LibItem != nullptr && variable == "LIB_ITEM") { - return *this->LibItem; - } - if (this->LinkItem != nullptr && variable == "LINK_ITEM") { - return *this->LinkItem; - } +cmComputeLinkInformation::FeatureDescriptor const& +cmComputeLinkInformation::GetGroupFeature(std::string const& feature) +{ + auto it = this->GroupFeatureDescriptors.find(feature); + if (it != this->GroupFeatureDescriptors.end()) { + return it->second; + } - return variable; + auto featureName = + cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_", feature); + cmValue featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + if (!featureSupported.IsOn()) { + featureName = cmStrCat("CMAKE_LINK_GROUP_USING_", feature); + featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + } + if (!featureSupported.IsOn()) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, + "', specified through generator-expression '$<LINK_GROUP>' to " + "link target '", + this->Target->GetName(), "', is not supported for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; } - const std::string* Library = nullptr; - const std::string* LibItem = nullptr; - const std::string* LinkItem = nullptr; -}; -} + cmValue langFeature = this->Makefile->GetDefinition(featureName); + if (!langFeature) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, + "', specified through generator-expression '$<LINK_GROUP>' to " + "link target '", + this->Target->GetName(), "', is not defined for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; + } -cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( - std::string name, std::string itemFormat) - : Name(std::move(name)) - , Supported(true) - , ItemPathFormat(std::move(itemFormat)) - , ItemNameFormat(this->ItemPathFormat) -{ -} -cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( - std::string name, std::string itemPathFormat, std::string itemNameFormat) - : Name(std::move(name)) - , Supported(true) - , ItemPathFormat(std::move(itemPathFormat)) - , ItemNameFormat(std::move(itemNameFormat)) -{ -} -cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( - std::string name, std::string prefix, std::string itemPathFormat, - std::string itemNameFormat, std::string suffix) - : Name(std::move(name)) - , Supported(true) - , Prefix(std::move(prefix)) - , Suffix(std::move(suffix)) - , ItemPathFormat(std::move(itemPathFormat)) - , ItemNameFormat(std::move(itemNameFormat)) -{ -} + auto items = + cmExpandListWithBacktrace(langFeature, this->Target->GetBacktrace(), true); -std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( - std::string const& library, ItemIsPath isPath) const -{ - auto format = - isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + // replace LINKER: pattern + this->Target->ResolveLinkerWrapper(items, this->LinkLanguage, true); - // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns with library path - FeaturePlaceHolderExpander expander(&library, &library, &library); - return expander.ExpandVariables(format); -} -std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( - std::string const& library, std::string const& libItem, - std::string const& linkItem, ItemIsPath isPath) const -{ - auto format = - isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + if (items.size() == 2) { + return this->GroupFeatureDescriptors + .emplace( + feature, + GroupFeatureDescriptor{ feature, items[0].Value, items[1].Value }) + .first->second; + } - // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns - FeaturePlaceHolderExpander expander(&library, &libItem, &linkItem); - return expander.ExpandVariables(format); + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (wrong number of elements) and cannot be used " + "to link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; } void cmComputeLinkInformation::AddImplicitLinkInfo() diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h index 4b7fb1a..a4ada1f 100644 --- a/Source/cmComputeLinkInformation.h +++ b/Source/cmComputeLinkInformation.h @@ -255,12 +255,6 @@ private: { public: FeatureDescriptor() = default; - FeatureDescriptor(std::string name, std::string itemFormat); - FeatureDescriptor(std::string name, std::string itemPathFormat, - std::string itemNameFormat); - FeatureDescriptor(std::string name, std::string prefix, - std::string itemPathFormat, std::string itemNameFormat, - std::string suffix); const std::string Name; const bool Supported = false; @@ -273,13 +267,44 @@ private: std::string const& defaultValue, ItemIsPath isPath) const; + protected: + FeatureDescriptor(std::string name, std::string itemFormat); + FeatureDescriptor(std::string name, std::string itemPathFormat, + std::string itemNameFormat); + FeatureDescriptor(std::string name, std::string prefix, + std::string itemPathFormat, std::string itemNameFormat, + std::string suffix); + + FeatureDescriptor(std::string name, std::string prefix, std::string suffix, + bool isGroup); + private: std::string ItemPathFormat; std::string ItemNameFormat; }; + + class LibraryFeatureDescriptor : public FeatureDescriptor + { + public: + LibraryFeatureDescriptor(std::string name, std::string itemFormat); + LibraryFeatureDescriptor(std::string name, std::string itemPathFormat, + std::string itemNameFormat); + LibraryFeatureDescriptor(std::string name, std::string prefix, + std::string itemPathFormat, + std::string itemNameFormat, std::string suffix); + }; std::map<std::string, FeatureDescriptor> LibraryFeatureDescriptors; bool AddLibraryFeature(std::string const& feature); FeatureDescriptor const& GetLibraryFeature(std::string const& feature) const; FeatureDescriptor const* FindLibraryFeature( std::string const& feature) const; + + class GroupFeatureDescriptor : public FeatureDescriptor + { + public: + GroupFeatureDescriptor(std::string name, std::string prefix, + std::string suffix); + }; + std::map<std::string, FeatureDescriptor> GroupFeatureDescriptors; + FeatureDescriptor const& GetGroupFeature(std::string const& feature); }; diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index b63b90b..db043ec 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -1231,7 +1231,15 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode return std::string(); } + static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); auto const& feature = list.front(); + if (!featureNameValidator.find(feature)) { + reportError(context, content->GetOriginalExpression(), + cmStrCat("The feature name '", feature, + "' contains invalid characters.")); + return std::string(); + } + const auto LL_BEGIN = cmStrCat("<LINK_LIBRARY:", feature, '>'); const auto LL_END = cmStrCat("</LINK_LIBRARY:", feature, '>'); @@ -1252,6 +1260,17 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode "$<LINK_LIBRARY:...> with different features cannot be nested."); return std::string(); } + // $<LINK_GROUP:...> must not appear as part of $<LINK_LIBRARY:...> + it = std::find_if(list.cbegin() + 1, list.cend(), + [](const std::string& item) -> bool { + return cmHasPrefix(item, "<LINK_GROUP:"_s); + }); + if (it != list.cend()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> cannot be nested inside a " + "$<LINK_LIBRARY:...> expression."); + return std::string(); + } list.front() = LL_BEGIN; list.push_back(LL_END); @@ -1260,6 +1279,71 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode } } linkLibraryNode; +static const struct LinkGroupNode : public cmGeneratorExpressionNode +{ + LinkGroupNode() {} // NOLINT(modernize-use-equals-default) + + int NumExpectedParameters() const override { return OneOrMoreParameters; } + + std::string Evaluate( + const std::vector<std::string>& parameters, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content, + cmGeneratorExpressionDAGChecker* dagChecker) const override + { + if (!context->HeadTarget || !dagChecker || + !dagChecker->EvaluatingLinkLibraries()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> may only be used with binary targets " + "to specify group of link libraries."); + return std::string(); + } + + std::vector<std::string> list; + cmExpandLists(parameters.begin(), parameters.end(), list); + if (list.empty()) { + reportError( + context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> expects a feature name as first argument."); + return std::string(); + } + // $<LINK_GROUP:..> cannot be nested + if (std::find_if(list.cbegin(), list.cend(), + [](const std::string& item) -> bool { + return cmHasPrefix(item, "<LINK_GROUP"_s); + }) != list.cend()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> cannot be nested."); + return std::string(); + } + if (list.size() == 1) { + // no libraries specified, ignore this genex + return std::string(); + } + + static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); + auto const& feature = list.front(); + if (!featureNameValidator.find(feature)) { + reportError(context, content->GetOriginalExpression(), + cmStrCat("The feature name '", feature, + "' contains invalid characters.")); + return std::string(); + } + + const auto LG_BEGIN = cmStrCat( + "<LINK_GROUP:", feature, ':', + cmJoin(cmRange<decltype(list.cbegin())>(list.cbegin() + 1, list.cend()), + "|"_s), + '>'); + const auto LG_END = cmStrCat("</LINK_GROUP:", feature, '>'); + + list.front() = LG_BEGIN; + list.push_back(LG_END); + + return cmJoin(list, ";"_s); + } +} linkGroupNode; + static const struct HostLinkNode : public cmGeneratorExpressionNode { HostLinkNode() {} // NOLINT(modernize-use-equals-default) @@ -2731,6 +2815,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode( { "LINK_LANG_AND_ID", &linkLanguageAndIdNode }, { "LINK_LANGUAGE", &linkLanguageNode }, { "LINK_LIBRARY", &linkLibraryNode }, + { "LINK_GROUP", &linkGroupNode }, { "HOST_LINK", &hostLinkNode }, { "DEVICE_LINK", &deviceLinkNode }, { "SHELL_PATH", &shellPathNode } diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index a246c12..e893b44 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -6341,7 +6341,8 @@ cm::string_view missingTargetPossibleReasons = bool cmGeneratorTarget::VerifyLinkItemColons(LinkItemRole role, cmLinkItem const& item) const { - if (item.Target || item.AsStr().find("::") == std::string::npos) { + if (item.Target || cmHasPrefix(item.AsStr(), "<LINK_GROUP:"_s) || + item.AsStr().find("::") == std::string::npos) { return true; } MessageType messageType = MessageType::FATAL_ERROR; @@ -6388,7 +6389,8 @@ bool cmGeneratorTarget::VerifyLinkItemIsTarget(LinkItemRole role, if (!str.empty() && (str[0] == '-' || str[0] == '$' || str[0] == '`' || str.find_first_of("/\\") != std::string::npos || - cmHasPrefix(str, "<LINK_LIBRARY:"_s))) { + cmHasPrefix(str, "<LINK_LIBRARY:"_s) || + cmHasPrefix(str, "<LINK_GROUP:"_s))) { return true; } diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 6a5d518..be189a6 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -3988,21 +3988,21 @@ void cmMakefile::CheckProperty(const std::string& prop) const if (prop == "LINK_LIBRARIES") { if (cmValue value = this->GetProperty(prop)) { // Look for <LINK_LIBRARY:> internal pattern - static cmsys::RegularExpression linkLibrary( - "(^|;)(</?LINK_LIBRARY:[^;>]*>)(;|$)"); - if (!linkLibrary.find(value)) { + static cmsys::RegularExpression linkPattern( + "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)"); + if (!linkPattern.find(value)) { return; } // Report an error. this->IssueMessage( MessageType::FATAL_ERROR, - cmStrCat( - "Property ", prop, " contains the invalid item \"", - linkLibrary.match(2), "\". The ", prop, - " property may contain the generator-expression " - "\"$<LINK_LIBRARY:...>\" " - "which may be used to specify how the libraries are linked.")); + cmStrCat("Property ", prop, " contains the invalid item \"", + linkPattern.match(2), "\". The ", prop, + " property may contain the generator-expression \"$<LINK_", + linkPattern.match(3), + ":...>\" which may be used to specify how the libraries are " + "linked.")); } } } diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 92a0ac4..4ca1b9b 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -1788,20 +1788,21 @@ void CheckLinkLibraryPattern(const std::string& property, const std::string& value, cmMakefile* context) { // Look for <LINK_LIBRARY:> and </LINK_LIBRARY:> internal tags - static cmsys::RegularExpression linkLibrary( - "(^|;)(</?LINK_LIBRARY:[^;>]*>)(;|$)"); - if (!linkLibrary.find(value)) { + static cmsys::RegularExpression linkPattern( + "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)"); + if (!linkPattern.find(value)) { return; } // Report an error. context->IssueMessage( MessageType::FATAL_ERROR, - cmStrCat("Property ", property, " contains the invalid item \"", - linkLibrary.match(2), "\". The ", property, - " property may contain the generator-expression " - "\"$<LINK_LIBRARY:...>\" " - "which may be used to specify how the libraries are linked.")); + cmStrCat( + "Property ", property, " contains the invalid item \"", + linkPattern.match(2), "\". The ", property, + " property may contain the generator-expression \"$<LINK_", + linkPattern.match(3), + ":...>\" which may be used to specify how the libraries are linked.")); } void CheckLINK_INTERFACE_LIBRARIES(const std::string& prop, |