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 /// \brief 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<const char *> &args, 95 StringRef Filename, bool wait, 96 std::string &ErrMsg) { 97 assert(args.back() == nullptr); 98 if (wait) { 99 if (sys::ExecuteAndWait(ExecPath, args.data(), nullptr, {}, 0, 0, 100 &ErrMsg)) { 101 errs() << "Error: " << ErrMsg << "\n"; 102 return true; 103 } 104 sys::fs::remove(Filename); 105 errs() << " done. \n"; 106 } else { 107 sys::ExecuteNoWait(ExecPath, args.data(), nullptr, {}, 0, &ErrMsg); 108 errs() << "Remember to erase graph file: " << Filename << "\n"; 109 } 110 return false; 111 } 112 113 namespace { 114 115 struct GraphSession { 116 std::string LogBuffer; 117 118 bool TryFindProgram(StringRef Names, std::string &ProgramPath) { 119 raw_string_ostream Log(LogBuffer); 120 SmallVector<StringRef, 8> parts; 121 Names.split(parts, '|'); 122 for (auto Name : parts) { 123 if (ErrorOr<std::string> P = sys::findProgramByName(Name)) { 124 ProgramPath = *P; 125 return true; 126 } 127 Log << " Tried '" << Name << "'\n"; 128 } 129 return false; 130 } 131 }; 132 133 } // end anonymous namespace 134 135 static const char *getProgramName(GraphProgram::Name program) { 136 switch (program) { 137 case GraphProgram::DOT: 138 return "dot"; 139 case GraphProgram::FDP: 140 return "fdp"; 141 case GraphProgram::NEATO: 142 return "neato"; 143 case GraphProgram::TWOPI: 144 return "twopi"; 145 case GraphProgram::CIRCO: 146 return "circo"; 147 } 148 llvm_unreachable("bad kind"); 149 } 150 151 bool llvm::DisplayGraph(StringRef FilenameRef, bool wait, 152 GraphProgram::Name program) { 153 std::string Filename = FilenameRef; 154 std::string ErrMsg; 155 std::string ViewerPath; 156 GraphSession S; 157 158 #ifdef __APPLE__ 159 wait &= !ViewBackground; 160 if (S.TryFindProgram("open", ViewerPath)) { 161 std::vector<const char *> args; 162 args.push_back(ViewerPath.c_str()); 163 if (wait) 164 args.push_back("-W"); 165 args.push_back(Filename.c_str()); 166 args.push_back(nullptr); 167 errs() << "Trying 'open' program... "; 168 if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg)) 169 return false; 170 } 171 #endif 172 if (S.TryFindProgram("xdg-open", ViewerPath)) { 173 std::vector<const char *> args; 174 args.push_back(ViewerPath.c_str()); 175 args.push_back(Filename.c_str()); 176 args.push_back(nullptr); 177 errs() << "Trying 'xdg-open' program... "; 178 if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg)) 179 return false; 180 } 181 182 // Graphviz 183 if (S.TryFindProgram("Graphviz", ViewerPath)) { 184 std::vector<const char *> args; 185 args.push_back(ViewerPath.c_str()); 186 args.push_back(Filename.c_str()); 187 args.push_back(nullptr); 188 189 errs() << "Running 'Graphviz' program... "; 190 return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg); 191 } 192 193 // xdot 194 if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) { 195 std::vector<const char *> args; 196 args.push_back(ViewerPath.c_str()); 197 args.push_back(Filename.c_str()); 198 199 args.push_back("-f"); 200 args.push_back(getProgramName(program)); 201 202 args.push_back(nullptr); 203 204 errs() << "Running 'xdot.py' program... "; 205 return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg); 206 } 207 208 enum ViewerKind { 209 VK_None, 210 VK_OSXOpen, 211 VK_XDGOpen, 212 VK_Ghostview, 213 VK_CmdStart 214 }; 215 ViewerKind Viewer = VK_None; 216 #ifdef __APPLE__ 217 if (!Viewer && S.TryFindProgram("open", ViewerPath)) 218 Viewer = VK_OSXOpen; 219 #endif 220 if (!Viewer && S.TryFindProgram("gv", ViewerPath)) 221 Viewer = VK_Ghostview; 222 if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath)) 223 Viewer = VK_XDGOpen; 224 #ifdef _WIN32 225 if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) { 226 Viewer = VK_CmdStart; 227 } 228 #endif 229 230 // PostScript or PDF graph generator + PostScript/PDF viewer 231 std::string GeneratorPath; 232 if (Viewer && 233 (S.TryFindProgram(getProgramName(program), GeneratorPath) || 234 S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) { 235 std::string OutputFilename = 236 Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps"); 237 238 std::vector<const char *> args; 239 args.push_back(GeneratorPath.c_str()); 240 if (Viewer == VK_CmdStart) 241 args.push_back("-Tpdf"); 242 else 243 args.push_back("-Tps"); 244 args.push_back("-Nfontname=Courier"); 245 args.push_back("-Gsize=7.5,10"); 246 args.push_back(Filename.c_str()); 247 args.push_back("-o"); 248 args.push_back(OutputFilename.c_str()); 249 args.push_back(nullptr); 250 251 errs() << "Running '" << GeneratorPath << "' program... "; 252 253 if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg)) 254 return true; 255 256 // The lifetime of StartArg must include the call of ExecGraphViewer 257 // because the args are passed as vector of char*. 258 std::string StartArg; 259 260 args.clear(); 261 args.push_back(ViewerPath.c_str()); 262 switch (Viewer) { 263 case VK_OSXOpen: 264 args.push_back("-W"); 265 args.push_back(OutputFilename.c_str()); 266 break; 267 case VK_XDGOpen: 268 wait = false; 269 args.push_back(OutputFilename.c_str()); 270 break; 271 case VK_Ghostview: 272 args.push_back("--spartan"); 273 args.push_back(OutputFilename.c_str()); 274 break; 275 case VK_CmdStart: 276 args.push_back("/S"); 277 args.push_back("/C"); 278 StartArg = 279 (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str(); 280 args.push_back(StartArg.c_str()); 281 break; 282 case VK_None: 283 llvm_unreachable("Invalid viewer"); 284 } 285 args.push_back(nullptr); 286 287 ErrMsg.clear(); 288 return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg); 289 } 290 291 // dotty 292 if (S.TryFindProgram("dotty", ViewerPath)) { 293 std::vector<const char *> args; 294 args.push_back(ViewerPath.c_str()); 295 args.push_back(Filename.c_str()); 296 args.push_back(nullptr); 297 298 // Dotty spawns another app and doesn't wait until it returns 299 #ifdef _WIN32 300 wait = false; 301 #endif 302 errs() << "Running 'dotty' program... "; 303 return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg); 304 } 305 306 errs() << "Error: Couldn't find a usable graph viewer program:\n"; 307 errs() << S.LogBuffer << "\n"; 308 return true; 309 } 310