xref: /llvm-project/llvm/lib/Support/GraphWriter.cpp (revision 75e164f61d391979b4829bf2746a5d74b94e95f2)
1 //===- GraphWriter.cpp - Implements GraphWriter support routines ----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements misc. GraphWriter support routines.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/Support/GraphWriter.h"
14 
15 #include "DebugOptions.h"
16 
17 #include "llvm/ADT/SmallString.h"
18 #include "llvm/ADT/SmallVector.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/Config/config.h"
21 #include "llvm/Support/Compiler.h"
22 #include "llvm/Support/ErrorHandling.h"
23 #include "llvm/Support/ErrorOr.h"
24 #include "llvm/Support/FileSystem.h"
25 #include "llvm/Support/Path.h"
26 #include "llvm/Support/Program.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include <string>
29 #include <system_error>
30 #include <vector>
31 
32 using namespace llvm;
33 
34 #ifdef __APPLE__
35 namespace {
36 struct CreateViewBackground {
37   static void *call() {
38     return new cl::opt<bool>("view-background", cl::Hidden,
39                              cl::desc("Execute graph viewer in the background. "
40                                       "Creates tmp file litter."));
41   }
42 };
43 } // namespace
44 static ManagedStatic<cl::opt<bool>, CreateViewBackground> ViewBackground;
45 void llvm::initGraphWriterOptions() { *ViewBackground; }
46 #else
47 void llvm::initGraphWriterOptions() {}
48 #endif
49 
50 std::string llvm::DOT::EscapeString(const std::string &Label) {
51   std::string Str(Label);
52   for (unsigned i = 0; i != Str.length(); ++i)
53   switch (Str[i]) {
54     case '\n':
55       Str.insert(Str.begin()+i, '\\');  // Escape character...
56       ++i;
57       Str[i] = 'n';
58       break;
59     case '\t':
60       Str.insert(Str.begin()+i, ' ');  // Convert to two spaces
61       ++i;
62       Str[i] = ' ';
63       break;
64     case '\\':
65       if (i+1 != Str.length())
66         switch (Str[i+1]) {
67           case 'l': continue; // don't disturb \l
68           case '|': case '{': case '}':
69             Str.erase(Str.begin()+i); continue;
70           default: break;
71         }
72       LLVM_FALLTHROUGH;
73     case '{': case '}':
74     case '<': case '>':
75     case '|': case '"':
76       Str.insert(Str.begin()+i, '\\');  // Escape character...
77       ++i;  // don't infinite loop
78       break;
79   }
80   return Str;
81 }
82 
83 /// Get a color string for this node number. Simply round-robin selects
84 /// from a reasonable number of colors.
85 StringRef llvm::DOT::getColorString(unsigned ColorNumber) {
86   static const int NumColors = 20;
87   static const char* Colors[NumColors] = {
88     "aaaaaa", "aa0000", "00aa00", "aa5500", "0055ff", "aa00aa", "00aaaa",
89     "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", "55ffff",
90     "ffaaaa", "aaffaa", "ffffaa", "aaaaff", "ffaaff", "aaffff"};
91   return Colors[ColorNumber % NumColors];
92 }
93 
94 static std::string replaceIllegalFilenameChars(std::string Filename,
95                                                const char ReplacementChar) {
96   std::string IllegalChars =
97       is_style_windows(sys::path::Style::native) ? "\\/:?\"<>|" : "/";
98 
99   for (char IllegalChar : IllegalChars) {
100     std::replace(Filename.begin(), Filename.end(), IllegalChar,
101                  ReplacementChar);
102   }
103 
104   return Filename;
105 }
106 
107 std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
108   FD = -1;
109   SmallString<128> Filename;
110 
111   // Windows can't always handle long paths, so limit the length of the name.
112   std::string N = Name.str();
113   N = N.substr(0, std::min<std::size_t>(N.size(), 140));
114 
115   // Replace illegal characters in graph Filename with '_' if needed
116   std::string CleansedName = replaceIllegalFilenameChars(N, '_');
117 
118   std::error_code EC =
119       sys::fs::createTemporaryFile(CleansedName, "dot", FD, Filename);
120   if (EC) {
121     errs() << "Error: " << EC.message() << "\n";
122     return "";
123   }
124 
125   errs() << "Writing '" << Filename << "'... ";
126   return std::string(Filename.str());
127 }
128 
129 // Execute the graph viewer. Return true if there were errors.
130 static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
131                             StringRef Filename, bool wait,
132                             std::string &ErrMsg) {
133   if (wait) {
134     if (sys::ExecuteAndWait(ExecPath, args, None, {}, 0, 0, &ErrMsg)) {
135       errs() << "Error: " << ErrMsg << "\n";
136       return true;
137     }
138     sys::fs::remove(Filename);
139     errs() << " done. \n";
140   } else {
141     sys::ExecuteNoWait(ExecPath, args, None, {}, 0, &ErrMsg);
142     errs() << "Remember to erase graph file: " << Filename << "\n";
143   }
144   return false;
145 }
146 
147 namespace {
148 
149 struct GraphSession {
150   std::string LogBuffer;
151 
152   bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
153     raw_string_ostream Log(LogBuffer);
154     SmallVector<StringRef, 8> parts;
155     Names.split(parts, '|');
156     for (auto Name : parts) {
157       if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
158         ProgramPath = *P;
159         return true;
160       }
161       Log << "  Tried '" << Name << "'\n";
162     }
163     return false;
164   }
165 };
166 
167 } // end anonymous namespace
168 
169 static const char *getProgramName(GraphProgram::Name program) {
170   switch (program) {
171   case GraphProgram::DOT:
172     return "dot";
173   case GraphProgram::FDP:
174     return "fdp";
175   case GraphProgram::NEATO:
176     return "neato";
177   case GraphProgram::TWOPI:
178     return "twopi";
179   case GraphProgram::CIRCO:
180     return "circo";
181   }
182   llvm_unreachable("bad kind");
183 }
184 
185 bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
186                         GraphProgram::Name program) {
187   std::string Filename = std::string(FilenameRef);
188   std::string ErrMsg;
189   std::string ViewerPath;
190   GraphSession S;
191 
192 #ifdef __APPLE__
193   wait &= !*ViewBackground;
194   if (S.TryFindProgram("open", ViewerPath)) {
195     std::vector<StringRef> args;
196     args.push_back(ViewerPath);
197     if (wait)
198       args.push_back("-W");
199     args.push_back(Filename);
200     errs() << "Trying 'open' program... ";
201     if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
202       return false;
203   }
204 #endif
205   if (S.TryFindProgram("xdg-open", ViewerPath)) {
206     std::vector<StringRef> args;
207     args.push_back(ViewerPath);
208     args.push_back(Filename);
209     errs() << "Trying 'xdg-open' program... ";
210     if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
211       return false;
212   }
213 
214   // Graphviz
215   if (S.TryFindProgram("Graphviz", ViewerPath)) {
216     std::vector<StringRef> args;
217     args.push_back(ViewerPath);
218     args.push_back(Filename);
219 
220     errs() << "Running 'Graphviz' program... ";
221     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
222   }
223 
224   // xdot
225   if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
226     std::vector<StringRef> args;
227     args.push_back(ViewerPath);
228     args.push_back(Filename);
229 
230     args.push_back("-f");
231     args.push_back(getProgramName(program));
232 
233     errs() << "Running 'xdot.py' program... ";
234     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
235   }
236 
237   enum ViewerKind {
238     VK_None,
239     VK_OSXOpen,
240     VK_XDGOpen,
241     VK_Ghostview,
242     VK_CmdStart
243   };
244   ViewerKind Viewer = VK_None;
245 #ifdef __APPLE__
246   if (!Viewer && S.TryFindProgram("open", ViewerPath))
247     Viewer = VK_OSXOpen;
248 #endif
249   if (!Viewer && S.TryFindProgram("gv", ViewerPath))
250     Viewer = VK_Ghostview;
251   if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
252     Viewer = VK_XDGOpen;
253 #ifdef _WIN32
254   if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
255     Viewer = VK_CmdStart;
256   }
257 #endif
258 
259   // PostScript or PDF graph generator + PostScript/PDF viewer
260   std::string GeneratorPath;
261   if (Viewer &&
262       (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
263        S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
264     std::string OutputFilename =
265         Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
266 
267     std::vector<StringRef> args;
268     args.push_back(GeneratorPath);
269     if (Viewer == VK_CmdStart)
270       args.push_back("-Tpdf");
271     else
272       args.push_back("-Tps");
273     args.push_back("-Nfontname=Courier");
274     args.push_back("-Gsize=7.5,10");
275     args.push_back(Filename);
276     args.push_back("-o");
277     args.push_back(OutputFilename);
278 
279     errs() << "Running '" << GeneratorPath << "' program... ";
280 
281     if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
282       return true;
283 
284     // The lifetime of StartArg must include the call of ExecGraphViewer
285     // because the args are passed as vector of char*.
286     std::string StartArg;
287 
288     args.clear();
289     args.push_back(ViewerPath);
290     switch (Viewer) {
291     case VK_OSXOpen:
292       args.push_back("-W");
293       args.push_back(OutputFilename);
294       break;
295     case VK_XDGOpen:
296       wait = false;
297       args.push_back(OutputFilename);
298       break;
299     case VK_Ghostview:
300       args.push_back("--spartan");
301       args.push_back(OutputFilename);
302       break;
303     case VK_CmdStart:
304       args.push_back("/S");
305       args.push_back("/C");
306       StartArg =
307           (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
308       args.push_back(StartArg);
309       break;
310     case VK_None:
311       llvm_unreachable("Invalid viewer");
312     }
313 
314     ErrMsg.clear();
315     return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
316   }
317 
318   // dotty
319   if (S.TryFindProgram("dotty", ViewerPath)) {
320     std::vector<StringRef> args;
321     args.push_back(ViewerPath);
322     args.push_back(Filename);
323 
324 // Dotty spawns another app and doesn't wait until it returns
325 #ifdef _WIN32
326     wait = false;
327 #endif
328     errs() << "Running 'dotty' program... ";
329     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
330   }
331 
332   errs() << "Error: Couldn't find a usable graph viewer program:\n";
333   errs() << S.LogBuffer << "\n";
334   return true;
335 }
336