1 /****************************************************************************
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
44 #include "codemarker.h"
45 #include "codeparser.h"
47 #include "cppcodemarker.h"
48 #include "cppcodeparser.h"
49 #include "ditaxmlgenerator.h"
51 #include "htmlgenerator.h"
52 #include "plaincodemarker.h"
53 #include "puredocparser.h"
54 #include "tokenizer.h"
56 #include "qdocdatabase.h"
57 #include "jscodemarker.h"
58 #include "qmlcodemarker.h"
59 #include "qmlcodeparser.h"
60 #include <qdatetime.h>
62 #include "qtranslator.h"
63 #ifndef QT_BOOTSTRAPPED
64 # include "qcoreapplication.h"
70 The default indent for code is 4.
71 The default value for false is 0.
72 The default supported file extensions are cpp, h, qdoc and qml.
73 The default language is c++.
74 The default output format is html.
75 The default tab size is 8.
76 And those are all the default values for configuration variables.
82 { CONFIG_CODEINDENT, "4" },
83 { CONFIG_FALSEHOODS, "0" },
84 { CONFIG_FILEEXTENSIONS, "*.cpp *.h *.qdoc *.qml"},
85 { CONFIG_LANGUAGE, "Cpp" },
86 { CONFIG_OUTPUTFORMATS, "HTML" },
87 { CONFIG_TABSIZE, "8" },
91 bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
93 return fi1.lastModified() < fi2.lastModified();
96 static bool highlighting = false;
97 static bool showInternal = false;
98 static bool obsoleteLinks = false;
99 static QStringList defines;
100 static QStringList dependModules;
101 static QStringList indexDirs;
104 Print the help message to \c stdout.
106 static void printHelp()
108 Location::information(tr("Usage: qdoc [options] file1.qdocconf ...\n"
111 "Define <name> as a macro while parsing sources\n"
113 "Specify dependant modules\n"
115 "Display this information and exit\n"
117 "Turn on syntax highlighting (makes qdoc run slower)\n"
119 "Specify a directory where QDoc should search for indices to link to\n"
121 "Specify the directory where the output will be after running \"make install\"\n"
123 "Do not generate documentation for examples\n"
125 "Report links from obsolete items to non-obsolete items\n"
127 "Specify output directory, overrides setting in qdocconf file\n"
129 "Specify output format, overrides setting in qdocconf file\n"
131 "Include content marked internal\n"
133 "Display version of qdoc and exit\n") );
137 Prints the qdoc version number to stdout.
139 static void printVersion()
141 QString s = tr("qdoc version %1").arg(QT_VERSION_STR);
142 Location::information(s);
146 Processes the qdoc config file \a fileName. This is the
147 controller for all of qdoc.
149 static void processQdocconfFile(const QString &fileName)
151 #ifndef QT_NO_TRANSLATION
152 QList<QTranslator *> translators;
156 The Config instance represents the configuration data for qdoc.
157 All the other classes are initialized with the config. Here we
158 initialize the configuration with some default values.
160 Config config(tr("qdoc"));
162 while (defaults[i].key) {
163 config.setStringList(defaults[i].key,
164 QStringList() << defaults[i].value);
167 config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false"));
168 config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false"));
169 config.setStringList(CONFIG_OBSOLETELINKS, QStringList(obsoleteLinks ? "true" : "false"));
172 With the default configuration values in place, load
173 the qdoc configuration file. Note that the configuration
174 file may include other configuration files.
176 The Location class keeps track of the current location
177 in the file being processed, mainly for error reporting
180 Location::initialize(config);
181 config.load(fileName);
184 Add the defines to the configuration variables.
186 QStringList defs = defines + config.getStringList(CONFIG_DEFINES);
187 config.setStringList(CONFIG_DEFINES,defs);
188 Location::terminate();
190 QString prevCurrentDir = QDir::currentPath();
191 QString dir = QFileInfo(fileName).path();
193 QDir::setCurrent(dir);
196 Initialize all the classes and data structures with the
199 Location::initialize(config);
200 Tokenizer::initialize(config);
201 Doc::initialize(config);
202 CodeMarker::initialize(config);
203 CodeParser::initialize(config);
204 Generator::initialize(config);
206 #ifndef QT_NO_TRANSLATION
208 Load the language translators, if the configuration specifies any.
210 QStringList fileNames = config.getStringList(CONFIG_TRANSLATORS);
211 QStringList::Iterator fn = fileNames.constBegin();
212 while (fn != fileNames.constEnd()) {
213 QTranslator *translator = new QTranslator(0);
214 if (!translator->load(*fn))
215 config.lastLocation().error(tr("Cannot load translator '%1'")
217 QCoreApplication::instance()->installTranslator(translator);
218 translators.append(translator);
223 //QSet<QString> outputLanguages = config.getStringSet(CONFIG_OUTPUTLANGUAGES);
226 Get the source language (Cpp) from the configuration
227 and the location in the configuration file where the
228 source language was set.
230 QString lang = config.getString(CONFIG_LANGUAGE);
231 Location langLocation = config.lastLocation();
234 Initialize the qdoc database, where all the parsed source files
235 will be stored. The database includes a tree of nodes, which gets
236 built as the source files are parsed. The documentation output is
237 generated by traversing that tree.
239 QDocDatabase* qdb = QDocDatabase::qdocDB();
240 qdb->setVersion(config.getString(CONFIG_VERSION));
243 By default, the only output format is HTML.
245 QSet<QString> outputFormats = config.getOutputFormats();
246 Location outputFormatsLocation = config.lastLocation();
249 Read some XML indexes containing definitions from other documentation sets.
251 QStringList indexFiles = config.getStringList(CONFIG_INDEXES);
253 dependModules += config.getStringList(CONFIG_DEPENDS);
255 if (dependModules.size() > 0) {
256 if (indexDirs.size() > 0) {
257 for (int i = 0; i < indexDirs.size(); i++) {
258 if (indexDirs[i].startsWith("..")) {
259 const QString prefix(QDir(dir).relativeFilePath(prevCurrentDir));
260 if (!prefix.isEmpty())
261 indexDirs[i].prepend(prefix + QLatin1Char('/'));
265 Add all subdirectories of the indexdirs as dependModules when an asterisk is used in
268 if (dependModules.contains("*")) {
269 dependModules.removeOne("*");
270 for (int i = 0; i < indexDirs.size(); i++) {
271 QDir scanDir = QDir(indexDirs[i]);
272 scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
273 QFileInfoList dirList = scanDir.entryInfoList();
274 for (int j = 0; j < dirList.size(); j++) {
275 if (dirList[j].fileName().toLower() != config.getString(CONFIG_PROJECT).toLower())
276 dependModules.append(dirList[j].fileName());
280 for (int i = 0; i < dependModules.size(); i++) {
282 QList<QFileInfo> foundIndices;
283 for (int j = 0; j < indexDirs.size(); j++) {
284 QString fileToLookFor = indexDirs[j] + QLatin1Char('/') + dependModules[i] +
285 QLatin1Char('/') + dependModules[i] + QLatin1String(".index");
286 if (QFile::exists(fileToLookFor)) {
287 QFileInfo tempFileInfo(fileToLookFor);
288 if (!foundIndices.contains(tempFileInfo))
289 foundIndices.append(tempFileInfo);
292 qSort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
293 if (foundIndices.size() > 1) {
295 QDoc should always use the last entry in the multimap when there are
296 multiple index files for a module, since the last modified file has the
297 highest UNIX timestamp.
299 qDebug() << "Multiple indices found for dependency:" << dependModules[i] << "\nFound:";
300 for (int k = 0; k < foundIndices.size(); k++)
301 qDebug() << foundIndices[k].absoluteFilePath();
302 qDebug() << "Using" << foundIndices[foundIndices.size() - 1].absoluteFilePath()
303 << "as index for" << dependModules[i];
304 indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
306 else if (foundIndices.size() == 1) {
307 indexToAdd = foundIndices[0].absoluteFilePath();
310 qDebug() << "No indices for" << dependModules[i] <<
311 "could be found in the specified index directories.";
313 if (!indexToAdd.isEmpty() && !indexFiles.contains(indexToAdd))
314 indexFiles << indexToAdd;
318 qDebug() << "Dependant modules specified, but no index directories or install directory were set."
319 << "There will probably be errors for missing links.";
322 qdb->readIndexes(indexFiles);
324 QSet<QString> excludedDirs;
325 QSet<QString> excludedFiles;
326 QStringList headerList;
327 QStringList sourceList;
328 QStringList excludedDirsList;
329 QStringList excludedFilesList;
331 Generator::debugSegfault("Reading excludedirs");
332 excludedDirsList = config.getCanonicalRelativePathList(CONFIG_EXCLUDEDIRS);
333 foreach (const QString &excludeDir, excludedDirsList) {
334 QString p = QDir::fromNativeSeparators(excludeDir);
335 excludedDirs.insert(p);
338 Generator::debugSegfault("Reading excludefiles");
339 excludedFilesList = config.getCleanPathList(CONFIG_EXCLUDEFILES);
340 foreach (const QString& excludeFile, excludedFilesList) {
341 QString p = QDir::fromNativeSeparators(excludeFile);
342 excludedFiles.insert(p);
345 Generator::debugSegfault("Reading headerdirs");
346 headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles);
347 QMap<QString,QString> headers;
348 QMultiMap<QString,QString> headerFileNames;
349 for (int i=0; i<headerList.size(); ++i) {
350 headers.insert(headerList[i],headerList[i]);
351 QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1);
352 headerFileNames.insert(t,t);
355 Generator::debugSegfault("Reading sourcedirs");
356 sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles);
357 QMap<QString,QString> sources;
358 QMultiMap<QString,QString> sourceFileNames;
359 for (int i=0; i<sourceList.size(); ++i) {
360 sources.insert(sourceList[i],sourceList[i]);
361 QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1);
362 sourceFileNames.insert(t,t);
365 Find all the qdoc files in the example dirs, and add
366 them to the source files to be parsed.
368 Generator::debugSegfault("Reading exampledirs");
369 QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
370 for (int i=0; i<exampleQdocList.size(); ++i) {
371 if (!sources.contains(exampleQdocList[i])) {
372 sources.insert(exampleQdocList[i],exampleQdocList[i]);
373 QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1);
374 sourceFileNames.insert(t,t);
378 Generator::debugSegfault("Adding doc/image dirs found in exampledirs to imagedirs");
379 QSet<QString> exampleImageDirs;
380 QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
381 for (int i=0; i<exampleImageList.size(); ++i) {
382 if (exampleImageList[i].contains("doc/images")) {
383 QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10);
384 if (!exampleImageDirs.contains(t)) {
385 exampleImageDirs.insert(t);
389 Generator::augmentImageDirs(exampleImageDirs);
392 Parse each header file in the set using the appropriate parser and add it
395 QSet<CodeParser *> usedParsers;
397 Generator::debugSegfault("Parsing header files");
399 QMap<QString,QString>::ConstIterator h = headers.constBegin();
400 while (h != headers.constEnd()) {
401 CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key());
404 codeParser->parseHeaderFile(config.location(), h.key());
405 usedParsers.insert(codeParser);
410 foreach (CodeParser *codeParser, usedParsers)
411 codeParser->doneParsingHeaderFiles();
415 Parse each source text file in the set using the appropriate parser and
416 add it to the big tree.
419 Generator::debugSegfault("Parsing source files");
420 QMap<QString,QString>::ConstIterator s = sources.constBegin();
421 while (s != sources.constEnd()) {
422 CodeParser *codeParser = CodeParser::parserForSourceFile(s.key());
425 codeParser->parseSourceFile(config.location(), s.key());
426 usedParsers.insert(codeParser);
431 foreach (CodeParser *codeParser, usedParsers)
432 codeParser->doneParsingSourceFiles();
435 Now the big tree has been built from all the header and
436 source files. Resolve all the class names, function names,
437 targets, URLs, links, and other stuff that needs resolving.
439 Generator::debugSegfault("Resolving stuff prior to generating docs");
440 qdb->resolveIssues();
443 The tree is built and all the stuff that needed resolving
444 has been resolved. Now traverse the tree and generate the
445 documentation output. More than one output format can be
446 requested. The tree is traversed for each one.
448 Generator::debugSegfault("Generating docs");
449 QSet<QString>::ConstIterator of = outputFormats.constBegin();
450 while (of != outputFormats.constEnd()) {
451 Generator* generator = Generator::generatorForFormat(*of);
453 outputFormatsLocation.fatal(tr("Unknown output format '%1'").arg(*of));
454 generator->generateTree();
459 //Generator::writeOutFileNames();
460 Generator::debugSegfault("Shutting down qdoc");
462 QDocDatabase::qdocDB()->setVersion(QString());
463 Generator::terminate();
464 CodeParser::terminate();
465 CodeMarker::terminate();
467 Tokenizer::terminate();
468 Location::terminate();
469 QDir::setCurrent(prevCurrentDir);
471 #ifndef QT_NO_TRANSLATION
472 qDeleteAll(translators);
474 #ifdef DEBUG_SHUTDOWN_CRASH
475 qDebug() << "main(): Delete qdoc database";
477 QDocDatabase::destroyQdocDB();
478 #ifdef DEBUG_SHUTDOWN_CRASH
479 qDebug() << "main(): qdoc database deleted";
481 Generator::debugSegfault("qdoc finished!");
486 int main(int argc, char **argv)
490 #ifndef QT_BOOTSTRAPPED
491 QCoreApplication app(argc, argv);
495 Create code parsers for the languages to be parsed,
496 and create a tree for C++.
498 CppCodeParser cppParser;
499 QmlCodeParser qmlParser;
500 PureDocParser docParser;
503 Create code markers for plain text, C++,
506 PlainCodeMarker plainMarker;
507 CppCodeMarker cppMarker;
508 JsCodeMarker jsMarker;
509 QmlCodeMarker qmlMarker;
511 HtmlGenerator htmlGenerator;
512 DitaXmlGenerator ditaxmlGenerator;
514 QStringList qdocFiles;
521 if (opt == "-help") {
525 else if (opt == "-version") {
529 else if (opt == "--") {
531 qdocFiles.append(argv[i++]);
533 else if (opt.startsWith("-D")) {
534 QString define = opt.mid(2);
537 else if (opt == "-depends") {
538 dependModules += argv[i];
541 else if (opt == "-highlighting") {
544 else if (opt == "-showinternal") {
547 else if (opt == "-no-examples") {
548 Config::generateExamples = false;
550 else if (opt == "-indexdir") {
551 if (QFile::exists(argv[i])) {
552 indexDirs += argv[i];
555 qDebug() << "Cannot find index directory" << argv[i];
559 else if (opt == "-installdir") {
560 Config::installDir = argv[i];
561 indexDirs += argv[i];
564 else if (opt == "-obsoletelinks") {
565 obsoleteLinks = true;
567 else if (opt == "-outputdir") {
568 Config::overrideOutputDir = argv[i];
571 else if (opt == "-outputformat") {
572 Config::overrideOutputFormats.insert(argv[i]);
575 else if (opt == "-debug") {
576 Generator::setDebugSegfaultFlag(true);
579 qdocFiles.append(opt);
583 if (qdocFiles.isEmpty()) {
591 foreach (const QString &qf, qdocFiles) {
592 //qDebug() << "PROCESSING:" << qf;
593 processQdocconfFile(qf);