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