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 | |
39 | QT_BEGIN_NAMESPACE |
40 | |
41 | using namespace Qt::StringLiterals; |
42 | |
43 | bool 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 | */ |
62 | static 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 | */ |
83 | static 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 | */ |
234 | void 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 | */ |
256 | static 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 ; |
457 | QStringList sourceList; |
458 | |
459 | qCDebug(lcQdoc, "Reading headerdirs" ); |
460 | headerList = |
461 | config.getAllFiles(CONFIG_HEADERS, CONFIG_HEADERDIRS, excludedDirs, excludedFiles); |
462 | QMap<QString, QString> ; |
463 | QMultiMap<QString, QString> ; |
464 | for (const auto & : 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 = 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 | */ |
576 | static 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 | */ |
594 | static 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 | */ |
611 | static void dualExecutionMode() |
612 | { |
613 | const QStringList qdocFiles = Config::instance().qdocFiles(); |
614 | clearModuleDependenciesAndProcessQdocconfFile(qdocFiles); |
615 | } |
616 | |
617 | QT_END_NAMESPACE |
618 | |
619 | int 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 | |