109467b48Spatrick //===- GraphWriter.cpp - Implements GraphWriter support routines ----------===//
209467b48Spatrick //
309467b48Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
409467b48Spatrick // See https://llvm.org/LICENSE.txt for license information.
509467b48Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
609467b48Spatrick //
709467b48Spatrick //===----------------------------------------------------------------------===//
809467b48Spatrick //
909467b48Spatrick // This file implements misc. GraphWriter support routines.
1009467b48Spatrick //
1109467b48Spatrick //===----------------------------------------------------------------------===//
1209467b48Spatrick
1309467b48Spatrick #include "llvm/Support/GraphWriter.h"
1473471bf0Spatrick
1573471bf0Spatrick #include "DebugOptions.h"
1673471bf0Spatrick
1709467b48Spatrick #include "llvm/ADT/SmallString.h"
1809467b48Spatrick #include "llvm/ADT/SmallVector.h"
1909467b48Spatrick #include "llvm/ADT/StringRef.h"
2009467b48Spatrick #include "llvm/Config/config.h"
2109467b48Spatrick #include "llvm/Support/Compiler.h"
2209467b48Spatrick #include "llvm/Support/ErrorHandling.h"
2309467b48Spatrick #include "llvm/Support/ErrorOr.h"
2409467b48Spatrick #include "llvm/Support/FileSystem.h"
25*d415bd75Srobert #include "llvm/Support/Path.h"
2609467b48Spatrick #include "llvm/Support/Program.h"
2709467b48Spatrick #include "llvm/Support/raw_ostream.h"
28*d415bd75Srobert
29*d415bd75Srobert #ifdef __APPLE__
30*d415bd75Srobert #include "llvm/Support/CommandLine.h"
31*d415bd75Srobert #endif
32*d415bd75Srobert
3309467b48Spatrick #include <string>
34*d415bd75Srobert #include <system_error>
3509467b48Spatrick #include <vector>
3609467b48Spatrick
3709467b48Spatrick using namespace llvm;
3809467b48Spatrick
3973471bf0Spatrick #ifdef __APPLE__
4073471bf0Spatrick namespace {
4173471bf0Spatrick struct CreateViewBackground {
call__anon715334420111::CreateViewBackground4273471bf0Spatrick static void *call() {
4373471bf0Spatrick return new cl::opt<bool>("view-background", cl::Hidden,
4473471bf0Spatrick cl::desc("Execute graph viewer in the background. "
4573471bf0Spatrick "Creates tmp file litter."));
4673471bf0Spatrick }
4773471bf0Spatrick };
4873471bf0Spatrick } // namespace
4973471bf0Spatrick static ManagedStatic<cl::opt<bool>, CreateViewBackground> ViewBackground;
initGraphWriterOptions()5073471bf0Spatrick void llvm::initGraphWriterOptions() { *ViewBackground; }
5173471bf0Spatrick #else
initGraphWriterOptions()5273471bf0Spatrick void llvm::initGraphWriterOptions() {}
5373471bf0Spatrick #endif
5409467b48Spatrick
EscapeString(const std::string & Label)5509467b48Spatrick std::string llvm::DOT::EscapeString(const std::string &Label) {
5609467b48Spatrick std::string Str(Label);
5709467b48Spatrick for (unsigned i = 0; i != Str.length(); ++i)
5809467b48Spatrick switch (Str[i]) {
5909467b48Spatrick case '\n':
6009467b48Spatrick Str.insert(Str.begin()+i, '\\'); // Escape character...
6109467b48Spatrick ++i;
6209467b48Spatrick Str[i] = 'n';
6309467b48Spatrick break;
6409467b48Spatrick case '\t':
6509467b48Spatrick Str.insert(Str.begin()+i, ' '); // Convert to two spaces
6609467b48Spatrick ++i;
6709467b48Spatrick Str[i] = ' ';
6809467b48Spatrick break;
6909467b48Spatrick case '\\':
7009467b48Spatrick if (i+1 != Str.length())
7109467b48Spatrick switch (Str[i+1]) {
7209467b48Spatrick case 'l': continue; // don't disturb \l
7309467b48Spatrick case '|': case '{': case '}':
7409467b48Spatrick Str.erase(Str.begin()+i); continue;
7509467b48Spatrick default: break;
7609467b48Spatrick }
77*d415bd75Srobert [[fallthrough]];
7809467b48Spatrick case '{': case '}':
7909467b48Spatrick case '<': case '>':
8009467b48Spatrick case '|': case '"':
8109467b48Spatrick Str.insert(Str.begin()+i, '\\'); // Escape character...
8209467b48Spatrick ++i; // don't infinite loop
8309467b48Spatrick break;
8409467b48Spatrick }
8509467b48Spatrick return Str;
8609467b48Spatrick }
8709467b48Spatrick
8809467b48Spatrick /// Get a color string for this node number. Simply round-robin selects
8909467b48Spatrick /// from a reasonable number of colors.
getColorString(unsigned ColorNumber)9009467b48Spatrick StringRef llvm::DOT::getColorString(unsigned ColorNumber) {
9109467b48Spatrick static const int NumColors = 20;
9209467b48Spatrick static const char* Colors[NumColors] = {
9309467b48Spatrick "aaaaaa", "aa0000", "00aa00", "aa5500", "0055ff", "aa00aa", "00aaaa",
9409467b48Spatrick "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", "55ffff",
9509467b48Spatrick "ffaaaa", "aaffaa", "ffffaa", "aaaaff", "ffaaff", "aaffff"};
9609467b48Spatrick return Colors[ColorNumber % NumColors];
9709467b48Spatrick }
9809467b48Spatrick
replaceIllegalFilenameChars(std::string Filename,const char ReplacementChar)99097a140dSpatrick static std::string replaceIllegalFilenameChars(std::string Filename,
100097a140dSpatrick const char ReplacementChar) {
101*d415bd75Srobert std::string IllegalChars =
102*d415bd75Srobert is_style_windows(sys::path::Style::native) ? "\\/:?\"<>|" : "/";
103097a140dSpatrick
104097a140dSpatrick for (char IllegalChar : IllegalChars) {
105097a140dSpatrick std::replace(Filename.begin(), Filename.end(), IllegalChar,
106097a140dSpatrick ReplacementChar);
107097a140dSpatrick }
108097a140dSpatrick
109097a140dSpatrick return Filename;
110097a140dSpatrick }
111097a140dSpatrick
createGraphFilename(const Twine & Name,int & FD)11209467b48Spatrick std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
11309467b48Spatrick FD = -1;
11409467b48Spatrick SmallString<128> Filename;
115097a140dSpatrick
116097a140dSpatrick // Windows can't always handle long paths, so limit the length of the name.
117097a140dSpatrick std::string N = Name.str();
118097a140dSpatrick N = N.substr(0, std::min<std::size_t>(N.size(), 140));
119097a140dSpatrick
120097a140dSpatrick // Replace illegal characters in graph Filename with '_' if needed
121097a140dSpatrick std::string CleansedName = replaceIllegalFilenameChars(N, '_');
122097a140dSpatrick
123097a140dSpatrick std::error_code EC =
124097a140dSpatrick sys::fs::createTemporaryFile(CleansedName, "dot", FD, Filename);
12509467b48Spatrick if (EC) {
12609467b48Spatrick errs() << "Error: " << EC.message() << "\n";
12709467b48Spatrick return "";
12809467b48Spatrick }
12909467b48Spatrick
13009467b48Spatrick errs() << "Writing '" << Filename << "'... ";
131097a140dSpatrick return std::string(Filename.str());
13209467b48Spatrick }
13309467b48Spatrick
13409467b48Spatrick // Execute the graph viewer. Return true if there were errors.
ExecGraphViewer(StringRef ExecPath,std::vector<StringRef> & args,StringRef Filename,bool wait,std::string & ErrMsg)13509467b48Spatrick static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
13609467b48Spatrick StringRef Filename, bool wait,
13709467b48Spatrick std::string &ErrMsg) {
13809467b48Spatrick if (wait) {
139*d415bd75Srobert if (sys::ExecuteAndWait(ExecPath, args, std::nullopt, {}, 0, 0, &ErrMsg)) {
14009467b48Spatrick errs() << "Error: " << ErrMsg << "\n";
14109467b48Spatrick return true;
14209467b48Spatrick }
14309467b48Spatrick sys::fs::remove(Filename);
14409467b48Spatrick errs() << " done. \n";
14509467b48Spatrick } else {
146*d415bd75Srobert sys::ExecuteNoWait(ExecPath, args, std::nullopt, {}, 0, &ErrMsg);
14709467b48Spatrick errs() << "Remember to erase graph file: " << Filename << "\n";
14809467b48Spatrick }
14909467b48Spatrick return false;
15009467b48Spatrick }
15109467b48Spatrick
15209467b48Spatrick namespace {
15309467b48Spatrick
15409467b48Spatrick struct GraphSession {
15509467b48Spatrick std::string LogBuffer;
15609467b48Spatrick
TryFindProgram__anon715334420211::GraphSession15709467b48Spatrick bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
15809467b48Spatrick raw_string_ostream Log(LogBuffer);
15909467b48Spatrick SmallVector<StringRef, 8> parts;
16009467b48Spatrick Names.split(parts, '|');
16109467b48Spatrick for (auto Name : parts) {
16209467b48Spatrick if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
16309467b48Spatrick ProgramPath = *P;
16409467b48Spatrick return true;
16509467b48Spatrick }
16609467b48Spatrick Log << " Tried '" << Name << "'\n";
16709467b48Spatrick }
16809467b48Spatrick return false;
16909467b48Spatrick }
17009467b48Spatrick };
17109467b48Spatrick
17209467b48Spatrick } // end anonymous namespace
17309467b48Spatrick
getProgramName(GraphProgram::Name program)17409467b48Spatrick static const char *getProgramName(GraphProgram::Name program) {
17509467b48Spatrick switch (program) {
17609467b48Spatrick case GraphProgram::DOT:
17709467b48Spatrick return "dot";
17809467b48Spatrick case GraphProgram::FDP:
17909467b48Spatrick return "fdp";
18009467b48Spatrick case GraphProgram::NEATO:
18109467b48Spatrick return "neato";
18209467b48Spatrick case GraphProgram::TWOPI:
18309467b48Spatrick return "twopi";
18409467b48Spatrick case GraphProgram::CIRCO:
18509467b48Spatrick return "circo";
18609467b48Spatrick }
18709467b48Spatrick llvm_unreachable("bad kind");
18809467b48Spatrick }
18909467b48Spatrick
DisplayGraph(StringRef FilenameRef,bool wait,GraphProgram::Name program)19009467b48Spatrick bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
19109467b48Spatrick GraphProgram::Name program) {
192097a140dSpatrick std::string Filename = std::string(FilenameRef);
19309467b48Spatrick std::string ErrMsg;
19409467b48Spatrick std::string ViewerPath;
19509467b48Spatrick GraphSession S;
19609467b48Spatrick
19709467b48Spatrick #ifdef __APPLE__
19873471bf0Spatrick wait &= !*ViewBackground;
19909467b48Spatrick if (S.TryFindProgram("open", ViewerPath)) {
20009467b48Spatrick std::vector<StringRef> args;
20109467b48Spatrick args.push_back(ViewerPath);
20209467b48Spatrick if (wait)
20309467b48Spatrick args.push_back("-W");
20409467b48Spatrick args.push_back(Filename);
20509467b48Spatrick errs() << "Trying 'open' program... ";
20609467b48Spatrick if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
20709467b48Spatrick return false;
20809467b48Spatrick }
20909467b48Spatrick #endif
21009467b48Spatrick if (S.TryFindProgram("xdg-open", ViewerPath)) {
21109467b48Spatrick std::vector<StringRef> args;
21209467b48Spatrick args.push_back(ViewerPath);
21309467b48Spatrick args.push_back(Filename);
21409467b48Spatrick errs() << "Trying 'xdg-open' program... ";
21509467b48Spatrick if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
21609467b48Spatrick return false;
21709467b48Spatrick }
21809467b48Spatrick
21909467b48Spatrick // Graphviz
22009467b48Spatrick if (S.TryFindProgram("Graphviz", ViewerPath)) {
22109467b48Spatrick std::vector<StringRef> args;
22209467b48Spatrick args.push_back(ViewerPath);
22309467b48Spatrick args.push_back(Filename);
22409467b48Spatrick
22509467b48Spatrick errs() << "Running 'Graphviz' program... ";
22609467b48Spatrick return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
22709467b48Spatrick }
22809467b48Spatrick
22909467b48Spatrick // xdot
23009467b48Spatrick if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
23109467b48Spatrick std::vector<StringRef> args;
23209467b48Spatrick args.push_back(ViewerPath);
23309467b48Spatrick args.push_back(Filename);
23409467b48Spatrick
23509467b48Spatrick args.push_back("-f");
23609467b48Spatrick args.push_back(getProgramName(program));
23709467b48Spatrick
23809467b48Spatrick errs() << "Running 'xdot.py' program... ";
23909467b48Spatrick return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
24009467b48Spatrick }
24109467b48Spatrick
24209467b48Spatrick enum ViewerKind {
24309467b48Spatrick VK_None,
24409467b48Spatrick VK_OSXOpen,
24509467b48Spatrick VK_XDGOpen,
24609467b48Spatrick VK_Ghostview,
24709467b48Spatrick VK_CmdStart
24809467b48Spatrick };
24909467b48Spatrick ViewerKind Viewer = VK_None;
25009467b48Spatrick #ifdef __APPLE__
25109467b48Spatrick if (!Viewer && S.TryFindProgram("open", ViewerPath))
25209467b48Spatrick Viewer = VK_OSXOpen;
25309467b48Spatrick #endif
25409467b48Spatrick if (!Viewer && S.TryFindProgram("gv", ViewerPath))
25509467b48Spatrick Viewer = VK_Ghostview;
25609467b48Spatrick if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
25709467b48Spatrick Viewer = VK_XDGOpen;
25809467b48Spatrick #ifdef _WIN32
25909467b48Spatrick if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
26009467b48Spatrick Viewer = VK_CmdStart;
26109467b48Spatrick }
26209467b48Spatrick #endif
26309467b48Spatrick
26409467b48Spatrick // PostScript or PDF graph generator + PostScript/PDF viewer
26509467b48Spatrick std::string GeneratorPath;
26609467b48Spatrick if (Viewer &&
26709467b48Spatrick (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
26809467b48Spatrick S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
26909467b48Spatrick std::string OutputFilename =
27009467b48Spatrick Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
27109467b48Spatrick
27209467b48Spatrick std::vector<StringRef> args;
27309467b48Spatrick args.push_back(GeneratorPath);
27409467b48Spatrick if (Viewer == VK_CmdStart)
27509467b48Spatrick args.push_back("-Tpdf");
27609467b48Spatrick else
27709467b48Spatrick args.push_back("-Tps");
27809467b48Spatrick args.push_back("-Nfontname=Courier");
27909467b48Spatrick args.push_back("-Gsize=7.5,10");
28009467b48Spatrick args.push_back(Filename);
28109467b48Spatrick args.push_back("-o");
28209467b48Spatrick args.push_back(OutputFilename);
28309467b48Spatrick
28409467b48Spatrick errs() << "Running '" << GeneratorPath << "' program... ";
28509467b48Spatrick
28609467b48Spatrick if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
28709467b48Spatrick return true;
28809467b48Spatrick
28909467b48Spatrick // The lifetime of StartArg must include the call of ExecGraphViewer
29009467b48Spatrick // because the args are passed as vector of char*.
29109467b48Spatrick std::string StartArg;
29209467b48Spatrick
29309467b48Spatrick args.clear();
29409467b48Spatrick args.push_back(ViewerPath);
29509467b48Spatrick switch (Viewer) {
29609467b48Spatrick case VK_OSXOpen:
29709467b48Spatrick args.push_back("-W");
29809467b48Spatrick args.push_back(OutputFilename);
29909467b48Spatrick break;
30009467b48Spatrick case VK_XDGOpen:
30109467b48Spatrick wait = false;
30209467b48Spatrick args.push_back(OutputFilename);
30309467b48Spatrick break;
30409467b48Spatrick case VK_Ghostview:
30509467b48Spatrick args.push_back("--spartan");
30609467b48Spatrick args.push_back(OutputFilename);
30709467b48Spatrick break;
30809467b48Spatrick case VK_CmdStart:
30909467b48Spatrick args.push_back("/S");
31009467b48Spatrick args.push_back("/C");
31109467b48Spatrick StartArg =
31209467b48Spatrick (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
31309467b48Spatrick args.push_back(StartArg);
31409467b48Spatrick break;
31509467b48Spatrick case VK_None:
31609467b48Spatrick llvm_unreachable("Invalid viewer");
31709467b48Spatrick }
31809467b48Spatrick
31909467b48Spatrick ErrMsg.clear();
32009467b48Spatrick return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
32109467b48Spatrick }
32209467b48Spatrick
32309467b48Spatrick // dotty
32409467b48Spatrick if (S.TryFindProgram("dotty", ViewerPath)) {
32509467b48Spatrick std::vector<StringRef> args;
32609467b48Spatrick args.push_back(ViewerPath);
32709467b48Spatrick args.push_back(Filename);
32809467b48Spatrick
32909467b48Spatrick // Dotty spawns another app and doesn't wait until it returns
33009467b48Spatrick #ifdef _WIN32
33109467b48Spatrick wait = false;
33209467b48Spatrick #endif
33309467b48Spatrick errs() << "Running 'dotty' program... ";
33409467b48Spatrick return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
33509467b48Spatrick }
33609467b48Spatrick
33709467b48Spatrick errs() << "Error: Couldn't find a usable graph viewer program:\n";
33809467b48Spatrick errs() << S.LogBuffer << "\n";
33909467b48Spatrick return true;
34009467b48Spatrick }
341