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