108bbd35aSDimitry Andric //===- GraphWriter.cpp - Implements GraphWriter support routines ----------===//
2009b1c42SEd Schouten //
3e6d15924SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e6d15924SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5e6d15924SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6009b1c42SEd Schouten //
7009b1c42SEd Schouten //===----------------------------------------------------------------------===//
8009b1c42SEd Schouten //
9009b1c42SEd Schouten // This file implements misc. GraphWriter support routines.
10009b1c42SEd Schouten //
11009b1c42SEd Schouten //===----------------------------------------------------------------------===//
12009b1c42SEd Schouten
13009b1c42SEd Schouten #include "llvm/Support/GraphWriter.h"
14344a3780SDimitry Andric
15344a3780SDimitry Andric #include "DebugOptions.h"
16344a3780SDimitry Andric
1708bbd35aSDimitry Andric #include "llvm/ADT/SmallString.h"
1808bbd35aSDimitry Andric #include "llvm/ADT/SmallVector.h"
1908bbd35aSDimitry Andric #include "llvm/ADT/StringRef.h"
204a16efa3SDimitry Andric #include "llvm/Config/config.h"
2108bbd35aSDimitry Andric #include "llvm/Support/Compiler.h"
2208bbd35aSDimitry Andric #include "llvm/Support/ErrorHandling.h"
2308bbd35aSDimitry Andric #include "llvm/Support/ErrorOr.h"
24f8af5cf6SDimitry Andric #include "llvm/Support/FileSystem.h"
25c0981da4SDimitry Andric #include "llvm/Support/Path.h"
26cf099d11SDimitry Andric #include "llvm/Support/Program.h"
2708bbd35aSDimitry Andric #include "llvm/Support/raw_ostream.h"
286f8fc217SDimitry Andric
296f8fc217SDimitry Andric #ifdef __APPLE__
306f8fc217SDimitry Andric #include "llvm/Support/CommandLine.h"
31ac9a064cSDimitry Andric #include "llvm/Support/ManagedStatic.h"
326f8fc217SDimitry Andric #endif
336f8fc217SDimitry Andric
3408bbd35aSDimitry Andric #include <string>
35c0981da4SDimitry Andric #include <system_error>
3608bbd35aSDimitry Andric #include <vector>
3708bbd35aSDimitry Andric
38009b1c42SEd Schouten using namespace llvm;
39009b1c42SEd Schouten
40344a3780SDimitry Andric #ifdef __APPLE__
41344a3780SDimitry Andric namespace {
42344a3780SDimitry Andric struct CreateViewBackground {
call__anon21082b000111::CreateViewBackground43344a3780SDimitry Andric static void *call() {
44344a3780SDimitry Andric return new cl::opt<bool>("view-background", cl::Hidden,
45344a3780SDimitry Andric cl::desc("Execute graph viewer in the background. "
46344a3780SDimitry Andric "Creates tmp file litter."));
47344a3780SDimitry Andric }
48344a3780SDimitry Andric };
49344a3780SDimitry Andric } // namespace
50344a3780SDimitry Andric static ManagedStatic<cl::opt<bool>, CreateViewBackground> ViewBackground;
initGraphWriterOptions()51344a3780SDimitry Andric void llvm::initGraphWriterOptions() { *ViewBackground; }
52344a3780SDimitry Andric #else
initGraphWriterOptions()53344a3780SDimitry Andric void llvm::initGraphWriterOptions() {}
54344a3780SDimitry Andric #endif
5563faed5bSDimitry Andric
EscapeString(const std::string & Label)5659850d08SRoman Divacky std::string llvm::DOT::EscapeString(const std::string &Label) {
5759850d08SRoman Divacky std::string Str(Label);
5859850d08SRoman Divacky for (unsigned i = 0; i != Str.length(); ++i)
5959850d08SRoman Divacky switch (Str[i]) {
6059850d08SRoman Divacky case '\n':
6159850d08SRoman Divacky Str.insert(Str.begin()+i, '\\'); // Escape character...
6259850d08SRoman Divacky ++i;
6359850d08SRoman Divacky Str[i] = 'n';
6459850d08SRoman Divacky break;
6559850d08SRoman Divacky case '\t':
6659850d08SRoman Divacky Str.insert(Str.begin()+i, ' '); // Convert to two spaces
6759850d08SRoman Divacky ++i;
6859850d08SRoman Divacky Str[i] = ' ';
6959850d08SRoman Divacky break;
7059850d08SRoman Divacky case '\\':
7159850d08SRoman Divacky if (i+1 != Str.length())
7259850d08SRoman Divacky switch (Str[i+1]) {
7359850d08SRoman Divacky case 'l': continue; // don't disturb \l
7459850d08SRoman Divacky case '|': case '{': case '}':
7559850d08SRoman Divacky Str.erase(Str.begin()+i); continue;
7659850d08SRoman Divacky default: break;
7759850d08SRoman Divacky }
78e3b55780SDimitry Andric [[fallthrough]];
7959850d08SRoman Divacky case '{': case '}':
8059850d08SRoman Divacky case '<': case '>':
8159850d08SRoman Divacky case '|': case '"':
8259850d08SRoman Divacky Str.insert(Str.begin()+i, '\\'); // Escape character...
8359850d08SRoman Divacky ++i; // don't infinite loop
8459850d08SRoman Divacky break;
8559850d08SRoman Divacky }
8659850d08SRoman Divacky return Str;
8759850d08SRoman Divacky }
8859850d08SRoman Divacky
89eb11fae6SDimitry Andric /// Get a color string for this node number. Simply round-robin selects
904a16efa3SDimitry Andric /// from a reasonable number of colors.
getColorString(unsigned ColorNumber)914a16efa3SDimitry Andric StringRef llvm::DOT::getColorString(unsigned ColorNumber) {
924a16efa3SDimitry Andric static const int NumColors = 20;
934a16efa3SDimitry Andric static const char* Colors[NumColors] = {
944a16efa3SDimitry Andric "aaaaaa", "aa0000", "00aa00", "aa5500", "0055ff", "aa00aa", "00aaaa",
954a16efa3SDimitry Andric "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", "55ffff",
964a16efa3SDimitry Andric "ffaaaa", "aaffaa", "ffffaa", "aaaaff", "ffaaff", "aaffff"};
974a16efa3SDimitry Andric return Colors[ColorNumber % NumColors];
984a16efa3SDimitry Andric }
994a16efa3SDimitry Andric
replaceIllegalFilenameChars(std::string Filename,const char ReplacementChar)100cfca06d7SDimitry Andric static std::string replaceIllegalFilenameChars(std::string Filename,
101cfca06d7SDimitry Andric const char ReplacementChar) {
102c0981da4SDimitry Andric std::string IllegalChars =
103c0981da4SDimitry Andric is_style_windows(sys::path::Style::native) ? "\\/:?\"<>|" : "/";
104cfca06d7SDimitry Andric
105cfca06d7SDimitry Andric for (char IllegalChar : IllegalChars) {
106cfca06d7SDimitry Andric std::replace(Filename.begin(), Filename.end(), IllegalChar,
107cfca06d7SDimitry Andric ReplacementChar);
108cfca06d7SDimitry Andric }
109cfca06d7SDimitry Andric
110cfca06d7SDimitry Andric return Filename;
111cfca06d7SDimitry Andric }
112cfca06d7SDimitry Andric
createGraphFilename(const Twine & Name,int & FD)113f8af5cf6SDimitry Andric std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
114f8af5cf6SDimitry Andric FD = -1;
115f8af5cf6SDimitry Andric SmallString<128> Filename;
116cfca06d7SDimitry Andric
117cfca06d7SDimitry Andric // Windows can't always handle long paths, so limit the length of the name.
118cfca06d7SDimitry Andric std::string N = Name.str();
119ac9a064cSDimitry Andric if (N.size() > 140)
120ac9a064cSDimitry Andric N.resize(140);
121cfca06d7SDimitry Andric
122cfca06d7SDimitry Andric // Replace illegal characters in graph Filename with '_' if needed
123cfca06d7SDimitry Andric std::string CleansedName = replaceIllegalFilenameChars(N, '_');
124cfca06d7SDimitry Andric
125cfca06d7SDimitry Andric std::error_code EC =
126cfca06d7SDimitry Andric sys::fs::createTemporaryFile(CleansedName, "dot", FD, Filename);
127f8af5cf6SDimitry Andric if (EC) {
128f8af5cf6SDimitry Andric errs() << "Error: " << EC.message() << "\n";
129f8af5cf6SDimitry Andric return "";
130f8af5cf6SDimitry Andric }
131f8af5cf6SDimitry Andric
132f8af5cf6SDimitry Andric errs() << "Writing '" << Filename << "'... ";
1334df029ccSDimitry Andric return std::string(Filename);
134f8af5cf6SDimitry Andric }
135f8af5cf6SDimitry Andric
1365ca98fd9SDimitry Andric // Execute the graph viewer. Return true if there were errors.
ExecGraphViewer(StringRef ExecPath,std::vector<StringRef> & args,StringRef Filename,bool wait,std::string & ErrMsg)137eb11fae6SDimitry Andric static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
1385ca98fd9SDimitry Andric StringRef Filename, bool wait,
1395ca98fd9SDimitry Andric std::string &ErrMsg) {
14063faed5bSDimitry Andric if (wait) {
141e3b55780SDimitry Andric if (sys::ExecuteAndWait(ExecPath, args, std::nullopt, {}, 0, 0, &ErrMsg)) {
14263faed5bSDimitry Andric errs() << "Error: " << ErrMsg << "\n";
14363faed5bSDimitry Andric return true;
14463faed5bSDimitry Andric }
1455ca98fd9SDimitry Andric sys::fs::remove(Filename);
1465ca98fd9SDimitry Andric errs() << " done. \n";
1475ca98fd9SDimitry Andric } else {
148e3b55780SDimitry Andric sys::ExecuteNoWait(ExecPath, args, std::nullopt, {}, 0, &ErrMsg);
1495a5ac124SDimitry Andric errs() << "Remember to erase graph file: " << Filename << "\n";
1505ca98fd9SDimitry Andric }
1515ca98fd9SDimitry Andric return false;
1525ca98fd9SDimitry Andric }
15359850d08SRoman Divacky
1545a5ac124SDimitry Andric namespace {
15508bbd35aSDimitry Andric
1565ca98fd9SDimitry Andric struct GraphSession {
1575ca98fd9SDimitry Andric std::string LogBuffer;
15808bbd35aSDimitry Andric
TryFindProgram__anon21082b000211::GraphSession1595ca98fd9SDimitry Andric bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
1605ca98fd9SDimitry Andric raw_string_ostream Log(LogBuffer);
1615ca98fd9SDimitry Andric SmallVector<StringRef, 8> parts;
162dd58ef01SDimitry Andric Names.split(parts, '|');
1635ca98fd9SDimitry Andric for (auto Name : parts) {
16467c32a98SDimitry Andric if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
16567c32a98SDimitry Andric ProgramPath = *P;
1665ca98fd9SDimitry Andric return true;
16767c32a98SDimitry Andric }
1685ca98fd9SDimitry Andric Log << " Tried '" << Name << "'\n";
1695ca98fd9SDimitry Andric }
1705ca98fd9SDimitry Andric return false;
1715ca98fd9SDimitry Andric }
1725ca98fd9SDimitry Andric };
17308bbd35aSDimitry Andric
17408bbd35aSDimitry Andric } // end anonymous namespace
1755ca98fd9SDimitry Andric
getProgramName(GraphProgram::Name program)1765ca98fd9SDimitry Andric static const char *getProgramName(GraphProgram::Name program) {
1775ca98fd9SDimitry Andric switch (program) {
1785ca98fd9SDimitry Andric case GraphProgram::DOT:
1795ca98fd9SDimitry Andric return "dot";
1805ca98fd9SDimitry Andric case GraphProgram::FDP:
1815ca98fd9SDimitry Andric return "fdp";
1825ca98fd9SDimitry Andric case GraphProgram::NEATO:
1835ca98fd9SDimitry Andric return "neato";
1845ca98fd9SDimitry Andric case GraphProgram::TWOPI:
1855ca98fd9SDimitry Andric return "twopi";
1865ca98fd9SDimitry Andric case GraphProgram::CIRCO:
1875ca98fd9SDimitry Andric return "circo";
1885ca98fd9SDimitry Andric }
1895ca98fd9SDimitry Andric llvm_unreachable("bad kind");
1905ca98fd9SDimitry Andric }
1915ca98fd9SDimitry Andric
DisplayGraph(StringRef FilenameRef,bool wait,GraphProgram::Name program)1925ca98fd9SDimitry Andric bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
19359850d08SRoman Divacky GraphProgram::Name program) {
194cfca06d7SDimitry Andric std::string Filename = std::string(FilenameRef);
195009b1c42SEd Schouten std::string ErrMsg;
1965ca98fd9SDimitry Andric std::string ViewerPath;
1975ca98fd9SDimitry Andric GraphSession S;
198009b1c42SEd Schouten
1995a5ac124SDimitry Andric #ifdef __APPLE__
200344a3780SDimitry Andric wait &= !*ViewBackground;
2015a5ac124SDimitry Andric if (S.TryFindProgram("open", ViewerPath)) {
202eb11fae6SDimitry Andric std::vector<StringRef> args;
203eb11fae6SDimitry Andric args.push_back(ViewerPath);
2045a5ac124SDimitry Andric if (wait)
2055a5ac124SDimitry Andric args.push_back("-W");
206eb11fae6SDimitry Andric args.push_back(Filename);
2075a5ac124SDimitry Andric errs() << "Trying 'open' program... ";
2085a5ac124SDimitry Andric if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
2095a5ac124SDimitry Andric return false;
2105a5ac124SDimitry Andric }
2115a5ac124SDimitry Andric #endif
2125a5ac124SDimitry Andric if (S.TryFindProgram("xdg-open", ViewerPath)) {
213eb11fae6SDimitry Andric std::vector<StringRef> args;
214eb11fae6SDimitry Andric args.push_back(ViewerPath);
215eb11fae6SDimitry Andric args.push_back(Filename);
2165a5ac124SDimitry Andric errs() << "Trying 'xdg-open' program... ";
2175a5ac124SDimitry Andric if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
2185a5ac124SDimitry Andric return false;
2195a5ac124SDimitry Andric }
2205a5ac124SDimitry Andric
2215ca98fd9SDimitry Andric // Graphviz
2225ca98fd9SDimitry Andric if (S.TryFindProgram("Graphviz", ViewerPath)) {
223eb11fae6SDimitry Andric std::vector<StringRef> args;
224eb11fae6SDimitry Andric args.push_back(ViewerPath);
225eb11fae6SDimitry Andric args.push_back(Filename);
226009b1c42SEd Schouten
22759850d08SRoman Divacky errs() << "Running 'Graphviz' program... ";
2285ca98fd9SDimitry Andric return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
229cf099d11SDimitry Andric }
230cf099d11SDimitry Andric
2315ca98fd9SDimitry Andric // xdot
2325ca98fd9SDimitry Andric if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
233eb11fae6SDimitry Andric std::vector<StringRef> args;
234eb11fae6SDimitry Andric args.push_back(ViewerPath);
235eb11fae6SDimitry Andric args.push_back(Filename);
2365ca98fd9SDimitry Andric
2375ca98fd9SDimitry Andric args.push_back("-f");
2385ca98fd9SDimitry Andric args.push_back(getProgramName(program));
2395ca98fd9SDimitry Andric
240cf099d11SDimitry Andric errs() << "Running 'xdot.py' program... ";
2415ca98fd9SDimitry Andric return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
2425ca98fd9SDimitry Andric }
24359850d08SRoman Divacky
244dd58ef01SDimitry Andric enum ViewerKind {
245dd58ef01SDimitry Andric VK_None,
246dd58ef01SDimitry Andric VK_OSXOpen,
247dd58ef01SDimitry Andric VK_XDGOpen,
248dd58ef01SDimitry Andric VK_Ghostview,
249dd58ef01SDimitry Andric VK_CmdStart
250dd58ef01SDimitry Andric };
251dd58ef01SDimitry Andric ViewerKind Viewer = VK_None;
2525ca98fd9SDimitry Andric #ifdef __APPLE__
253dd58ef01SDimitry Andric if (!Viewer && S.TryFindProgram("open", ViewerPath))
254dd58ef01SDimitry Andric Viewer = VK_OSXOpen;
2555ca98fd9SDimitry Andric #endif
256dd58ef01SDimitry Andric if (!Viewer && S.TryFindProgram("gv", ViewerPath))
257dd58ef01SDimitry Andric Viewer = VK_Ghostview;
258dd58ef01SDimitry Andric if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
259dd58ef01SDimitry Andric Viewer = VK_XDGOpen;
260eb11fae6SDimitry Andric #ifdef _WIN32
261dd58ef01SDimitry Andric if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
262dd58ef01SDimitry Andric Viewer = VK_CmdStart;
263dd58ef01SDimitry Andric }
264dd58ef01SDimitry Andric #endif
2655ca98fd9SDimitry Andric
266dd58ef01SDimitry Andric // PostScript or PDF graph generator + PostScript/PDF viewer
2675ca98fd9SDimitry Andric std::string GeneratorPath;
268dd58ef01SDimitry Andric if (Viewer &&
2695ca98fd9SDimitry Andric (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
2705ca98fd9SDimitry Andric S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
271dd58ef01SDimitry Andric std::string OutputFilename =
272dd58ef01SDimitry Andric Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
273009b1c42SEd Schouten
274eb11fae6SDimitry Andric std::vector<StringRef> args;
275eb11fae6SDimitry Andric args.push_back(GeneratorPath);
276dd58ef01SDimitry Andric if (Viewer == VK_CmdStart)
277dd58ef01SDimitry Andric args.push_back("-Tpdf");
278dd58ef01SDimitry Andric else
279009b1c42SEd Schouten args.push_back("-Tps");
280009b1c42SEd Schouten args.push_back("-Nfontname=Courier");
281009b1c42SEd Schouten args.push_back("-Gsize=7.5,10");
282eb11fae6SDimitry Andric args.push_back(Filename);
283009b1c42SEd Schouten args.push_back("-o");
284eb11fae6SDimitry Andric args.push_back(OutputFilename);
285009b1c42SEd Schouten
2865ca98fd9SDimitry Andric errs() << "Running '" << GeneratorPath << "' program... ";
28759850d08SRoman Divacky
288dd58ef01SDimitry Andric if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
2895ca98fd9SDimitry Andric return true;
290009b1c42SEd Schouten
291dd58ef01SDimitry Andric // The lifetime of StartArg must include the call of ExecGraphViewer
292dd58ef01SDimitry Andric // because the args are passed as vector of char*.
293dd58ef01SDimitry Andric std::string StartArg;
294dd58ef01SDimitry Andric
295009b1c42SEd Schouten args.clear();
296eb11fae6SDimitry Andric args.push_back(ViewerPath);
297dd58ef01SDimitry Andric switch (Viewer) {
298dd58ef01SDimitry Andric case VK_OSXOpen:
2995ca98fd9SDimitry Andric args.push_back("-W");
300eb11fae6SDimitry Andric args.push_back(OutputFilename);
3015ca98fd9SDimitry Andric break;
302dd58ef01SDimitry Andric case VK_XDGOpen:
3035ca98fd9SDimitry Andric wait = false;
304eb11fae6SDimitry Andric args.push_back(OutputFilename);
3055ca98fd9SDimitry Andric break;
306dd58ef01SDimitry Andric case VK_Ghostview:
30767a71b31SRoman Divacky args.push_back("--spartan");
308eb11fae6SDimitry Andric args.push_back(OutputFilename);
3095ca98fd9SDimitry Andric break;
310dd58ef01SDimitry Andric case VK_CmdStart:
311dd58ef01SDimitry Andric args.push_back("/S");
312dd58ef01SDimitry Andric args.push_back("/C");
313dd58ef01SDimitry Andric StartArg =
314dd58ef01SDimitry Andric (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
315eb11fae6SDimitry Andric args.push_back(StartArg);
316dd58ef01SDimitry Andric break;
317dd58ef01SDimitry Andric case VK_None:
3185ca98fd9SDimitry Andric llvm_unreachable("Invalid viewer");
3195ca98fd9SDimitry Andric }
320009b1c42SEd Schouten
321009b1c42SEd Schouten ErrMsg.clear();
322dd58ef01SDimitry Andric return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
3235ca98fd9SDimitry Andric }
32463faed5bSDimitry Andric
3255ca98fd9SDimitry Andric // dotty
3265ca98fd9SDimitry Andric if (S.TryFindProgram("dotty", ViewerPath)) {
327eb11fae6SDimitry Andric std::vector<StringRef> args;
328eb11fae6SDimitry Andric args.push_back(ViewerPath);
329eb11fae6SDimitry Andric args.push_back(Filename);
330009b1c42SEd Schouten
331d7f7719eSRoman Divacky // Dotty spawns another app and doesn't wait until it returns
332eb11fae6SDimitry Andric #ifdef _WIN32
33363faed5bSDimitry Andric wait = false;
334009b1c42SEd Schouten #endif
33563faed5bSDimitry Andric errs() << "Running 'dotty' program... ";
3365ca98fd9SDimitry Andric return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
3375ca98fd9SDimitry Andric }
3385ca98fd9SDimitry Andric
3395ca98fd9SDimitry Andric errs() << "Error: Couldn't find a usable graph viewer program:\n";
3405ca98fd9SDimitry Andric errs() << S.LogBuffer << "\n";
3415ca98fd9SDimitry Andric return true;
342009b1c42SEd Schouten }
343