1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
48 #include "codemarker.h"
49 #include "codeparser.h"
51 #include "cppcodemarker.h"
52 #include "cppcodeparser.h"
53 #include "ditaxmlgenerator.h"
55 #include "htmlgenerator.h"
56 #include "plaincodemarker.h"
57 #include "puredocparser.h"
58 #include "tokenizer.h"
61 #include "jscodemarker.h"
62 #include "qmlcodemarker.h"
63 #include "qmlcodeparser.h"
65 #include <qdatetime.h>
68 #include "qtranslator.h"
69 #ifndef QT_BOOTSTRAPPED
70 # include "qcoreapplication.h"
76 The default indent for code is 4.
77 The default value for false is 0.
78 The default supported file extensions are cpp, h, qdoc and qml.
79 The default language is c++.
80 The default output format is html.
81 The default tab size is 8.
82 And those are all the default values for configuration variables.
88 { CONFIG_CODEINDENT, "4" },
89 { CONFIG_FALSEHOODS, "0" },
90 { CONFIG_FILEEXTENSIONS, "*.cpp *.h *.qdoc *.qml"},
91 { CONFIG_LANGUAGE, "Cpp" },
92 { CONFIG_OUTPUTFORMATS, "HTML" },
93 { CONFIG_TABSIZE, "8" },
97 bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
99 return fi1.lastModified() < fi2.lastModified();
102 static bool highlighting = false;
103 static bool showInternal = false;
104 static bool obsoleteLinks = false;
105 static QStringList defines;
106 static QStringList dependModules;
107 static QStringList indexDirs;
108 static QHash<QString, Tree *> trees;
111 Print the help message to \c stdout.
113 static void printHelp()
115 Location::information(tr("Usage: qdoc [options] file1.qdocconf ...\n"
118 "Define <name> as a macro while parsing sources\n"
120 "Specify dependant modules\n"
122 "Display this information and exit\n"
124 "Turn on syntax highlighting (makes qdoc run slower)\n"
126 "Specify a directory where QDoc should search for indices to link to\n"
128 "Specify the directory where the output will be after running \"make install\"\n"
130 "Do not generate documentation for examples\n"
132 "Report links from obsolete items to non-obsolete items\n"
134 "Specify output directory, overrides setting in qdocconf file\n"
136 "Specify output format, overrides setting in qdocconf file\n"
138 "Include content marked internal\n"
140 "Display version of qdoc and exit\n") );
144 Prints the qdoc version number to stdout.
146 static void printVersion()
148 QString s = tr("qdoc version %1").arg(QT_VERSION_STR);
149 Location::information(s);
153 Processes the qdoc config file \a fileName. This is the
154 controller for all of qdoc.
156 static void processQdocconfFile(const QString &fileName)
158 #ifndef QT_NO_TRANSLATION
159 QList<QTranslator *> translators;
163 The Config instance represents the configuration data for qdoc.
164 All the other classes are initialized with the config. Here we
165 initialize the configuration with some default values.
167 Config config(tr("qdoc"));
169 while (defaults[i].key) {
170 config.setStringList(defaults[i].key,
171 QStringList() << defaults[i].value);
174 config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false"));
175 config.setStringList(CONFIG_SHOWINTERNAL,
176 QStringList(showInternal ? "true" : "false"));
177 config.setStringList(CONFIG_OBSOLETELINKS,
178 QStringList(obsoleteLinks ? "true" : "false"));
181 With the default configuration values in place, load
182 the qdoc configuration file. Note that the configuration
183 file may include other configuration files.
185 The Location class keeps track of the current location
186 in the file being processed, mainly for error reporting
189 Location::initialize(config);
190 config.load(fileName);
193 Add the defines to the configuration variables.
195 QStringList defs = defines + config.getStringList(CONFIG_DEFINES);
196 config.setStringList(CONFIG_DEFINES,defs);
197 Location::terminate();
199 QString prevCurrentDir = QDir::currentPath();
200 QString dir = QFileInfo(fileName).path();
202 QDir::setCurrent(dir);
205 Initialize all the classes and data structures with the
208 Location::initialize(config);
209 Tokenizer::initialize(config);
210 Doc::initialize(config);
211 CodeMarker::initialize(config);
212 CodeParser::initialize(config);
213 Generator::initialize(config);
215 #ifndef QT_NO_TRANSLATION
217 Load the language translators, if the configuration specifies any.
219 QStringList fileNames = config.getStringList(CONFIG_TRANSLATORS);
220 QStringList::Iterator fn = fileNames.constBegin();
221 while (fn != fileNames.constEnd()) {
222 QTranslator *translator = new QTranslator(0);
223 if (!translator->load(*fn))
224 config.lastLocation().error(tr("Cannot load translator '%1'")
226 QCoreApplication::instance()->installTranslator(translator);
227 translators.append(translator);
232 //QSet<QString> outputLanguages = config.getStringSet(CONFIG_OUTPUTLANGUAGES);
235 Get the source language (Cpp) from the configuration
236 and the location in the configuration file where the
237 source language was set.
239 QString lang = config.getString(CONFIG_LANGUAGE);
240 Location langLocation = config.lastLocation();
243 Initialize the tree where all the parsed sources will be stored.
244 The tree gets built as the source files are parsed, and then the
245 documentation output is generated by traversing the tree.
247 Tree *tree = new Tree;
248 tree->setVersion(config.getString(CONFIG_VERSION));
251 By default, the only output format is HTML.
253 QSet<QString> outputFormats = config.getOutputFormats();
254 Location outputFormatsLocation = config.lastLocation();
257 Read some XML indexes containing definitions from other documentation sets.
259 QStringList indexFiles = config.getStringList(CONFIG_INDEXES);
261 dependModules += config.getStringList(CONFIG_DEPENDS);
263 if (dependModules.size() > 0) {
264 if (indexDirs.size() > 0) {
265 for (int i = 0; i < indexDirs.size(); i++) {
266 if (indexDirs[i].startsWith("..")) {
267 indexDirs[i].prepend(QDir(dir).relativeFilePath(prevCurrentDir));
271 Add all subdirectories of the indexdirs as dependModules when an asterisk is used in
274 if (dependModules.contains("*")) {
275 dependModules.removeOne("*");
276 for (int i = 0; i < indexDirs.size(); i++) {
277 QDir scanDir = QDir(indexDirs[i]);
278 scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
279 QFileInfoList dirList = scanDir.entryInfoList();
280 for (int j = 0; j < dirList.size(); j++) {
281 if (dirList[j].fileName().toLower() != config.getString(CONFIG_PROJECT).toLower())
282 dependModules.append(dirList[j].fileName());
286 for (int i = 0; i < dependModules.size(); i++) {
288 QList<QFileInfo> foundIndices;
289 for (int j = 0; j < indexDirs.size(); j++) {
290 QString fileToLookFor = indexDirs[j] + QLatin1Char('/') + dependModules[i] +
291 QLatin1Char('/') + dependModules[i] + QLatin1String(".index");
292 if (QFile::exists(fileToLookFor)) {
293 QFileInfo tempFileInfo(fileToLookFor);
294 if (!foundIndices.contains(tempFileInfo))
295 foundIndices.append(tempFileInfo);
298 qSort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
299 if (foundIndices.size() > 1) {
301 QDoc should always use the last entry in the multimap when there are
302 multiple index files for a module, since the last modified file has the
303 highest UNIX timestamp.
305 qDebug() << "Multiple indices found for dependency:" << dependModules[i] << "\nFound:";
306 for (int k = 0; k < foundIndices.size(); k++)
307 qDebug() << foundIndices[k].absoluteFilePath();
308 qDebug() << "Using" << foundIndices[foundIndices.size() - 1].absoluteFilePath()
309 << "as index for" << dependModules[i];
310 indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
312 else if (foundIndices.size() == 1) {
313 indexToAdd = foundIndices[0].absoluteFilePath();
316 qDebug() << "No indices for" << dependModules[i] <<
317 "could be found in the specified index directories.";
319 if (!indexToAdd.isEmpty() && !indexFiles.contains(indexToAdd))
320 indexFiles << indexToAdd;
324 qDebug() << "Dependant modules specified, but no index directories or install directory were set."
325 << "There will probably be errors for missing links.";
328 tree->readIndexes(indexFiles);
330 QSet<QString> excludedDirs;
331 QSet<QString> excludedFiles;
332 QStringList headerList;
333 QStringList sourceList;
334 QStringList excludedDirsList;
335 QStringList excludedFilesList;
337 excludedDirsList = config.getCleanPathList(CONFIG_EXCLUDEDIRS);
338 foreach (const QString &excludeDir, excludedDirsList) {
339 QString p = QDir::fromNativeSeparators(excludeDir);
340 excludedDirs.insert(p);
343 excludedFilesList = config.getCleanPathList(CONFIG_EXCLUDEFILES);
344 foreach (const QString& excludeFile, excludedFilesList) {
345 QString p = QDir::fromNativeSeparators(excludeFile);
346 excludedFiles.insert(p);
349 headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles);
350 QMap<QString,QString> headers;
351 QMultiMap<QString,QString> headerFileNames;
352 for (int i=0; i<headerList.size(); ++i) {
353 headers.insert(headerList[i],headerList[i]);
354 QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1);
355 headerFileNames.insert(t,t);
358 sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles);
359 QMap<QString,QString> sources;
360 QMultiMap<QString,QString> sourceFileNames;
361 for (int i=0; i<sourceList.size(); ++i) {
362 sources.insert(sourceList[i],sourceList[i]);
363 QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1);
364 sourceFileNames.insert(t,t);
367 Find all the qdoc files in the example dirs, and add
368 them to the source files to be parsed.
370 QStringList exampleQdocList = config.getExampleQdocFiles();
371 for (int i=0; i<exampleQdocList.size(); ++i) {
372 sources.insert(exampleQdocList[i],exampleQdocList[i]);
373 QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1);
374 sourceFileNames.insert(t,t);
378 Parse each header file in the set using the appropriate parser and add it
381 QSet<CodeParser *> usedParsers;
384 QMap<QString,QString>::ConstIterator h = headers.constBegin();
385 while (h != headers.constEnd()) {
386 CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key());
389 codeParser->parseHeaderFile(config.location(), h.key(), tree);
390 usedParsers.insert(codeParser);
395 foreach (CodeParser *codeParser, usedParsers)
396 codeParser->doneParsingHeaderFiles(tree);
400 Parse each source text file in the set using the appropriate parser and
401 add it to the big tree.
404 QMap<QString,QString>::ConstIterator s = sources.constBegin();
405 while (s != sources.constEnd()) {
406 CodeParser *codeParser = CodeParser::parserForSourceFile(s.key());
409 codeParser->parseSourceFile(config.location(), s.key(), tree);
410 usedParsers.insert(codeParser);
415 foreach (CodeParser *codeParser, usedParsers)
416 codeParser->doneParsingSourceFiles(tree);
419 Now the big tree has been built from all the header and
420 source files. Resolve all the class names, function names,
421 targets, URLs, links, and other stuff that needs resolving.
423 tree->resolveGroups();
424 tree->resolveTargets(tree->root());
425 tree->resolveCppToQmlLinks();
426 tree->resolveQmlInheritance();
429 The tree is built and all the stuff that needed resolving
430 has been resolved. Now traverse the tree and generate the
431 documentation output. More than one output format can be
432 requested. The tree is traversed for each one.
434 QSet<QString>::ConstIterator of = outputFormats.constBegin();
435 while (of != outputFormats.constEnd()) {
436 Generator* generator = Generator::generatorForFormat(*of);
438 outputFormatsLocation.fatal(tr("Unknown output format '%1'").arg(*of));
439 generator->generateTree(tree);
444 Generate the XML tag file, if it was requested.
446 QString tagFile = config.getString(CONFIG_TAGFILE);
447 if (!tagFile.isEmpty()) {
448 tree->generateTagFile(tagFile);
451 //Generator::writeOutFileNames();
453 tree->setVersion(QString());
454 Generator::terminate();
455 CodeParser::terminate();
456 CodeMarker::terminate();
458 Tokenizer::terminate();
459 Location::terminate();
460 QDir::setCurrent(prevCurrentDir);
462 #ifndef QT_NO_TRANSLATION
463 qDeleteAll(translators);
465 #ifdef DEBUG_SHUTDOWN_CRASH
466 qDebug() << "main(): Delete tree";
469 #ifdef DEBUG_SHUTDOWN_CRASH
470 qDebug() << "main(): Tree deleted";
476 int main(int argc, char **argv)
480 #ifndef QT_BOOTSTRAPPED
481 QCoreApplication app(argc, argv);
485 Create code parsers for the languages to be parsed,
486 and create a tree for C++.
488 CppCodeParser cppParser;
489 QmlCodeParser qmlParser;
490 PureDocParser docParser;
493 Create code markers for plain text, C++,
496 PlainCodeMarker plainMarker;
497 CppCodeMarker cppMarker;
498 JsCodeMarker jsMarker;
499 QmlCodeMarker qmlMarker;
501 HtmlGenerator htmlGenerator;
502 DitaXmlGenerator ditaxmlGenerator;
504 QStringList qdocFiles;
511 if (opt == "-help") {
515 else if (opt == "-version") {
519 else if (opt == "--") {
521 qdocFiles.append(argv[i++]);
523 else if (opt.startsWith("-D")) {
524 QString define = opt.mid(2);
527 else if (opt == "-depends") {
528 dependModules += argv[i];
531 else if (opt == "-highlighting") {
534 else if (opt == "-showinternal") {
537 else if (opt == "-no-examples") {
538 Config::generateExamples = false;
540 else if (opt == "-indexdir") {
541 if (QFile::exists(argv[i])) {
542 indexDirs += argv[i];
545 qDebug() << "Cannot find index directory" << argv[i];
549 else if (opt == "-installdir") {
550 Config::installDir = argv[i];
551 indexDirs += argv[i];
554 else if (opt == "-obsoletelinks") {
555 obsoleteLinks = true;
557 else if (opt == "-outputdir") {
558 Config::overrideOutputDir = argv[i];
561 else if (opt == "-outputformat") {
562 Config::overrideOutputFormats.insert(argv[i]);
566 qdocFiles.append(opt);
570 if (qdocFiles.isEmpty()) {
578 foreach (const QString &qf, qdocFiles) {
579 //qDebug() << "PROCESSING:" << qf;
580 processQdocconfFile(qf);