qdoc: qdoc was too slow

The hard-coded search order is now removed. The search order
is now constructed from the depends variable in the qdocconf
file.

The basic idea is that qdoc is run once. It gets a list of all the
qdocconf files for the modules in Qt5.
First, qdoc runs in -prepare mode for each qdocconf file in the list. It
generates the index file for each module, but these index files are
never used. At the end of the -prepare phase for each module, qdoc keeps
the tree structure for the module in a collection of trees.
Second, qdoc runs in -generate mode for each qdocconf file in the list.
But now it uses the existing tree for that module, so it doesn't have to
read the sources files again, and it doesn't have to read any index
files. Now it generates the docs for each module.

The runtime for qdoc has been reduced
by 90% when running qdoc for all of Qt5 on a not so new iMac.
Before this update, qdoc took about 10 minutes to generate
docs for Qt5. Now it takes a little over 1 minute. The new
way to run qdoc is described in the Qt bug report referenced
here.

Note that running qdoc this new (old) way also generates
fewer qdoc errors than when running qdoc the old way. This
indicates that the index files qdoc uses when running the
old way are incomplete.

Note also that the old way of running qdoc is not affected
by this update. The old way is still required for running
qdoc in the current qmake/make system. That process must be
changed to be able to use the faster qdoc. The details are
provided in the Qt bug report.

Change-Id: Ibec41d6fbaa9fc8cd070a05d04357bd02c4478f0
Task-number: QTBUG-41705
Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
This commit is contained in:
Martin Smith 2014-10-01 11:48:29 +02:00
parent 880986be23
commit 77165553af
18 changed files with 565 additions and 385 deletions

View File

@ -65,9 +65,9 @@ QT_BEGIN_NAMESPACE
#define COMMAND_TITLE Doc::alias(QLatin1String("title"))
#define COMMAND_WRAPPER Doc::alias(QLatin1String("wrapper"))
QString CodeParser::currentSubDir_;
QList<CodeParser *> CodeParser::parsers;
bool CodeParser::showInternal = false;
bool CodeParser::showInternal_ = false;
bool CodeParser::singleExec_ = false;
/*!
The constructor adds this code parser to the static
@ -93,7 +93,8 @@ CodeParser::~CodeParser()
*/
void CodeParser::initializeParser(const Config& config)
{
showInternal = config.getBool(CONFIG_SHOWINTERNAL);
showInternal_ = config.getBool(CONFIG_SHOWINTERNAL);
singleExec_ = config.getBool(CONFIG_SINGLEEXEC);
}
/*!
@ -262,7 +263,7 @@ void CodeParser::processCommonMetaCommand(const Location& location,
node->setStatus(Node::Preliminary);
}
else if (command == COMMAND_INTERNAL) {
if (!showInternal) {
if (!showInternal_) {
node->setAccess(Node::Private);
node->setStatus(Node::Internal);
if (node->type() == Node::QmlPropertyGroup) {

View File

@ -74,7 +74,6 @@ public:
static CodeParser *parserForHeaderFile(const QString &filePath);
static CodeParser *parserForSourceFile(const QString &filePath);
static void setLink(Node* node, Node::LinkType linkType, const QString& arg);
static const QString& currentOutputSubdirectory() { return currentSubDir_; }
protected:
const QSet<QString>& commonMetaCommands();
@ -89,9 +88,9 @@ protected:
QDocDatabase* qdb_;
private:
static QString currentSubDir_;
static QList<CodeParser *> parsers;
static bool showInternal;
static bool showInternal_;
static bool singleExec_;
};
QT_END_NAMESPACE

View File

@ -98,6 +98,7 @@ QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation")
QString ConfigStrings::SCRIPTDIRS = QStringLiteral("scriptdirs");
QString ConfigStrings::SCRIPTS = QStringLiteral("scripts");
QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal");
QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec");
QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs");
QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding");
QString ConfigStrings::SOURCES = QStringLiteral("sources");
@ -350,6 +351,10 @@ QString Config::getOutputDir() const
t = getString(CONFIG_OUTPUTDIR);
else
t = overrideOutputDir;
if (Generator::singleExec()) {
QString project = getString(CONFIG_PROJECT);
t += QLatin1Char('/') + project.toLower();
}
if (!Generator::useOutputSubdirs()) {
t = t.left(t.lastIndexOf('/'));
QString singleOutputSubdir = getString("HTML.outputsubdir");
@ -868,6 +873,36 @@ bool Config::isMetaKeyChar(QChar ch)
|| ch == QLatin1Char(',');
}
/*!
\a fileName is a master qdocconf file. It contains a list of
qdocconf files and nothing else. Read the list and return it.
*/
QStringList Config::loadMaster(const QString& fileName)
{
Location location = Location::null;
QFile fin(fileName);
if (!fin.open(QFile::ReadOnly | QFile::Text)) {
if (!Config::installDir.isEmpty()) {
int prefix = location.filePath().length() - location.fileName().length();
fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
}
if (!fin.open(QFile::ReadOnly | QFile::Text))
location.fatal(tr("Cannot open master qdocconf file '%1': %2").arg(fileName).arg(fin.errorString()));
}
QTextStream stream(&fin);
#ifndef QT_NO_TEXTCODEC
stream.setCodec("UTF-8");
#endif
QStringList qdocFiles;
QString line = stream.readLine();
while (!line.isNull()) {
qdocFiles.append(line);
line = stream.readLine();
}
fin.close();
return qdocFiles;
}
/*!
Load, parse, and process a qdoc configuration file. This
function is only called by the other load() function, but

View File

@ -108,6 +108,7 @@ public:
QStringList getExampleQdocFiles(const QSet<QString> &excludedDirs, const QSet<QString> &excludedFiles);
QStringList getExampleImageFiles(const QSet<QString> &excludedDirs, const QSet<QString> &excludedFiles);
static QStringList loadMaster(const QString& fileName);
static QStringList getFilesHere(const QString& dir,
const QString& nameFilter,
const Location &location = Location(),
@ -209,6 +210,7 @@ struct ConfigStrings
static QString SCRIPTDIRS;
static QString SCRIPTS;
static QString SHOWINTERNAL;
static QString SINGLEEXEC;
static QString SOURCEDIRS;
static QString SOURCEENCODING;
static QString SOURCES;
@ -282,6 +284,7 @@ struct ConfigStrings
#define CONFIG_SCRIPTDIRS ConfigStrings::SCRIPTDIRS
#define CONFIG_SCRIPTS ConfigStrings::SCRIPTS
#define CONFIG_SHOWINTERNAL ConfigStrings::SHOWINTERNAL
#define CONFIG_SINGLEEXEC ConfigStrings::SINGLEEXEC
#define CONFIG_SOURCEDIRS ConfigStrings::SOURCEDIRS
#define CONFIG_SOURCEENCODING ConfigStrings::SOURCEENCODING
#define CONFIG_SOURCES ConfigStrings::SOURCES

View File

@ -663,10 +663,10 @@ GuidMap* DitaXmlGenerator::lookupGuidMap(const QString& fileName)
*/
void DitaXmlGenerator::generateDocs()
{
if (!runPrepareOnly())
if (!preparing())
Generator::generateDocs();
if (!runGenerateOnly()) {
if (!generating()) {
QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index",
projectUrl,
@ -675,7 +675,7 @@ void DitaXmlGenerator::generateDocs()
true);
}
if (!runPrepareOnly()) {
if (!preparing()) {
writeDitaMap();
/*
Generate the XML tag file, if it was requested.

View File

@ -2816,18 +2816,6 @@ QString DocParser::slashed(const QString& str)
#define COMMAND_BRIEF Doc::alias("brief")
#define COMMAND_QMLBRIEF Doc::alias("qmlbrief")
#if 0
Doc::Doc(const Location& start_loc,
const Location& end_loc,
const QString& source,
const QSet<QString>& metaCommandSet)
{
priv = new DocPrivate(start_loc,end_loc,source);
DocParser parser;
parser.parse(source,priv,metaCommandSet,QSet<QString>());
}
#endif
/*!
Parse the qdoc comment \a source. Build up a list of all the topic
commands found including their arguments. This constructor is used
@ -3234,6 +3222,9 @@ void Doc::initialize(const Config& config)
}
}
/*!
All the heap allocated variables are deleted.
*/
void Doc::terminate()
{
DocParser::exampleFiles.clear();

View File

@ -92,7 +92,8 @@ bool Generator::debugging_ = false;
bool Generator::noLinkErrors_ = false;
bool Generator::autolinkErrors_ = false;
bool Generator::redirectDocumentationToDevNull_ = false;
Generator::Passes Generator::qdocPass_ = Both;
Generator::QDocPass Generator::qdocPass_ = Generator::Neither;
bool Generator::qdocSingleExec_ = false;
bool Generator::useOutputSubdirs_ = true;
void Generator::startDebugging(const QString& message)
@ -134,6 +135,7 @@ Generator::Generator()
inTableHeader_(false),
threeColumnEnumValueTable_(true),
showInternal_(false),
singleExec_(false),
numTableRows_(0)
{
qdb_ = QDocDatabase::qdocDB();
@ -259,7 +261,8 @@ void Generator::writeOutFileNames()
void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
{
QString path = outputDir() + QLatin1Char('/');
if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty())
if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() &&
!outputDir().endsWith(node->outputSubdirectory()))
path += node->outputSubdirectory() + QLatin1Char('/');
path += fileName;
@ -1529,7 +1532,7 @@ void Generator::initialize(const Config &config)
QDir dirInfo;
if (dirInfo.exists(outDir_)) {
if (!runGenerateOnly() && Generator::useOutputSubdirs()) {
if (!generating() && Generator::useOutputSubdirs()) {
if (!Config::removeDirContents(outDir_))
config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
}
@ -1678,6 +1681,7 @@ void Generator::initializeGenerator(const Config& config)
{
config_ = &config;
showInternal_ = config.getBool(CONFIG_SHOWINTERNAL);
singleExec_ = config.getBool(CONFIG_SINGLEEXEC);
}
bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)

View File

@ -61,7 +61,7 @@ class Generator
Q_DECLARE_TR_FUNCTIONS(QDoc::Generator)
public:
enum Passes { Both, Prepare, Generate };
enum QDocPass { Neither, Prepare, Generate };
enum ListType { Generic, Obsolete };
Generator();
@ -91,9 +91,11 @@ public:
static bool debugging() { return debugging_; }
static bool noLinkErrors() { return noLinkErrors_; }
static bool autolinkErrors() { return autolinkErrors_; }
static void setQDocPass(Passes pass) { qdocPass_ = pass; }
static bool runPrepareOnly() { return (qdocPass_ == Prepare); }
static bool runGenerateOnly() { return (qdocPass_ == Generate); }
static void setQDocPass(QDocPass t) { qdocPass_ = t; }
static bool preparing() { return (qdocPass_ == Prepare); }
static bool generating() { return (qdocPass_ == Generate); }
static bool singleExec() { return qdocSingleExec_; }
static void setSingleExec() { qdocSingleExec_ = true; }
static QString defaultModuleName() { return project; }
static void resetUseOutputSubdirs() { useOutputSubdirs_ = false; }
static bool useOutputSubdirs() { return useOutputSubdirs_; }
@ -212,7 +214,8 @@ private:
static bool noLinkErrors_;
static bool autolinkErrors_;
static bool redirectDocumentationToDevNull_;
static Passes qdocPass_;
static QDocPass qdocPass_;
static bool qdocSingleExec_;
static bool useOutputSubdirs_;
void generateReimplementedFrom(const FunctionNode *func, CodeMarker *marker);
@ -232,6 +235,7 @@ private:
bool inTableHeader_;
bool threeColumnEnumValueTable_;
bool showInternal_;
bool singleExec_;
int numTableRows_;
QString link_;
QString sectionNumber_;

View File

@ -100,8 +100,10 @@ HtmlGenerator::HtmlGenerator()
*/
HtmlGenerator::~HtmlGenerator()
{
if (helpProjectWriter)
if (helpProjectWriter) {
delete helpProjectWriter;
helpProjectWriter = 0;
}
}
/*!
@ -130,6 +132,11 @@ void HtmlGenerator::initializeGenerator(const Config &config)
Generator::initializeGenerator(config);
obsoleteLinks = config.getBool(CONFIG_OBSOLETELINKS);
setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif");
/*
The formatting maps are owned by Generator. They are cleared in
Generator::terminate().
*/
int i = 0;
while (defaults[i].key) {
formattingLeftMap().insert(defaults[i].key, defaults[i].left);
@ -211,7 +218,12 @@ void HtmlGenerator::initializeGenerator(const Config &config)
// The following line was changed to fix QTBUG-27798
//codeIndent = config.getInt(CONFIG_CODEINDENT);
helpProjectWriter = new HelpProjectWriter(config, project.toLower() + ".qhp", this);
/*
The help file write should be allocated once and only once
per qdoc execution.
*/
if (helpProjectWriter == 0)
helpProjectWriter = new HelpProjectWriter(config, project.toLower() + ".qhp", this);
// Documentation template handling
headerScripts = config.getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS);
@ -266,10 +278,10 @@ void HtmlGenerator::generateDocs()
Node* qflags = qdb_->findClassNode(QStringList("QFlags"));
if (qflags)
qflagsHref_ = linkForNode(qflags,0);
if (!runPrepareOnly())
if (!preparing())
Generator::generateDocs();
if (!runGenerateOnly()) {
if (!generating()) {
QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index",
projectUrl,
@ -278,7 +290,7 @@ void HtmlGenerator::generateDocs()
true);
}
if (!runPrepareOnly()) {
if (!preparing()) {
helpProjectWriter->generate();
generateManifestFiles();
/*
@ -2313,7 +2325,7 @@ QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner,
out() << ", including inherited members.</p>\n";
Section section = sections.first();
generateSectionList(section, 0, marker, CodeMarker::Subpage);
generateSectionList(section, inner, marker, CodeMarker::Subpage);
generateFooter();
endSubPage();
@ -2369,7 +2381,7 @@ QString HtmlGenerator::generateAllQmlMembersFile(QmlClassNode* qml_cn, CodeMarke
prefix = keys.at(j).mid(1);
prefix = prefix.left(keys.at(j).indexOf("::")+1);
}
generateQmlItem(nodes[j], qcn, marker, true);
generateQmlItem(nodes[j], qml_cn, marker, true);
if (nodes[j]->isAttached())
out() << " [attached]";
//generateSynopsis(nodes[j], qcn, marker, CodeMarker::Subpage, false, &prefix);
@ -2796,8 +2808,9 @@ void HtmlGenerator::generateCompactList(ListType listType,
else if (listType == Obsolete) {
QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension();
QString link;
if (useOutputSubdirs())
if (useOutputSubdirs()) {
link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/'));
}
link += fileName;
out() << "<a href=\"" << link << "\">";
}
@ -2837,7 +2850,7 @@ void HtmlGenerator::generateFunctionIndex(const Node *relative)
char currentLetter;
out() << "<ul>\n";
NodeMapMap funcIndex = qdb_->getFunctionIndex();
NodeMapMap& funcIndex = qdb_->getFunctionIndex();
QMap<QString, NodeMap >::ConstIterator f = funcIndex.constBegin();
while (f != funcIndex.constEnd()) {
out() << "<li>";
@ -3723,7 +3736,6 @@ QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const
return link;
}
/*!
Construct the link string for the \a node and return it.
The \a relative node is use to decide the link we are
@ -3778,7 +3790,10 @@ QString HtmlGenerator::linkForNode(const Node *node, const Node *relative)
if (node && relative && (node != relative)) {
if (useOutputSubdirs() && !node->isExternalPage() &&
node->outputSubdirectory() != relative->outputSubdirectory()) {
link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/')));
if (link.startsWith(node->outputSubdirectory()))
link.prepend(QString("../"));
else
link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/')));
}
}
return link;
@ -4507,6 +4522,9 @@ void HtmlGenerator::generateManifestFile(QString manifest, QString element)
Reads metacontent - additional attributes and tags to apply
when generating manifest files, read from config. Takes the
configuration class \a config as a parameter.
The manifest metacontent map is cleared immediately after
the manifest files have been generated.
*/
void HtmlGenerator::readManifestMetaContent(const Config &config)
{

View File

@ -256,7 +256,7 @@ QString Location::canonicalRelativePath(const QString &path)
*/
void Location::warning(const QString& message, const QString& details) const
{
if (!Generator::runPrepareOnly())
if (!Generator::preparing())
emitMessage(Warning, message, details);
}
@ -267,7 +267,7 @@ void Location::warning(const QString& message, const QString& details) const
*/
void Location::error(const QString& message, const QString& details) const
{
if (!Generator::runPrepareOnly())
if (!Generator::preparing())
emitMessage(Error, message, details);
}

View File

@ -63,7 +63,6 @@
QT_BEGIN_NAMESPACE
bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
{
return fi1.lastModified() < fi2.lastModified();
@ -71,6 +70,7 @@ bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
static bool highlighting = false;
static bool showInternal = false;
static bool singleExec = false;
static bool redirectDocumentationToDevNull = false;
static bool noLinkErrors = false;
static bool autolinkErrors = false;
@ -80,14 +80,22 @@ static QStringList dependModules;
static QStringList indexDirs;
static QString currentDir;
static QString prevCurrentDir;
static QHash<QString,QString> defaults;
#ifndef QT_NO_TRANSLATION
typedef QPair<QString, QTranslator*> Translator;
static QList<Translator> translators;
#endif
/*!
Read some XML indexes containing definitions from other
documentation sets. \a config contains a variable that
lists directories where index files can bge found. It also
contains the \c depends variable, which lists the modules
that the current module depends on.
*/
static void loadIndexFiles(Config& config)
{
QDocDatabase* qdb = QDocDatabase::qdocDB();
/*
Read some XML indexes containing definitions from other documentation sets.
*/
QStringList indexFiles;
QStringList configIndexes = config.getStringList(CONFIG_INDEXES);
foreach (const QString &index, configIndexes) {
@ -194,42 +202,25 @@ static void loadIndexFiles(Config& config)
*/
static void processQdocconfFile(const QString &fileName)
{
#ifndef QT_NO_TRANSLATION
QList<QTranslator *> translators;
#endif
/*
The Config instance represents the configuration data for qdoc.
All the other classes are initialized with the config. Here we
All the other classes are initialized with the config. Below, we
initialize the configuration with some default values.
I don't think the call to translate() does anything here. For one
thing, the translators haven't been installed at this point. And
I doubt any translator would translate QDoc anyway. But I left it
here because it does no harm.
*/
Config config(QCoreApplication::translate("QDoc", "qdoc"));
/*
The default indent for code is 4.
The default value for false is 0.
The default supported file extensions are cpp, h, qdoc and qml.
The default language is c++.
The default output format is html.
The default tab size is 8.
And those are all the default values for configuration variables.
*/
static QHash<QString,QString> defaults;
if (defaults.isEmpty()) {
defaults.insert(CONFIG_CODEINDENT, QLatin1String("4"));
defaults.insert(CONFIG_FALSEHOODS, QLatin1String("0"));
defaults.insert(CONFIG_FILEEXTENSIONS, QLatin1String("*.cpp *.h *.qdoc *.qml"));
defaults.insert(CONFIG_LANGUAGE, QLatin1String("Cpp"));
defaults.insert(CONFIG_OUTPUTFORMATS, QLatin1String("HTML"));
defaults.insert(CONFIG_TABSIZE, QLatin1String("8"));
}
QHash<QString,QString>::iterator iter;
for (iter = defaults.begin(); iter != defaults.end(); ++iter)
config.setStringList(iter.key(), QStringList() << iter.value());
config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false"));
config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false"));
config.setStringList(CONFIG_SINGLEEXEC, QStringList(singleExec ? "true" : "false"));
config.setStringList(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, QStringList(redirectDocumentationToDevNull ? "true" : "false"));
config.setStringList(CONFIG_NOLINKERRORS, QStringList(noLinkErrors ? "true" : "false"));
config.setStringList(CONFIG_AUTOLINKERRORS, QStringList(autolinkErrors ? "true" : "false"));
@ -247,8 +238,8 @@ static void processQdocconfFile(const QString &fileName)
currentDir = QFileInfo(fileName).path();
Location::initialize(config);
config.load(fileName);
QString project = config.getString(CONFIG_PROJECT).toLower();
//qDebug() << "\nStart project:" << project;
QString project = config.getString(CONFIG_PROJECT);
//qDebug() << "Start project:" << project;
/*
Add the defines to the configuration variables.
*/
@ -261,17 +252,24 @@ static void processQdocconfFile(const QString &fileName)
if (!currentDir.isEmpty())
QDir::setCurrent(currentDir);
QString phase;
if (Generator::runPrepareOnly())
phase = " in -prepare mode ";
else if (Generator::runGenerateOnly())
phase = " in -generate mode ";
QString phase = " in -";
if (Generator::singleExec())
phase += "single exec mode, ";
else
phase += "separate exec mode, ";
if (Generator::preparing())
phase += "prepare phase ";
else if (Generator::generating())
phase += "generate phase ";
QString msg = "Running qdoc for " + config.getString(CONFIG_PROJECT) + phase;
Location::logToStdErr(msg);
/*
Initialize all the classes and data structures with the
qdoc configuration.
qdoc configuration. This is safe to do for each qdocconf
file processed, because all the data structures created
are either cleared after they have been used, or they
are cleared in the terminate() functions below.
*/
Location::initialize(config);
Tokenizer::initialize(config);
@ -282,16 +280,32 @@ static void processQdocconfFile(const QString &fileName)
#ifndef QT_NO_TRANSLATION
/*
Load the language translators, if the configuration specifies any.
Load the language translators, if the configuration specifies any,
but only if they haven't already been loaded. This works in both
-prepare/-generate mode and -singleexec mode.
*/
QStringList fileNames = config.getStringList(CONFIG_TRANSLATORS);
QStringList::ConstIterator fn = fileNames.constBegin();
while (fn != fileNames.constEnd()) {
QTranslator *translator = new QTranslator(0);
if (!translator->load(*fn))
config.lastLocation().error(QCoreApplication::translate("QDoc", "Cannot load translator '%1'").arg(*fn));
QCoreApplication::instance()->installTranslator(translator);
translators.append(translator);
bool found = false;
if (!translators.isEmpty()) {
for (int i=0; i<translators.size(); ++i) {
if (translators.at(i).first == *fn) {
found = true;
break;
}
}
}
if (!found) {
QTranslator *translator = new QTranslator(0);
if (!translator->load(*fn)) {
config.lastLocation().error(QCoreApplication::translate("QDoc", "Cannot load translator '%1'").arg(*fn));
}
else {
QCoreApplication::instance()->installTranslator(translator);
translators.append(Translator(*fn, translator));
}
}
++fn;
}
#endif
@ -311,175 +325,227 @@ static void processQdocconfFile(const QString &fileName)
will be stored. The database includes a tree of nodes, which gets
built as the source files are parsed. The documentation output is
generated by traversing that tree.
Note: qdocDB() allocates a new instance only if no instance exists.
So it is safe to call qdocDB() any time.
*/
QDocDatabase* qdb = QDocDatabase::qdocDB();
qdb->setVersion(config.getString(CONFIG_VERSION));
qdb->setShowInternal(config.getBool(CONFIG_SHOWINTERNAL));
qdb->setSingleExec(config.getBool(CONFIG_SINGLEEXEC));
/*
By default, the only output format is HTML.
*/
QSet<QString> outputFormats = config.getOutputFormats();
Location outputFormatsLocation = config.lastLocation();
//if (!Generator::runPrepareOnly())
Generator::debug(" loading index files");
loadIndexFiles(config);
qdb->newPrimaryTree(config.getString(CONFIG_PROJECT));
qdb->setSearchOrder();
Generator::debug(" done loading index files");
qdb->clearSearchOrder();
QString p = config.getString(CONFIG_PROJECT).toLower();
if (!Generator::singleExec()) {
Generator::debug(" loading index files");
loadIndexFiles(config);
Generator::debug(" done loading index files");
qdb->newPrimaryTree(p);
}
else if (Generator::preparing())
qdb->newPrimaryTree(p);
else
qdb->setPrimaryTree(p);
dependModules = config.getStringList(CONFIG_DEPENDS);
dependModules.removeDuplicates();
qdb->setSearchOrder(dependModules);
QSet<QString> excludedDirs;
QSet<QString> excludedFiles;
QStringList headerList;
QStringList sourceList;
QStringList excludedDirsList;
QStringList excludedFilesList;
Generator::debug("Reading excludedirs");
excludedDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS);
foreach (const QString &excludeDir, excludedDirsList) {
QString p = QDir::fromNativeSeparators(excludeDir);
QDir tmp(p);
if (tmp.exists())
excludedDirs.insert(p);
}
if (!Generator::singleExec() || !Generator::generating()) {
QStringList headerList;
QStringList sourceList;
Generator::debug("Reading excludefiles");
excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES);
foreach (const QString& excludeFile, excludedFilesList) {
QString p = QDir::fromNativeSeparators(excludeFile);
excludedFiles.insert(p);
}
Generator::debug("Reading excludedirs");
excludedDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS);
foreach (const QString &excludeDir, excludedDirsList) {
QString p = QDir::fromNativeSeparators(excludeDir);
QDir tmp(p);
if (tmp.exists())
excludedDirs.insert(p);
}
Generator::debug("Reading headerdirs");
headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles);
QMap<QString,QString> headers;
QMultiMap<QString,QString> headerFileNames;
for (int i=0; i<headerList.size(); ++i) {
if (headerList[i].contains(QString("doc/snippets")))
continue;
if (headers.contains(headerList[i]))
continue;
headers.insert(headerList[i],headerList[i]);
QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1);
headerFileNames.insert(t,t);
}
Generator::debug("Reading excludefiles");
excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES);
foreach (const QString& excludeFile, excludedFilesList) {
QString p = QDir::fromNativeSeparators(excludeFile);
excludedFiles.insert(p);
}
Generator::debug("Reading sourcedirs");
sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles);
QMap<QString,QString> sources;
QMultiMap<QString,QString> sourceFileNames;
for (int i=0; i<sourceList.size(); ++i) {
if (sourceList[i].contains(QString("doc/snippets")))
continue;
if (sources.contains(sourceList[i]))
continue;
sources.insert(sourceList[i],sourceList[i]);
QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1);
sourceFileNames.insert(t,t);
}
/*
Find all the qdoc files in the example dirs, and add
them to the source files to be parsed.
*/
Generator::debug("Reading exampledirs");
QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
for (int i=0; i<exampleQdocList.size(); ++i) {
if (!sources.contains(exampleQdocList[i])) {
sources.insert(exampleQdocList[i],exampleQdocList[i]);
QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1);
Generator::debug("Reading headerdirs");
headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles);
QMap<QString,QString> headers;
QMultiMap<QString,QString> headerFileNames;
for (int i=0; i<headerList.size(); ++i) {
if (headerList[i].contains(QString("doc/snippets")))
continue;
if (headers.contains(headerList[i]))
continue;
headers.insert(headerList[i],headerList[i]);
QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1);
headerFileNames.insert(t,t);
}
Generator::debug("Reading sourcedirs");
sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles);
QMap<QString,QString> sources;
QMultiMap<QString,QString> sourceFileNames;
for (int i=0; i<sourceList.size(); ++i) {
if (sourceList[i].contains(QString("doc/snippets")))
continue;
if (sources.contains(sourceList[i]))
continue;
sources.insert(sourceList[i],sourceList[i]);
QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1);
sourceFileNames.insert(t,t);
}
}
Generator::debug("Adding doc/image dirs found in exampledirs to imagedirs");
QSet<QString> exampleImageDirs;
QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
for (int i=0; i<exampleImageList.size(); ++i) {
if (exampleImageList[i].contains("doc/images")) {
QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10);
if (!exampleImageDirs.contains(t)) {
exampleImageDirs.insert(t);
/*
Find all the qdoc files in the example dirs, and add
them to the source files to be parsed.
*/
Generator::debug("Reading exampledirs");
QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
for (int i=0; i<exampleQdocList.size(); ++i) {
if (!sources.contains(exampleQdocList[i])) {
sources.insert(exampleQdocList[i],exampleQdocList[i]);
QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1);
sourceFileNames.insert(t,t);
}
}
}
Generator::augmentImageDirs(exampleImageDirs);
/*
Parse each header file in the set using the appropriate parser and add it
to the big tree.
*/
QSet<CodeParser *> usedParsers;
Generator::debug("Parsing header files");
int parsed = 0;
QMap<QString,QString>::ConstIterator h = headers.constBegin();
while (h != headers.constEnd()) {
CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key());
if (codeParser) {
++parsed;
Generator::debug(QString("Parsing " + h.key()));
codeParser->parseHeaderFile(config.location(), h.key());
usedParsers.insert(codeParser);
Generator::debug("Adding doc/image dirs found in exampledirs to imagedirs");
QSet<QString> exampleImageDirs;
QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
for (int i=0; i<exampleImageList.size(); ++i) {
if (exampleImageList[i].contains("doc/images")) {
QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10);
if (!exampleImageDirs.contains(t)) {
exampleImageDirs.insert(t);
}
}
}
++h;
}
Generator::augmentImageDirs(exampleImageDirs);
foreach (CodeParser *codeParser, usedParsers)
codeParser->doneParsingHeaderFiles();
/*
Parse each header file in the set using the appropriate parser and add it
to the big tree.
*/
QSet<CodeParser *> usedParsers;
usedParsers.clear();
qdb->resolveInheritance();
/*
Parse each source text file in the set using the appropriate parser and
add it to the big tree.
*/
parsed = 0;
Generator::debug("Parsing source files");
QMap<QString,QString>::ConstIterator s = sources.constBegin();
while (s != sources.constEnd()) {
CodeParser *codeParser = CodeParser::parserForSourceFile(s.key());
if (codeParser) {
++parsed;
Generator::debug(QString("Parsing " + s.key()));
codeParser->parseSourceFile(config.location(), s.key());
usedParsers.insert(codeParser);
Generator::debug("Parsing header files");
int parsed = 0;
QMap<QString,QString>::ConstIterator h = headers.constBegin();
while (h != headers.constEnd()) {
CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key());
if (codeParser) {
++parsed;
Generator::debug(QString("Parsing " + h.key()));
codeParser->parseHeaderFile(config.location(), h.key());
usedParsers.insert(codeParser);
}
++h;
}
++s;
foreach (CodeParser *codeParser, usedParsers)
codeParser->doneParsingHeaderFiles();
usedParsers.clear();
//qDebug() << "CALL: resolveInheritance()";
qdb->resolveInheritance();
/*
Parse each source text file in the set using the appropriate parser and
add it to the big tree.
*/
parsed = 0;
Generator::debug("Parsing source files");
QMap<QString,QString>::ConstIterator s = sources.constBegin();
while (s != sources.constEnd()) {
CodeParser *codeParser = CodeParser::parserForSourceFile(s.key());
if (codeParser) {
++parsed;
Generator::debug(QString("Parsing " + s.key()));
codeParser->parseSourceFile(config.location(), s.key());
usedParsers.insert(codeParser);
}
++s;
}
Generator::debug(QString("Parsing done."));
foreach (CodeParser *codeParser, usedParsers)
codeParser->doneParsingSourceFiles();
/*
Now the primary tree has been built from all the header and
source files. Resolve all the class names, function names,
targets, URLs, links, and other stuff that needs resolving.
*/
Generator::debug("Resolving stuff prior to generating docs");
//qDebug() << "CALL: resolveIssues()";
qdb->resolveIssues();
}
Generator::debug(QString("Parsing done."));
else {
Generator::debug("Reading excludedirs");
excludedDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS);
foreach (const QString &excludeDir, excludedDirsList) {
QString p = QDir::fromNativeSeparators(excludeDir);
QDir tmp(p);
if (tmp.exists())
excludedDirs.insert(p);
}
foreach (CodeParser *codeParser, usedParsers)
codeParser->doneParsingSourceFiles();
Generator::debug("Reading excludefiles");
excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES);
foreach (const QString& excludeFile, excludedFilesList) {
QString p = QDir::fromNativeSeparators(excludeFile);
excludedFiles.insert(p);
}
Generator::debug("Adding doc/image dirs found in exampledirs to imagedirs");
QSet<QString> exampleImageDirs;
QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
for (int i=0; i<exampleImageList.size(); ++i) {
if (exampleImageList[i].contains("doc/images")) {
QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10);
if (!exampleImageDirs.contains(t)) {
exampleImageDirs.insert(t);
}
}
}
Generator::augmentImageDirs(exampleImageDirs);
qdb->resolveStuff();
}
/*
Now the big tree has been built from all the header and
source files. Resolve all the class names, function names,
targets, URLs, links, and other stuff that needs resolving.
*/
Generator::debug("Resolving stuff prior to generating docs");
qdb->resolveIssues();
/*
The tree is built and all the stuff that needed resolving
has been resolved. Now traverse the tree and generate the
documentation output. More than one output format can be
requested. The tree is traversed for each one.
The primary tree is built and all the stuff that needed
resolving has been resolved. Now traverse the tree and
generate the documentation output. More than one output
format can be requested. The tree is traversed for each
one.
*/
Generator::debug("Generating docs");
//qDebug() << "CALL: generateDocs()";
QSet<QString>::ConstIterator of = outputFormats.constBegin();
while (of != outputFormats.constEnd()) {
Generator* generator = Generator::generatorForFormat(*of);
if (generator == 0)
outputFormatsLocation.fatal(QCoreApplication::translate("QDoc", "Unknown output format '%1'").arg(*of));
outputFormatsLocation.fatal(QCoreApplication::translate("QDoc",
"Unknown output format '%1'").arg(*of));
generator->generateDocs();
++of;
}
//Generator::writeOutFileNames();
Generator::debug("Shutting down qdoc");
Generator::debug("Terminating qdoc classes");
if (Generator::debugging())
Generator::stopDebugging(project);
@ -492,17 +558,7 @@ static void processQdocconfFile(const QString &fileName)
Location::terminate();
QDir::setCurrent(prevCurrentDir);
#ifndef QT_NO_TRANSLATION
qDeleteAll(translators);
#endif
#ifdef DEBUG_SHUTDOWN_CRASH
qDebug() << "main(): Delete qdoc database";
#endif
QDocDatabase::destroyQdocDB();
#ifdef DEBUG_SHUTDOWN_CRASH
qDebug() << "main(): qdoc database deleted";
#endif
Generator::debug("qdoc finished!");
Generator::debug("qdoc classes terminated");
}
extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed;
@ -621,12 +677,17 @@ int main(int argc, char **argv)
logProgressOption.setDescription(QCoreApplication::translate("qdoc", "Log progress on stderr."));
parser.addOption(logProgressOption);
QCommandLineOption singleExecOption(QStringList() << QStringLiteral("single-exec"));
singleExecOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc once over all the qdoc conf files."));
parser.addOption(singleExecOption);
parser.process(app);
defines += parser.values(defineOption);
dependModules += parser.values(dependsOption);
highlighting = parser.isSet(highlightingOption);
showInternal = parser.isSet(showInternalOption);
singleExec = parser.isSet(singleExecOption);
redirectDocumentationToDevNull = parser.isSet(redirectDocumentationToDevNullOption);
Config::generateExamples = !parser.isSet(noExamplesOption);
foreach (const QString &indexDir, parser.values(indexDirOption)) {
@ -650,21 +711,71 @@ int main(int argc, char **argv)
Generator::setQDocPass(Generator::Prepare);
if (parser.isSet(generateOption))
Generator::setQDocPass(Generator::Generate);
if (parser.isSet(singleExecOption))
Generator::setSingleExec();
if (parser.isSet(logProgressOption))
Location::startLoggingProgress();
const QStringList qdocFiles = parser.positionalArguments();
/*
The default indent for code is 4.
The default value for false is 0.
The default supported file extensions are cpp, h, qdoc and qml.
The default language is c++.
The default output format is html.
The default tab size is 8.
And those are all the default values for configuration variables.
*/
if (defaults.isEmpty()) {
defaults.insert(CONFIG_CODEINDENT, QLatin1String("4"));
defaults.insert(CONFIG_FALSEHOODS, QLatin1String("0"));
defaults.insert(CONFIG_FILEEXTENSIONS, QLatin1String("*.cpp *.h *.qdoc *.qml"));
defaults.insert(CONFIG_LANGUAGE, QLatin1String("Cpp"));
defaults.insert(CONFIG_OUTPUTFORMATS, QLatin1String("HTML"));
defaults.insert(CONFIG_TABSIZE, QLatin1String("8"));
}
QStringList qdocFiles = parser.positionalArguments();
if (qdocFiles.isEmpty())
parser.showHelp();
if (singleExec)
qdocFiles = Config::loadMaster(qdocFiles.at(0));
/*
Main loop.
Main loop is now modified to handle single exec mode.
*/
if (Generator::singleExec())
Generator::setQDocPass(Generator::Prepare);
foreach (const QString &qf, qdocFiles) {
//qDebug() << "PROCESSING:" << qf;
dependModules.clear();
processQdocconfFile(qf);
}
if (Generator::singleExec()) {
Generator::setQDocPass(Generator::Generate);
QDocDatabase* qdb = QDocDatabase::qdocDB();
qdb->processForest();
foreach (const QString &qf, qdocFiles) {
dependModules.clear();
processQdocconfFile(qf);
}
}
#ifndef QT_NO_TRANSLATION
if (!translators.isEmpty()) {
for (int i=0; i<translators.size(); ++i) {
delete translators.at(i).second;
}
}
translators.clear();
#endif
#ifdef DEBUG_SHUTDOWN_CRASH
qDebug() << "main(): Delete qdoc database";
#endif
QDocDatabase::destroyQdocDB();
#ifdef DEBUG_SHUTDOWN_CRASH
qDebug() << "main(): qdoc database deleted";
#endif
return EXIT_SUCCESS;
}

View File

@ -218,7 +218,7 @@ Node::Node(Type type, InnerNode *parent, const QString& name)
{
if (parent_)
parent_->addChild(this);
outSubDir_ = CodeParser::currentOutputSubdirectory();
outSubDir_ = Generator::outputSubdir();
if (operators_.isEmpty()) {
operators_.insert("++","inc");
operators_.insert("--","dec");

View File

@ -127,143 +127,55 @@ Tree* QDocForest::nextTree()
Returns the pointer to the primary tree.
*/
/*!
Finds the tree for module \a t in the forest and
sets the primary tree to be that tree. After the
primary tree is set, that tree is removed from the
forest.
\node It gets re-inserted into the forest after the
search order is built.
*/
void QDocForest::setPrimaryTree(const QString& t)
{
primaryTree_ = findTree(t);
forest_.remove(t);
if (!primaryTree_)
qDebug() << "ERROR: Could not set primary tree to:" << t;
}
/*!
If the search order array is empty, create the search order.
If the search order array is not empty, do nothing.
*/
void QDocForest::setSearchOrder()
void QDocForest::setSearchOrder(QStringList& t)
{
if (!searchOrder_.isEmpty())
return;
QString primaryName = primaryTree()->moduleName();
searchOrder_.clear();
/* Allocate space for the search order. */
searchOrder_.reserve(forest_.size()+1);
searchOrder_.clear();
moduleNames_.reserve(forest_.size()+1);
moduleNames_.clear();
/* The primary tree is always first in the search order. */
QString primaryName = primaryTree()->moduleName();
searchOrder_.append(primaryTree_);
moduleNames_.append(primaryName);
forest_.remove(primaryName);
QMap<QString, Tree*>::iterator i;
if (primaryName != "QtCore") {
i = forest_.find("QtCore");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtCore");
forest_.erase(i);
foreach (QString m, t) {
if (primaryName != m) {
i = forest_.find(m);
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append(m);
forest_.remove(m);
}
}
}
if (primaryName != "QtGui") {
i = forest_.find("QtGui");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtGui");
forest_.erase(i);
}
}
if (primaryName != "QtNetwork") {
i = forest_.find("QtNetwork");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtNetwork");
forest_.erase(i);
}
}
if (primaryName != "QtOpenGL") {
i = forest_.find("QtOpenGL");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtOpenGL");
forest_.erase(i);
}
}
if (primaryName != "QtWidgets") {
i = forest_.find("QtWidgets");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtWidgets");
forest_.erase(i);
}
}
if (primaryName != "QtSql") {
i = forest_.find("QtSql");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtSql");
forest_.erase(i);
}
}
if (primaryName != "QtXml") {
i = forest_.find("QtXml");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtXml");
forest_.erase(i);
}
}
if (primaryName != "QtSvg") {
i = forest_.find("QtSvg");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtSvg");
forest_.erase(i);
}
}
if (primaryName != "QtDoc") {
i = forest_.find("QtDoc");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtDoc");
forest_.erase(i);
}
}
if (primaryName != "QtQuick") {
i = forest_.find("QtQuick");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtQuick");
forest_.erase(i);
}
}
if (primaryName != "QtQml") {
i = forest_.find("QtQml");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtQml");
forest_.erase(i);
}
}
if (primaryName != "QtPrintSupport") {
i = forest_.find("QtPrintSupport");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtPrintSupport");
forest_.erase(i);
}
}
if (primaryName != "QtGraphicalEffects") {
i = forest_.find("QtGraphicalEffects");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtGraphicalEffects");
forest_.erase(i);
}
}
if (primaryName != "QtConcurrent") {
i = forest_.find("QtConcurrent");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("QtConcurrent");
forest_.erase(i);
}
}
#if 0
if (primaryName != "zzz") {
i = forest_.find("zzz");
if (i != forest_.end()) {
searchOrder_.append(i.value());
moduleNames_.append("zzz");
forest_.erase(i);
}
}
#endif
/*
If any trees remain in the forest, just add them
to the search order sequentially, because we don't
@ -283,15 +195,21 @@ void QDocForest::setSearchOrder()
Rebuild the forest after constructing the search order.
It was destroyed during construction of the search order,
but it is needed for module-specific searches.
Note that this loop also inserts the primary tree into the
forrest. That is a requirement.
*/
for (int i=0; i<searchOrder_.size(); ++i) {
forest_.insert(moduleNames_.at(i).toLower(), searchOrder_.at(i));
if (!forest_.contains(moduleNames_.at(i))) {
forest_.insert(moduleNames_.at(i), searchOrder_.at(i));
}
}
#if 0
qDebug() << " SEARCH ORDER:";
qDebug() << " SEARCH ORDER:";
for (int i=0; i<moduleNames_.size(); ++i)
qDebug() << " " << i+1 << "." << moduleNames_.at(i);
qDebug() << " " << i+1 << "." << moduleNames_.at(i);
qDebug() << " FOREST:" << forest_.keys();
qDebug() << "SEARCH ORDER:" << moduleNames_;
#endif
}
@ -356,6 +274,7 @@ const QVector<Tree*>& QDocForest::indexSearchOrder()
*/
NamespaceNode* QDocForest::newIndexTree(const QString& module)
{
//qDebug() << " New index tree:" << module;
primaryTree_ = new Tree(module, qdb_);
forest_.insert(module, primaryTree_);
return primaryTree_->root();
@ -363,10 +282,11 @@ NamespaceNode* QDocForest::newIndexTree(const QString& module)
/*!
Create a new Tree for use as the primary tree. This tree
will represent the primary module.
will represent the primary module. \a module is camel case.
*/
void QDocForest::newPrimaryTree(const QString& module)
{
//qDebug() << " New primary tree:" << module;
primaryTree_ = new Tree(module, qdb_);
}
@ -445,8 +365,19 @@ NodeMap QDocDatabase::typeNodeMap_;
constructs the \a forest_ object, which is also a singleton.
\a showInternal_ is normally false. If it is true, qdoc will
write documentation for nodes marked \c internal.
\a singleExec_ is false when qdoc is being used in the standard
way of running qdoc twices for each module, first with -prepare
and then with -generate. First the -prepare phase is run for
each module, then the -generate phase is run for each module.
When \a singleExec_ is true, qdoc is run only once. During the
single execution, qdoc processes the qdocconf files for all the
modules sequentially in a loop. Each source file for each module
is read exactly once.
*/
QDocDatabase::QDocDatabase() : showInternal_(false), forest_(this)
QDocDatabase::QDocDatabase()
: showInternal_(false), singleExec_(false), forest_(this)
{
// nothing
}
@ -809,13 +740,41 @@ QmlClassNode* QDocDatabase::findQmlType(const ImportRec& import, const QString&
}
/*!
This function calls \a func for each tree in the forest.
This function calls a set of functions for each tree in the
forest that has not already been analyzed. In this way, when
running qdoc in \e singleExec mode, each tree is analyzed in
turn, and its classes and types are added to the appropriate
node maps.
*/
void QDocDatabase::processForest()
{
Tree* t = forest_.firstTree();
while (t) {
findAllNamespaces(t->root());
findAllClasses(t->root());
findAllFunctions(t->root());
findAllObsoleteThings(t->root());
findAllLegaleseTexts(t->root());
findAllSince(t->root());
t->setTreeHasBeenAnalyzed();
t = forest_.nextTree();
}
}
/*!
This function calls \a func for each tree in the forest,
but only if Tree::treeHasBeenAnalyzed() returns false for
the tree. In this way, when running qdoc in \e singleExec
mode, each tree is analyzed in turn, and its classes and
types are added to the appropriate node maps.
*/
void QDocDatabase::processForest(void (QDocDatabase::*func) (InnerNode*))
{
Tree* t = forest_.firstTree();
while (t) {
(this->*(func))(t->root());
if (!t->treeHasBeenAnalyzed()) {
(this->*(func))(t->root());
}
t = forest_.nextTree();
}
}
@ -887,7 +846,7 @@ NodeMap& QDocDatabase::getNamespaces()
*/
NodeMap& QDocDatabase::getServiceClasses()
{
if (nonCompatClasses_.isEmpty() && qmlClasses_.isEmpty())
if (cppClasses_.isEmpty() && qmlTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return serviceClasses_;
}
@ -899,7 +858,7 @@ NodeMap& QDocDatabase::getServiceClasses()
*/
NodeMap& QDocDatabase::getQmlBasicTypes()
{
if (nonCompatClasses_.isEmpty() && qmlBasicTypes_.isEmpty())
if (cppClasses_.isEmpty() && qmlBasicTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return qmlBasicTypes_;
}
@ -911,9 +870,9 @@ NodeMap& QDocDatabase::getQmlBasicTypes()
*/
NodeMap& QDocDatabase::getQmlTypes()
{
if (nonCompatClasses_.isEmpty() && qmlClasses_.isEmpty())
if (cppClasses_.isEmpty() && qmlTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return qmlClasses_;
return qmlTypes_;
}
/*!
@ -935,7 +894,7 @@ NodeMap& QDocDatabase::getObsoleteClasses()
*/
NodeMap& QDocDatabase::getCompatibilityClasses()
{
if (nonCompatClasses_.isEmpty() && qmlClasses_.isEmpty())
if (cppClasses_.isEmpty() && qmlTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return compatClasses_;
}
@ -950,7 +909,7 @@ NodeMap& QDocDatabase::getCompatibilityClasses()
*/
NodeMap& QDocDatabase::getMainClasses()
{
if (nonCompatClasses_.isEmpty() && qmlClasses_.isEmpty())
if (cppClasses_.isEmpty() && qmlTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return mainClasses_;
}
@ -962,9 +921,9 @@ NodeMap& QDocDatabase::getMainClasses()
*/
NodeMap& QDocDatabase::getCppClasses()
{
if (nonCompatClasses_.isEmpty() && qmlClasses_.isEmpty())
if (cppClasses_.isEmpty() && qmlTypes_.isEmpty())
processForest(&QDocDatabase::findAllClasses);
return nonCompatClasses_;
return cppClasses_;
}
/*!
@ -987,7 +946,7 @@ void QDocDatabase::findAllClasses(InnerNode* node)
compatClasses_.insert(className, *c);
}
else {
nonCompatClasses_.insert(className, *c);
cppClasses_.insert(className, *c);
if ((*c)->status() == Node::Main)
mainClasses_.insert(className, *c);
}
@ -1000,9 +959,9 @@ void QDocDatabase::findAllClasses(InnerNode* node)
else if (((*c)->isQmlType() || (*c)->isQmlBasicType())&& !(*c)->doc().isEmpty()) {
QString qmlTypeName = (*c)->name();
if (qmlTypeName.startsWith(QLatin1String("QML:")))
qmlClasses_.insert(qmlTypeName.mid(4),*c);
qmlTypes_.insert(qmlTypeName.mid(4),*c);
else
qmlClasses_.insert(qmlTypeName,*c);
qmlTypes_.insert(qmlTypeName,*c);
//also add to the QML basic type map
if ((*c)->isQmlBasicType())
@ -1022,7 +981,6 @@ void QDocDatabase::findAllClasses(InnerNode* node)
*/
NodeMapMap& QDocDatabase::getFunctionIndex()
{
funcIndex_.clear();
processForest(&QDocDatabase::findAllFunctions);
return funcIndex_;
}
@ -1314,7 +1272,15 @@ const NodeMultiMap& QDocDatabase::getSinceMap(const QString& key)
*/
void QDocDatabase::resolveIssues() {
resolveQmlInheritance(primaryTreeRoot());
resolveTargets();
primaryTree()->resolveTargets(primaryTreeRoot());
primaryTree()->resolveCppToQmlLinks();
}
void QDocDatabase::resolveStuff()
{
primaryTree()->resolveInheritance();
resolveQmlInheritance(primaryTreeRoot());
//primaryTree()->resolveTargets(primaryTreeRoot());
primaryTree()->resolveCppToQmlLinks();
}
@ -1424,10 +1390,18 @@ void QDocDatabase::generateTagFile(const QString& name, Generator* g)
}
/*!
Reads and parses the qdoc index files listed in \a indexFiles.
Reads and parses the qdoc index files listed in \a t.
*/
void QDocDatabase::readIndexes(const QStringList& indexFiles)
void QDocDatabase::readIndexes(const QStringList& t)
{
QStringList indexFiles;
foreach (const QString& f, t) {
QString fn = f.mid(f.lastIndexOf(QChar('/'))+1);
if (!isLoaded(fn))
indexFiles << f;
else
qDebug() << "This index file is already in memory:" << f;
}
QDocIndexFiles::qdocIndexFiles()->readIndexes(indexFiles);
QDocIndexFiles::destroyQDocIndexFiles();
}
@ -1443,6 +1417,8 @@ void QDocDatabase::generateIndex(const QString& fileName,
Generator* g,
bool generateInternalNodes)
{
QString t = fileName.mid(fileName.lastIndexOf(QChar('/'))+1);
primaryTree()->setIndexFileName(t);
QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g, generateInternalNodes);
QDocIndexFiles::destroyQDocIndexFiles();
}

View File

@ -78,7 +78,14 @@ class QDocForest
bool done() { return (currentIndex_ >= searchOrder().size()); }
const QVector<Tree*>& searchOrder();
const QVector<Tree*>& indexSearchOrder();
void setSearchOrder();
void setSearchOrder(QStringList& t);
bool isLoaded(const QString& fn) {
foreach (Tree* t, searchOrder()) {
if (fn == t->indexFileName())
return true;
}
return false;
}
const Node* findNode(const QStringList& path,
const Node* relative,
@ -186,8 +193,11 @@ class QDocForest
}
}
void clearSearchOrder() { searchOrder_.clear(); }
private:
void newPrimaryTree(const QString& module);
void setPrimaryTree(const QString& t);
NamespaceNode* newIndexTree(const QString& module);
private:
@ -274,12 +284,10 @@ class QDocDatabase
void resolveInheritance() { primaryTree()->resolveInheritance(); }
void resolveQmlInheritance(InnerNode* root);
void resolveIssues();
void resolveStuff();
void fixInheritance() { primaryTree()->fixInheritance(); }
void resolveProperties() { primaryTree()->resolveProperties(); }
void resolveTargets() {
primaryTree()->resolveTargets(primaryTreeRoot());
}
void insertTarget(const QString& name,
const QString& title,
TargetRec::Type type,
@ -355,18 +363,22 @@ class QDocDatabase
void clearOpenNamespaces() { openNamespaces_.clear(); }
void insertOpenNamespace(const QString& path) { openNamespaces_.insert(path); }
void setShowInternal(bool value) { showInternal_ = value; }
void setSingleExec(bool value) { singleExec_ = value; }
void processForest();
// Try to make this function private.
QDocForest& forest() { return forest_; }
NamespaceNode* primaryTreeRoot() { return forest_.primaryTreeRoot(); }
void newPrimaryTree(const QString& module) { forest_.newPrimaryTree(module); }
void setPrimaryTree(const QString& t) { forest_.setPrimaryTree(t); }
NamespaceNode* newIndexTree(const QString& module) { return forest_.newIndexTree(module); }
const QVector<Tree*>& searchOrder() { return forest_.searchOrder(); }
void setLocalSearch() { forest_.searchOrder_ = QVector<Tree*>(1, primaryTree()); }
void setSearchOrder(const QVector<Tree*>& searchOrder) { forest_.searchOrder_ = searchOrder; }
void setSearchOrder() { forest_.setSearchOrder(); }
void setSearchOrder(QStringList& t) { forest_.setSearchOrder(t); }
void mergeCollections(Node::Type nt, CNMap& cnm, const Node* relative);
void mergeCollections(CollectionNode* cn);
void clearSearchOrder() { forest_.clearSearchOrder(); }
private:
friend class QDocIndexFiles;
@ -379,6 +391,7 @@ class QDocDatabase
return forest_.findNode(path, relative, findFlags, genus);
}
void processForest(void (QDocDatabase::*) (InnerNode*));
bool isLoaded(const QString& t) { return forest_.isLoaded(t); }
static void initializeDB();
private:
@ -394,20 +407,21 @@ class QDocDatabase
static QDocDatabase* qdocDB_;
static NodeMap typeNodeMap_;
bool showInternal_;
bool singleExec_;
QString version_;
QDocForest forest_;
NodeMap nonCompatClasses_;
NodeMap mainClasses_;
NodeMap cppClasses_;
NodeMap mainClasses_; // MWS: not needed, should be delete
NodeMap compatClasses_;
NodeMap obsoleteClasses_;
NodeMap classesWithObsoleteMembers_;
NodeMap obsoleteQmlTypes_;
NodeMap qmlTypesWithObsoleteMembers_;
NodeMap namespaceIndex_;
NodeMap serviceClasses_;
NodeMap serviceClasses_; // MWS: not needed, should be deleted
NodeMap qmlBasicTypes_;
NodeMap qmlClasses_;
NodeMap qmlTypes_;
NodeMapMap newClassMaps_;
NodeMapMap newQmlTypeMaps_;
NodeMultiMapMap newSinceMaps_;

View File

@ -564,7 +564,6 @@ void QDocIndexFiles::readIndexSection(const QDomElement& element,
node->setReconstitutedBrief(briefAttr);
}
// zzz
bool useParent = (element.nodeName() == "namespace" && name.isEmpty());
if (element.hasChildNodes()) {
QDomElement child = element.firstChildElement();
@ -806,11 +805,14 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer,
QString fullName = node->fullDocumentName();
if (fullName != objName)
writer.writeAttribute("fullname", fullName);
#if 0
if (Generator::useOutputSubdirs())
href = node->outputSubdirectory();
if (!href.isEmpty())
href.append(QLatin1Char('/'));
href.append(gen_->fullDocumentLocation(node));
#endif
href = gen_->fullDocumentLocation(node);
}
else
href = node->name();

View File

@ -511,6 +511,9 @@ void Tokenizer::initialize(const Config &config)
defines = new QRegExp(d.join('|'));
falsehoods = new QRegExp(config.getStringList(CONFIG_FALSEHOODS).join('|'));
/*
The keyword hash table is always cleared before any words are inserted.
*/
memset(kwordHashTable, 0, sizeof(kwordHashTable));
for (int i = 0; i < Tok_LastKeyword - Tok_FirstKeyword + 1; i++)
insertKwordIntoHash(kwords[i], i + 1);
@ -533,6 +536,11 @@ void Tokenizer::initialize(const Config &config)
}
}
/*!
The heap allocated variables are freed here. The keyword
hash table is not cleared here, but it is cleared in the
initialize() function, before any keywords are inserted.
*/
void Tokenizer::terminate()
{
delete comment;

View File

@ -66,7 +66,11 @@ QT_BEGIN_NAMESPACE
be necessary, and it might be removed later.
*/
Tree::Tree(const QString& module, QDocDatabase* qdb)
: module_(module), qdb_(qdb), root_(0, QString())
: treeHasBeenAnalyzed_(false),
docsHaveBeenGenerated_(false),
module_(module),
qdb_(qdb),
root_(0, QString())
{
root_.setModuleName(module_);
root_.setTree(this);

View File

@ -187,12 +187,22 @@ class Tree
void addExampleNode(ExampleNode* n) { exampleNodeMap_.insert(n->title(), n); }
ExampleNodeMap& exampleNodeMap() { return exampleNodeMap_; }
const Node* checkForCollision(const QString& name);
void setIndexFileName(const QString& t) { indexFileName_ = t; }
bool treeHasBeenAnalyzed() const { return treeHasBeenAnalyzed_; }
bool docsHaveBeenGenerated() const { return docsHaveBeenGenerated_; }
void setTreeHasBeenAnalyzed() { treeHasBeenAnalyzed_ = true; }
void setdocsHaveBeenGenerated() { docsHaveBeenGenerated_ = true; }
public:
const QString& moduleName() const { return module_; }
const QString& indexFileName() const { return indexFileName_; }
private:
bool treeHasBeenAnalyzed_;
bool docsHaveBeenGenerated_;
QString module_;
QString indexFileName_;
QDocDatabase* qdb_;
NamespaceNode root_;
PropertyMap unresolvedPropertyMap;