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