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