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