75 std::vector<QString>&& sources,
80 std::stable_sort(sources.begin(), sources.end());
83 std::unique(sources.begin(), sources.end()),
87 sources.erase(
std::remove_if(sources.begin(), sources.end(),
88 [](
const QString &source) {
89 return Utilities::isGeneratedFile(source);
94 std::stable_partition(sources.begin(), sources.end(), [](
const QString& source){
95 return CodeParser::parserForSourceFile(source) == CodeParser::parserForLanguage(
"QML");
99 std::for_each(qml_sources, sources.end(),
100 [&source_file_parser, &cpp_code_parser, &error_handler](
const QString& source){
101 qCDebug(lcQdoc,
"Parsing %s", qPrintable(source));
103 auto [untied_documentation, tied_documentation] = source_file_parser(tag_source_file(source));
104 std::vector<FnMatchError> errors{};
106 for (
const auto &untied : std::as_const(untied_documentation)) {
107 auto result = cpp_code_parser.processTopicArgs(untied);
108 tied_documentation.insert(tied_documentation.end(), result.first.begin(), result.first.end());
109 errors.insert(errors.end(), result.second.begin(), result.second.end());
115 for (
const auto &e : errors)
119 std::for_each(sources.begin(), qml_sources, [&cpp_code_parser](
const QString& source){
121 if (!codeParser)
return;
123 qCDebug(lcQdoc,
"Parsing %s", qPrintable(source));
124 codeParser
->parseSourceFile(Config::instance().location(), source, cpp_code_parser);
140 Config &config = Config::instance();
145 config.dependModules() += config.get(
CONFIG_DEPENDS).asStringList();
146 config.dependModules().removeDuplicates();
147 bool useNoSubDirs =
false;
148 QSet<QString> subDirs;
152 for (
const auto &format : formats) {
153 if (config.get(format + Config::dot +
"nosubdirs").asBool()) {
155 const auto singleOutputSubdir{QDir(config.getOutputDir(format)).dirName()};
156 if (!singleOutputSubdir.isEmpty())
157 subDirs << singleOutputSubdir;
161 if (!config.dependModules().empty()) {
162 if (!config.indexDirs().empty()) {
163 for (
auto &dir : config.indexDirs()) {
164 if (dir.startsWith(
"..")) {
165 const QString prefix(QDir(config.currentDir())
166 .relativeFilePath(config.previousCurrentDir()));
167 if (!prefix.isEmpty())
168 dir.prepend(prefix + QLatin1Char(
'/'));
172
173
174
175
176
177
178 bool asteriskUsed =
false;
179 if (config.dependModules().contains(
"*")) {
180 config.dependModules().removeOne(
"*");
183 std::for_each(formats.begin(), formats.end(), [&](
const QString &format) {
184 QDir scanDir(config.getOutputDir(format));
185 QStringList foundModules =
186 scanDir.entryList(QStringList(
"*.index"), QDir::Files);
188 foundModules.begin(), foundModules.end(), foundModules.begin(),
189 [](
const QString &index) {
return QFileInfo(index).baseName(); });
190 config.dependModules() << foundModules;
193 for (
const auto &indexDir : config.indexDirs()) {
194 QDir scanDir = QDir(indexDir);
195 scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
196 QFileInfoList dirList = scanDir.entryInfoList();
197 for (
const auto &dir : dirList)
198 config.dependModules().append(dir.fileName());
203 config.dependModules().removeAll(project.toLower());
204 config.dependModules().removeDuplicates();
205 qCCritical(lcQdoc) <<
"Configuration file for"
206 << project <<
"has depends = *; loading all"
207 << config.dependModules().size()
208 <<
"index files found";
210 for (
const auto &module : config.dependModules()) {
211 QList<QFileInfo> foundIndices;
213 bool useModuleSubDir = !subDirs.contains(module);
216 for (
const auto &dir : config.indexDirs()) {
217 for (
const auto &subDir : std::as_const(subDirs)) {
218 QString fileToLookFor = dir + QLatin1Char(
'/') + subDir + QLatin1Char(
'/')
219 + module.toLower() +
".index";
220 if (QFile::exists(fileToLookFor)) {
221 QFileInfo tempFileInfo(fileToLookFor);
222 if (!foundIndices.contains(tempFileInfo))
223 foundIndices.append(tempFileInfo);
229 subDirs.remove(module);
230 std::sort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
232 if (foundIndices.size() > 1) {
234
235
236
237
238 QStringList indexPaths;
239 indexPaths.reserve(foundIndices.size());
240 for (
const auto &found : std::as_const(foundIndices))
241 indexPaths << found.absoluteFilePath();
244 QString(
"Multiple index files found for dependency \"%1\":\n%2")
245 .arg(module, indexPaths.join(
'\n')));
247 QString(
"Using %1 as index file for dependency \"%2\"")
248 .arg(foundIndices[foundIndices.size() - 1].absoluteFilePath(),
251 indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
252 }
else if (foundIndices.size() == 1) {
253 indexToAdd = foundIndices[0].absoluteFilePath();
255 if (!indexToAdd.isEmpty()) {
256 if (!indexFiles.contains(indexToAdd))
257 indexFiles << indexToAdd;
258 }
else if (!asteriskUsed && warn) {
260 QString(R"("%1" Cannot locate index file for dependency "%2")")
266 QLatin1String(
"Dependent modules specified, but no index directories were set. "
267 "There will probably be errors for missing links."));
270 qdb->readIndexes(indexFiles);
315 const QString url = config.get(
CONFIG_URL).asString();
317 .asString(project +
" Reference Documentation"_L1);
321 QString outputDirPath;
322 if (config.getOutputFormats().contains(u"HTML"_s)) {
323 outputDirPath = config.getOutputDir(u"HTML"_s);
329 if (config.singleExec())
330 outputDirPath +=
'/'_L1 + project.toLower();
333 const QString fileBase = project.toLower().simplified().replace(
' '_L1,
'-'_L1);
336 const QString indexFileName = fileBase +
".index"_L1;
337 const QString indexPath = outputDir.absoluteFilePath(indexFileName);
341 if (!htmlGenerator) {
342 qCWarning(lcQdoc) <<
"Cannot generate index file: no href generator available"
343 " (HTML generator missing)";
348 context.documentLocation = [htmlGenerator](
const Node *node) {
349 return htmlGenerator->fullDocumentLocation(node);
351 context.exampleFileLocation = [module = Generator::defaultModuleName(),
352 extension = htmlGenerator->fileExtension()](
const QString &file) {
353 return Utilities::linkForExampleFile(file, module, extension);
356 context.project = project;
369 Config &config = Config::instance();
370 config.setPreviousCurrentDir(QDir::currentPath());
373
374
375
376
377
378
379
380
382 config.load(fileName);
384 if (project.isEmpty()) {
385 qCCritical(lcQdoc) << QLatin1String(
"qdoc can't run; no project set in qdocconf file");
390 config.setCurrentDir(QFileInfo(fileName).path());
391 if (!config.currentDir().isEmpty())
392 QDir::setCurrent(config.currentDir());
394 logStartEndMessage(QLatin1String(
"Start"), config);
397 Utilities::startDebugging(QString(
"command line"));
398 qCDebug(lcQdoc).noquote() <<
"Arguments:" << QCoreApplication::arguments();
415 QStringList image_search_directories{config.getCanonicalPathList(
CONFIG_IMAGEDIRS)};
419 qCDebug(lcQdoc,
"Adding doc/image dirs found in exampledirs to imagedirs");
420 QSet<QString> exampleImageDirs;
421 QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
422 for (
const auto &image : exampleImageList) {
423 if (image.contains(
"doc/images")) {
424 QString t = image.left(image.lastIndexOf(
"doc/images") + 10);
425 if (!exampleImageDirs.contains(t))
426 exampleImageDirs.insert(t);
453 std::copy(image_search_directories.begin(), image_search_directories.end(),
std::back_inserter(search_directories));
454 std::copy(exampleImageDirs.begin(), exampleImageDirs.end(),
std::back_inserter(search_directories));
456 std::vector<DirectoryPath> validated_search_directories{};
457 for (
const QString& path : search_directories) {
458 auto maybe_validated_path{DirectoryPath::refine(path)};
459 if (!maybe_validated_path)
461 qCDebug(lcQdoc).noquote() << u"%1 is not a valid path, it will be ignored when resolving a file"_s.arg(path);
462 else validated_search_directories.push_back(*maybe_validated_path);
502#ifdef QDOC_TEMPLATE_GENERATOR_ENABLED
512 auto configuredFormats = config.getOutputFormats().values();
513 std::sort(configuredFormats.begin(), configuredFormats.end(),
514 [](
const QString &a,
const QString &b) {
515 return QString::compare(a, b, Qt::CaseInsensitive) < 0;
517 std::vector<std::unique_ptr<TemplateGenerator>> templateGenerators;
518 templateGenerators.reserve(configuredFormats.size());
519 const auto reportTemplateRenderFailure = [](
const QString &message) {
520 Location{}.error(message);
521 Location::increaseErrorCount();
523 for (
const auto &fmt : configuredFormats) {
524 if (fmt.startsWith(
"template"_L1, Qt::CaseInsensitive))
525 templateGenerators.push_back(
526 std::make_unique<TemplateGenerator>(file_resolver, *QDocDatabase::qdocDB(),
527 fmt, reportTemplateRenderFailure));
534
535
536
537
538
539
540
541
545
546
547 const QSet<QString> outputFormats = config.getOutputFormats();
552 qCDebug(lcQdoc,
" loading index files");
553 loadIndexFiles(outputFormats);
554 qCDebug(lcQdoc,
" done loading index files");
556 qdb->newPrimaryTree(project);
558 qdb->newPrimaryTree(project);
560 qdb->setPrimaryTree(project);
563 if (config.dependModules().isEmpty()) {
564 config.dependModules() = config.get(
CONFIG_DEPENDS).asStringList();
565 config.dependModules().removeDuplicates();
567 qdb->setSearchOrder(config.dependModules());
578 std::vector<QByteArray> include_paths{};
583 args.append(Utilities::getInternalIncludePaths(QStringLiteral(
"clang++")));
584#elif defined(Q_OS_LINUX)
585 args.append(Utilities::getInternalIncludePaths(QStringLiteral(
"g++")));
588 for (
const auto &path : std::as_const(args)) {
590 include_paths.push_back(path.toUtf8());
593 include_paths.erase(
std::unique(include_paths.begin(), include_paths.end()),
594 include_paths.end());
597 QList<QByteArray> clang_defines{};
599 const QStringList config_defines{config.get(
CONFIG_DEFINES).asStringList()};
600 for (
const QString &def : config_defines) {
601 if (!def.contains(QChar(
'*'))) {
602 QByteArray tmp(
"-D");
603 tmp.append(def.toUtf8());
604 clang_defines.append(tmp.constData());
610
611
612
613
614
615
616
617
618
619
620
621
632 QDocDatabase::qdocDB(),
633 moduleHeader.isNull() ? project : moduleHeader,
634 Config::instance().getHeaderFiles(),
637 Config::instance().createInclusionPolicy()
641 ClangCodeParser clangParser(QDocDatabase::qdocDB(), Config::instance(), include_paths, clang_defines, pch);
645 QStringList sourceList;
647 qCDebug(lcQdoc,
"Reading sourcedirs");
656 std::vector<QString> sources{};
657 for (
const auto &source : sourceList) {
658 if (!source.contains(QLatin1String(
"doc/snippets")))
659 sources.emplace_back(source);
662
663
664
665 qCDebug(lcQdoc,
"Reading exampledirs");
666 QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
667 for (
const auto &example : exampleQdocList) {
668 if (!example.contains(QLatin1String(
"doc/snippets")))
669 sources.emplace_back(example);
673
674
675
677 qCInfo(lcQdoc) <<
"Parse source files for" << project;
680 auto headers = config.getHeaderFiles();
684 parseSourceFiles(
std::move(sources), source_file_parser, cpp_code_parser);
687 qCInfo(lcQdoc) <<
"Source files parsed for" << project;
690
691
692
693
694 qCDebug(lcQdoc,
"Resolving stuff prior to generating docs");
698
699
700
701
702
703
704 qCDebug(lcQdoc,
"Generating docs");
705 int producedFormats = 0;
706 for (
const auto &format : outputFormats) {
707 auto *generator = Generator::generatorForFormat(format);
709 generator->initializeFormat();
710 generator->generateDocs();
712 }
else if (
auto *producer = OutputProducerRegistry::instance().producerForFormat(format)) {
713 if (!config.preparing()) {
716 producer->finalize();
722 .warning(QStringLiteral(
"QDoc: Unknown output format '%1'; skipping. "
723 "Check for typos or ensure the corresponding "
724 "generator is enabled in this QDoc build.")
729 if (!outputFormats.isEmpty() && producedFormats == 0) {
732 .fatal(QStringLiteral(
"QDoc: None of the configured output formats could be produced; aborting"));
737 qCDebug(lcQdoc,
"Terminating qdoc classes");
741 logStartEndMessage(QLatin1String(
"End"), config);
749 QDir::setCurrent(config.previousCurrentDir());
751 qCDebug(lcQdoc,
"qdoc classes terminated");