Doxygen源码分析:doxygen执行过程的拆解
2023-05-19 23:09:17 ~ 2023-05-20 16:38:13
ChrisZZ imzhuo@foxmailcom
Hompage https://github.com/zchrissirhcz
文章目录
- Doxygen源码分析:doxygen执行过程的拆解
- 1. doxygen 版本
- 2. doxygen 可执行程序的入口: `src/main.cpp`
- 3. `initDoxygen()`: 初始化 doxygen 的过程浅析
- 4. `readConfiguration(argc, argv)`: 读取配置文件的过程浅析
- 查看 doxygen 内置的 emoji
- 开启 Tracing
- 5. `checkConfiguration()`: 检查和解析配置选项的过程浅析
- 6. `adjustConfiguration()`: 调整配置文件的过程浅析
- 7. `parseInput()`: 解析输入的过程浅析
- 8. `generateOutput()`: 生成输出的过程浅析
1. doxygen 版本
本次使用的 doxygen 版本如下, 相比于前几篇源码分析时用的版本,有更新,对应到 doxygen 正式版1.9.8(没有特别大的变化):
$ git log
commit 5fded4215d4f9271fe92c940fc4532d4704f5be1 (HEAD -> master, upstream/master)
Author: Dimitri van Heesch <doxygen@gmail.com>
Date: Thu May 18 22:14:30 2023 +0200bump version to 1.9.8 for development
2. doxygen 可执行程序的入口: src/main.cpp
在 src/CMakeLists.txt
中看到 doxygen
这个 target 的描述, 它由 main.cpp
和 doxygen.rc
两个文件组成:
add_executable(doxygenmain.cpp${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
doxygen.rc
不是代码, 忽略; main.cpp
给出了整个 doxygen 执行的6部分内容:
#include "doxygen.h"/*! \file* \brief main entry point for doxygen** This file contains main()*//*! Default main. The idea of separating this from the rest of doxygen,* is to make it possible to write your own main, with a different* generateOutput() function for instance.*/
int main(int argc,char **argv)
{initDoxygen(); // 初始化 doxygenreadConfiguration(argc,argv); // 读取配置, 通常是读取 Doxyfile 这一配置文件checkConfiguration(); // 检查和解析配置选项adjustConfiguration(); // 调整配置:有些全局变量依赖于配置parseInput(); // 解析输入generateOutput(); // 生成输出, 例如 html, xml, latex, pdfreturn 0;
}
3. initDoxygen()
: 初始化 doxygen 的过程浅析
包括如下部分:
- 初始化资源:加载 templates 目录下的文件
- 设置语言、编码等本地化的配置
- 检查和修正路径
- 调试工具的计时器开始计时
- 给各编程语言注册 parser
- 为 LinkedMap 类的子类实例执行初始化:成员名字、函数名字、namespace、class 等
- 其他全局配置的初始化
以下是包含注释的 initDoxygen()
函数源码:
// src/doxyge.cpp, L10724-L10803
void initDoxygen()
{initResources(); // 初始化资源, 在 build/generated_src/resources.cpp 里实现, 把 templates 目录下的每个文件都注册进来QCString lang = Portable::getenv("LC_ALL"); // QCString 是 doxygen 中重新实现的, 接口和 Qt 的 QCString 保持一致。内部使用 std::string.if (!lang.isEmpty()) Portable::setenv("LANG",lang); // 设置语言,编码等std::setlocale(LC_ALL,"");std::setlocale(LC_CTYPE,"C"); // to get isspace(0xA0)==0, needed for UTF-8std::setlocale(LC_NUMERIC,"C");Doxygen::symbolMap = new SymbolMap<Definition>; // SymbolMap 是模板类, 把符号名字映射为对象。 Definition 是所有定义实体(类,成员函数,文件,作用域等)的公共基类Portable::correct_path(); // Portable 是一个 namespace, 提供了跨平台的函数, 如 sysmtem(), pid,(), getenv(), sleep(), isAbsolutePath() 等。// correct_path() 用于修正一个可能错误的路径,例如在 MSVC 编译器下, 把路径中的 "/" 替换为 "\\"Debug::startTimer(); // 开启全局计时器。 Debug 是一个类, 包含了打印函数 print(), 用于调试。// ParserManger 管理各种编程语言的解析器,提供注册 parser、根据文件扩展获取 parser 的功能。// 默认注册的语言是: C, Python, Fortran, FortranFree, FortranFixed, VHDL, XML, SQL, Markdown, LexDoxygen::parserManager = new ParserManager( make_parser_factory<NullOutlineParser>(), make_parser_factory<FileCodeParser>());Doxygen::parserManager->registerParser("c", make_parser_factory<COutlineParser>(),make_parser_factory<CCodeParser>());Doxygen::parserManager->registerParser("python", make_parser_factory<PythonOutlineParser>(),make_parser_factory<PythonCodeParser>());Doxygen::parserManager->registerParser("fortran", make_parser_factory<FortranOutlineParser>(),make_parser_factory<FortranCodeParser>());Doxygen::parserManager->registerParser("fortranfree", make_parser_factory<FortranOutlineParserFree>(),make_parser_factory<FortranCodeParserFree>());Doxygen::parserManager->registerParser("fortranfixed", make_parser_factory<FortranOutlineParserFixed>(),make_parser_factory<FortranCodeParserFixed>());Doxygen::parserManager->registerParser("vhdl", make_parser_factory<VHDLOutlineParser>(),make_parser_factory<VHDLCodeParser>());Doxygen::parserManager->registerParser("xml", make_parser_factory<NullOutlineParser>(),make_parser_factory<XMLCodeParser>());Doxygen::parserManager->registerParser("sql", make_parser_factory<NullOutlineParser>(),make_parser_factory<SQLCodeParser>());Doxygen::parserManager->registerParser("md", make_parser_factory<MarkdownOutlineParser>(),make_parser_factory<FileCodeParser>());Doxygen::parserManager->registerParser("lex", make_parser_factory<LexOutlineParser>(),make_parser_factory<LexCodeParser>());// register any additional parsers here...// 初始化默认的扩展映射: 把文件的扩展名(如 .hpp) 映射到 parser(如parser id 为 "c")initDefaultExtensionMapping();// doxygen 默认没开启 libclang. 用户可手动开启, 开启后会使用 clang parser 以获得更准确的解析结果, 不过速度会慢// USE_LIBCLANG 适合大量使用了 C++ 模板的代码(的工程中的文档生成), doxygen 内置的 parser 对必要的类型信息比较缺乏。
#if USE_LIBCLANGDoxygen::clangUsrMap = new ClangUsrMap;
#endifDoxygen::memberNameLinkedMap = new MemberNameLinkedMap; // MemberNameLinkedMap 类是 “成员名字对象”的有序字典类. 是 LinkedMap<MemberName>Doxygen::functionNameLinkedMap = new MemberNameLinkedMap; // MemberNameLinkedMap 类是 LinkedMap<MemberName>Doxygen::groupLinkedMap = new GroupLinkedMap; // GroupLinkedMap 类是 LinkedMap<GroupDef>Doxygen::namespaceLinkedMap = new NamespaceLinkedMap; // NamespaceLinkedMap 类是 LinkedMap<NamespaceDef>Doxygen::classLinkedMap = new ClassLinkedMap; // ClassLinkedMap 类是 LinkedMap<ClassDef>Doxygen::hiddenClassLinkedMap = new ClassLinkedMap;Doxygen::conceptLinkedMap = new ConceptLinkedMap; // ConceptLinkedMap 类是 LinkedMap<ConceptDef>Doxygen::dirLinkedMap = new DirLinkedMap; // DirLinkedMap 类是 LinkedMap<DirDef>Doxygen::pageLinkedMap = new PageLinkedMap; // all doc pages // PageLinkedMap 类是 LinkedMap<PageDef>Doxygen::exampleLinkedMap = new PageLinkedMap; // all examples//Doxygen::tagDestinationDict.setAutoDelete(TRUE);Doxygen::indexList = new IndexList; // 索引接口列表类// initialisation of these globals depends on// configuration switches so we need to postpone these// 如下全局变量的值,可以在配置文件中覆盖,此处仅做默认值的赋值// 实际上它们都是空指针, 使用 nullptr 替代 0 更合适Doxygen::globalScope = 0;Doxygen::inputNameLinkedMap = 0;Doxygen::includeNameLinkedMap = 0;Doxygen::exampleNameLinkedMap = 0;Doxygen::imageNameLinkedMap = 0;Doxygen::dotFileNameLinkedMap = 0;Doxygen::mscFileNameLinkedMap = 0;Doxygen::diaFileNameLinkedMap = 0;/*************************************************************************** Initialize some global constants**************************************************************************/// g_compoundKeywords 是一个 set, 基本元素是 stringg_compoundKeywords.insert("template class");g_compoundKeywords.insert("template struct");g_compoundKeywords.insert("class");g_compoundKeywords.insert("struct");g_compoundKeywords.insert("union");g_compoundKeywords.insert("interface");g_compoundKeywords.insert("exception");
}
4. readConfiguration(argc, argv)
: 读取配置文件的过程浅析
- 获取 doxygen 版本信息
- 定义
writeFile()
函数 - 解析 argv 参数
- …
直接看这个函数的源码, 不是很容易连贯起来, 可以直接从 doxygen --help
给出的整段提示内容得到启发,再结合源码可以得到更好的理解:
zz@Legion-R7000P% doxygen --help
Doxygen version 1.9.8 (5fded4215d4f9271fe92c940fc4532d4704f5be1*)
Copyright Dimitri van Heesch 1997-2021You can use doxygen in a number of ways:1) Use doxygen to generate a template configuration file*:doxygen [-s] -g [configName]2) Use doxygen to update an old configuration file*:doxygen [-s] -u [configName]3) Use doxygen to generate documentation using an existing configuration file*:doxygen [configName]4) Use doxygen to generate a template file controlling the layout of thegenerated documentation:doxygen -l [layoutFileName]In case layoutFileName is omitted DoxygenLayout.xml will be used as filename.If - is used for layoutFileName doxygen will write to standard output.5) Use doxygen to generate a template style sheet file for RTF, HTML or Latex.RTF: doxygen -w rtf styleSheetFileHTML: doxygen -w html headerFile footerFile styleSheetFile [configFile]LaTeX: doxygen -w latex headerFile footerFile styleSheetFile [configFile]6) Use doxygen to generate a rtf extensions filedoxygen -e rtf extensionsFileIf - is used for extensionsFile doxygen will write to standard output.7) Use doxygen to compare the used configuration file with the template configuration filedoxygen -x [configFile]Use doxygen to compare the used configuration file with the template configuration filewithout replacing the environment variables or CMake type replacement variablesdoxygen -x_noenv [configFile]8) Use doxygen to show a list of built-in emojis.doxygen -f emoji outputFileNameIf - is used for outputFileName doxygen will write to standard output.*) If -s is specified the comments of the configuration items in the config file will be omitted.If configName is omitted 'Doxyfile' will be used as a default.If - is used for configFile doxygen will write / read the configuration to /from standard output / input.If -q is used for a doxygen documentation run, doxygen will see this as if QUIET=YES has been set.-v print version string, -V print extended version information
-h,-? prints usage help information
doxygen -d prints additional usage flags for debugging purposes
带注释的 readConfiguration(argc, argv)
代码如下:
// src/doxygen.cpp, L10847-L11248
void readConfiguration(int argc, char **argv)
{// getFullVersion() 在 libversion/fullversion.cpp 中实现, 结果形如 1.9.8 (git commit id)// 其中获取 git commit id 是在 build/generated_src/gitversion.cpp 的 getGitVersion() 函数实现的QCString versionString = getFullVersion();// helper that calls \a func to write to file \a fileName via a TextStream// writeFile 是一个 把 TextStream 写入文件的 lambda 函数。// TextStream 类是 std::ostringstream 类的改进版, 更简单,性能也更高auto writeFile = [](const char *fileName,std::function<void(TextStream&)> func) -> bool{std::ofstream f;if (openOutputFile(fileName,f)){TextStream t(&f);func(t);return true;}return false;};/*************************************************************************** Handle arguments ***************************************************************************/// 解析 argv[1]...argv[argc-1] 参数。对于每个 argv[i] 来说, 需要 argv[i][0] 为 '-', argv[i][1] 为具体的选项(单个字母)// 例如, doxygen -g 其中 g 表示要生成(generate)配置文件int optInd=1;QCString configName;QCString layoutName;QCString debugLabel;QCString formatName;QCString listName;QCString traceName;bool genConfig=FALSE;bool shortList=FALSE;Config::CompareMode diffList=Config::CompareMode::Full;bool updateConfig=FALSE;int retVal;bool quiet = false;while (optInd<argc && argv[optInd][0]=='-' &&(isalpha(argv[optInd][1]) || argv[optInd][1]=='?' ||argv[optInd][1]=='-')){switch(argv[optInd][1]){case 'g': // g = generate, 生成配置文件genConfig=TRUE;break;case 'l': // l = layout, 指定 xml 布局文件if (optInd+1>=argc){layoutName="DoxygenLayout.xml";}else{layoutName=argv[optInd+1];}writeDefaultLayoutFile(layoutName);cleanUpDoxygen();exit(0);break;case 'd': // d = debug, 生成调试标签。给 doxygen 开发者用的。debugLabel=getArg(argc,argv,optInd);if (debugLabel.isEmpty()){devUsage();cleanUpDoxygen();exit(0);}retVal = Debug::setFlagStr(debugLabel);if (!retVal){err("option \"-d\" has unknown debug specifier: \"%s\".\n",qPrint(debugLabel));devUsage();cleanUpDoxygen();exit(1);}break;case 't': // t = tracing, 开启开发 log{
#if ENABLE_TRACINGif (optInd+1>=argc || argv[optInd+1][0] == '-'){traceName="trace.txt";}else{traceName=argv[optInd+1];optInd++;}
#elseerr("support for option \"-t\" has not been compiled in (use a debug build or a release build with tracing enabled).\n");cleanUpDoxygen();exit(1);
#endif}break;case 'x': // x = compare,意思是比较当前实用的配置文件和模板中的配置文件。类型定义位于 src/config.h 中的 config::CompareMode 类。if (!strcmp(argv[optInd]+1,"x_noenv")) diffList=Config::CompareMode::CompressedNoEnv;else if (!strcmp(argv[optInd]+1,"x")) diffList=Config::CompareMode::Compressed;else{err("option should be \"-x\" or \"-x_noenv\", found: \"%s\".\n",argv[optInd]);cleanUpDoxygen();exit(1);}break;case 's': // s = shortlist, 含义暂时不明shortList=TRUE;break;case 'u': // u = update config, 更新配置文件updateConfig=TRUE;break;case 'e': // e = extension, 用于生成 RTF 扩展文件, 例如 doxygen -e rtf extensionsFileformatName=getArg(argc,argv,optInd);if (formatName.isEmpty()){err("option \"-e\" is missing format specifier rtf.\n");cleanUpDoxygen();exit(1);}if (qstricmp(formatName.data(),"rtf")==0){if (optInd+1>=argc){err("option \"-e rtf\" is missing an extensions file name\n");cleanUpDoxygen();exit(1);}writeFile(argv[optInd+1],RTFGenerator::writeExtensionsFile);cleanUpDoxygen();exit(0);}err("option \"-e\" has invalid format specifier.\n");cleanUpDoxygen();exit(1);break;case 'f': // f = filename, 显示 doxygen 内置的 emoji, 并打印输出到文件 listName=getArg(argc,argv,optInd);if (listName.isEmpty()){err("option \"-f\" is missing list specifier.\n");cleanUpDoxygen();exit(1);}if (qstricmp(listName.data(),"emoji")==0){if (optInd+1>=argc){err("option \"-f emoji\" is missing an output file name\n");cleanUpDoxygen();exit(1);}writeFile(argv[optInd+1],[](TextStream &t) { EmojiEntityMapper::instance().writeEmojiFile(t); });cleanUpDoxygen();exit(0);}err("option \"-f\" has invalid list specifier.\n");cleanUpDoxygen();exit(1);break;case 'w': // w = ? , 让 doxygen 为 RTF, HTML or Latex 输出生成样式文件formatName=getArg(argc,argv,optInd);if (formatName.isEmpty()){err("option \"-w\" is missing format specifier rtf, html or latex\n");cleanUpDoxygen();exit(1);}if (qstricmp(formatName.data(),"rtf")==0){if (optInd+1>=argc){err("option \"-w rtf\" is missing a style sheet file name\n");cleanUpDoxygen();exit(1);}if (!writeFile(argv[optInd+1],RTFGenerator::writeStyleSheetFile)){err("error opening RTF style sheet file %s!\n",argv[optInd+1]);cleanUpDoxygen();exit(1);}cleanUpDoxygen();exit(0);}else if (qstricmp(formatName.data(),"html")==0){Config::init();if (optInd+4<argc || FileInfo("Doxyfile").exists())// explicit config file mentioned or default found on disk{QCString df = optInd+4<argc ? argv[optInd+4] : QCString("Doxyfile");if (!Config::parse(df)) // parse the config file{err("error opening or reading configuration file %s!\n",argv[optInd+4]);cleanUpDoxygen();exit(1);}}if (optInd+3>=argc){err("option \"-w html\" does not have enough arguments\n");cleanUpDoxygen();exit(1);}Config::postProcess(TRUE);Config::updateObsolete();Config::checkAndCorrect(Config_getBool(QUIET), false);setTranslator(Config_getEnum(OUTPUT_LANGUAGE));writeFile(argv[optInd+1],[&](TextStream &t) { HtmlGenerator::writeHeaderFile(t,argv[optInd+3]); });writeFile(argv[optInd+2],HtmlGenerator::writeFooterFile);writeFile(argv[optInd+3],HtmlGenerator::writeStyleSheetFile);cleanUpDoxygen();exit(0);}else if (qstricmp(formatName.data(),"latex")==0){Config::init();if (optInd+4<argc || FileInfo("Doxyfile").exists()){QCString df = optInd+4<argc ? argv[optInd+4] : QCString("Doxyfile");if (!Config::parse(df)){err("error opening or reading configuration file %s!\n",argv[optInd+4]);cleanUpDoxygen();exit(1);}}if (optInd+3>=argc){err("option \"-w latex\" does not have enough arguments\n");cleanUpDoxygen();exit(1);}Config::postProcess(TRUE);Config::updateObsolete();Config::checkAndCorrect(Config_getBool(QUIET), false);setTranslator(Config_getEnum(OUTPUT_LANGUAGE));writeFile(argv[optInd+1],LatexGenerator::writeHeaderFile);writeFile(argv[optInd+2],LatexGenerator::writeFooterFile);writeFile(argv[optInd+3],LatexGenerator::writeStyleSheetFile);cleanUpDoxygen();exit(0);}else{err("Illegal format specifier \"%s\": should be one of rtf, html or latex\n",qPrint(formatName));cleanUpDoxygen();exit(1);}break;case 'm': // m = map, 导出符号映射文件g_dumpSymbolMap = TRUE;break;case 'v': // v = version, 用 msg() 打印版本信息, false 表示不要打印扩展的版本信息内容(如sqlite, libclang等)version(false);cleanUpDoxygen();exit(0);break;case 'V': // V = version, 打印常规的版本信息之外, 还打印扩展的版本信息内容version(true);cleanUpDoxygen();exit(0);break;case '-': if (qstrcmp(&argv[optInd][2],"help")==0) // --help 打印帮助信息{usage(argv[0],versionString);exit(0);}else if (qstrcmp(&argv[optInd][2],"version")==0) // --version 打印版本信息{version(false);cleanUpDoxygen();exit(0);}else if ((qstrcmp(&argv[optInd][2],"Version")==0) ||(qstrcmp(&argv[optInd][2],"VERSION")==0)) // --Version 和 --VERSION 打印版本信息{version(true);cleanUpDoxygen();exit(0);}else{err("Unknown option \"-%s\"\n",&argv[optInd][1]);usage(argv[0],versionString);exit(1);}break;case 'b': // b = buf, 指定IO流的缓冲区群, `_IONBF` 意思是“不使用缓冲区”,因此会立即输出到控制台setvbuf(stdout,NULL,_IONBF,0);break;case 'q': // q = quiet, 安静模式quiet = true;break;case 'T': // T = template, 意思是使用 Django 风格的模板文件。目前还处于实验状态, 请谨慎使用。msg("Warning: this option activates output generation via Django like template files. ""This option is scheduled for doxygen 2.0, is currently incomplete and highly experimental! ""Only use if you are a doxygen developer\n");g_useOutputTemplate=TRUE;break;case 'h': // -h 和 -? : 打印用法case '?':usage(argv[0],versionString);exit(0);break;default:err("Unknown option \"-%c\"\n",argv[optInd][1]);usage(argv[0],versionString);exit(1);}optInd++;}/*************************************************************************** Parse or generate the config file ***************************************************************************/// 解析或生成配置文件initTracing(traceName.data());TRACE("Doxygen version used: {}",getFullVersion());Config::init();FileInfo configFileInfo1("Doxyfile"),configFileInfo2("doxyfile");if (optInd>=argc){if (configFileInfo1.exists()){configName="Doxyfile";}else if (configFileInfo2.exists()){configName="doxyfile";}else if (genConfig){configName="Doxyfile";}else{err("Doxyfile not found and no input file specified!\n");usage(argv[0],versionString);exit(1);}}else{FileInfo fi(argv[optInd]);if (fi.exists() || qstrcmp(argv[optInd],"-")==0 || genConfig){configName=argv[optInd];}else{err("configuration file %s not found!\n",argv[optInd]);usage(argv[0],versionString);exit(1);}}if (genConfig && g_useOutputTemplate){generateTemplateFiles("templates");cleanUpDoxygen();exit(0);}if (genConfig){generateConfigFile(configName,shortList);cleanUpDoxygen();exit(0);}if (!Config::parse(configName,updateConfig,diffList)){err("could not open or read configuration file %s!\n",qPrint(configName));cleanUpDoxygen();exit(1);}if (diffList!=Config::CompareMode::Full){Config::updateObsolete();compareDoxyfile(diffList);cleanUpDoxygen();exit(0);}if (updateConfig){Config::updateObsolete();generateConfigFile(configName,shortList,TRUE);cleanUpDoxygen();exit(0);}/* Perlmod wants to know the path to the config file.*/FileInfo configFileInfo(configName.str());setPerlModDoxyfile(configFileInfo.absFilePath());/* handle -q option */if (quiet) Config_updateBool(QUIET,TRUE);
}
查看 doxygen 内置的 emoji
doxygen -f emoji doxygen-emoji.md
然后用 VSCode 的 markdown 预览查看:
开启 Tracing
需要在 cmake 构建阶段传入参数 -D enable_tracing
, 或指定构建类型-D CMAKE_BUILD_TYPE=Debug
, 这样可以开启 ENABLE_TRACING
宏定义。e.g.
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/home/zz/soft/doxygen -Denable_tracing=ON
cmake --build build -j14
cmake --build build -j14 --target install
还需要在执行 doxygen 时, 传入 -t
参数:
# 假设当前路径下已经有 Doxyfile 了
# 如下是正确的三种调用方式
doxygen -t # 会开启 trace, 并记录在名为 trace.txt 的文件中
doxygen -t mytrace.txt # 会开启 trace, 并记录在名为 trace.txt 的文件中
doxygen -t mytrace.txt Doxyfile # 会开启 trace, 并记录在名为 mytrace.txt 的文件中# 如下是错误的三种调用方式
doxygen # 不会开启 trace
doxygen Doxyfile # 不会开启 trace
doxygen Doxxyfile -t mytrace.txt # 不会开启 trace
doxygen -t Doxyfile # 错误:会把 trace 内容输出到 Doxyfile 文件中,覆盖原有内容
5. checkConfiguration()
: 检查和解析配置选项的过程浅析
代码只有10行,
/** check and resolve config options */
void checkConfiguration()
{AUTO_TRACE(); // 如果开启了 tracing 功能, 会执行 traceConfig::postProcess(FALSE); // 配置文件的后处理, 如默认值的填充等Config::updateObsolete(); // 更新淘汰的配置Config::checkAndCorrect(Config_getBool(QUIET), true); // 检查和纠正配置initWarningFormat(); // 初始化警告格式
}
6. adjustConfiguration()
: 调整配置文件的过程浅析
- 设置输出文档的自然语言
- 设置输出 html 文件的后缀名字
- 判断是否需要额外的 parse , 用于生成额外的调用依赖关系图
- 检查和处理指定的特殊 parser 映射关系(文件扩展名对应的 parser)
- 检查全局的输入文件编码配置项目, 以及单个文件级别的文件编码配置项
- 读取用户定义的宏、别名
- 读取制表符的宽度
/** adjust globals that depend on configuration settings. */
void adjustConfiguration()
{AUTO_TRACE(); // 开启自动 tracing。 取决于编译时是否开启trace, 以及运行 doxygen 时是否传入 -t 参数// 一些全局对象的创建。比较奇怪的是,为什么有些用 unique_ptr, 有些则还是 raw pointer.Doxygen::globalNamespaceDef = createNamespaceDef("<globalScope>",1,1,"<globalScope>");Doxygen::globalScope = toNamespaceDefMutable(Doxygen::globalNamespaceDef.get());Doxygen::inputNameLinkedMap = new FileNameLinkedMap;Doxygen::includeNameLinkedMap = new FileNameLinkedMap;Doxygen::exampleNameLinkedMap = new FileNameLinkedMap;Doxygen::imageNameLinkedMap = new FileNameLinkedMap;Doxygen::dotFileNameLinkedMap = new FileNameLinkedMap;Doxygen::mscFileNameLinkedMap = new FileNameLinkedMap;Doxygen::diaFileNameLinkedMap = new FileNameLinkedMap;// 设置翻译器。取决于输出的语言。// 例如对于中文: case OUTPUT_LANGUAGE_t::Chinese: theTranslator = new TranslatorChinese; break;setTranslator(Config_getEnum(OUTPUT_LANGUAGE));/* Set the global html file extension. */// 设置全局 html 文件扩展. 默认使用 .html, 也可以改为 .htm, .php, .asp 等// https://www.doxygen.nl/manual/config.html#cfg_extension_mappingDoxygen::htmlFileExtension = Config_getString(HTML_FILE_EXTENSION);// 是否需要解析源代码: 需要这4个配置中至少有一个为真: CALL_GRAPH, CALLER_GRAPH, REFERENCES_RELATION, REFERENCED_BY_RELATION// CALL_GRAPH: https://www.doxygen.nl/manual/config.html#cfg_call_graph// 如果为真, 则doxygen将为每个全局函数或类方法生成一个调用依赖图。// 启用此选项将显著增加运行时间。因此,在大多数情况下,最好只使用\callgraph命令为所选函数启用调用图。禁用调用图可以通过命令\hidecallgraph来完成。// CALLER_GRAPH: https://www.doxygen.nl/manual/config.html#cfg_caller_graph// 如果为真, 则为每个全局函数和类方法生成“谁调用了它”的依赖关系图// REFERENCES_RELATION: https://www.doxygen.nl/manual/config.html#cfg_references_relation// 如果为真, 则为每个被文档化的函数, 列出调用了的、使用了的函数 。(列出“它调用了谁”)// REFERENCED_BY_RELATION: https://www.doxygen.nl/manual/config.html#cfg_referenced_by_relation// 如果为真, 则对于每个文档化实体,将列出引用该实体的所有文档化函数。 (列出“谁调用了它”)Doxygen::parseSourcesNeeded = Config_getBool(CALL_GRAPH) ||Config_getBool(CALLER_GRAPH) ||Config_getBool(REFERENCES_RELATION) ||Config_getBool(REFERENCED_BY_RELATION);/*************************************************************************** Add custom extension mappings**************************************************************************/// 对于每一种扩展的映射的处理// doxygen 内置了每一种扩展对应的 parser, 而使用 EXTENSION_MAPPING 则可以覆盖默认的 parser 的 mapping。// https://www.doxygen.nl/manual/config.html#cfg_extension_mappingconst StringVector &extMaps = Config_getList(EXTENSION_MAPPING);for (const auto &mapping : extMaps){QCString mapStr = mapping.c_str();int i=mapStr.find('=');if (i==-1){continue;}else{QCString ext = mapStr.left(i).stripWhiteSpace().lower();QCString language = mapStr.mid(i+1).stripWhiteSpace().lower();if (ext.isEmpty() || language.isEmpty()){continue;}if (!updateLanguageMapping(ext,language)){err("Failed to map file extension '%s' to unsupported language '%s'.\n""Check the EXTENSION_MAPPING setting in the config file.\n",qPrint(ext),qPrint(language));}else{msg("Adding custom extension mapping: '%s' will be treated as language '%s'\n",qPrint(ext),qPrint(language));}}}// create input file exncodings// check INPUT_ENCODING// 检查输入的文件编码。 用到了 iconv 库, 基于 iconv 的 API 封装实现了 portable_iconv_open()// https://www.doxygen.nl/manual/config.html#cfg_input_encodingvoid *cd = portable_iconv_open("UTF-8",Config_getString(INPUT_ENCODING).data());if (cd==reinterpret_cast<void *>(-1)){term("unsupported character conversion: '%s'->'%s': %s\n""Check the 'INPUT_ENCODING' setting in the config file!\n",qPrint(Config_getString(INPUT_ENCODING)),qPrint("UTF-8"),strerror(errno));}else{portable_iconv_close(cd);}// check and split INPUT_FILE_ENCODING// 拆解 INPUT_FILE_ENCODING 配置项的取值. INPUT_FILE_ENCODING 允许为单个文件设置编码格式// https://www.doxygen.nl/manual/config.html#cfg_input_file_encodingconst StringVector &fileEncod = Config_getList(INPUT_FILE_ENCODING);for (const auto &mapping : fileEncod){QCString mapStr = mapping.c_str();int i=mapStr.find('=');// 如果没有找到等号, 说明没给出配置项的取值。if (i==-1){continue;}else{QCString pattern = mapStr.left(i).stripWhiteSpace().lower();QCString encoding = mapStr.mid(i+1).stripWhiteSpace().lower();if (pattern.isEmpty() || encoding.isEmpty()){continue;}cd = portable_iconv_open("UTF-8",encoding.data());if (cd==reinterpret_cast<void *>(-1)){term("unsupported character conversion: '%s'->'%s': %s\n""Check the 'INPUT_FILE_ENCODING' setting in the config file!\n",qPrint(encoding),qPrint("UTF-8"),strerror(errno));}else{portable_iconv_close(cd);}Doxygen::inputFileEncodingList.push_back(InputFileEncoding(pattern, encoding));}}// add predefined macro name to a dictionary// 添加预定义的宏定义到字典中// https://www.doxygen.nl/manual/config.html#cfg_expand_as_definedconst StringVector &expandAsDefinedList =Config_getList(EXPAND_AS_DEFINED);for (const auto &s : expandAsDefinedList){Doxygen::expandAsDefinedSet.insert(s.c_str());}// read aliases and store them in a dictionary// 读取别名, 并存储到字典中// https://www.doxygen.nl/manual/config.html#cfg_aliasesreadAliases();// store number of spaces in a tab into Doxygen::spaces// 读取 tab 制表符的宽度. 默认是4// https://www.doxygen.nl/manual/config.html#cfg_tab_sizeint tabSize = Config_getInt(TAB_SIZE);Doxygen::spaces.resize(tabSize+1);int sp;for (sp=0;sp<tabSize;sp++) Doxygen::spaces.at(sp)=' ';Doxygen::spaces.at(tabSize)='\0';
}
7. parseInput()
: 解析输入的过程浅析
函数本体大约600行代码, 考虑到调用的一些函数的代码, 总计超过1000行。过程较复杂,包含的内容很多。
// src/doxygen.cpp, L11727-L12348
void parseInput()
{AUTO_TRACE();std::atexit(exitDoxygen); // atexit 是注册整个程序运行结束(main()之后)要执行的函数。如果doxygen运行中出错提前结束,并且 DB 文件(filterDBFileNmae)不为空, 则删除这个文件// 检查是否配置了使用 clang parser。默认是 False。 如果要开启, 还需要编译 doxygen 时传入 -Duse_libclang=ON// https://www.doxygen.nl/manual/config.html#cfg_clang_assisted_parsing
#if USE_LIBCLANGDoxygen::clangAssistedParsing = Config_getBool(CLANG_ASSISTED_PARSING);
#endif// we would like to show the versionString earlier, but we first have to handle the configuration file// to know the value of the QUIET setting.QCString versionString = getFullVersion(); // 获取完整的版本字符串msg("Doxygen version used: %s\n",qPrint(versionString));// DOT_PATH 配置项, 给出了 dot 命令所在的目录// https://www.doxygen.nl/manual/config.html#cfg_DOT_PATH// computeVerifiedDotPath() 根据给出的 DOT_PATH 的取值作为目录, 拼接得到 dot 命令的路径。如果是 windows 还会添加 .exe 后缀computeVerifiedDotPath();/*************************************************************************** Make sure the output directory exists**************************************************************************/// OUTPUT_DIRECTORY: https://www.doxygen.nl/manual/config.html#cfg_output_directory// 解析用户指定的文档输出路径。支持相对路径。如果留空则用当前目录。QCString outputDirectory = Config_getString(OUTPUT_DIRECTORY);if (outputDirectory.isEmpty()){outputDirectory = Config_updateString(OUTPUT_DIRECTORY,Dir::currentDirPath().c_str());}else{Dir dir(outputDirectory.str());if (!dir.exists()){dir.setPath(Dir::currentDirPath());if (!dir.mkdir(outputDirectory.str())){err("tag OUTPUT_DIRECTORY: Output directory '%s' does not ""exist and cannot be created\n",qPrint(outputDirectory));cleanUpDoxygen();exit(1);}else{msg("Notice: Output directory '%s' does not exist. ""I have created it for you.\n", qPrint(outputDirectory));}dir.setPath(outputDirectory.str());}outputDirectory = Config_updateString(OUTPUT_DIRECTORY,dir.absPath().c_str());}AUTO_TRACE_ADD("outputDirectory={}",outputDirectory);/*************************************************************************** Initialize global lists and dictionaries**************************************************************************/// 初始化全局的列表和字典// 可以使用lookup_cache_size设置符号查找缓存的大小。此缓存用于解析给定名称和范围的符号。// also scale lookup cache with SYMBOL_CACHE_SIZE// https://www.doxygen.nl/manual/config.html#cfg_lookup_cache_sizeint cacheSize = Config_getInt(LOOKUP_CACHE_SIZE);if (cacheSize<0) cacheSize=0;if (cacheSize>9) cacheSize=9;uint32_t lookupSize = 65536 << cacheSize;Doxygen::typeLookupCache = new Cache<std::string,LookupInfo>(lookupSize);Doxygen::symbolLookupCache = new Cache<std::string,LookupInfo>(lookupSize);#ifdef HAS_SIGNALSsignal(SIGINT, stopDoxygen);
#endif// 获取当前进程的 ID, 用于 filterdb 文件的命名uint32_t pid = Portable::pid();Doxygen::filterDBFileName.sprintf("doxygen_filterdb_%d.tmp",pid);Doxygen::filterDBFileName.prepend(outputDirectory+"/");/*************************************************************************** Check/create output directories ***************************************************************************/// 检查输出目录QCString htmlOutput;// GENERATE_HTML: https://www.doxygen.nl/manual/config.html#cfg_generate_html// 是否输出 html 文件。 默认为 YES。bool generateHtml = Config_getBool(GENERATE_HTML);if (generateHtml || g_useOutputTemplate /* TODO: temp hack */){htmlOutput = createOutputDirectory(outputDirectory,Config_getString(HTML_OUTPUT),"/html");Config_updateString(HTML_OUTPUT,htmlOutput);// SITEMAP_URL: https://www.doxygen.nl/manual/config.html#cfg_sitemap_url// html 文档部署时使用的完整 URLQCString sitemapUrl = Config_getString(SITEMAP_URL);bool generateSitemap = !sitemapUrl.isEmpty();if (generateSitemap && !sitemapUrl.endsWith("/")){Config_updateString(SITEMAP_URL,sitemapUrl+"/");}// add HTML indexers that are enabled// GENERATE_HTMLHELP: https://www.doxygen.nl/manual/config.html#cfg_generate_htmlhelp 。默认为 NO。// 如果为YES,则生成三个额外的文件: index.hhp, index.hhc, and index.hhk// - index.hhp: 生成 HTML Help Project 文件. 在 Windows 系统上,使用 HTML Help Workshop 工具, 选择 hhp 文件和 html 文件,可以生成 .chm 文件// .chm 是 compiled HTML file 缩写,是单个文件.bool generateHtmlHelp = Config_getBool(GENERATE_HTMLHELP);// GENERATE_ECLIPSEHELP: https://www.doxygen.nl/manual/config.html#cfg_generate_eclipsehelp// 用于生成 eclipse 里的文档页面bool generateEclipseHelp = Config_getBool(GENERATE_ECLIPSEHELP);// GENERATE_QHP: https://www.doxygen.nl/manual/config.html#cfg_generate_qhp// 用于生成 Qt 里的文档页面bool generateQhp = Config_getBool(GENERATE_QHP);// GENERATE_TREEVIEW: https://www.doxygen.nl/manual/config.html#cfg_generate_treeview// GENERATE_TREEVIEW标记用于指定是否应生成树状索引结构来显示分层信息。// 如果标记值设置为YES,将生成一个包含树状索引结构的侧面板(就像为HTML帮助生成的一样)。为此,需要一个支持JavaScript、DHTML、CSS和框架的浏览器(即任何现代浏览器)。// // 通过自定义样式表(请参阅HTML_EXTRA_STYLESHEET),可以进一步微调索引的外观(请参阅微调输出)。// 例如,doxygen生成的默认样式表有一个示例,显示了如何将图像放在树的根而不是PROJECT_NAME。// // 由于树基本上具有与选项卡索引相同的信息,因此在启用此选项时,可以考虑将DISABLE_index设置为YES。bool generateTreeView = Config_getBool(GENERATE_TREEVIEW);// GENERATE_DOCSET: https://www.doxygen.nl/manual/config.html#cfg_generate_docset// 用于生成 XCode 里的文档bool generateDocSet = Config_getBool(GENERATE_DOCSET);if (generateEclipseHelp) Doxygen::indexList->addIndex<EclipseHelp>(); // 如果用 Eclipse, 可以开启这个选项。if (generateHtmlHelp) Doxygen::indexList->addIndex<HtmlHelp>(); // 如果配置了, 则生成 index.hhp 文件, 如果打算生成 Windows .chm电子书,则开启它。if (generateQhp) Doxygen::indexList->addIndex<Qhp>(); // 如果配置了, 则生成 qt 的文档支持。 本人不用 Qt, 关闭。if (generateSitemap) Doxygen::indexList->addIndex<Sitemap>(); // 如果配置了, 则生成 sitemap 的支持。 通常对于 html 输出, 应该开启这个。if (generateTreeView) Doxygen::indexList->addIndex<FTVHelp>(TRUE); // 如果配置了, 则添加 treeview 的支持。通常对于 html 输出, 应该开启这个。if (generateDocSet) Doxygen::indexList->addIndex<DocSets>(); // 如果配置了, 则添加 XCode 文档生成的支持Doxygen::indexList->initialize(); // 对每个 list item, 执行 dispatch_call(). 详见 src/dispatcher.h}// GENERATE_DOCBOOK: https://www.doxygen.nl/manual/config.html#cfg_generate_docbook// 如果为 YES, 则生成 docbook。 docbook 用于生成 pdf。 docbook 基本上还是用xml写的一些文件。QCString docbookOutput;bool generateDocbook = Config_getBool(GENERATE_DOCBOOK);if (generateDocbook){docbookOutput = createOutputDirectory(outputDirectory,Config_getString(DOCBOOK_OUTPUT),"/docbook");Config_updateString(DOCBOOK_OUTPUT,docbookOutput);}// GENERATE_XML: https://www.doxygen.nl/manual/config.html#cfg_generate_xml// If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that captures the structure of the code including all documentation. // 不是很熟悉 xml 输出, 看起来 xml 输出更多的是作为结构化结果, 是一种中间表示(IR),而不是最终给用户看的QCString xmlOutput;bool generateXml = Config_getBool(GENERATE_XML);if (generateXml){xmlOutput = createOutputDirectory(outputDirectory,Config_getString(XML_OUTPUT),"/xml");Config_updateString(XML_OUTPUT,xmlOutput);}// 生成 latex。QCString latexOutput;bool generateLatex = Config_getBool(GENERATE_LATEX);if (generateLatex){latexOutput = createOutputDirectory(outputDirectory,Config_getString(LATEX_OUTPUT), "/latex");Config_updateString(LATEX_OUTPUT,latexOutput);}// 生成 rtfQCString rtfOutput;bool generateRtf = Config_getBool(GENERATE_RTF);if (generateRtf){rtfOutput = createOutputDirectory(outputDirectory,Config_getString(RTF_OUTPUT),"/rtf");Config_updateString(RTF_OUTPUT,rtfOutput);}// 生成 man 页面QCString manOutput;bool generateMan = Config_getBool(GENERATE_MAN);if (generateMan){manOutput = createOutputDirectory(outputDirectory,Config_getString(MAN_OUTPUT),"/man");Config_updateString(MAN_OUTPUT,manOutput);}// 生成 sqlite3 输出
#if USE_SQLITE3QCString sqlOutput;bool generateSql = Config_getBool(GENERATE_SQLITE3);if (generateSql){sqlOutput = createOutputDirectory(outputDirectory,Config_getString(SQLITE3_OUTPUT),"/sqlite3");Config_updateString(SQLITE3_OUTPUT,sqlOutput);}
#endif// 如果安装了 dot 命令, 并且 DOT_FONTPATH 配置项为 YES, 则配置 dot 使用的字体if (Config_getBool(HAVE_DOT)){QCString curFontPath = Config_getString(DOT_FONTPATH);if (curFontPath.isEmpty()){Portable::getenv("DOTFONTPATH");QCString newFontPath = ".";if (!curFontPath.isEmpty()){newFontPath+=Portable::pathListSeparator();newFontPath+=curFontPath;}Portable::setenv("DOTFONTPATH",qPrint(newFontPath));}else{Portable::setenv("DOTFONTPATH",qPrint(curFontPath));}}/*************************************************************************** Handle layout file ***************************************************************************/// 处理布局文件. 默认布局文件是 DoxygenLayout.xmlLayoutDocManager::instance().init();QCString layoutFileName = Config_getString(LAYOUT_FILE);bool defaultLayoutUsed = FALSE;if (layoutFileName.isEmpty()){layoutFileName = Config_updateString(LAYOUT_FILE,"DoxygenLayout.xml");defaultLayoutUsed = TRUE;}AUTO_TRACE_ADD("defaultLayoutUsed={}, layoutFileName={}",defaultLayoutUsed,layoutFileName);FileInfo fi(layoutFileName.str());if (fi.exists()){msg("Parsing layout file %s...\n",qPrint(layoutFileName));LayoutDocManager::instance().parse(layoutFileName);}else if (!defaultLayoutUsed){warn_uncond("failed to open layout file '%s' for reading! Using default settings.\n",qPrint(layoutFileName));}/*************************************************************************** Read and preprocess input ***************************************************************************/// prevent search in the output directories// 对于每一种输出类型(html,latex等), 不要作为生成文档的输入。// EXCLUDE_PATTERNS: https://www.doxygen.nl/manual/config.html#cfg_exclude_patternsStringVector exclPatterns = Config_getList(EXCLUDE_PATTERNS);if (generateHtml) exclPatterns.push_back(htmlOutput.str());if (generateDocbook) exclPatterns.push_back(docbookOutput.str());if (generateXml) exclPatterns.push_back(xmlOutput.str());if (generateLatex) exclPatterns.push_back(latexOutput.str());if (generateRtf) exclPatterns.push_back(rtfOutput.str());if (generateMan) exclPatterns.push_back(manOutput.str());Config_updateList(EXCLUDE_PATTERNS,exclPatterns);// 搜索输入文件。 包括这几种类型: // INCLUDE_PATH: 包含的文件// EXAMPLE_PATH: 文件名字或目录, 存储例子代码。 用于使用 `\include` 命令时将一个文件的内容作为一个block引入// IMAGE_PATH: 图像路径。// DOTFILE_DIRS:类似前面几个。// MSCFILE_DIRS// DIAFILE_DIRS// EXCLUDEsearchInputFiles();// 检查 markdown 主文件, 用于作为 html 页面的主页checkMarkdownMainfile();// Notice: the order of the function calls below is very important!// 如下几个判断的顺序不能修改if (Config_getBool(GENERATE_HTML) && !Config_getBool(USE_MATHJAX)){FormulaManager::instance().initFromRepository(Config_getString(HTML_OUTPUT));}if (Config_getBool(GENERATE_RTF)){FormulaManager::instance().initFromRepository(Config_getString(RTF_OUTPUT));}if (Config_getBool(GENERATE_DOCBOOK)){FormulaManager::instance().initFromRepository(Config_getString(DOCBOOK_OUTPUT));}FormulaManager::instance().checkRepositories();/*************************************************************************** Handle Tag Files ***************************************************************************/// 处理 tag 文件std::shared_ptr<Entry> root = std::make_shared<Entry>();msg("Reading and parsing tag files\n");const StringVector &tagFileList = Config_getList(TAGFILES);for (const auto &s : tagFileList){readTagFile(root,s.c_str());}/*************************************************************************** Parse source files ***************************************************************************/// 处理源代码文件// 增加 STL 支持addSTLSupport(root);g_s.begin("Parsing files\n");// doxygen 解析文件时, 默认单线程, 也可以指定多线程的数量, 最大32// https://www.doxygen.nl/manual/config.html#cfg_num_proc_threadsif (Config_getInt(NUM_PROC_THREADS)==1){parseFilesSingleThreading(root);}else{parseFilesMultiThreading(root);}g_s.end();/*************************************************************************** Gather information ***************************************************************************/// 收集信息. g_s 是 Statistics 类的实例。g_s.begin("Building macro definition list...\n");// 宏定义: 把预处理阶段扫描出的宏定义, 作为文件成员buildDefineList();g_s.end();g_s.begin("Building group list...\n");// group 的处理。也就是被提取文档的 C/C++ 代码中的 @defgroups, @addtogroup, @weakgroup 区块中的groupbuildGroupList(root.get());organizeSubGroups(root.get());g_s.end();g_s.begin("Building directory list...\n");// 目录的处理buildDirectories();findDirDocumentation(root.get());g_s.end();g_s.begin("Building namespace list...\n");// 命名空间的处理buildNamespaceList(root.get());findUsingDirectives(root.get());g_s.end();g_s.begin("Building file list...\n");// 文件的处理buildFileList(root.get());g_s.end();g_s.begin("Building class list...\n");// 类的处理buildClassList(root.get());g_s.end();g_s.begin("Building concept list...\n");// C++ concept 的处理buildConceptList(root.get());g_s.end();// build list of using declarations here (global list)// C++ using 的处理,例如 using cout=std::coutbuildListOfUsingDecls(root.get());g_s.end();g_s.begin("Computing nesting relations for classes...\n");// 解决 C++ 类的嵌套关系resolveClassNestingRelations();g_s.end();// 1.8.2-20121111: no longer add nested classes to the group as well//distributeClassGroupRelations();// calling buildClassList may result in cached relations that// become invalid after resolveClassNestingRelations(), that's why// we need to clear the cache hereDoxygen::typeLookupCache->clear();// we don't need the list of using declaration anymoreg_usingDeclarations.clear();g_s.begin("Associating documentation with classes...\n");// 构建类文档列表buildClassDocList(root.get());g_s.end();g_s.begin("Associating documentation with concepts...\n");// 构建concept的文档列表buildConceptDocList(root.get());distributeConceptGroups();g_s.end();g_s.begin("Building example list...\n");// 构建 examples 列表buildExampleList(root.get());g_s.end();g_s.begin("Searching for enumerations...\n");// 查找枚举类型findEnums(root.get());g_s.end();// Since buildVarList calls isVarWithConstructor// and this calls getResolvedClass we need to process// typedefs first so the relations between classes via typedefs// are properly resolved. See bug 536385 for an example.g_s.begin("Searching for documented typedefs...\n");// 处理 typedefbuildTypedefList(root.get());g_s.end();if (Config_getBool(OPTIMIZE_OUTPUT_SLICE)){g_s.begin("Searching for documented sequences...\n");buildSequenceList(root.get());g_s.end();g_s.begin("Searching for documented dictionaries...\n");buildDictionaryList(root.get());g_s.end();}g_s.begin("Searching for members imported via using declarations...\n");// this should be after buildTypedefList in order to properly import// used typedefsfindUsingDeclarations(root.get(),TRUE); // do for python packages firstfindUsingDeclarations(root.get(),FALSE); // then the restg_s.end();g_s.begin("Searching for included using directives...\n");findIncludedUsingDirectives();g_s.end();g_s.begin("Searching for documented variables...\n");// 处理变量buildVarList(root.get());g_s.end();g_s.begin("Building interface member list...\n");buildInterfaceAndServiceList(root.get()); // UNO IDLg_s.begin("Building member list...\n"); // using class info only !// 处理函数buildFunctionList(root.get());g_s.end();g_s.begin("Searching for friends...\n");// 处理 C++ 友元findFriends();g_s.end();g_s.begin("Searching for documented defines...\n");findDefineDocumentation(root.get());g_s.end();g_s.begin("Computing class inheritance relations...\n");findClassEntries(root.get());findInheritedTemplateInstances();g_s.end();g_s.begin("Computing class usage relations...\n");// 计算类之间的关系findUsedTemplateInstances();g_s.end();if (Config_getBool(INLINE_SIMPLE_STRUCTS)){g_s.begin("Searching for tag less structs...\n");findTagLessClasses();g_s.end();}g_s.begin("Flushing cached template relations that have become invalid...\n");flushCachedTemplateRelations();g_s.end();g_s.begin("Computing class relations...\n");computeTemplateClassRelations();flushUnresolvedRelations();if (Config_getBool(OPTIMIZE_OUTPUT_VHDL)){VhdlDocGen::computeVhdlComponentRelations();}computeClassRelations();g_classEntries.clear();g_s.end();g_s.begin("Add enum values to enums...\n");addEnumValuesToEnums(root.get());findEnumDocumentation(root.get());g_s.end();g_s.begin("Searching for member function documentation...\n");findObjCMethodDefinitions(root.get());findMemberDocumentation(root.get()); // may introduce new members !findUsingDeclImports(root.get()); // may introduce new members !transferRelatedFunctionDocumentation();transferFunctionDocumentation();g_s.end();// moved to after finding and copying documentation,// as this introduces new members see bug 722654g_s.begin("Creating members for template instances...\n");// 处理模板实例的成员createTemplateInstanceMembers();g_s.end();g_s.begin("Building page list...\n");// 处理页面buildPageList(root.get());g_s.end();g_s.begin("Search for main page...\n");// 文档主页findMainPage(root.get());findMainPageTagFiles(root.get());g_s.end();g_s.begin("Computing page relations...\n");computePageRelations(root.get());checkPageRelations();g_s.end();g_s.begin("Determining the scope of groups...\n");findGroupScope(root.get());g_s.end();// 成员名字比较函数, 用于排序auto memberNameComp = [](const MemberNameLinkedMap::Ptr &n1,const MemberNameLinkedMap::Ptr &n2){return qstricmp(n1->memberName().data()+getPrefixIndex(n1->memberName()),n2->memberName().data()+getPrefixIndex(n2->memberName()))<0;};// 类比较函数, 用于排序auto classComp = [](const ClassLinkedMap::Ptr &c1,const ClassLinkedMap::Ptr &c2){if (Config_getBool(SORT_BY_SCOPE_NAME)){return qstricmp(c1->name(), c2->name())<0;}else{int i = qstricmp(c1->className(), c2->className());return i==0 ? qstricmp(c1->name(), c2->name())<0 : i<0;}};// 命名空间比较函数,用于排序auto namespaceComp = [](const NamespaceLinkedMap::Ptr &n1,const NamespaceLinkedMap::Ptr &n2){return qstricmp(n1->name(),n2->name())<0;};// concept 比较函数, 用于排序auto conceptComp = [](const ConceptLinkedMap::Ptr &c1,const ConceptLinkedMap::Ptr &c2){return qstricmp(c1->name(),c2->name())<0;};// 排序列表g_s.begin("Sorting lists...\n");std::sort(Doxygen::memberNameLinkedMap->begin(),Doxygen::memberNameLinkedMap->end(),memberNameComp);std::sort(Doxygen::functionNameLinkedMap->begin(),Doxygen::functionNameLinkedMap->end(),memberNameComp);std::sort(Doxygen::hiddenClassLinkedMap->begin(),Doxygen::hiddenClassLinkedMap->end(),classComp);std::sort(Doxygen::classLinkedMap->begin(),Doxygen::classLinkedMap->end(),classComp);std::sort(Doxygen::conceptLinkedMap->begin(),Doxygen::conceptLinkedMap->end(),conceptComp);std::sort(Doxygen::namespaceLinkedMap->begin(),Doxygen::namespaceLinkedMap->end(),namespaceComp);g_s.end();g_s.begin("Determining which enums are documented\n");// 寻找被文档标注的枚举值findDocumentedEnumValues();g_s.end();g_s.begin("Computing member relations...\n");// 合并类别mergeCategories();// 计算成员关系computeMemberRelations();g_s.end();g_s.begin("Building full member lists recursively...\n");buildCompleteMemberLists();g_s.end();g_s.begin("Adding members to member groups.\n");addMembersToMemberGroup();g_s.end();if (Config_getBool(DISTRIBUTE_GROUP_DOC)){g_s.begin("Distributing member group documentation.\n");distributeMemberGroupDocumentation();g_s.end();}g_s.begin("Computing member references...\n");computeMemberReferences();g_s.end();if (Config_getBool(INHERIT_DOCS)){g_s.begin("Inheriting documentation...\n");inheritDocumentation();g_s.end();}// compute the shortest possible names of all files// without losing the uniqueness of the file names.g_s.begin("Generating disk names...\n");generateDiskNames();g_s.end();g_s.begin("Adding source references...\n");addSourceReferences();g_s.end();g_s.begin("Adding xrefitems...\n");addListReferences();generateXRefPages();g_s.end();g_s.begin("Sorting member lists...\n");sortMemberLists();g_s.end();g_s.begin("Setting anonymous enum type...\n");setAnonymousEnumType();g_s.end();if (Config_getBool(DIRECTORY_GRAPH)){g_s.begin("Computing dependencies between directories...\n");computeDirDependencies();g_s.end();}g_s.begin("Generating citations page...\n");CitationManager::instance().generatePage();g_s.end();g_s.begin("Counting members...\n");countMembers();g_s.end();g_s.begin("Counting data structures...\n");Index::instance().countDataStructures();g_s.end();g_s.begin("Resolving user defined references...\n");resolveUserReferences();g_s.end();g_s.begin("Finding anchors and sections in the documentation...\n");findSectionsInDocumentation();g_s.end();g_s.begin("Transferring function references...\n");transferFunctionReferences();g_s.end();g_s.begin("Combining using relations...\n");combineUsingRelations();g_s.end();initSearchIndexer();g_s.begin("Adding members to index pages...\n");addMembersToIndex();addToIndices();g_s.end();g_s.begin("Correcting members for VHDL...\n");vhdlCorrectMemberProperties();g_s.end();g_s.begin("Computing tooltip texts...\n");computeTooltipTexts();g_s.end();if (Config_getBool(SORT_GROUP_NAMES)){std::sort(Doxygen::groupLinkedMap->begin(),Doxygen::groupLinkedMap->end(),[](const auto &g1,const auto &g2){ return g1->groupTitle() < g2->groupTitle(); });for (const auto &gd : *Doxygen::groupLinkedMap){gd->sortSubGroups();}}}
8. generateOutput()
: 生成输出的过程浅析
- dump 所有的符号表
- 生成 html
- 生成 latex
- 生成 man
- 生成 docbook
- 生成 RTF
- 生成 HTAGS
这里主要关注 html, 其他格式忽略。 html 相关的具体生成, 涉及如下功能/函数调用:
- 生成图片 generateImages()
- 生成例子文档 generateExampleDocs()
- 生成文件源代码 generateFileSources()
- 生成文件列表 generateFileDocs()
- 生成page页面 generatePageDocs()
- 生成group页面 generateGroupDocs()
- 生成class页面 generateClassDocs()
- 生成concept页面 generateConceptDocs()
- 生成命名空间页面 generateNamespaceDocs()
- 生成目录页面 generateDirDocs()
- 生成 tag 文件 writeTagFile()
- 生成 html 搜索页面 writeSearchPage()
- 生成 .chm 电子书 runHtmlHelpCompiler()
- 输出统计信息(文档生成耗时)
代码过多, 这一小节没有具体的注释, 但后续值得具体分析和调试。
// src/doxygen.cpp L12350-L12718
void generateOutput()
{AUTO_TRACE();/*************************************************************************** Initialize output generators ***************************************************************************//// add extra languages for which we can only produce syntax highlighted codeaddCodeOnlyMappings(); dump all symbolsif (g_dumpSymbolMap){dumpSymbolMap();exit(0);}bool generateHtml = Config_getBool(GENERATE_HTML);bool generateLatex = Config_getBool(GENERATE_LATEX);bool generateMan = Config_getBool(GENERATE_MAN);bool generateRtf = Config_getBool(GENERATE_RTF);bool generateDocbook = Config_getBool(GENERATE_DOCBOOK);g_outputList = new OutputList;if (generateHtml){g_outputList->add<HtmlGenerator>();HtmlGenerator::init();HtmlGenerator::writeTabData();}if (generateLatex){g_outputList->add<LatexGenerator>();LatexGenerator::init();}if (generateDocbook){g_outputList->add<DocbookGenerator>();DocbookGenerator::init();}if (generateMan){g_outputList->add<ManGenerator>();ManGenerator::init();}if (generateRtf){g_outputList->add<RTFGenerator>();RTFGenerator::init();}if (Config_getBool(USE_HTAGS)){Htags::useHtags = TRUE;QCString htmldir = Config_getString(HTML_OUTPUT);if (!Htags::execute(htmldir))err("USE_HTAGS is YES but htags(1) failed. \n");else if (!Htags::loadFilemap(htmldir))err("htags(1) ended normally but failed to load the filemap. \n");}/*************************************************************************** Generate documentation ***************************************************************************/g_s.begin("Generating style sheet...\n");//printf("writing style info\n");g_outputList->writeStyleInfo(0); // write first partg_s.end();bool searchEngine = Config_getBool(SEARCHENGINE);bool serverBasedSearch = Config_getBool(SERVER_BASED_SEARCH);g_s.begin("Generating search indices...\n");if (searchEngine && !serverBasedSearch && (generateHtml || g_useOutputTemplate)){createJavaScriptSearchIndex();}// generate search indices (need to do this before writing other HTML// pages as these contain a drop down menu with options depending on// what categories we find in this function.if (generateHtml && searchEngine){QCString searchDirName = Config_getString(HTML_OUTPUT)+"/search";Dir searchDir(searchDirName.str());if (!searchDir.exists() && !searchDir.mkdir(searchDirName.str())){term("Could not create search results directory '%s' $PWD='%s'\n",qPrint(searchDirName),Dir::currentDirPath().c_str());}HtmlGenerator::writeSearchData(searchDirName);if (!serverBasedSearch) // client side search index{writeJavaScriptSearchIndex();}}g_s.end();// copy static stuffif (generateHtml){FTVHelp::generateTreeViewImages();copyStyleSheet();copyLogo(Config_getString(HTML_OUTPUT));copyExtraFiles(Config_getList(HTML_EXTRA_FILES),"HTML_EXTRA_FILES",Config_getString(HTML_OUTPUT));}if (generateLatex){copyLatexStyleSheet();copyLogo(Config_getString(LATEX_OUTPUT));copyExtraFiles(Config_getList(LATEX_EXTRA_FILES),"LATEX_EXTRA_FILES",Config_getString(LATEX_OUTPUT));}if (generateDocbook){copyLogo(Config_getString(DOCBOOK_OUTPUT));}if (generateRtf){copyLogo(Config_getString(RTF_OUTPUT));}FormulaManager &fm = FormulaManager::instance();if (fm.hasFormulas() && generateHtml&& !Config_getBool(USE_MATHJAX)){g_s.begin("Generating images for formulas in HTML...\n");fm.generateImages(Config_getString(HTML_OUTPUT), Config_getEnum(HTML_FORMULA_FORMAT)==HTML_FORMULA_FORMAT_t::svg ?FormulaManager::Format::Vector : FormulaManager::Format::Bitmap, FormulaManager::HighDPI::On);g_s.end();}if (fm.hasFormulas() && generateRtf){g_s.begin("Generating images for formulas in RTF...\n");fm.generateImages(Config_getString(RTF_OUTPUT),FormulaManager::Format::Bitmap);g_s.end();}if (fm.hasFormulas() && generateDocbook){g_s.begin("Generating images for formulas in Docbook...\n");fm.generateImages(Config_getString(DOCBOOK_OUTPUT),FormulaManager::Format::Bitmap);g_s.end();}g_s.begin("Generating example documentation...\n");generateExampleDocs();g_s.end();warn_flush();g_s.begin("Generating file sources...\n");generateFileSources();g_s.end();g_s.begin("Generating file documentation...\n");generateFileDocs();g_s.end();g_s.begin("Generating page documentation...\n");generatePageDocs();g_s.end();g_s.begin("Generating group documentation...\n");generateGroupDocs();g_s.end();g_s.begin("Generating class documentation...\n");generateClassDocs();g_s.end();g_s.begin("Generating concept documentation...\n");generateConceptDocs();g_s.end();g_s.begin("Generating namespace documentation...\n");generateNamespaceDocs();g_s.end();if (Config_getBool(GENERATE_LEGEND)){g_s.begin("Generating graph info page...\n");writeGraphInfo(*g_outputList);g_s.end();}g_s.begin("Generating directory documentation...\n");generateDirDocs(*g_outputList);g_s.end();if (g_outputList->size()>0){writeIndexHierarchy(*g_outputList);}g_s.begin("finalizing index lists...\n");Doxygen::indexList->finalize();g_s.end();g_s.begin("writing tag file...\n");writeTagFile();g_s.end();if (Config_getBool(GENERATE_XML)){g_s.begin("Generating XML output...\n");Doxygen::generatingXmlOutput=TRUE;generateXML();Doxygen::generatingXmlOutput=FALSE;g_s.end();}
#if USE_SQLITE3if (Config_getBool(GENERATE_SQLITE3)){g_s.begin("Generating SQLITE3 output...\n");generateSqlite3();g_s.end();}
#endifif (Config_getBool(GENERATE_AUTOGEN_DEF)){g_s.begin("Generating AutoGen DEF output...\n");generateDEF();g_s.end();}if (Config_getBool(GENERATE_PERLMOD)){g_s.begin("Generating Perl module output...\n");generatePerlMod();g_s.end();}if (generateHtml && searchEngine && serverBasedSearch){g_s.begin("Generating search index\n");if (Doxygen::searchIndex->kind()==SearchIndexIntf::Internal) // write own search index{HtmlGenerator::writeSearchPage();Doxygen::searchIndex->write(Config_getString(HTML_OUTPUT)+"/search/search.idx");}else // write data for external search index{HtmlGenerator::writeExternalSearchPage();QCString searchDataFile = Config_getString(SEARCHDATA_FILE);if (searchDataFile.isEmpty()){searchDataFile="searchdata.xml";}if (!Portable::isAbsolutePath(searchDataFile.data())){searchDataFile.prepend(Config_getString(OUTPUT_DIRECTORY)+"/");}Doxygen::searchIndex->write(searchDataFile);}g_s.end();}if (g_useOutputTemplate){g_s.begin("Generating output via template engine...\n");generateOutputViaTemplate();g_s.end();}warn_flush();if (generateRtf){g_s.begin("Combining RTF output...\n");if (!RTFGenerator::preProcessFileInplace(Config_getString(RTF_OUTPUT),"refman.rtf")){err("An error occurred during post-processing the RTF files!\n");}g_s.end();}warn_flush();g_s.begin("Running plantuml with JAVA...\n");PlantumlManager::instance().run();g_s.end();warn_flush();if (Config_getBool(HAVE_DOT)){g_s.begin("Running dot...\n");DotManager::instance()->run();g_s.end();}if (generateHtml &&Config_getBool(GENERATE_HTMLHELP) &&!Config_getString(HHC_LOCATION).isEmpty()){g_s.begin("Running html help compiler...\n");runHtmlHelpCompiler();g_s.end();}warn_flush();if ( generateHtml &&Config_getBool(GENERATE_QHP) &&!Config_getString(QHG_LOCATION).isEmpty()){g_s.begin("Running qhelpgenerator...\n");runQHelpGenerator();g_s.end();}g_outputList->cleanup();msg("type lookup cache used %zu/%zu hits=%" PRIu64 " misses=%" PRIu64 "\n",Doxygen::typeLookupCache->size(),Doxygen::typeLookupCache->capacity(),Doxygen::typeLookupCache->hits(),Doxygen::typeLookupCache->misses());msg("symbol lookup cache used %zu/%zu hits=%" PRIu64 " misses=%" PRIu64 "\n",Doxygen::symbolLookupCache->size(),Doxygen::symbolLookupCache->capacity(),Doxygen::symbolLookupCache->hits(),Doxygen::symbolLookupCache->misses());int typeCacheParam = computeIdealCacheParam(static_cast<size_t>(Doxygen::typeLookupCache->misses()*2/3)); // part of the cache is flushed, hence the 2/3 correction factorint symbolCacheParam = computeIdealCacheParam(static_cast<size_t>(Doxygen::symbolLookupCache->misses()));int cacheParam = std::max(typeCacheParam,symbolCacheParam);if (cacheParam>Config_getInt(LOOKUP_CACHE_SIZE)){msg("Note: based on cache misses the ideal setting for LOOKUP_CACHE_SIZE is %d at the cost of higher memory usage.\n",cacheParam);}if (Debug::isFlagSet(Debug::Time)){std::size_t numThreads = static_cast<std::size_t>(Config_getInt(NUM_PROC_THREADS));if (numThreads<1) numThreads=1;msg("Total elapsed time: %.6f seconds\n(of which an average of %.6f seconds per thread waiting for external tools to finish)\n",(static_cast<double>(Debug::elapsedTime())),Portable::getSysElapsedTime()/static_cast<double>(numThreads));g_s.print();Debug::clearFlag(Debug::Time);msg("finished...\n");Debug::setFlag(Debug::Time);}else{msg("finished...\n");}/*************************************************************************** Start cleaning up ***************************************************************************/cleanUpDoxygen();finalizeSearchIndexer();Dir thisDir;thisDir.remove(Doxygen::filterDBFileName.str());finishWarnExit();exitTracing();Config::deinit();delete Doxygen::clangUsrMap;g_successfulRun=TRUE;//dumpDocNodeSizes();
}