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 +0200
bump version to 1.9.8 for development
2. doxygen 可执行程序的入口: src/main.cpp
在 src/CMakeLists.txt
中看到 doxygen
这个 target 的描述, 它由 main.cpp
和 doxygen.rc
两个文件组成:
add_executable(doxygen
main.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(); // 初始化 doxygen
readConfiguration(argc,argv); // 读取配置, 通常是读取 Doxyfile 这一配置文件
checkConfiguration(); // 检查和解析配置选项
adjustConfiguration(); // 调整配置:有些全局变量依赖于配置
parseInput(); // 解析输入
generateOutput(); // 生成输出, 例如 html, xml, latex, pdf
return 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-8
std::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, Lex
Doxygen::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_LIBCLANG
Doxygen::clangUsrMap = new ClangUsrMap;
#endif
Doxygen::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, 基本元素是 string
g_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-2021
You 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 the
generated 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 styleSheetFile
HTML: doxygen -w html headerFile footerFile styleSheetFile [configFile]
LaTeX: doxygen -w latex headerFile footerFile styleSheetFile [configFile]
6) Use doxygen to generate a rtf extensions file
doxygen -e rtf extensionsFile
If - is used for extensionsFile doxygen will write to standard output.
7) Use doxygen to compare the used configuration file with the template configuration file
doxygen -x [configFile]
Use doxygen to compare the used configuration file with the template configuration file
without replacing the environment variables or CMake type replacement variables
doxygen -x_noenv [configFile]
8) Use doxygen to show a list of built-in emojis.
doxygen -f emoji outputFileName
If - 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_TRACING
if (optInd+1>=argc || argv[optInd+1][0] == '-')
{
traceName="trace.txt";
}
else
{
traceName=argv[optInd+1];
optInd++;
}
#else
err("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 extensionsFile
formatName=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 功能, 会执行 trace
Config::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_mapping
Doxygen::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_mapping
const 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_encoding
void *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_encoding
const 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_defined
const 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_aliases
readAliases();
// store number of spaces in a tab into Doxygen::spaces
// 读取 tab 制表符的宽度. 默认是4
// https://www.doxygen.nl/manual/config.html#cfg_tab_size
int 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_LIBCLANG
Doxygen::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_size
int 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_SIGNALS
signal(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 文档部署时使用的完整 URL
QCString 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);
}
// 生成 rtf
QCString 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_SQLITE3
QCString 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.xml
LayoutDocManager::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_patterns
StringVector 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
// EXCLUDE
searchInputFiles();
// 检查 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_threads
if (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 区块中的group
buildGroupList(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::cout
buildListOfUsingDecls(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 here
Doxygen::typeLookupCache->clear();
// we don't need the list of using declaration anymore
g_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");
// 处理 typedef
buildTypedefList(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 typedefs
findUsingDeclarations(root.get(),TRUE); // do for python packages first
findUsingDeclarations(root.get(),FALSE); // then the rest
g_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 IDL
g_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 722654
g_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 code
addCodeOnlyMappings();
dump all symbols
if (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 part
g_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 stuff
if (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_SQLITE3
if (Config_getBool(GENERATE_SQLITE3))
{
g_s.begin("Generating SQLITE3 output...\n");
generateSqlite3();
g_s.end();
}
#endif
if (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 factor
int 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();
}