qdoc: Don't load index in -prepare phase
[qt:qtbase.git] / src / tools / qdoc / main.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
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.
16 **
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.
24 **
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.
28 **
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.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <qglobal.h>
43 #include <qlibraryinfo.h>
44 #include <stdlib.h>
45 #include "codemarker.h"
46 #include "codeparser.h"
47 #include "config.h"
48 #include "cppcodemarker.h"
49 #include "cppcodeparser.h"
50 #include "ditaxmlgenerator.h"
51 #include "doc.h"
52 #include "htmlgenerator.h"
53 #include "plaincodemarker.h"
54 #include "puredocparser.h"
55 #include "tokenizer.h"
56 #include "tree.h"
57 #include "qdocdatabase.h"
58 #include "jscodemarker.h"
59 #include "qmlcodemarker.h"
60 #include "qmlcodeparser.h"
61 #include <qdatetime.h>
62 #include <qdebug.h>
63 #include "qtranslator.h"
64 #ifndef QT_BOOTSTRAPPED
65 #  include "qcoreapplication.h"
66 #endif
67
68 QT_BEGIN_NAMESPACE
69
70 /*
71   The default indent for code is 4.
72   The default value for false is 0.
73   The default supported file extensions are cpp, h, qdoc and qml.
74   The default language is c++.
75   The default output format is html.
76   The default tab size is 8.
77   And those are all the default values for configuration variables.
78  */
79 static const struct {
80     const char *key;
81     const char *value;
82 } defaults[] = {
83     { CONFIG_CODEINDENT, "4" },
84     { CONFIG_FALSEHOODS, "0" },
85     { CONFIG_FILEEXTENSIONS, "*.cpp *.h *.qdoc *.qml"},
86     { CONFIG_LANGUAGE, "Cpp" },
87     { CONFIG_OUTPUTFORMATS, "HTML" },
88     { CONFIG_TABSIZE, "8" },
89     { 0, 0 }
90 };
91
92 bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
93 {
94     return fi1.lastModified() < fi2.lastModified();
95 }
96
97 static bool highlighting = false;
98 static bool showInternal = false;
99 static bool noLinkErrors = false;
100 static bool obsoleteLinks = false;
101 static QStringList defines;
102 static QStringList dependModules;
103 static QStringList indexDirs;
104 static QString currentDir;
105 static QString prevCurrentDir;
106 static QString documentationPath;
107
108 /*!
109   Print the help message to \c stdout.
110  */
111 static void printHelp()
112 {
113     Location::information(tr("Usage: qdoc [options] file1.qdocconf ...\n"
114                              "Options:\n"
115                              "    -D<name>       "
116                              "Define <name> as a macro while parsing sources\n"
117                              "    -depends       "
118                              "Specify dependant modules\n"
119                              "    -help          "
120                              "Display this information and exit\n"
121                              "    -highlighting  "
122                              "Turn on syntax highlighting (makes qdoc run slower)\n"
123                              "    -indexdir      "
124                              "Specify a directory where QDoc should search for index files to load\n"
125                              "    -installdir    "
126                              "Specify the directory where the output will be after running \"make install\"\n"
127                              "    -no-examples   "
128                              "Do not generate documentation for examples\n"
129                              "    -no-link-errors   "
130                              "Do not print link errors (i.e. missing targets)\n"
131                              "    -obsoletelinks "
132                              "Report links from obsolete items to non-obsolete items\n"
133                              "    -outputdir     "
134                              "Specify output directory, overrides setting in qdocconf file\n"
135                              "    -outputformat  "
136                              "Specify output format, overrides setting in qdocconf file\n"
137                              "    -prepare        "
138                              "Run qdoc only to generate an index file, not the docs\n"
139                              "    -generate        "
140                              "Run qdoc to read the index files and generate the docs\n"
141                              "    -showinternal  "
142                              "Include content marked internal\n"
143                              "    -version       "
144                              "Display version of qdoc and exit\n") );
145 }
146
147 /*!
148   Prints the qdoc version number to stdout.
149  */
150 static void printVersion()
151 {
152     QString s = tr("qdoc version %1").arg(QT_VERSION_STR);
153     Location::information(s);
154 }
155
156 static void loadIndexFiles(Config& config)
157 {
158     QDocDatabase* qdb = QDocDatabase::qdocDB();
159     /*
160       Read some XML indexes containing definitions from other documentation sets.
161      */
162     QStringList indexFiles = config.getStringList(CONFIG_INDEXES);
163
164     dependModules += config.getStringList(CONFIG_DEPENDS);
165
166     // Allow modules and third-party application/libraries to link
167     // to the Qt docs without having to explicitly pass --indexdir.
168     if (!indexDirs.contains(documentationPath))
169         indexDirs.append(documentationPath);
170
171     if (dependModules.size() > 0) {
172         if (indexDirs.size() > 0) {
173             for (int i = 0; i < indexDirs.size(); i++) {
174                 if (indexDirs[i].startsWith("..")) {
175                     const QString prefix(QDir(currentDir).relativeFilePath(prevCurrentDir));
176                     if (!prefix.isEmpty())
177                         indexDirs[i].prepend(prefix + QLatin1Char('/'));
178                 }
179             }
180             /*
181               Add all subdirectories of the indexdirs as dependModules,
182               when an asterisk is used in the 'depends' list.
183             */
184             if (dependModules.contains("*")) {
185                 dependModules.removeOne("*");
186                 for (int i = 0; i < indexDirs.size(); i++) {
187                     QDir scanDir = QDir(indexDirs[i]);
188                     scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
189                     QFileInfoList dirList = scanDir.entryInfoList();
190                     for (int j = 0; j < dirList.size(); j++) {
191                         if (dirList[j].fileName().toLower() != config.getString(CONFIG_PROJECT).toLower())
192                             dependModules.append(dirList[j].fileName());
193                     }
194                 }
195             }
196             for (int i = 0; i < dependModules.size(); i++) {
197                 QString indexToAdd;
198                 QList<QFileInfo> foundIndices;
199                 for (int j = 0; j < indexDirs.size(); j++) {
200                     QString fileToLookFor = indexDirs[j] + QLatin1Char('/') + dependModules[i] +
201                             QLatin1Char('/') + dependModules[i] + QLatin1String(".index");
202                     if (QFile::exists(fileToLookFor)) {
203                         QFileInfo tempFileInfo(fileToLookFor);
204                         if (!foundIndices.contains(tempFileInfo))
205                             foundIndices.append(tempFileInfo);
206                     }
207                 }
208                 qSort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
209                 if (foundIndices.size() > 1) {
210                     /*
211                         QDoc should always use the last entry in the multimap when there are
212                         multiple index files for a module, since the last modified file has the
213                         highest UNIX timestamp.
214                     */
215                     qDebug() << "Multiple indices found for dependency:" << dependModules[i] << "\nFound:";
216                     for (int k = 0; k < foundIndices.size(); k++)
217                         qDebug() << foundIndices[k].absoluteFilePath();
218                     qDebug() << "Using" << foundIndices[foundIndices.size() - 1].absoluteFilePath()
219                             << "as index for" << dependModules[i];
220                     indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
221                 }
222                 else if (foundIndices.size() == 1) {
223                     indexToAdd = foundIndices[0].absoluteFilePath();
224                 }
225                 if (!indexToAdd.isEmpty() && !indexFiles.contains(indexToAdd))
226                     indexFiles << indexToAdd;
227             }
228         }
229         else {
230             qDebug() << "Dependant modules specified, but no index directories or "
231                      << "install directory were set."
232                      << "There will probably be errors for missing links.";
233         }
234     }
235     qdb->readIndexes(indexFiles);
236 }
237
238 /*!
239   Processes the qdoc config file \a fileName. This is the
240   controller for all of qdoc.
241  */
242 static void processQdocconfFile(const QString &fileName)
243 {
244 #ifndef QT_NO_TRANSLATION
245     QList<QTranslator *> translators;
246 #endif
247
248     /*
249       The Config instance represents the configuration data for qdoc.
250       All the other classes are initialized with the config. Here we
251       initialize the configuration with some default values.
252      */
253     Config config(tr("qdoc"));
254     int i = 0;
255     while (defaults[i].key) {
256         config.setStringList(defaults[i].key, QStringList() << defaults[i].value);
257         ++i;
258     }
259     config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false"));
260     config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false"));
261     config.setStringList(CONFIG_NOLINKERRORS, QStringList(noLinkErrors ? "true" : "false"));
262     config.setStringList(CONFIG_OBSOLETELINKS, QStringList(obsoleteLinks ? "true" : "false"));
263
264     documentationPath = QLibraryInfo::rawLocation(QLibraryInfo::DocumentationPath,
265                                                   QLibraryInfo::EffectivePaths);
266
267     // Set a few environment variables that can be used from the qdocconf file
268     qputenv("QT_INSTALL_DOCS", documentationPath.toLatin1());
269
270     /*
271       With the default configuration values in place, load
272       the qdoc configuration file. Note that the configuration
273       file may include other configuration files.
274
275       The Location class keeps track of the current location
276       in the file being processed, mainly for error reporting
277       purposes.
278      */
279     Location::initialize(config);
280     config.load(fileName);
281
282     /*
283       Add the defines to the configuration variables.
284      */
285     QStringList defs = defines + config.getStringList(CONFIG_DEFINES);
286     config.setStringList(CONFIG_DEFINES,defs);
287     Location::terminate();
288
289     prevCurrentDir = QDir::currentPath();
290     currentDir = QFileInfo(fileName).path();
291     if (!currentDir.isEmpty())
292         QDir::setCurrent(currentDir);
293
294     QString phase;
295     if (Generator::runPrepareOnly())
296         phase = "in -prepare mode ";
297     else if (Generator::runGenerateOnly())
298         phase = "in -generate mode ";
299     QString msg = "Running qdoc " + phase + "for " + config.getString(CONFIG_PROJECT);
300     Location::logToStdErr(msg);
301
302     /*
303       Initialize all the classes and data structures with the
304       qdoc configuration.
305      */
306     Location::initialize(config);
307     Tokenizer::initialize(config);
308     Doc::initialize(config);
309     CodeMarker::initialize(config);
310     CodeParser::initialize(config);
311     Generator::initialize(config);
312
313 #ifndef QT_NO_TRANSLATION
314     /*
315       Load the language translators, if the configuration specifies any.
316      */
317     QStringList fileNames = config.getStringList(CONFIG_TRANSLATORS);
318     QStringList::Iterator fn = fileNames.constBegin();
319     while (fn != fileNames.constEnd()) {
320         QTranslator *translator = new QTranslator(0);
321         if (!translator->load(*fn))
322             config.lastLocation().error(tr("Cannot load translator '%1'").arg(*fn));
323         QCoreApplication::instance()->installTranslator(translator);
324         translators.append(translator);
325         ++fn;
326     }
327 #endif
328
329     //QSet<QString> outputLanguages = config.getStringSet(CONFIG_OUTPUTLANGUAGES);
330
331     /*
332       Get the source language (Cpp) from the configuration
333       and the location in the configuration file where the
334       source language was set.
335      */
336     QString lang = config.getString(CONFIG_LANGUAGE);
337     Location langLocation = config.lastLocation();
338
339     /*
340       Initialize the qdoc database, where all the parsed source files
341       will be stored. The database includes a tree of nodes, which gets
342       built as the source files are parsed. The documentation output is
343       generated by traversing that tree.
344      */
345     QDocDatabase* qdb = QDocDatabase::qdocDB();
346     qdb->setVersion(config.getString(CONFIG_VERSION));
347
348     /*
349       By default, the only output format is HTML.
350      */
351     QSet<QString> outputFormats = config.getOutputFormats();
352     Location outputFormatsLocation = config.lastLocation();
353
354     if (!Generator::runPrepareOnly())
355         loadIndexFiles(config);
356
357     QSet<QString> excludedDirs;
358     QSet<QString> excludedFiles;
359     QStringList headerList;
360     QStringList sourceList;
361     QStringList excludedDirsList;
362     QStringList excludedFilesList;
363
364     Generator::debugSegfault("Reading excludedirs");
365     excludedDirsList = config.getCanonicalRelativePathList(CONFIG_EXCLUDEDIRS);
366     foreach (const QString &excludeDir, excludedDirsList) {
367         QString p = QDir::fromNativeSeparators(excludeDir);
368         excludedDirs.insert(p);
369     }
370
371     Generator::debugSegfault("Reading excludefiles");
372     excludedFilesList = config.getCleanPathList(CONFIG_EXCLUDEFILES);
373     foreach (const QString& excludeFile, excludedFilesList) {
374         QString p = QDir::fromNativeSeparators(excludeFile);
375         excludedFiles.insert(p);
376     }
377
378     Generator::debugSegfault("Reading headerdirs");
379     headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles);
380     QMap<QString,QString> headers;
381     QMultiMap<QString,QString> headerFileNames;
382     for (int i=0; i<headerList.size(); ++i) {
383         headers.insert(headerList[i],headerList[i]);
384         QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1);
385         headerFileNames.insert(t,t);
386     }
387
388     Generator::debugSegfault("Reading sourcedirs");
389     sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles);
390     QMap<QString,QString> sources;
391     QMultiMap<QString,QString> sourceFileNames;
392     for (int i=0; i<sourceList.size(); ++i) {
393         sources.insert(sourceList[i],sourceList[i]);
394         QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1);
395         sourceFileNames.insert(t,t);
396     }
397     /*
398       Find all the qdoc files in the example dirs, and add
399       them to the source files to be parsed.
400      */
401     Generator::debugSegfault("Reading exampledirs");
402     QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
403     for (int i=0; i<exampleQdocList.size(); ++i) {
404         if (!sources.contains(exampleQdocList[i])) {
405             sources.insert(exampleQdocList[i],exampleQdocList[i]);
406             QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1);
407             sourceFileNames.insert(t,t);
408         }
409     }
410
411     Generator::debugSegfault("Adding doc/image dirs found in exampledirs to imagedirs");
412     QSet<QString> exampleImageDirs;
413     QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
414     for (int i=0; i<exampleImageList.size(); ++i) {
415         if (exampleImageList[i].contains("doc/images")) {
416             QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10);
417             if (!exampleImageDirs.contains(t)) {
418                 exampleImageDirs.insert(t);
419             }
420         }
421     }
422     Generator::augmentImageDirs(exampleImageDirs);
423
424     /*
425       Parse each header file in the set using the appropriate parser and add it
426       to the big tree.
427      */
428     QSet<CodeParser *> usedParsers;
429
430     Generator::debugSegfault("Parsing header files");
431     int parsed = 0;
432     QMap<QString,QString>::ConstIterator h = headers.constBegin();
433     while (h != headers.constEnd()) {
434         CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key());
435         if (codeParser) {
436             ++parsed;
437             codeParser->parseHeaderFile(config.location(), h.key());
438             usedParsers.insert(codeParser);
439         }
440         ++h;
441     }
442
443     foreach (CodeParser *codeParser, usedParsers)
444         codeParser->doneParsingHeaderFiles();
445
446     usedParsers.clear();
447     /*
448       Parse each source text file in the set using the appropriate parser and
449       add it to the big tree.
450      */
451     parsed = 0;
452     Generator::debugSegfault("Parsing source files");
453     QMap<QString,QString>::ConstIterator s = sources.constBegin();
454     while (s != sources.constEnd()) {
455         CodeParser *codeParser = CodeParser::parserForSourceFile(s.key());
456         if (codeParser) {
457             ++parsed;
458             codeParser->parseSourceFile(config.location(), s.key());
459             usedParsers.insert(codeParser);
460         }
461         ++s;
462     }
463
464     foreach (CodeParser *codeParser, usedParsers)
465         codeParser->doneParsingSourceFiles();
466
467     /*
468       Now the big tree has been built from all the header and
469       source files. Resolve all the class names, function names,
470       targets, URLs, links, and other stuff that needs resolving.
471      */
472     Generator::debugSegfault("Resolving stuff prior to generating docs");
473     qdb->resolveIssues();
474
475     /*
476       The tree is built and all the stuff that needed resolving
477       has been resolved. Now traverse the tree and generate the
478       documentation output. More than one output format can be
479       requested. The tree is traversed for each one.
480      */
481     Generator::debugSegfault("Generating docs");
482     QSet<QString>::ConstIterator of = outputFormats.constBegin();
483     while (of != outputFormats.constEnd()) {
484         Generator* generator = Generator::generatorForFormat(*of);
485         if (generator == 0)
486             outputFormatsLocation.fatal(tr("Unknown output format '%1'").arg(*of));
487         generator->generateTree();
488         ++of;
489     }
490
491
492     //Generator::writeOutFileNames();
493     Generator::debugSegfault("Shutting down qdoc");
494
495     QDocDatabase::qdocDB()->setVersion(QString());
496     Generator::terminate();
497     CodeParser::terminate();
498     CodeMarker::terminate();
499     Doc::terminate();
500     Tokenizer::terminate();
501     Location::terminate();
502     QDir::setCurrent(prevCurrentDir);
503
504 #ifndef QT_NO_TRANSLATION
505     qDeleteAll(translators);
506 #endif
507 #ifdef DEBUG_SHUTDOWN_CRASH
508     qDebug() << "main(): Delete qdoc database";
509 #endif
510     QDocDatabase::destroyQdocDB();
511 #ifdef DEBUG_SHUTDOWN_CRASH
512     qDebug() << "main(): qdoc database deleted";
513 #endif
514     Generator::debugSegfault("qdoc finished!");
515 }
516
517 QT_END_NAMESPACE
518
519 int main(int argc, char **argv)
520 {
521     QT_USE_NAMESPACE
522
523 #ifndef QT_BOOTSTRAPPED
524     QCoreApplication app(argc, argv);
525 #endif
526
527     /*
528       Create code parsers for the languages to be parsed,
529       and create a tree for C++.
530      */
531     CppCodeParser cppParser;
532     QmlCodeParser qmlParser;
533     PureDocParser docParser;
534
535     /*
536       Create code markers for plain text, C++,
537       javascript, and QML.
538      */
539     PlainCodeMarker plainMarker;
540     CppCodeMarker cppMarker;
541     JsCodeMarker jsMarker;
542     QmlCodeMarker qmlMarker;
543
544     HtmlGenerator htmlGenerator;
545     DitaXmlGenerator ditaxmlGenerator;
546
547     QStringList qdocFiles;
548     QString opt;
549     int i = 1;
550
551     while (i < argc) {
552         opt = argv[i++];
553
554         if (opt == "-help") {
555             printHelp();
556             return EXIT_SUCCESS;
557         }
558         else if (opt == "-version") {
559             printVersion();
560             return EXIT_SUCCESS;
561         }
562         else if (opt == "--") {
563             while (i < argc)
564                 qdocFiles.append(argv[i++]);
565         }
566         else if (opt.startsWith("-D")) {
567             QString define = opt.mid(2);
568             defines += define;
569         }
570         else if (opt == "-depends") {
571             dependModules += argv[i];
572             i++;
573         }
574         else if (opt == "-highlighting") {
575             highlighting = true;
576         }
577         else if (opt == "-showinternal") {
578             showInternal = true;
579         }
580         else if (opt == "-no-examples") {
581             Config::generateExamples = false;
582         }
583         else if (opt == "-indexdir") {
584             if (QFile::exists(argv[i])) {
585                 indexDirs += argv[i];
586             }
587             else {
588                 qDebug() << "Cannot find index directory" << argv[i];
589             }
590             i++;
591         }
592         else if (opt == "-installdir") {
593             Config::installDir = argv[i];
594             indexDirs += argv[i];
595             i++;
596         }
597         else if (opt == "-obsoletelinks") {
598             obsoleteLinks = true;
599         }
600         else if (opt == "-outputdir") {
601             Config::overrideOutputDir = argv[i];
602             i++;
603         }
604         else if (opt == "-outputformat") {
605             Config::overrideOutputFormats.insert(argv[i]);
606             i++;
607         }
608         else if (opt == "-no-link-errors") {
609             noLinkErrors = true;
610         }
611         else if (opt == "-debug") {
612             Generator::setDebugSegfaultFlag(true);
613         }
614         else if (opt == "-prepare") {
615             Generator::setQDocPass(Generator::Prepare);
616         }
617         else if (opt == "-generate") {
618             Generator::setQDocPass(Generator::Generate);
619         }
620         else {
621             qdocFiles.append(opt);
622         }
623     }
624
625     if (qdocFiles.isEmpty()) {
626         printHelp();
627         return EXIT_FAILURE;
628     }
629
630     /*
631       Main loop.
632      */
633     foreach (const QString &qf, qdocFiles) {
634         //qDebug() << "PROCESSING:" << qf;
635         processQdocconfFile(qf);
636     }
637
638     return EXIT_SUCCESS;
639 }
640