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

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