/src/CMake/Source/cmGraphVizWriter.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmGraphVizWriter.h" |
4 | | |
5 | | #include <algorithm> |
6 | | #include <iostream> |
7 | | #include <memory> |
8 | | #include <set> |
9 | | #include <unordered_set> |
10 | | #include <utility> |
11 | | |
12 | | #include <cm/memory> |
13 | | |
14 | | #include "cmsys/RegularExpression.hxx" |
15 | | #include "cmsys/String.h" |
16 | | |
17 | | #include "cmGeneratedFileStream.h" |
18 | | #include "cmGeneratorTarget.h" |
19 | | #include "cmGlobalGenerator.h" |
20 | | #include "cmLinkItem.h" |
21 | | #include "cmList.h" |
22 | | #include "cmLocalGenerator.h" |
23 | | #include "cmMakefile.h" |
24 | | #include "cmState.h" |
25 | | #include "cmStateSnapshot.h" |
26 | | #include "cmStringAlgorithms.h" |
27 | | #include "cmSystemTools.h" |
28 | | #include "cmValue.h" |
29 | | #include "cmake.h" |
30 | | |
31 | | namespace { |
32 | | |
33 | | char const* const GRAPHVIZ_EDGE_STYLE_PUBLIC = "solid"; |
34 | | char const* const GRAPHVIZ_EDGE_STYLE_INTERFACE = "dashed"; |
35 | | char const* const GRAPHVIZ_EDGE_STYLE_PRIVATE = "dotted"; |
36 | | |
37 | | char const* const GRAPHVIZ_NODE_SHAPE_EXECUTABLE = "egg"; // egg-xecutable |
38 | | |
39 | | // Normal libraries. |
40 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC = "octagon"; |
41 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED = "doubleoctagon"; |
42 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE = "tripleoctagon"; |
43 | | |
44 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE = "pentagon"; |
45 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT = "hexagon"; |
46 | | char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN = "septagon"; |
47 | | |
48 | | char const* const GRAPHVIZ_NODE_SHAPE_UTILITY = "box"; |
49 | | |
50 | | char const* getShapeForTarget(cmLinkItem const& item) |
51 | 0 | { |
52 | 0 | if (!item.Target) { |
53 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN; |
54 | 0 | } |
55 | | |
56 | 0 | switch (item.Target->GetType()) { |
57 | 0 | case cmStateEnums::EXECUTABLE: |
58 | 0 | return GRAPHVIZ_NODE_SHAPE_EXECUTABLE; |
59 | 0 | case cmStateEnums::STATIC_LIBRARY: |
60 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC; |
61 | 0 | case cmStateEnums::SHARED_LIBRARY: |
62 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED; |
63 | 0 | case cmStateEnums::MODULE_LIBRARY: |
64 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE; |
65 | 0 | case cmStateEnums::OBJECT_LIBRARY: |
66 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT; |
67 | 0 | case cmStateEnums::UTILITY: |
68 | 0 | return GRAPHVIZ_NODE_SHAPE_UTILITY; |
69 | 0 | case cmStateEnums::INTERFACE_LIBRARY: |
70 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE; |
71 | 0 | case cmStateEnums::UNKNOWN_LIBRARY: |
72 | 0 | default: |
73 | 0 | return GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN; |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | struct DependeesDir |
78 | | { |
79 | | template <typename T> |
80 | | static cmLinkItem const& src(T const& con) |
81 | 0 | { |
82 | 0 | return con.src; |
83 | 0 | } |
84 | | |
85 | | template <typename T> |
86 | | static cmLinkItem const& dst(T const& con) |
87 | 0 | { |
88 | 0 | return con.dst; |
89 | 0 | } |
90 | | }; |
91 | | |
92 | | struct DependersDir |
93 | | { |
94 | | template <typename T> |
95 | | static cmLinkItem const& src(T const& con) |
96 | 0 | { |
97 | 0 | return con.dst; |
98 | 0 | } |
99 | | |
100 | | template <typename T> |
101 | | static cmLinkItem const& dst(T const& con) |
102 | 0 | { |
103 | 0 | return con.src; |
104 | 0 | } |
105 | | }; |
106 | | } |
107 | | |
108 | | cmGraphVizWriter::cmGraphVizWriter(std::string const& fileName, |
109 | | cmGlobalGenerator const* globalGenerator) |
110 | 0 | : FileName(fileName) |
111 | 0 | , GlobalFileStream(fileName) |
112 | 0 | , GraphName(globalGenerator->GetSafeGlobalSetting("CMAKE_PROJECT_NAME")) |
113 | 0 | , GraphHeader("node [\n fontsize = \"12\"\n];") |
114 | 0 | , GraphNodePrefix("node") |
115 | 0 | , GlobalGenerator(globalGenerator) |
116 | 0 | { |
117 | 0 | } |
118 | | |
119 | | cmGraphVizWriter::~cmGraphVizWriter() |
120 | 0 | { |
121 | 0 | this->WriteFooter(this->GlobalFileStream); |
122 | 0 | } |
123 | | |
124 | | void cmGraphVizWriter::VisitGraph(std::string const&) |
125 | 0 | { |
126 | 0 | this->WriteHeader(this->GlobalFileStream, this->GraphName); |
127 | 0 | this->WriteLegend(this->GlobalFileStream); |
128 | 0 | } |
129 | | |
130 | | void cmGraphVizWriter::OnItem(cmLinkItem const& item) |
131 | 0 | { |
132 | 0 | if (this->ItemExcluded(item)) { |
133 | 0 | return; |
134 | 0 | } |
135 | | |
136 | 0 | this->NodeNames[item.AsStr()] = |
137 | 0 | cmStrCat(this->GraphNodePrefix, this->NextNodeId); |
138 | 0 | ++this->NextNodeId; |
139 | |
|
140 | 0 | this->WriteNode(this->GlobalFileStream, item); |
141 | 0 | } |
142 | | |
143 | | std::unique_ptr<cmGeneratedFileStream> cmGraphVizWriter::CreateTargetFile( |
144 | | cmLinkItem const& item, std::string const& fileNameSuffix) |
145 | 0 | { |
146 | 0 | auto const pathSafeItemName = PathSafeString(item.AsStr()); |
147 | 0 | auto const perTargetFileName = |
148 | 0 | cmStrCat(this->FileName, '.', pathSafeItemName, fileNameSuffix); |
149 | 0 | auto perTargetFileStream = |
150 | 0 | cm::make_unique<cmGeneratedFileStream>(perTargetFileName); |
151 | |
|
152 | 0 | this->WriteHeader(*perTargetFileStream, item.AsStr()); |
153 | 0 | this->WriteNode(*perTargetFileStream, item); |
154 | |
|
155 | 0 | return perTargetFileStream; |
156 | 0 | } |
157 | | |
158 | | void cmGraphVizWriter::OnDirectLink(cmLinkItem const& depender, |
159 | | cmLinkItem const& dependee, |
160 | | DependencyType dt) |
161 | 0 | { |
162 | 0 | this->VisitLink(depender, dependee, true, GetEdgeStyle(dt)); |
163 | 0 | } |
164 | | |
165 | | void cmGraphVizWriter::OnIndirectLink(cmLinkItem const& depender, |
166 | | cmLinkItem const& dependee) |
167 | 0 | { |
168 | 0 | this->VisitLink(depender, dependee, false); |
169 | 0 | } |
170 | | |
171 | | void cmGraphVizWriter::VisitLink(cmLinkItem const& depender, |
172 | | cmLinkItem const& dependee, bool isDirectLink, |
173 | | std::string const& scopeType) |
174 | 0 | { |
175 | 0 | if (this->ItemExcluded(depender) || this->ItemExcluded(dependee)) { |
176 | 0 | return; |
177 | 0 | } |
178 | | |
179 | 0 | if (!isDirectLink) { |
180 | 0 | return; |
181 | 0 | } |
182 | | |
183 | | // write global data directly |
184 | 0 | this->WriteConnection(this->GlobalFileStream, depender, dependee, scopeType); |
185 | |
|
186 | 0 | if (this->GeneratePerTarget) { |
187 | 0 | this->PerTargetConnections[depender].emplace_back(depender, dependee, |
188 | 0 | scopeType); |
189 | 0 | } |
190 | |
|
191 | 0 | if (this->GenerateDependers) { |
192 | 0 | this->TargetDependersConnections[dependee].emplace_back(dependee, depender, |
193 | 0 | scopeType); |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | void cmGraphVizWriter::ReadSettings( |
198 | | std::string const& settingsFileName, |
199 | | std::string const& fallbackSettingsFileName) |
200 | 0 | { |
201 | 0 | cmake cm(cmState::Role::Script); |
202 | 0 | cm.GetCurrentSnapshot().SetDefaultDefinitions(); |
203 | 0 | cmGlobalGenerator ggi(&cm); |
204 | 0 | cmMakefile mf(&ggi, cm.GetCurrentSnapshot()); |
205 | 0 | std::unique_ptr<cmLocalGenerator> lg(ggi.CreateLocalGenerator(&mf)); |
206 | |
|
207 | 0 | std::string inFileName = settingsFileName; |
208 | 0 | if (!cmSystemTools::FileExists(inFileName)) { |
209 | 0 | inFileName = fallbackSettingsFileName; |
210 | 0 | if (!cmSystemTools::FileExists(inFileName)) { |
211 | 0 | return; |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | 0 | if (!mf.ReadListFile(inFileName)) { |
216 | 0 | cmSystemTools::Error("Problem opening GraphViz options file: " + |
217 | 0 | inFileName); |
218 | 0 | return; |
219 | 0 | } |
220 | | |
221 | 0 | std::cout << "Reading GraphViz options file: " << inFileName << std::endl; |
222 | |
|
223 | 0 | #define set_if_set(var, cmakeDefinition) \ |
224 | 0 | do { \ |
225 | 0 | cmValue value = mf.GetDefinition(cmakeDefinition); \ |
226 | 0 | if (value) { \ |
227 | 0 | (var) = *value; \ |
228 | 0 | } \ |
229 | 0 | } while (false) |
230 | |
|
231 | 0 | set_if_set(this->GraphName, "GRAPHVIZ_GRAPH_NAME"); |
232 | 0 | set_if_set(this->GraphHeader, "GRAPHVIZ_GRAPH_HEADER"); |
233 | 0 | set_if_set(this->GraphNodePrefix, "GRAPHVIZ_NODE_PREFIX"); |
234 | |
|
235 | 0 | #define set_bool_if_set(var, cmakeDefinition) \ |
236 | 0 | do { \ |
237 | 0 | cmValue value = mf.GetDefinition(cmakeDefinition); \ |
238 | 0 | if (value) { \ |
239 | 0 | (var) = cmIsOn(*value); \ |
240 | 0 | } \ |
241 | 0 | } while (false) |
242 | |
|
243 | 0 | set_bool_if_set(this->GenerateForExecutables, "GRAPHVIZ_EXECUTABLES"); |
244 | 0 | set_bool_if_set(this->GenerateForStaticLibs, "GRAPHVIZ_STATIC_LIBS"); |
245 | 0 | set_bool_if_set(this->GenerateForSharedLibs, "GRAPHVIZ_SHARED_LIBS"); |
246 | 0 | set_bool_if_set(this->GenerateForModuleLibs, "GRAPHVIZ_MODULE_LIBS"); |
247 | 0 | set_bool_if_set(this->GenerateForInterfaceLibs, "GRAPHVIZ_INTERFACE_LIBS"); |
248 | 0 | set_bool_if_set(this->GenerateForObjectLibs, "GRAPHVIZ_OBJECT_LIBS"); |
249 | 0 | set_bool_if_set(this->GenerateForUnknownLibs, "GRAPHVIZ_UNKNOWN_LIBS"); |
250 | 0 | set_bool_if_set(this->GenerateForCustomTargets, "GRAPHVIZ_CUSTOM_TARGETS"); |
251 | 0 | set_bool_if_set(this->GenerateForExternals, "GRAPHVIZ_EXTERNAL_LIBS"); |
252 | 0 | set_bool_if_set(this->GeneratePerTarget, "GRAPHVIZ_GENERATE_PER_TARGET"); |
253 | 0 | set_bool_if_set(this->GenerateDependers, "GRAPHVIZ_GENERATE_DEPENDERS"); |
254 | |
|
255 | 0 | std::string ignoreTargetsRegexes; |
256 | 0 | set_if_set(ignoreTargetsRegexes, "GRAPHVIZ_IGNORE_TARGETS"); |
257 | |
|
258 | 0 | this->TargetsToIgnoreRegex.clear(); |
259 | 0 | if (!ignoreTargetsRegexes.empty()) { |
260 | 0 | cmList ignoreTargetsRegExList{ ignoreTargetsRegexes }; |
261 | 0 | for (std::string const& currentRegexString : ignoreTargetsRegExList) { |
262 | 0 | cmsys::RegularExpression currentRegex; |
263 | 0 | if (!currentRegex.compile(currentRegexString)) { |
264 | 0 | std::cerr << "Could not compile bad regex \"" << currentRegexString |
265 | 0 | << "\"" << std::endl; |
266 | 0 | } |
267 | 0 | this->TargetsToIgnoreRegex.push_back(std::move(currentRegex)); |
268 | 0 | } |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | | void cmGraphVizWriter::Write() |
273 | 0 | { |
274 | 0 | auto const* gg = this->GlobalGenerator; |
275 | |
|
276 | 0 | this->VisitGraph(gg->GetName()); |
277 | | |
278 | | // We want to traverse in a determined order, such that the output is always |
279 | | // the same for a given project (this makes tests reproducible, etc.) |
280 | 0 | std::set<cmGeneratorTarget const*, cmGeneratorTarget::StrictTargetComparison> |
281 | 0 | sortedGeneratorTargets; |
282 | |
|
283 | 0 | for (auto const& lg : gg->GetLocalGenerators()) { |
284 | 0 | for (auto const& gt : lg->GetGeneratorTargets()) { |
285 | | // Reserved targets have inconsistent names across platforms (e.g. 'all' |
286 | | // vs. 'ALL_BUILD'), which can disrupt the traversal ordering. |
287 | | // We don't need or want them anyway. |
288 | 0 | if (!cmGlobalGenerator::IsReservedTarget(gt->GetName()) && |
289 | 0 | !cmHasLiteralPrefix(gt->GetName(), "__cmake_")) { |
290 | 0 | sortedGeneratorTargets.insert(gt.get()); |
291 | 0 | } |
292 | 0 | } |
293 | 0 | } |
294 | | |
295 | | // write global data and collect all connection data for per target graphs |
296 | 0 | for (auto const* const gt : sortedGeneratorTargets) { |
297 | 0 | auto item = cmLinkItem(gt, false, gt->GetBacktrace()); |
298 | 0 | this->VisitItem(item); |
299 | 0 | } |
300 | |
|
301 | 0 | if (this->GeneratePerTarget) { |
302 | 0 | this->WritePerTargetConnections<DependeesDir>(this->PerTargetConnections); |
303 | 0 | } |
304 | |
|
305 | 0 | if (this->GenerateDependers) { |
306 | 0 | this->WritePerTargetConnections<DependersDir>( |
307 | 0 | this->TargetDependersConnections, ".dependers"); |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | void cmGraphVizWriter::FindAllConnections(ConnectionsMap const& connectionMap, |
312 | | cmLinkItem const& rootItem, |
313 | | Connections& extendedCons, |
314 | | std::set<cmLinkItem>& visitedItems) |
315 | 0 | { |
316 | | // some "targets" are not in map, e.g. linker flags as -lm or |
317 | | // targets without dependency. |
318 | | // in both cases we are finished with traversing the graph |
319 | 0 | if (connectionMap.find(rootItem) == connectionMap.cend()) { |
320 | 0 | return; |
321 | 0 | } |
322 | | |
323 | 0 | Connections const& origCons = connectionMap.at(rootItem); |
324 | |
|
325 | 0 | for (Connection const& con : origCons) { |
326 | 0 | extendedCons.emplace_back(con); |
327 | 0 | cmLinkItem const& dstItem = con.dst; |
328 | 0 | bool const visited = visitedItems.find(dstItem) != visitedItems.cend(); |
329 | 0 | if (!visited) { |
330 | 0 | visitedItems.insert(dstItem); |
331 | 0 | this->FindAllConnections(connectionMap, dstItem, extendedCons, |
332 | 0 | visitedItems); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | void cmGraphVizWriter::FindAllConnections(ConnectionsMap const& connectionMap, |
338 | | cmLinkItem const& rootItem, |
339 | | Connections& extendedCons) |
340 | 0 | { |
341 | 0 | std::set<cmLinkItem> visitedItems = { rootItem }; |
342 | 0 | this->FindAllConnections(connectionMap, rootItem, extendedCons, |
343 | 0 | visitedItems); |
344 | 0 | } |
345 | | |
346 | | template <typename DirFunc> |
347 | | void cmGraphVizWriter::WritePerTargetConnections( |
348 | | ConnectionsMap const& connections, std::string const& fileNameSuffix) |
349 | 0 | { |
350 | | // the per target connections must be extended by indirect dependencies |
351 | 0 | ConnectionsMap extendedConnections; |
352 | 0 | for (auto const& conPerTarget : connections) { |
353 | 0 | cmLinkItem const& rootItem = conPerTarget.first; |
354 | 0 | Connections& extendedCons = extendedConnections[conPerTarget.first]; |
355 | 0 | this->FindAllConnections(connections, rootItem, extendedCons); |
356 | 0 | } |
357 | |
|
358 | 0 | for (auto const& conPerTarget : extendedConnections) { |
359 | 0 | cmLinkItem const& rootItem = conPerTarget.first; |
360 | | |
361 | | // some of the nodes are excluded completely and are not written |
362 | 0 | if (this->ItemExcluded(rootItem)) { |
363 | 0 | continue; |
364 | 0 | } |
365 | | |
366 | 0 | Connections const& cons = conPerTarget.second; |
367 | |
|
368 | 0 | std::unique_ptr<cmGeneratedFileStream> fileStream = |
369 | 0 | this->CreateTargetFile(rootItem, fileNameSuffix); |
370 | | |
371 | | // avoid write same node multiple times |
372 | 0 | std::unordered_set<std::string> writtenNodes = { rootItem.AsStr() }; |
373 | 0 | for (Connection const& con : cons) { |
374 | 0 | cmLinkItem const& src = DirFunc::src(con); |
375 | 0 | cmLinkItem const& dst = DirFunc::dst(con); |
376 | 0 | if (writtenNodes.emplace(con.dst.AsStr()).second) { |
377 | 0 | this->WriteNode(*fileStream, con.dst); |
378 | 0 | } |
379 | 0 | this->WriteConnection(*fileStream, src, dst, con.scopeType); |
380 | 0 | } |
381 | |
|
382 | 0 | this->WriteFooter(*fileStream); |
383 | 0 | } |
384 | 0 | } Unexecuted instantiation: cmGraphVizWriter.cxx:void cmGraphVizWriter::WritePerTargetConnections<(anonymous namespace)::DependeesDir>(std::__1::map<cmLinkItem, std::__1::vector<cmGraphVizWriter::Connection, std::__1::allocator<cmGraphVizWriter::Connection> >, std::__1::less<cmLinkItem>, std::__1::allocator<std::__1::pair<cmLinkItem const, std::__1::vector<cmGraphVizWriter::Connection, std::__1::allocator<cmGraphVizWriter::Connection> > > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: cmGraphVizWriter.cxx:void cmGraphVizWriter::WritePerTargetConnections<(anonymous namespace)::DependersDir>(std::__1::map<cmLinkItem, std::__1::vector<cmGraphVizWriter::Connection, std::__1::allocator<cmGraphVizWriter::Connection> >, std::__1::less<cmLinkItem>, std::__1::allocator<std::__1::pair<cmLinkItem const, std::__1::vector<cmGraphVizWriter::Connection, std::__1::allocator<cmGraphVizWriter::Connection> > > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) |
385 | | |
386 | | void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& fs, |
387 | | std::string const& name) |
388 | 0 | { |
389 | 0 | auto const escapedGraphName = EscapeForDotFile(name); |
390 | 0 | fs << "digraph \"" << escapedGraphName << "\" {\n" |
391 | 0 | << this->GraphHeader << '\n'; |
392 | 0 | } |
393 | | |
394 | | void cmGraphVizWriter::WriteFooter(cmGeneratedFileStream& fs) |
395 | 0 | { |
396 | 0 | fs << "}\n"; |
397 | 0 | } |
398 | | |
399 | | void cmGraphVizWriter::WriteLegend(cmGeneratedFileStream& fs) |
400 | 0 | { |
401 | | // Note that the subgraph name must start with "cluster", as done here, to |
402 | | // make Graphviz layout engines do the right thing and keep the nodes |
403 | | // together. |
404 | | /* clang-format off */ |
405 | 0 | fs << "subgraph clusterLegend {\n" |
406 | 0 | " label = \"Legend\";\n" |
407 | | // Set the color of the box surrounding the legend. |
408 | 0 | " color = black;\n" |
409 | | // We use invisible edges just to enforce the layout. |
410 | 0 | " edge [ style = invis ];\n" |
411 | | // Nodes. |
412 | 0 | " legendNode0 [ label = \"Executable\", shape = " |
413 | 0 | << GRAPHVIZ_NODE_SHAPE_EXECUTABLE << " ];\n" |
414 | 0 | " legendNode1 [ label = \"Static Library\", shape = " |
415 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC << " ];\n" |
416 | 0 | " legendNode2 [ label = \"Shared Library\", shape = " |
417 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED << " ];\n" |
418 | 0 | " legendNode3 [ label = \"Module Library\", shape = " |
419 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE << " ];\n" |
420 | 0 | " legendNode4 [ label = \"Interface Library\", shape = " |
421 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE << " ];\n" |
422 | 0 | " legendNode5 [ label = \"Object Library\", shape = " |
423 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT << " ];\n" |
424 | 0 | " legendNode6 [ label = \"Unknown Library\", shape = " |
425 | 0 | << GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN << " ];\n" |
426 | 0 | " legendNode7 [ label = \"Custom Target\", shape = " |
427 | 0 | << GRAPHVIZ_NODE_SHAPE_UTILITY << " ];\n" |
428 | | // Edges. |
429 | | // Some of those are dummy (invisible) edges to enforce a layout. |
430 | 0 | " legendNode0 -> legendNode1 [ style = " |
431 | 0 | << GRAPHVIZ_EDGE_STYLE_PUBLIC << " ];\n" |
432 | 0 | " legendNode0 -> legendNode2 [ style = " |
433 | 0 | << GRAPHVIZ_EDGE_STYLE_PUBLIC << " ];\n" |
434 | 0 | " legendNode0 -> legendNode3;\n" |
435 | 0 | " legendNode1 -> legendNode4 [ label = \"Interface\", style = " |
436 | 0 | << GRAPHVIZ_EDGE_STYLE_INTERFACE << " ];\n" |
437 | 0 | " legendNode2 -> legendNode5 [ label = \"Private\", style = " |
438 | 0 | << GRAPHVIZ_EDGE_STYLE_PRIVATE << " ];\n" |
439 | 0 | " legendNode3 -> legendNode6 [ style = " |
440 | 0 | << GRAPHVIZ_EDGE_STYLE_PUBLIC << " ];\n" |
441 | 0 | " legendNode0 -> legendNode7;\n" |
442 | 0 | "}\n"; |
443 | | /* clang-format off */ |
444 | 0 | } |
445 | | |
446 | | void cmGraphVizWriter::WriteNode(cmGeneratedFileStream& fs, |
447 | | cmLinkItem const& item) |
448 | 0 | { |
449 | 0 | auto const& itemName = item.AsStr(); |
450 | 0 | auto const& nodeName = this->NodeNames[itemName]; |
451 | |
|
452 | 0 | auto const itemNameWithAliases = this->ItemNameWithAliases(itemName); |
453 | 0 | auto const escapedLabel = EscapeForDotFile(itemNameWithAliases); |
454 | |
|
455 | 0 | fs << " \"" << nodeName << "\" [ label = \"" << escapedLabel |
456 | 0 | << "\", shape = " << getShapeForTarget(item) << " ];\n"; |
457 | 0 | } |
458 | | |
459 | | void cmGraphVizWriter::WriteConnection(cmGeneratedFileStream& fs, |
460 | | cmLinkItem const& depender, |
461 | | cmLinkItem const& dependee, |
462 | | std::string const& edgeStyle) |
463 | 0 | { |
464 | 0 | auto const& dependerName = depender.AsStr(); |
465 | 0 | auto const& dependeeName = dependee.AsStr(); |
466 | |
|
467 | 0 | fs << " \"" << this->NodeNames[dependerName] << "\" -> \"" |
468 | 0 | << this->NodeNames[dependeeName] << "\" " |
469 | 0 | << edgeStyle |
470 | 0 | << " // " << dependerName << " -> " << dependeeName << '\n'; |
471 | 0 | } |
472 | | |
473 | | bool cmGraphVizWriter::ItemExcluded(cmLinkItem const& item) |
474 | 0 | { |
475 | 0 | auto const itemName = item.AsStr(); |
476 | |
|
477 | 0 | if (this->ItemNameFilteredOut(itemName)) { |
478 | 0 | return true; |
479 | 0 | } |
480 | | |
481 | 0 | if (!item.Target) { |
482 | 0 | return !this->GenerateForExternals; |
483 | 0 | } |
484 | | |
485 | 0 | if (item.Target->GetType() == cmStateEnums::UTILITY) { |
486 | 0 | if (cmHasLiteralPrefix(itemName, "Nightly") || |
487 | 0 | cmHasLiteralPrefix(itemName, "Continuous") || |
488 | 0 | cmHasLiteralPrefix(itemName, "Experimental")) { |
489 | 0 | return true; |
490 | 0 | } |
491 | 0 | } |
492 | | |
493 | 0 | if (item.Target->IsImported() && !this->GenerateForExternals) { |
494 | 0 | return true; |
495 | 0 | } |
496 | | |
497 | 0 | return !this->TargetTypeEnabled(item.Target->GetType()); |
498 | 0 | } |
499 | | |
500 | | bool cmGraphVizWriter::ItemNameFilteredOut(std::string const& itemName) |
501 | 0 | { |
502 | 0 | if (itemName == ">") { |
503 | | // FIXME: why do we even receive such a target here? |
504 | 0 | return true; |
505 | 0 | } |
506 | | |
507 | 0 | if (cmGlobalGenerator::IsReservedTarget(itemName)) { |
508 | 0 | return true; |
509 | 0 | } |
510 | | |
511 | 0 | for (cmsys::RegularExpression& regEx : this->TargetsToIgnoreRegex) { |
512 | 0 | if (regEx.is_valid()) { |
513 | 0 | if (regEx.find(itemName)) { |
514 | 0 | return true; |
515 | 0 | } |
516 | 0 | } |
517 | 0 | } |
518 | | |
519 | 0 | return false; |
520 | 0 | } |
521 | | |
522 | | bool cmGraphVizWriter::TargetTypeEnabled( |
523 | | cmStateEnums::TargetType targetType) const |
524 | 0 | { |
525 | 0 | switch (targetType) { |
526 | 0 | case cmStateEnums::EXECUTABLE: |
527 | 0 | return this->GenerateForExecutables; |
528 | 0 | case cmStateEnums::STATIC_LIBRARY: |
529 | 0 | return this->GenerateForStaticLibs; |
530 | 0 | case cmStateEnums::SHARED_LIBRARY: |
531 | 0 | return this->GenerateForSharedLibs; |
532 | 0 | case cmStateEnums::MODULE_LIBRARY: |
533 | 0 | return this->GenerateForModuleLibs; |
534 | 0 | case cmStateEnums::INTERFACE_LIBRARY: |
535 | 0 | return this->GenerateForInterfaceLibs; |
536 | 0 | case cmStateEnums::OBJECT_LIBRARY: |
537 | 0 | return this->GenerateForObjectLibs; |
538 | 0 | case cmStateEnums::UNKNOWN_LIBRARY: |
539 | 0 | return this->GenerateForUnknownLibs; |
540 | 0 | case cmStateEnums::UTILITY: |
541 | 0 | return this->GenerateForCustomTargets; |
542 | 0 | case cmStateEnums::GLOBAL_TARGET: |
543 | | // Built-in targets like edit_cache, etc. |
544 | | // We don't need/want those in the dot file. |
545 | 0 | return false; |
546 | 0 | default: |
547 | 0 | break; |
548 | 0 | } |
549 | 0 | return false; |
550 | 0 | } |
551 | | |
552 | | std::string cmGraphVizWriter::ItemNameWithAliases( |
553 | | std::string const& itemName) const |
554 | 0 | { |
555 | 0 | std::vector<std::string> items; |
556 | 0 | for (auto const& lg : this->GlobalGenerator->GetLocalGenerators()) { |
557 | 0 | for (auto const& aliasTargets : lg->GetMakefile()->GetAliasTargets()) { |
558 | 0 | if (aliasTargets.second == itemName) { |
559 | 0 | items.push_back(aliasTargets.first); |
560 | 0 | } |
561 | 0 | } |
562 | 0 | } |
563 | |
|
564 | 0 | std::sort(items.begin(), items.end()); |
565 | 0 | items.erase(std::unique(items.begin(), items.end()), items.end()); |
566 | |
|
567 | 0 | auto nameWithAliases = itemName; |
568 | 0 | for(auto const& item : items) { |
569 | 0 | nameWithAliases += "\\n(" + item + ")"; |
570 | 0 | } |
571 | |
|
572 | 0 | return nameWithAliases; |
573 | 0 | } |
574 | | |
575 | | std::string cmGraphVizWriter::GetEdgeStyle(DependencyType dt) |
576 | 0 | { |
577 | 0 | std::string style; |
578 | 0 | switch (dt) { |
579 | 0 | case DependencyType::LinkPrivate: |
580 | 0 | style = "[ style = " + std::string(GRAPHVIZ_EDGE_STYLE_PRIVATE) + " ]"; |
581 | 0 | break; |
582 | 0 | case DependencyType::LinkInterface: |
583 | 0 | style = "[ style = " + std::string(GRAPHVIZ_EDGE_STYLE_INTERFACE) + " ]"; |
584 | 0 | break; |
585 | 0 | default: |
586 | 0 | break; |
587 | 0 | } |
588 | 0 | return style; |
589 | 0 | } |
590 | | |
591 | | std::string cmGraphVizWriter::EscapeForDotFile(std::string const& str) |
592 | 0 | { |
593 | 0 | return cmSystemTools::EscapeChars(str.data(), "\""); |
594 | 0 | } |
595 | | |
596 | | std::string cmGraphVizWriter::PathSafeString(std::string const& str) |
597 | 0 | { |
598 | 0 | std::string pathSafeStr; |
599 | | |
600 | | // We'll only keep alphanumerical characters, plus the following ones that |
601 | | // are common, and safe on all platforms: |
602 | 0 | auto const extra_chars = std::set<char>{ '.', '-', '_' }; |
603 | |
|
604 | 0 | for (char c : str) { |
605 | 0 | if (cmsysString_isalnum(c) || extra_chars.find(c) != extra_chars.cend()) { |
606 | 0 | pathSafeStr += c; |
607 | 0 | } |
608 | 0 | } |
609 | |
|
610 | 0 | return pathSafeStr; |
611 | 0 | } |