1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "clangcodeparser.h"
5#include "codemarker.h"
6#include "codeparser.h"
7#include "config.h"
8#include "cppcodemarker.h"
9#include "doc.h"
10#include "docbookgenerator.h"
11#include "htmlgenerator.h"
12#include "location.h"
13#include "puredocparser.h"
14#include "qdocdatabase.h"
15#include "qmlcodemarker.h"
16#include "qmlcodeparser.h"
17#include "utilities.h"
18#include "tokenizer.h"
19#include "tree.h"
20#include "webxmlgenerator.h"
21
22#include "filesystem/fileresolver.h"
23#include "boundaries/filesystem/directorypath.h"
24
25#include <QtCore/qdatetime.h>
26#include <QtCore/qdebug.h>
27#include <QtCore/qglobal.h>
28#include <QtCore/qhashfunctions.h>
29
30#include <set>
31
32#ifndef QT_BOOTSTRAPPED
33# include <QtCore/qcoreapplication.h>
34#endif
35
36#include <algorithm>
37#include <cstdlib>
38
39QT_BEGIN_NAMESPACE
40
41using namespace Qt::StringLiterals;
42
43bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
44{
45 return fi1.lastModified() < fi2.lastModified();
46}
47
48/*!
49 \internal
50 Inspects each file path in \a sources. File paths with a known
51 source file type are parsed to extract user-provided
52 documentation and information about source code level elements.
53
54 \note Unknown source file types are silently ignored.
55
56 The validity or availability of the file paths may or may not cause QDoc
57 to generate warnings; this depends on the implementation of
58 parseSourceFile() for the relevant parser.
59
60 \sa CodeParser::parserForSourceFile, CodeParser::sourceFileNameFilter
61*/
62static void parseSourceFiles(const std::set<QString> &sources)
63{
64 CppCodeParser cpp_code_parser{};
65 for (const QString& source_file_path : sources) {
66 auto *codeParser = CodeParser::parserForSourceFile(filePath: source_file_path);
67 if (!codeParser) continue;
68
69 qCDebug(lcQdoc, "Parsing %s", qPrintable(source_file_path));
70 codeParser->parseSourceFile(location: Config::instance().location(), filePath: source_file_path, cpp_code_parser);
71 }
72}
73
74/*!
75 Read some XML indexes containing definitions from other
76 documentation sets. \a config contains a variable that
77 lists directories where index files can be found. It also
78 contains the \c depends variable, which lists the modules
79 that the current module depends on. \a formats contains
80 a list of output formats; each format may have a different
81 output subdirectory where index files are located.
82*/
83static void loadIndexFiles(const QSet<QString> &formats)
84{
85 Config &config = Config::instance();
86 QDocDatabase *qdb = QDocDatabase::qdocDB();
87 QStringList indexFiles;
88 const QStringList configIndexes{config.get(CONFIG_INDEXES).asStringList()};
89 bool warn = !config.get(CONFIG_NOLINKERRORS).asBool();
90
91 for (const auto &index : configIndexes) {
92 QFileInfo fi(index);
93 if (fi.exists() && fi.isFile())
94 indexFiles << index;
95 else if (warn)
96 Location().warning(message: QString("Index file not found: %1").arg(a: index));
97 }
98
99 config.dependModules() += config.get(CONFIG_DEPENDS).asStringList();
100 config.dependModules().removeDuplicates();
101 bool useNoSubDirs = false;
102 QSet<QString> subDirs;
103
104 for (const auto &format : formats) {
105 if (config.get(var: format + Config::dot + "nosubdirs").asBool()) {
106 useNoSubDirs = true;
107 QString singleOutputSubdir{config.get(var: format + Config::dot + "outputsubdir").asString()};
108 if (singleOutputSubdir.isEmpty())
109 singleOutputSubdir = "html";
110 subDirs << singleOutputSubdir;
111 }
112 }
113
114 if (!config.dependModules().empty()) {
115 if (!config.indexDirs().empty()) {
116 for (auto &dir : config.indexDirs()) {
117 if (dir.startsWith(s: "..")) {
118 const QString prefix(QDir(config.currentDir())
119 .relativeFilePath(fileName: config.previousCurrentDir()));
120 if (!prefix.isEmpty())
121 dir.prepend(s: prefix + QLatin1Char('/'));
122 }
123 }
124 /*
125 Load all dependencies:
126 Either add all subdirectories of the indexdirs as dependModules,
127 when an asterisk is used in the 'depends' list, or
128 when <format>.nosubdirs is set, we need to look for all .index files
129 in the output subdirectory instead.
130 */
131 bool asteriskUsed = false;
132 if (config.dependModules().contains(t: "*")) {
133 config.dependModules().removeOne(t: "*");
134 asteriskUsed = true;
135 if (useNoSubDirs) {
136 std::for_each(first: formats.begin(), last: formats.end(), f: [&](const QString &format) {
137 QDir scanDir(config.getOutputDir(format));
138 QStringList foundModules =
139 scanDir.entryList(nameFilters: QStringList("*.index"), filters: QDir::Files);
140 std::transform(
141 first: foundModules.begin(), last: foundModules.end(), result: foundModules.begin(),
142 unary_op: [](const QString &index) { return QFileInfo(index).baseName(); });
143 config.dependModules() << foundModules;
144 });
145 } else {
146 for (const auto &indexDir : config.indexDirs()) {
147 QDir scanDir = QDir(indexDir);
148 scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
149 QFileInfoList dirList = scanDir.entryInfoList();
150 for (const auto &dir : dirList)
151 config.dependModules().append(t: dir.fileName());
152 }
153 }
154 // Remove self-dependencies and possible duplicates
155 QString project{config.get(CONFIG_PROJECT).asString()};
156 config.dependModules().removeAll(t: project.toLower());
157 config.dependModules().removeDuplicates();
158 qCCritical(lcQdoc) << "Configuration file for"
159 << project << "has depends = *; loading all"
160 << config.dependModules().size()
161 << "index files found";
162 }
163 for (const auto &module : config.dependModules()) {
164 QList<QFileInfo> foundIndices;
165 // Always look in module-specific subdir, even with *.nosubdirs config
166 bool useModuleSubDir = !subDirs.contains(value: module);
167 subDirs << module;
168
169 for (const auto &dir : config.indexDirs()) {
170 for (const auto &subDir : std::as_const(t&: subDirs)) {
171 QString fileToLookFor = dir + QLatin1Char('/') + subDir + QLatin1Char('/')
172 + module + ".index";
173 if (QFile::exists(fileName: fileToLookFor)) {
174 QFileInfo tempFileInfo(fileToLookFor);
175 if (!foundIndices.contains(t: tempFileInfo))
176 foundIndices.append(t: tempFileInfo);
177 }
178 }
179 }
180 // Clear the temporary module-specific subdir
181 if (useModuleSubDir)
182 subDirs.remove(value: module);
183 std::sort(first: foundIndices.begin(), last: foundIndices.end(), comp: creationTimeBefore);
184 QString indexToAdd;
185 if (foundIndices.size() > 1) {
186 /*
187 QDoc should always use the last entry in the multimap when there are
188 multiple index files for a module, since the last modified file has the
189 highest UNIX timestamp.
190 */
191 QStringList indexPaths;
192 indexPaths.reserve(asize: foundIndices.size());
193 for (const auto &found : std::as_const(t&: foundIndices))
194 indexPaths << found.absoluteFilePath();
195 if (warn) {
196 Location().warning(
197 message: QString("Multiple index files found for dependency \"%1\":\n%2")
198 .arg(args: module, args: indexPaths.join(sep: '\n')));
199 Location().warning(
200 message: QString("Using %1 as index file for dependency \"%2\"")
201 .arg(args: foundIndices[foundIndices.size() - 1].absoluteFilePath(),
202 args: module));
203 }
204 indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
205 } else if (foundIndices.size() == 1) {
206 indexToAdd = foundIndices[0].absoluteFilePath();
207 }
208 if (!indexToAdd.isEmpty()) {
209 if (!indexFiles.contains(str: indexToAdd))
210 indexFiles << indexToAdd;
211 } else if (!asteriskUsed && warn) {
212 Location().warning(
213 message: QString(R"("%1" Cannot locate index file for dependency "%2")")
214 .arg(args: config.get(CONFIG_PROJECT).asString(), args: module));
215 }
216 }
217 } else if (warn) {
218 Location().warning(
219 message: QLatin1String("Dependent modules specified, but no index directories were set. "
220 "There will probably be errors for missing links."));
221 }
222 }
223 qdb->readIndexes(indexFiles);
224}
225
226/*!
227 \internal
228 Prints to stderr the name of the project that QDoc is running for,
229 in which mode and which phase.
230
231 If QDoc is not running in debug mode or --log-progress command line
232 option is not set, do nothing.
233 */
234void logStartEndMessage(const QLatin1String &startStop, Config &config)
235{
236 if (!config.get(CONFIG_LOGPROGRESS).asBool())
237 return;
238
239 const QString runName = " qdoc for "
240 + config.get(CONFIG_PROJECT).asString()
241 + QLatin1String(" in ")
242 + QLatin1String(config.singleExec() ? "single" : "dual")
243 + QLatin1String(" process mode: ")
244 + QLatin1String(config.preparing() ? "prepare" : "generate")
245 + QLatin1String(" phase.");
246
247 const QString msg = startStop + runName;
248 qCInfo(lcQdoc) << msg.toUtf8().data();
249}
250
251/*!
252 Processes the qdoc config file \a fileName. This is the controller for all
253 of QDoc. The \a config instance represents the configuration data for QDoc.
254 All other classes are initialized with the same config.
255 */
256static void processQdocconfFile(const QString &fileName)
257{
258 Config &config = Config::instance();
259 config.setPreviousCurrentDir(QDir::currentPath());
260 ClangCodeParser clangParser;
261
262 /*
263 With the default configuration values in place, load
264 the qdoc configuration file. Note that the configuration
265 file may include other configuration files.
266
267 The Location class keeps track of the current location
268 in the file being processed, mainly for error reporting
269 purposes.
270 */
271 Location::initialize();
272 config.load(fileName);
273 QString project{config.get(CONFIG_PROJECT).asString()};
274 if (project.isEmpty()) {
275 qCCritical(lcQdoc) << QLatin1String("qdoc can't run; no project set in qdocconf file");
276 exit(status: 1);
277 }
278 Location::terminate();
279
280 config.setCurrentDir(QFileInfo(fileName).path());
281 if (!config.currentDir().isEmpty())
282 QDir::setCurrent(config.currentDir());
283
284 logStartEndMessage(startStop: QLatin1String("Start"), config);
285
286 if (config.getDebug()) {
287 Utilities::startDebugging(message: QString("command line"));
288 qCDebug(lcQdoc).noquote() << "Arguments:" << QCoreApplication::arguments();
289 }
290
291 // <<TODO: [cleanup-temporary-kludges]
292 // The underlying validation should be performed at the
293 // configuration level during parsing.
294 // This cannot be done straightforwardly with how the Config class
295 // is implemented.
296 // When the Config class will be deprived of logic and
297 // restructured, the compiler will notify us of this kludge, but
298 // remember to reevaluate the code itself considering the new
299 // data-flow and the possibility for optimizations as this is not
300 // done for temporary code. Indeed some of the code is visibly wasteful.
301 // Similarly, ensure that the loose definition that we use here is
302 // not preserved.
303
304 QStringList search_directories{config.getCanonicalPathList(CONFIG_EXAMPLEDIRS)};
305 QStringList image_search_directories{config.getCanonicalPathList(CONFIG_IMAGEDIRS)};
306
307 const auto &excludedDirList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS);
308 QSet<QString> excludedDirs = QSet<QString>(excludedDirList.cbegin(), excludedDirList.cend());
309 const auto &excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES);
310 QSet<QString> excludedFiles =
311 QSet<QString>(excludedFilesList.cbegin(), excludedFilesList.cend());
312
313 qCDebug(lcQdoc, "Adding doc/image dirs found in exampledirs to imagedirs");
314 QSet<QString> exampleImageDirs;
315 QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
316 for (const auto &image : exampleImageList) {
317 if (image.contains(s: "doc/images")) {
318 QString t = image.left(n: image.lastIndexOf(s: "doc/images") + 10);
319 if (!exampleImageDirs.contains(value: t))
320 exampleImageDirs.insert(value: t);
321 }
322 }
323
324 // REMARK: The previous system discerned between search directories based on the kind of file that was searched for.
325 // For example, an image search was bounded to some directories
326 // that may or may not be the same as the ones where examples are
327 // searched for.
328 // The current Qt documentation does not use this feature. That
329 // is, the output of QDoc when a unified search list is used is
330 // the same as the output for that of separated lists.
331 // For this reason, we currently simplify the process, albeit this
332 // may at some point change, by joining the various lists into a
333 // single search list and a unified interface.
334 // Do note that the configuration still allows for those
335 // parameters to be user defined in a split-way as this will not
336 // be able to be changed until Config itself is looked upon.
337 // Hence, we join the various directory sources into one list for the time being.
338 // Do note that this means that the amount of searched directories for a file is now increased.
339 // This shouldn't matter as the amount of directories is expected
340 // to be generally small and the search routine complexity is
341 // linear in the amount of directories.
342 // There are some complications that may arise in very specific
343 // cases by this choice (some of which where there before under
344 // possibly different circumstances), making some files
345 // unreachable.
346 // See the remarks in FileResolver for more infomration.
347 std::copy(first: image_search_directories.begin(), last: image_search_directories.end(), result: std::back_inserter(x&: search_directories));
348 std::copy(first: exampleImageDirs.begin(), last: exampleImageDirs.end(), result: std::back_inserter(x&: search_directories));
349
350 std::vector<DirectoryPath> validated_search_directories{};
351 for (const QString& path : search_directories) {
352 auto maybe_validated_path{DirectoryPath::refine(value: path)};
353 if (!maybe_validated_path)
354 // TODO: [uncentralized-admonition]
355 qCDebug(lcQdoc).noquote() << u"%1 is not a valid path, it will be ignored when resolving a file"_s.arg(a: path);
356 else validated_search_directories.push_back(x: *maybe_validated_path);
357 }
358
359 // TODO>>
360
361 FileResolver file_resolver{std::move(validated_search_directories)};
362
363 // REMARK: The constructor for generators doesn't actually perform
364 // initialization of their content.
365 // Indeed, Generators use the general antipattern of the static
366 // initialize-terminate non-scoped mutable state that we see in
367 // many parts of QDoc.
368 // In their constructor, Generators mainly register themselves into a static list.
369 // Previously, this was done at the start of main.
370 // To be able to pass a correct FileResolver or other systems, we
371 // need to construct them after the configuration has been read
372 // and has been destructured.
373 // For this reason, their construction was moved here.
374 // This function may be called more than once for some of QDoc's
375 // call, albeit this should not actually happen in Qt's
376 // documentation.
377 // Then, constructing the generators here might provide for some
378 // unexpected behavior as new generators are appended to the list
379 // and never used, considering that the list is searched in a
380 // linearly fashion and each generator of some type T, in the
381 // current codebase, will always be found if another instance of
382 // that same type would have been found.
383 // Furthermore, those instances would be destroyed by then, such
384 // that accessing them would be erroneous.
385 // To avoid this, the static list was made to be cleared in
386 // Generator::terminate, which, in theory, will be called before
387 // the generators will be constructed again.
388 // We could have used the initialize method for this, but this
389 // would force us into a limited and more complex semantic, see an
390 // example of this in DocParser, and would restrain us further to
391 // the initialize-terminate idiom which is expect to be purged in
392 // the future.
393 HtmlGenerator htmlGenerator{file_resolver};
394 WebXMLGenerator webXMLGenerator{file_resolver};
395 DocBookGenerator docBookGenerator{file_resolver};
396
397 /*
398 Initialize all the classes and data structures with the
399 qdoc configuration. This is safe to do for each qdocconf
400 file processed, because all the data structures created
401 are either cleared after they have been used, or they
402 are cleared in the terminate() functions below.
403 */
404 Location::initialize();
405 Tokenizer::initialize();
406 CodeMarker::initialize();
407 CodeParser::initialize();
408 Generator::initialize();
409 Doc::initialize(file_resolver);
410
411 /*
412 Initialize the qdoc database, where all the parsed source files
413 will be stored. The database includes a tree of nodes, which gets
414 built as the source files are parsed. The documentation output is
415 generated by traversing that tree.
416
417 Note: qdocDB() allocates a new instance only if no instance exists.
418 So it is safe to call qdocDB() any time.
419 */
420 QDocDatabase *qdb = QDocDatabase::qdocDB();
421 qdb->setVersion(config.get(CONFIG_VERSION).asString());
422 /*
423 By default, the only output format is HTML.
424 */
425 const QSet<QString> outputFormats = config.getOutputFormats();
426
427 qdb->clearSearchOrder();
428 if (!config.singleExec()) {
429 if (!config.preparing()) {
430 qCDebug(lcQdoc, " loading index files");
431 loadIndexFiles(formats: outputFormats);
432 qCDebug(lcQdoc, " done loading index files");
433 }
434 qdb->newPrimaryTree(module: project);
435 } else if (config.preparing())
436 qdb->newPrimaryTree(module: project);
437 else
438 qdb->setPrimaryTree(project);
439
440 // Retrieve the dependencies if loadIndexFiles() was not called
441 if (config.dependModules().isEmpty()) {
442 config.dependModules() = config.get(CONFIG_DEPENDS).asStringList();
443 config.dependModules().removeDuplicates();
444 }
445 qdb->setSearchOrder(config.dependModules());
446
447 // Store the title of the index (landing) page
448 NamespaceNode *root = qdb->primaryTreeRoot();
449 if (root) {
450 QString title{config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE).asString()};
451 root->tree()->setIndexTitle(
452 config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGTITLE).asString(defaultString: title));
453 }
454
455 if (config.dualExec() || config.preparing()) {
456 QStringList headerList;
457 QStringList sourceList;
458
459 qCDebug(lcQdoc, "Reading headerdirs");
460 headerList =
461 config.getAllFiles(CONFIG_HEADERS, CONFIG_HEADERDIRS, excludedDirs, excludedFiles);
462 QMap<QString, QString> headers;
463 QMultiMap<QString, QString> headerFileNames;
464 for (const auto &header : headerList) {
465 if (header.contains(s: QLatin1String("doc/snippets")))
466 continue;
467
468 if (headers.contains(key: header))
469 continue;
470
471 if (!ClangCodeParser::accepted_header_file_extensions.contains(str: QFileInfo{header}.suffix()))
472 continue;
473
474 headers.insert(key: header, value: header);
475 QString t = header.mid(position: header.lastIndexOf(c: '/') + 1);
476 headerFileNames.insert(key: t, value: t);
477 }
478
479 qCDebug(lcQdoc, "Reading sourcedirs");
480 sourceList =
481 config.getAllFiles(CONFIG_SOURCES, CONFIG_SOURCEDIRS, excludedDirs, excludedFiles);
482 std::set<QString> sources{};
483 for (const auto &source : sourceList) {
484 if (source.contains(s: QLatin1String("doc/snippets")))
485 continue;
486 sources.emplace(args: source);
487 }
488 /*
489 Find all the qdoc files in the example dirs, and add
490 them to the source files to be parsed.
491 */
492 qCDebug(lcQdoc, "Reading exampledirs");
493 QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
494 for (const auto &example : exampleQdocList) {
495 sources.emplace(args: example);
496 }
497 /*
498 Parse each header file in the set using the appropriate parser and add it
499 to the big tree.
500 */
501
502 qCDebug(lcQdoc, "Parsing header files");
503 for (auto it = headers.constBegin(); it != headers.constEnd(); ++it) {
504 qCDebug(lcQdoc, "Parsing %s", qPrintable(it.key()));
505 clangParser.parseHeaderFile(location: config.location(), filePath: it.key());
506 }
507
508 const QString moduleHeader = config.get(CONFIG_MODULEHEADER).asString();
509 clangParser.precompileHeaders(module_header: moduleHeader.isNull() ? project : moduleHeader);
510
511 /*
512 Parse each source text file in the set using the appropriate parser and
513 add it to the big tree.
514 */
515 if (config.get(CONFIG_LOGPROGRESS).asBool())
516 qCInfo(lcQdoc) << "Parse source files for" << project;
517
518
519 parseSourceFiles(sources);
520
521 if (config.get(CONFIG_LOGPROGRESS).asBool())
522 qCInfo(lcQdoc) << "Source files parsed for" << project;
523 }
524 /*
525 Now the primary tree has been built from all the header and
526 source files. Resolve all the class names, function names,
527 targets, URLs, links, and other stuff that needs resolving.
528 */
529 qCDebug(lcQdoc, "Resolving stuff prior to generating docs");
530 qdb->resolveStuff();
531
532 /*
533 The primary tree is built and all the stuff that needed
534 resolving has been resolved. Now traverse the tree and
535 generate the documentation output. More than one output
536 format can be requested. The tree is traversed for each
537 one.
538 */
539 qCDebug(lcQdoc, "Generating docs");
540 for (const auto &format : outputFormats) {
541 auto *generator = Generator::generatorForFormat(format);
542 if (generator) {
543 generator->initializeFormat();
544 generator->generateDocs();
545 } else {
546 config.get(CONFIG_OUTPUTFORMATS)
547 .location()
548 .fatal(QStringLiteral("QDoc: Unknown output format '%1'").arg(a: format));
549 }
550 }
551
552 qCDebug(lcQdoc, "Terminating qdoc classes");
553 if (Utilities::debugging())
554 Utilities::stopDebugging(message: project);
555
556 logStartEndMessage(startStop: QLatin1String("End"), config);
557 QDocDatabase::qdocDB()->setVersion(QString());
558 Generator::terminate();
559 CodeParser::terminate();
560 CodeMarker::terminate();
561 Doc::terminate();
562 Tokenizer::terminate();
563 Location::terminate();
564 QDir::setCurrent(config.previousCurrentDir());
565
566 qCDebug(lcQdoc, "qdoc classes terminated");
567}
568
569/*!
570 \internal
571 For each file in \a qdocFiles, first clear the configured module
572 dependencies and then pass the file to processQdocconfFile().
573
574 \sa processQdocconfFile(), singleExecutionMode(), dualExecutionMode()
575*/
576static void clearModuleDependenciesAndProcessQdocconfFile(const QStringList &qdocFiles)
577{
578 for (const auto &file : std::as_const(t: qdocFiles)) {
579 Config::instance().dependModules().clear();
580 processQdocconfFile(fileName: file);
581 }
582}
583
584/*!
585 \internal
586
587 A single QDoc process for prepare and generate phases.
588 The purpose is to first generate all index files for all documentation
589 projects that combined make out the documentation set being generated.
590 This allows QDoc to link to all content contained in all projects, e.g.
591 user-defined types or overview documentation, regardless of the project
592 that content belongs to when generating the final output.
593*/
594static void singleExecutionMode()
595{
596 const QStringList qdocFiles = Config::loadMaster(fileName: Config::instance().qdocFiles().at(i: 0));
597
598 Config::instance().setQDocPass(Config::Prepare);
599 clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
600
601 Config::instance().setQDocPass(Config::Generate);
602 QDocDatabase::qdocDB()->processForest();
603 clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
604}
605
606/*!
607 \internal
608
609 Process each .qdocconf-file passed as command line argument(s).
610*/
611static void dualExecutionMode()
612{
613 const QStringList qdocFiles = Config::instance().qdocFiles();
614 clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
615}
616
617QT_END_NAMESPACE
618
619int main(int argc, char **argv)
620{
621 QT_USE_NAMESPACE
622
623 // Initialize Qt:
624#ifndef QT_BOOTSTRAPPED
625 // use deterministic hash seed
626 QHashSeed::setDeterministicGlobalSeed();
627#endif
628 QCoreApplication app(argc, argv);
629 app.setApplicationVersion(QLatin1String(QT_VERSION_STR));
630
631 // Instantiate various singletons (used via static methods):
632 /*
633 Create code parsers for the languages to be parsed,
634 and create a tree for C++.
635 */
636 QmlCodeParser qmlParser;
637 PureDocParser docParser;
638
639 /*
640 Create code markers for plain text, C++,
641 and QML.
642
643 The plain CodeMarker must be instantiated first because it is used as
644 fallback when the other markers cannot be used.
645
646 Each marker instance is prepended to the CodeMarker::markers list by the
647 base class constructor.
648 */
649 CodeMarker fallbackMarker;
650 CppCodeMarker cppMarker;
651 QmlCodeMarker qmlMarker;
652
653 Config::instance().init(programName: "QDoc", args: app.arguments());
654
655 if (Config::instance().qdocFiles().isEmpty())
656 Config::instance().showHelp();
657
658 if (Config::instance().singleExec()) {
659 singleExecutionMode();
660 } else {
661 dualExecutionMode();
662 }
663
664 // Tidy everything away:
665 QmlTypeNode::terminate();
666 QDocDatabase::destroyQdocDB();
667 return Location::exitCode();
668}
669

source code of qttools/src/qdoc/qdoc/main.cpp