From 93549b9224821eda5b6402c7fc4fc77708bcd86f Mon Sep 17 00:00:00 2001 From: Stephan Rohmen Date: Tue, 21 Jul 2020 19:45:45 +0200 Subject: Graphviz: Restore support for per-target dependency graph options The behaviors controlled by options `GRAPHVIZ_GENERATE_PER_TARGET` and `GRAPHVIZ_GENERATE_DEPENDERS` were broken by commit 553658393c (Graphviz: added test suite, fixes, enhancements, 2019-10-08, v3.17.0-rc1~615^2). It had not been covered in the test suite previously, and those changes left out checks for these features from the `default_options` case. Implement the previously-existing behavior in the new graphviz generation engine added by the above-mentioned commit. Fixes: #20928 --- Source/cmGraphVizWriter.cxx | 116 +++++++++++++++++++++++++++++++++++++++++--- Source/cmGraphVizWriter.h | 36 +++++++++++++- 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/Source/cmGraphVizWriter.cxx b/Source/cmGraphVizWriter.cxx index 1b77678..e03b2ca 100644 --- a/Source/cmGraphVizWriter.cxx +++ b/Source/cmGraphVizWriter.cxx @@ -67,6 +67,36 @@ const char* getShapeForTarget(const cmLinkItem& item) return GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN; } } + +struct DependeesDir +{ + template + static const cmLinkItem& src(const T& con) + { + return con.src; + } + + template + static const cmLinkItem& dst(const T& con) + { + return con.dst; + } +}; + +struct DependersDir +{ + template + static const cmLinkItem& src(const T& con) + { + return con.dst; + } + + template + static const cmLinkItem& dst(const T& con) + { + return con.src; + } +}; } cmGraphVizWriter::cmGraphVizWriter(std::string const& fileName, @@ -173,18 +203,16 @@ void cmGraphVizWriter::VisitLink(cmLinkItem const& depender, return; } + // write global data directly this->WriteConnection(this->GlobalFileStream, depender, dependee, scopeType); if (this->GeneratePerTarget) { - auto fileStream = PerTargetFileStreams[depender.AsStr()].get(); - this->WriteNode(*fileStream, dependee); - this->WriteConnection(*fileStream, depender, dependee, scopeType); + PerTargetConnections[depender].emplace_back(depender, dependee, scopeType); } if (this->GenerateDependers) { - auto fileStream = TargetDependersFileStreams[dependee.AsStr()].get(); - this->WriteNode(*fileStream, depender); - this->WriteConnection(*fileStream, depender, dependee, scopeType); + TargetDependersConnections[dependee].emplace_back(dependee, depender, + scopeType); } } @@ -288,10 +316,86 @@ void cmGraphVizWriter::Write() } } + // write global data and collect all connection data for per target graphs for (auto const gt : sortedGeneratorTargets) { auto item = cmLinkItem(gt, false, gt->GetBacktrace()); this->VisitItem(item); } + + if (this->GeneratePerTarget) { + WritePerTargetConnections(PerTargetConnections, + PerTargetFileStreams); + } + + if (this->GenerateDependers) { + WritePerTargetConnections(TargetDependersConnections, + TargetDependersFileStreams); + } +} + +void cmGraphVizWriter::FindAllConnections(const ConnectionsMap& connectionMap, + const cmLinkItem& rootItem, + Connections& extendedCons, + std::set& visitedItems) +{ + // some "targets" are not in map, e.g. linker flags as -lm or + // targets without dependency. + // in both cases we are finished with traversing the graph + if (connectionMap.find(rootItem) == connectionMap.cend()) { + return; + } + + const Connections& origCons = connectionMap.at(rootItem); + + for (const Connection& con : origCons) { + extendedCons.emplace_back(con); + const cmLinkItem& dstItem = con.dst; + bool const visited = visitedItems.find(dstItem) != visitedItems.cend(); + if (!visited) { + visitedItems.insert(dstItem); + FindAllConnections(connectionMap, dstItem, extendedCons, visitedItems); + } + } +} + +void cmGraphVizWriter::FindAllConnections(const ConnectionsMap& connectionMap, + const cmLinkItem& rootItem, + Connections& extendedCons) +{ + std::set visitedItems = { rootItem }; + FindAllConnections(connectionMap, rootItem, extendedCons, visitedItems); +} + +template +void cmGraphVizWriter::WritePerTargetConnections( + const ConnectionsMap& connections, const FileStreamMap& streams) +{ + // the per target connections must be extended by indirect dependencies + ConnectionsMap extendedConnections; + for (auto const& conPerTarget : connections) { + const cmLinkItem& rootItem = conPerTarget.first; + Connections& extendedCons = extendedConnections[conPerTarget.first]; + FindAllConnections(connections, rootItem, extendedCons); + } + + for (auto const& conPerTarget : extendedConnections) { + const cmLinkItem& rootItem = conPerTarget.first; + + // some of the nodes are excluded completely and are not written + if (this->ItemExcluded(rootItem)) { + continue; + } + + const Connections& cons = conPerTarget.second; + auto fileStream = streams.at(rootItem.AsStr()).get(); + + for (const Connection& con : cons) { + const cmLinkItem& src = DirFunc::src(con); + const cmLinkItem& dst = DirFunc::dst(con); + this->WriteNode(*fileStream, con.dst); + this->WriteConnection(*fileStream, src, dst, con.scopeType); + } + } } void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& fs, diff --git a/Source/cmGraphVizWriter.h b/Source/cmGraphVizWriter.h index 578660d..9766068 100644 --- a/Source/cmGraphVizWriter.h +++ b/Source/cmGraphVizWriter.h @@ -7,16 +7,18 @@ #include #include +#include #include +#include #include #include "cmsys/RegularExpression.hxx" #include "cmGeneratedFileStream.h" +#include "cmLinkItem.h" #include "cmLinkItemGraphVisitor.h" #include "cmStateTypes.h" -class cmLinkItem; class cmGlobalGenerator; /** This class implements writing files for graphviz (dot) for graphs @@ -47,6 +49,22 @@ private: using FileStreamMap = std::map>; + struct Connection + { + Connection(cmLinkItem s, cmLinkItem d, std::string scope) + : src(std::move(s)) + , dst(std::move(d)) + , scopeType(std::move(scope)) + { + } + + cmLinkItem src; + cmLinkItem dst; + std::string scopeType; + }; + using Connections = std::vector; + using ConnectionsMap = std::map; + void VisitLink(cmLinkItem const& depender, cmLinkItem const& dependee, bool isDirectLink, std::string const& scopeType = ""); @@ -66,6 +84,19 @@ private: cmLinkItem const& dependeeTargetName, std::string const& edgeStyle); + void FindAllConnections(const ConnectionsMap& connectionMap, + const cmLinkItem& rootItem, + Connections& extendedCons, + std::set& visitedItems); + + void FindAllConnections(const ConnectionsMap& connectionMap, + const cmLinkItem& rootItem, + Connections& extendedCons); + + template + void WritePerTargetConnections(const ConnectionsMap& connections, + const FileStreamMap& streams); + bool ItemExcluded(cmLinkItem const& item); bool ItemNameFilteredOut(std::string const& itemName); bool TargetTypeEnabled(cmStateEnums::TargetType targetType) const; @@ -83,6 +114,9 @@ private: FileStreamMap PerTargetFileStreams; FileStreamMap TargetDependersFileStreams; + ConnectionsMap PerTargetConnections; + ConnectionsMap TargetDependersConnections; + std::string GraphName; std::string GraphHeader; std::string GraphNodePrefix; -- cgit v0.12 From f3a6b4a209674ee1b3d7fa6f0724c2828fbe23e8 Mon Sep 17 00:00:00 2001 From: Stephan Rohmen Date: Tue, 21 Jul 2020 19:47:34 +0200 Subject: Tests: Cover Graphviz support for per-target dependency graph options Issue: #20928 --- .../RunCMake/Graphviz/default_options-check.cmake | 8 +++++++ ...raph_target_dependencies.dot.GraphicApplication | 22 ++++++++++++++++++ ...ph_target_dependers.dot.CompilerFlags.dependers | 26 ++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependencies.dot.GraphicApplication create mode 100644 Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependers.dot.CompilerFlags.dependers diff --git a/Tests/RunCMake/Graphviz/default_options-check.cmake b/Tests/RunCMake/Graphviz/default_options-check.cmake index c9a7562..584e276 100644 --- a/Tests/RunCMake/Graphviz/default_options-check.cmake +++ b/Tests/RunCMake/Graphviz/default_options-check.cmake @@ -3,3 +3,11 @@ include(RunCMake) ensure_files_match( ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_default_options.dot ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot) + +ensure_files_match( + ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_target_dependencies.dot.GraphicApplication + ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot.GraphicApplication) + +ensure_files_match( + ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_target_dependers.dot.CompilerFlags.dependers + ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot.CompilerFlags.dependers) diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependencies.dot.GraphicApplication b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependencies.dot.GraphicApplication new file mode 100644 index 0000000..6893fd1 --- /dev/null +++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependencies.dot.GraphicApplication @@ -0,0 +1,22 @@ +digraph "GraphicApplication" { +node [ + fontsize = "12" +]; + "node5" [ label = "GraphicApplication", shape = egg ]; + "node2" [ label = "CoreLibrary", shape = octagon ]; + "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary + "node0" [ label = "CompilerFlags", shape = pentagon ]; + "node2" -> "node0" // CoreLibrary -> CompilerFlags + "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ]; + "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary + "node6" [ label = "GraphicLibrary", shape = doubleoctagon ]; + "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary + "node7" [ label = "\"-lm\"", shape = septagon ]; + "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm" + "node0" [ label = "CompilerFlags", shape = pentagon ]; + "node6" -> "node0" // GraphicLibrary -> CompilerFlags + "node2" [ label = "CoreLibrary", shape = octagon ]; + "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary + "node8" [ label = "GraphicLibraryObjects", shape = hexagon ]; + "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects +} diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependers.dot.CompilerFlags.dependers b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependers.dot.CompilerFlags.dependers new file mode 100644 index 0000000..3352b1a --- /dev/null +++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_target_dependers.dot.CompilerFlags.dependers @@ -0,0 +1,26 @@ +digraph "CompilerFlags" { +node [ + fontsize = "12" +]; + "node0" [ label = "CompilerFlags", shape = pentagon ]; + "node2" [ label = "CoreLibrary", shape = octagon ]; + "node2" -> "node0" // CoreLibrary -> CompilerFlags + "node1" [ label = "ConsoleApplication", shape = egg ]; + "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary + "node5" [ label = "GraphicApplication", shape = egg ]; + "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary + "node6" [ label = "GraphicLibrary", shape = doubleoctagon ]; + "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary + "node5" [ label = "GraphicApplication", shape = egg ]; + "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary + "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ]; + "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary + "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ]; + "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary + "node6" [ label = "GraphicLibrary", shape = doubleoctagon ]; + "node6" -> "node0" // GraphicLibrary -> CompilerFlags + "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ]; + "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags + "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ]; + "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags +} -- cgit v0.12