Doxygen
dotrunner.cpp
浏览该文件的文档.
1 /******************************************************************************
2 *
3 * Copyright (C) 1997-2019 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15 
16 #include <cassert>
17 
18 #include "dotrunner.h"
19 #include "util.h"
20 #include "portable.h"
21 #include "dot.h"
22 #include "message.h"
23 #include "config.h"
24 #include "dir.h"
25 
26 // the graphicx LaTeX has a limitation of maximum size of 16384
27 // To be on the save side we take it a little bit smaller i.e. 150 inch * 72 dpi
28 // It is anyway hard to view these size of images
29 #define MAX_LATEX_GRAPH_INCH 150
30 #define MAX_LATEX_GRAPH_SIZE (MAX_LATEX_GRAPH_INCH * 72)
31 
32 //-----------------------------------------------------------------------------------------
33 
34 // since dot silently reproduces the input file when it does not
35 // support the PNG format, we need to check the result.
36 static void checkPngResult(const QCString &imgName)
37 {
38  FILE *f = Portable::fopen(imgName,"rb");
39  if (f)
40  {
41  char data[4];
42  if (fread(data,1,4,f)==4)
43  {
44  if (!(data[1]=='P' && data[2]=='N' && data[3]=='G'))
45  {
46  err("Image '%s' produced by dot is not a valid PNG!\n"
47  "You should either select a different format "
48  "(DOT_IMAGE_FORMAT in the config file) or install a more "
49  "recent version of graphviz (1.7+)\n",qPrint(imgName)
50  );
51  }
52  }
53  else
54  {
55  err("Could not read image '%s' generated by dot!\n",qPrint(imgName));
56  }
57  fclose(f);
58  }
59  else
60  {
61  err("Could not open image '%s' generated by dot!\n",qPrint(imgName));
62  }
63 }
64 
65 static bool resetPDFSize(const int width,const int height, const QCString &base)
66 {
67  std::string tmpName = base.str()+".tmp";
68  std::string patchFile = base.str()+".dot";
69  Dir thisDir;
70  if (!thisDir.rename(patchFile,tmpName))
71  {
72  err("Failed to rename file %s to %s!\n",qPrint(patchFile),qPrint(tmpName));
73  return FALSE;
74  }
75  std::ifstream fi(tmpName,std::ifstream::in);
76  std::ofstream t(patchFile,std::ofstream::out | std::ofstream::binary);
77  if (!fi.is_open())
78  {
79  err("problem opening file %s for patching!\n",qPrint(tmpName));
80  thisDir.rename(tmpName,patchFile);
81  return FALSE;
82  }
83  if (!t.is_open())
84  {
85  err("problem opening file %s for patching!\n",qPrint(patchFile));
86  thisDir.rename(tmpName,patchFile);
87  return FALSE;
88  }
89  std::string line;
90  while (getline(fi,line)) // foreach line
91  {
92  if (line.find("LATEX_PDF_SIZE") != std::string::npos)
93  {
94  double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH);
95  t << " size=\""<<width/scale << "," <<height/scale << "\";\n";
96  }
97  else
98  t << line << "\n";
99  }
100  fi.close();
101  t.close();
102  // remove temporary file
103  thisDir.remove(tmpName);
104  return TRUE;
105 }
106 
107 bool DotRunner::readBoundingBox(const QCString &fileName,int *width,int *height,bool isEps)
108 {
109  const char *bb = isEps ? "%%PageBoundingBox:" : "/MediaBox [";
110  int bblen = (int)strlen(bb);
111  FILE *f = Portable::fopen(fileName,"rb");
112  if (!f)
113  {
114  //printf("readBoundingBox: could not open %s\n",fileName);
115  return FALSE;
116  }
117  const int maxLineLen=1024;
118  char buf[maxLineLen];
119  while (fgets(buf,maxLineLen,f)!=NULL)
120  {
121  const char *p = strstr(buf,bb);
122  if (p) // found PageBoundingBox or /MediaBox string
123  {
124  int x,y;
125  fclose(f);
126  if (sscanf(p+bblen,"%d %d %d %d",&x,&y,width,height)!=4)
127  {
128  //printf("readBoundingBox sscanf fail\n");
129  return FALSE;
130  }
131  return TRUE;
132  }
133  }
134  err("Failed to extract bounding box from generated diagram file %s\n",qPrint(fileName));
135  fclose(f);
136  return FALSE;
137 }
138 
139 //---------------------------------------------------------------------------------
140 
141 DotRunner::DotRunner(const QCString& absDotName, const QCString& md5Hash)
142  : m_file(absDotName)
143  , m_md5Hash(md5Hash)
144  , m_dotExe(Config_getString(DOT_PATH)+"dot")
145  , m_cleanUp(Config_getBool(DOT_CLEANUP))
146 {
147 }
148 
149 
150 void DotRunner::addJob(const QCString &format, const QCString &output,
151  const QCString &srcFile,int srcLine)
152 {
153 
154  for (auto& s: m_jobs)
155  {
156  if (s.format != format) continue;
157  if (s.output != output) continue;
158  // we have this job already
159  return;
160  }
161  auto args = QCString("-T") + format + " -o \"" + output + "\"";
162  m_jobs.emplace_back(format.str(), output, args, srcFile, srcLine);
163 }
164 
166 {
167  int index = output.findRev('.');
168  if (index < 0) return output;
169  return output.left(index);
170 }
171 
173 {
174  int exitCode=0;
175 
176  QCString dotArgs;
177 
178  QCString srcFile;
179  int srcLine=-1;
180 
181  // create output
182  if (Config_getBool(DOT_MULTI_TARGETS))
183  {
184  dotArgs=QCString("\"")+m_file+"\"";
185  for (auto& s: m_jobs)
186  {
187  dotArgs+=' ';
188  dotArgs+=s.args;
189  }
190  if (!m_jobs.empty())
191  {
192  srcFile = m_jobs.front().srcFile;
193  srcLine = m_jobs.front().srcLine;
194  }
195  if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
196  }
197  else
198  {
199  for (auto& s : m_jobs)
200  {
201  srcFile = s.srcFile;
202  srcLine = s.srcLine;
203  dotArgs=QCString("\"")+m_file+"\" "+s.args;
204  if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
205  }
206  }
207 
208  // check output
209  // As there should be only one pdf file be generated, we don't need code for regenerating multiple pdf files in one call
210  for (auto& s : m_jobs)
211  {
212  if (s.format.left(3)=="pdf")
213  {
214  int width=0,height=0;
215  if (!readBoundingBox(s.output,&width,&height,FALSE)) goto error;
216  if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE))
217  {
218  if (!resetPDFSize(width,height,getBaseNameOfOutput(s.output))) goto error;
219  dotArgs=QCString("\"")+m_file+"\" "+s.args;
220  if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
221  }
222  }
223 
224  if (s.format.left(3)=="png")
225  {
226  checkPngResult(s.output);
227  }
228  }
229 
230  // remove .dot files
231  if (m_cleanUp)
232  {
233  //printf("removing dot file %s\n",qPrint(m_file));
235  }
236 
237  // create checksum file
238  if (!m_md5Hash.isEmpty())
239  {
240  QCString md5Name = getBaseNameOfOutput(m_file) + ".md5";
241  FILE *f = Portable::fopen(md5Name,"w");
242  if (f)
243  {
244  fwrite(m_md5Hash.data(),1,32,f);
245  fclose(f);
246  }
247  }
248  return TRUE;
249 error:
250  err_full(srcFile,srcLine,"Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
251  exitCode,qPrint(m_dotExe),qPrint(dotArgs));
252  return FALSE;
253 }
254 
255 
256 //--------------------------------------------------------------------
257 
259 {
260  std::lock_guard<std::mutex> locker(m_mutex);
261  m_queue.push(runner);
262  m_bufferNotEmpty.notify_all();
263 }
264 
266 {
267  std::unique_lock<std::mutex> locker(m_mutex);
268 
269  // wait until something is added to the queue
270  m_bufferNotEmpty.wait(locker, [this]() { return !m_queue.empty(); });
271 
272  DotRunner *result = m_queue.front();
273  m_queue.pop();
274  return result;
275 }
276 
277 size_t DotRunnerQueue::size() const
278 {
279  std::lock_guard<std::mutex> locker(m_mutex);
280  return m_queue.size();
281 }
282 
283 //--------------------------------------------------------------------
284 
286  : m_queue(queue)
287 {
288 }
289 
291 {
292  if (isRunning())
293  {
294  wait();
295  }
296 }
297 
299 {
300  DotRunner *runner;
301  while ((runner=m_queue->dequeue()))
302  {
303  runner->run();
304  }
305 }
306 
308 {
309  assert(!m_thread);
310  m_thread = std::make_unique<std::thread>(&DotWorkerThread::run, this);
311 }
DotRunner::run
bool run()
Runs dot for all jobs added.
Definition: dotrunner.cpp:172
Portable::fopen
FILE * fopen(const QCString &fileName, const QCString &mode)
Definition: portable.cpp:322
Dir::remove
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition: dir.cpp:256
Dir
Class representing a directory in the file system
Definition: dir.h:68
DotRunner
Helper class to run dot from doxygen from multiple threads.
Definition: dotrunner.h:30
DotWorkerThread::isRunning
bool isRunning()
Definition: dotrunner.h:93
QCString::findRev
int findRev(char c, int index=-1, bool cs=TRUE) const
Definition: qcstring.cpp:86
QCString::isEmpty
bool isEmpty() const
Returns TRUE iff the string is empty
Definition: qcstring.h:144
DotWorkerThread::wait
void wait()
Definition: dotrunner.h:94
DotRunnerQueue::size
size_t size() const
Definition: dotrunner.cpp:277
DotRunner::m_cleanUp
bool m_cleanUp
Definition: dotrunner.h:80
QCString::str
std::string str() const
Definition: qcstring.h:442
DotWorkerThread::start
void start()
Definition: dotrunner.cpp:307
err
void err(const char *fmt,...)
Definition: message.cpp:203
DotRunnerQueue::m_queue
std::queue< DotRunner * > m_queue
Definition: dotrunner.h:81
DotRunner::addJob
void addJob(const QCString &format, const QCString &output, const QCString &srcFile, int srcLine)
Adds an additional job to the run.
Definition: dotrunner.cpp:150
MAX_LATEX_GRAPH_SIZE
#define MAX_LATEX_GRAPH_SIZE
Definition: dotrunner.cpp:30
DotRunner::readBoundingBox
static bool readBoundingBox(const QCString &fileName, int *width, int *height, bool isEps)
Definition: dotrunner.cpp:107
dot.h
DotRunner::m_file
QCString m_file
Definition: dotrunner.h:77
Dir::rename
bool rename(const std::string &orgName, const std::string &newName, bool acceptsAbsPath=true) const
Definition: dir.cpp:263
QCString::left
QCString left(size_t len) const
Definition: qcstring.h:212
message.h
Portable::fclose
int fclose(FILE *f)
Definition: portable.cpp:342
DotRunnerQueue::enqueue
void enqueue(DotRunner *runner)
Definition: dotrunner.cpp:258
DotRunnerQueue
Queue of dot jobs to run.
Definition: dotrunner.h:73
DotRunner::m_md5Hash
QCString m_md5Hash
Definition: dotrunner.h:78
MAX_LATEX_GRAPH_INCH
#define MAX_LATEX_GRAPH_INCH
Definition: dotrunner.cpp:29
TRUE
#define TRUE
Definition: qcstring.h:36
Portable::system
int system(const QCString &command, const QCString &args, bool commandHasConsole=true)
Definition: portable.cpp:42
DotWorkerThread::run
void run()
Definition: dotrunner.cpp:298
dotrunner.h
DotRunnerQueue::m_mutex
std::mutex m_mutex
Definition: dotrunner.h:82
DotRunner::DotRunner
DotRunner(const QCString &absDotName, const QCString &md5Hash=QCString())
Creates a runner for a dot file.
Definition: dotrunner.cpp:141
resetPDFSize
static bool resetPDFSize(const int width, const int height, const QCString &base)
Definition: dotrunner.cpp:65
DotWorkerThread::DotWorkerThread
DotWorkerThread(DotRunnerQueue *queue)
Definition: dotrunner.cpp:285
Config_getBool
#define Config_getBool(name)
Definition: config.h:33
DotRunner::m_jobs
std::vector< DotJob > m_jobs
Definition: dotrunner.h:81
DotRunnerQueue::dequeue
DotRunner * dequeue()
Definition: dotrunner.cpp:265
qPrint
const char * qPrint(const char *s)
Definition: qcstring.h:589
Config_getString
#define Config_getString(name)
Definition: config.h:32
config.h
err_full
void err_full(const QCString &file, int line, const char *fmt,...)
Definition: message.cpp:212
QCString::data
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string
Definition: qcstring.h:153
DotRunner::m_dotExe
QCString m_dotExe
Definition: dotrunner.h:79
DotWorkerThread::m_thread
std::unique_ptr< std::thread > m_thread
Definition: dotrunner.h:96
DotWorkerThread::~DotWorkerThread
~DotWorkerThread()
Definition: dotrunner.cpp:290
portable.h
Portable versions of functions that are platform dependent.
Portable::unlink
void unlink(const QCString &fileName)
Definition: portable.cpp:525
DotRunnerQueue::m_bufferNotEmpty
std::condition_variable m_bufferNotEmpty
Definition: dotrunner.h:80
dir.h
util.h
A bunch of utility functions.
getBaseNameOfOutput
QCString getBaseNameOfOutput(const QCString &output)
Definition: dotrunner.cpp:165
DotWorkerThread::m_queue
DotRunnerQueue * m_queue
Definition: dotrunner.h:97
checkPngResult
static void checkPngResult(const QCString &imgName)
Definition: dotrunner.cpp:36
FALSE
#define FALSE
Definition: qcstring.h:33
QCString
This is an alternative implementation of QCString.
Definition: qcstring.h:108