/***************************************************************************** * * Copyright (C) 1997-2019 by Dimitri van Heesch. * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby * granted. No representations are made about the suitability of this software * for any purpose. It is provided "as is" without express or implied warranty. * See the GNU General Public License for more details. * * Documents produced by Doxygen are derivative works derived from the * input used in their production; they are not affected by this license. * */ #include #include #include #include "config.h" #include "dot.h" #include "dotrunner.h" #include "dotfilepatcher.h" #include "util.h" #include "portable.h" #include "message.h" #include "ftextstream.h" #include "doxygen.h" #include "language.h" #include "index.h" #define MAP_CMD "cmapx" //-------------------------------------------------------------------- static QCString g_dotFontPath; static void setDotFontPath(const char *path) { ASSERT(g_dotFontPath.isEmpty()); g_dotFontPath = Portable::getenv("DOTFONTPATH"); QCString newFontPath = Config_getString(DOT_FONTPATH); QCString spath = path; if (!newFontPath.isEmpty() && !spath.isEmpty()) { newFontPath.prepend(spath+Portable::pathListSeparator()); } else if (newFontPath.isEmpty() && !spath.isEmpty()) { newFontPath=path; } else { Portable::unsetenv("DOTFONTPATH"); return; } Portable::setenv("DOTFONTPATH",newFontPath); } static void unsetDotFontPath() { if (g_dotFontPath.isEmpty()) { Portable::unsetenv("DOTFONTPATH"); } else { Portable::setenv("DOTFONTPATH",g_dotFontPath); } g_dotFontPath=""; } //-------------------------------------------------------------------- DotManager *DotManager::m_theInstance = 0; DotManager *DotManager::instance() { if (!m_theInstance) { m_theInstance = new DotManager; } return m_theInstance; } void DotManager::deleteInstance() { delete m_theInstance; m_theInstance=0; } DotManager::DotManager() : m_runners(), m_filePatchers() { m_queue = new DotRunnerQueue; int i; int dotNumThreads = Config_getInt(DOT_NUM_THREADS); if (dotNumThreads!=1) { for (i=0;i thread = std::make_unique(m_queue); thread->start(); if (thread->isRunning()) { m_workers.push_back(std::move(thread)); } else // no more threads available! { } } ASSERT(m_workers.size()>0); } } DotManager::~DotManager() { delete m_queue; } DotRunner* DotManager::createRunner(const std::string &absDotName, const std::string& md5Hash) { DotRunner* rv = nullptr; auto const runit = m_runners.find(absDotName); if (runit == m_runners.end()) { auto insobj = std::make_unique(absDotName, md5Hash); rv = insobj.get(); m_runners.emplace(absDotName, std::move(insobj)); } else { // we have a match if (md5Hash != runit->second->getMd5Hash()) { err("md5 hash does not match for two different runs of %s !\n", absDotName.data()); } rv = runit->second.get(); } assert(rv); return rv; } DotFilePatcher *DotManager::createFilePatcher(const std::string &fileName) { auto patcher = m_filePatchers.find(fileName); if (patcher != m_filePatchers.end()) return &(patcher->second); auto rv = m_filePatchers.emplace(fileName, fileName.c_str()); assert(rv.second); return &(rv.first->second); } bool DotManager::run() const { size_t numDotRuns = m_runners.size(); size_t numFilePatchers = m_filePatchers.size(); if (numDotRuns+numFilePatchers>1) { if (m_workers.size()==0) { msg("Generating dot graphs in single threaded mode...\n"); } else { msg("Generating dot graphs using %zu parallel threads...\n",QMIN(numDotRuns+numFilePatchers,m_workers.size())); } } size_t i=1; bool setPath=FALSE; if (Config_getBool(GENERATE_HTML)) { setDotFontPath(Config_getString(HTML_OUTPUT)); setPath=TRUE; } else if (Config_getBool(GENERATE_LATEX)) { setDotFontPath(Config_getString(LATEX_OUTPUT)); setPath=TRUE; } else if (Config_getBool(GENERATE_RTF)) { setDotFontPath(Config_getString(RTF_OUTPUT)); setPath=TRUE; } else if (Config_getBool(GENERATE_DOCBOOK)) { setDotFontPath(Config_getString(DOCBOOK_OUTPUT)); setPath=TRUE; } Portable::sysTimerStart(); // fill work queue with dot operations size_t prev=1; if (m_workers.size()==0) // no threads to work with { for (auto & dr : m_runners) { msg("Running dot for graph %zu/%zu\n",prev,numDotRuns); dr.second->run(); prev++; } } else // use multiple threads to run instances of dot in parallel { for (auto & dr: m_runners) { m_queue->enqueue(dr.second.get()); } // wait for the queue to become empty while ((i=m_queue->size())>0) { i = numDotRuns - i; while (i>=prev) { msg("Running dot for graph %zu/%zu\n",prev,numDotRuns); prev++; } Portable::sleep(100); } while (numDotRuns>=prev) { msg("Running dot for graph %zu/%zu\n",prev,numDotRuns); prev++; } // signal the workers we are done for (i=0;ienqueue(0); // add terminator for each worker } // wait for the workers to finish for (i=0;iwait(); } } Portable::sysTimerStop(); if (setPath) { unsetDotFontPath(); } // patch the output file and insert the maps and figures i=1; // since patching the svg files may involve patching the header of the SVG // (for zoomable SVGs), and patching the .html files requires reading that // header after the SVG is patched, we first process the .svg files and // then the other files. for (auto & fp : m_filePatchers) { if (fp.second.isSVGFile()) { msg("Patching output file %zu/%zu\n",i,numFilePatchers); if (!fp.second.run()) return FALSE; i++; } } for (auto& fp : m_filePatchers) { if (!fp.second.isSVGFile()) { msg("Patching output file %zu/%zu\n",i,numFilePatchers); if (!fp.second.run()) return FALSE; i++; } } return TRUE; } //-------------------------------------------------------------------- void writeDotGraphFromFile(const char *inFile,const char *outDir, const char *outFile,GraphOutputFormat format) { QDir d(outDir); if (!d.exists()) { term("Output dir %s does not exist!\n",outDir); } QCString imgExt = getDotImageExtension(); QCString imgName = (QCString)outFile+"."+imgExt; QCString absImgName = d.absPath().utf8()+"/"+imgName; QCString absOutFile = d.absPath().utf8()+"/"+outFile; DotRunner dotRun(inFile); if (format==GOF_BITMAP) { dotRun.addJob(Config_getEnum(DOT_IMAGE_FORMAT),absImgName); } else // format==GOF_EPS { if (Config_getBool(USE_PDFLATEX)) { dotRun.addJob("pdf",absOutFile+".pdf"); } else { dotRun.addJob("ps",absOutFile+".eps"); } } dotRun.preventCleanUp(); if (!dotRun.run()) { return; } Doxygen::indexList->addImageFile(imgName); } /*! Writes user defined image map to the output. * \param t text stream to write to * \param inFile just the basename part of the filename * \param outDir output directory * \param relPath relative path the to root of the output dir * \param baseName the base name of the output files * \param context the scope in which this graph is found (for resolving links) * \param graphId a unique id for this graph, use for dynamic sections */ void writeDotImageMapFromFile(FTextStream &t, const QCString &inFile, const QCString &outDir, const QCString &relPath, const QCString &baseName, const QCString &context,int graphId) { QDir d(outDir); if (!d.exists()) { term("Output dir %s does not exist!\n",outDir.data()); } QCString mapName = baseName+".map"; QCString imgExt = getDotImageExtension(); QCString imgName = baseName+"."+imgExt; QCString absOutFile = d.absPath().utf8()+"/"+mapName; DotRunner dotRun(inFile.data()); dotRun.addJob(MAP_CMD,absOutFile); dotRun.preventCleanUp(); if (!dotRun.run()) { return; } if (imgExt=="svg") // vector graphics { QCString svgName=outDir+"/"+baseName+".svg"; DotFilePatcher::writeSVGFigureLink(t,relPath,baseName,svgName); DotFilePatcher patcher(svgName); patcher.addSVGConversion("",TRUE,context,TRUE,graphId); patcher.run(); } else // bitmap graphics { QGString result; FTextStream tt(&result); t << "\""" << endl; DotFilePatcher::convertMapFile(tt, absOutFile, relPath ,TRUE, context); if (!result.isEmpty()) { t << ""; t << result; t << "" << endl; } } d.remove(absOutFile); }