前言
Doxygen 是一个源代码文档生成工具:它扫描源码中的特殊注释,提取函数、类、参数、返回值等信息,生成 HTML、LaTeX 等格式的文档。它最常被用于 C++ 项目,但也支持 Python、C、Java、C# 等语言。当一个项目同时包含 C++ 核心库和 Python 绑定/脚本时,用一套工具生成统一风格的文档是很自然的需求。
需要先说明的是,Doxygen 是第三方工具,不同版本的行为和配置项可能有差异。本文给出的 Doxyfile 配置和注释写法是常见用法,具体细节应以你所安装版本的官方文档为准。本文不会假装运行过doxygen,也不会给出「生成效果如下」这类断言;只讲配置的含义和预期的标准行为。
常见的误解有三个。第一,以为 Doxygen 只能处理 C++;实际上它有 Python 解析器,能读取 docstring。第二,以为写好注释就自动生成完整文档;还需要正确配置INPUT、RECURSIVE、FILE_PATTERNS、EXTRACT_ALL等选项,否则文件可能根本没被扫描。第三,以为 Doxygen 能理解所有 Python 类型注解和 Sphinx 字段;跨语言支持有边界,复杂项目往往需要 Sphinx + Breathe 等组合方案。
一、安装与基本流程
Doxygen 的典型工作流只有两步:
# 在项目根目录生成配置模板
doxygen -g Doxyfile
# 按配置生成文档
doxygen Doxyfile第一条命令生成一个带完整注释的Doxyfile,里面每个配置项都有说明;第二条命令读取配置、扫描源码、输出 HTML 到OUTPUT_DIRECTORY指定的目录(默认是当前目录下的html)。
如果需要调用图、被调用图、类继承图,还要安装 Graphviz,并确保dot命令在PATH中,然后在 Doxyfile 里把HAVE_DOT设为YES。没有 Graphviz 时,Doxygen 仍能生成文档,只是没有图。
在 Windows 上,Doxygen 提供图形界面doxywizard,可以可视化编辑 Doxyfile;命令行用法与上面一致。安装方式(安装包、包管理器、源码编译)不影响 Doxyfile 的语义,按官方文档操作即可。
二、Doxyfile 的关键配置
下面是一份适合「C++ 头文件 + Python 脚本」混合项目的配置片段。行内#之后是注释,实际文件中可以保留,Doxygen 会忽略。
# Doxyfile 片段(适用于 Doxygen 1.9 系列,具体项以官方文档为准)
PROJECT_NAME = "MyProject"
PROJECT_BRIEF = "C++ core with Python bindings"
OUTPUT_DIRECTORY = docs
INPUT = include src python
RECURSIVE = YES
FILE_PATTERNS = *.h *.hpp *.cpp *.py
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
GENERATE_HTML = YES
GENERATE_LATEX = NO
GENERATE_TREEVIEW = YES
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
HAVE_DOT = YES
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = YES
INPUT_ENCODING = UTF-8
QUIET = NO
WARN_IF_UNDOCUMENTED = YES逐项说明:
INPUT列出要扫描的目录或文件。多个路径用空格分隔。路径相对于 Doxyfile 所在目录,或使用绝对路径。RECURSIVE = YES让 Doxygen 递归进入子目录;没有它,INPUT只扫描顶层。FILE_PATTERNS决定哪些扩展名会被当作源文件。默认值通常包含 C/C++ 常见扩展名,但不一定包含.py,所以显式加上*.py。EXTRACT_ALL = YES让没有文档注释的实体也出现在文档里。它适合快速浏览,但会掩盖「哪些函数没写注释」;正式项目可以设为NO,配合WARN_IF_UNDOCUMENTED找出缺失的注释。EXTRACT_PRIVATE控制是否输出私有成员;对库的对外文档通常设为NO。OPTIMIZE_OUTPUT_FOR_C对纯 C 项目设为YES,会调整输出布局(例如不显示类)。C++ 项目保持NO。OPTIMIZE_OUTPUT_JAVA的官方说明是「针对 Java 或 Python 优化输出」,所以混合项目里有 Python 时可以设为YES。它不会改变解析逻辑,只影响展示。INPUT_ENCODING = UTF-8对中文注释很重要;如果源码是其他编码,要改成对应值,否则文档里会出现乱码。GENERATE_LATEX = NO可以加快生成速度,除非确实需要 PDF。HAVE_DOT需要 Graphviz;没有安装时设为NO,避免警告。
三、C++ 注释写法
Doxygen 识别几种注释块:/** ... */、/*! ... */、///、//!。推荐用/** ... */配合反斜杠命令:
// 适用于 C++17
/**
* @file matrix.h
* @brief 简单的二维矩阵类。
*/
/**
* @brief 计算两个矩阵的和。
*
* 要求两个矩阵维度相同,否则抛出 std::invalid_argument。
*
* @param lhs 左操作数。
* @param rhs 右操作数。
* @return 逐元素相加后的新矩阵。
* @throws std::invalid_argument 当维度不匹配时抛出。
* @warning 浮点元素使用严格相等比较,不适合容差场景。
*/
Matrix operator+(const Matrix& lhs, const Matrix& rhs);常用命令:
| 命令 | 作用 |
|---|
@file | 标记文件,通常放在文件开头 |
@brief | 一句话摘要 |
@param | 描述参数 |
@return | 描述返回值 |
@throws/@exception | 描述异常 |
@warning/@note | 警告与提示 |
@see | 交叉引用 |
@code/@endcode | 代码片段 |
@todo | 待办事项 |
@和\是等价的,选一种并在项目内统一。命令后面的文字可以跨行,Doxygen 会把连续的段落合并;空行表示新段落。
一个常见问题是注释和声明的位置。Doxygen 默认把「紧挨在实体之前」的注释块关联到该实体;如果注释放在声明之后,需要用///<或/**< ... */表示「后置注释」。成员变量的简短说明常用后置写法:
// 适用于 C++17
class Config {
public:
int port = 8080; ///< 监听端口。
int maxConn = 64; ///< 最大连接数。
};四、Python 注释写法
Doxygen 的 Python 解析器会读取 docstring。也就是说,下面这种写法可以被识别:
# 适用于 Python 3
def add(a, b):
"""返回两个数的和。
参数:
a: 第一个加数。
b: 第二个加数。
返回:
两个数的和。
"""
return a + bDoxygen 能提取函数名、参数列表和 docstring 文本,但是否把「参数:」这种中文段落解析成结构化的参数表,取决于版本和配置。更稳妥的做法是使用 Doxygen 支持的@param风格,或者接受「整段文本作为详细说明」的效果:
# 适用于 Python 3
def add(a, b):
"""返回两个数的和。
@param a 第一个加数。
@param b 第二个加数。
@return 两个数的和。
"""
return a + b这里必须提醒:Doxygen 对 Python 的支持不如对 C++ 成熟,类型注解、装饰器、动态属性、*args/**kwargs的展示方式都可能与预期不同。如果项目以 Python 为主,通常更推荐 Sphinx(autodoc扩展);如果已经用 Doxygen 生成 C++ 文档,又想把 Python API 合并进来,可以考虑:
- 用同一个 Doxyfile 扫描
.py,接受较简单的展示。 - 用 Sphinx 写 Python 文档,再用 Breathe 扩展读取 Doxygen 生成的 XML(需要在 Doxyfile 中设置
GENERATE_XML = YES),把 C++ 文档嵌入 Sphinx。 - 分别生成两套文档,在入口页互相链接。
三种方案各有取舍,选择时以团队的工具链和维护成本为准,不要为了「统一」而强行让一个工具做它不擅长的事。
五、混合项目的组织方式
C++ 与 Python 混合时,常见的目录结构是:
project/
include/ # C++ 公共头文件
src/ # C++ 实现
python/ # Python 包与脚本
docs/ # 生成的文档
Doxyfile对应的INPUT写include src python,FILE_PATTERNS同时包含*.h *.hpp *.cpp *.py。如果 Python 代码里有大量第三方依赖或生成代码,建议把生成目录排除:
EXCLUDE = python/build python/vendor
EXCLUDE_PATTERNS = */tests/* */build/*EXCLUDE按路径排除,EXCLUDE_PATTERNS按通配符排除。两者都能减少扫描时间和文档噪音。
如果 C++ 和 Python 需要不同的抽取策略(例如 C++ 要EXTRACT_PRIVATE = NO,Python 要全量),可以维护两份 Doxyfile,分别输出到docs/cpp和docs/python,再用一个简单的 HTML 入口页链接。两份配置的公共部分可以手动同步,或者用脚本生成;不要指望 Doxygen 原生支持「按语言分支配置」。
六、接入构建流程
最常见的做法是在 CI 或本地脚本里加一条命令:
doxygen Doxyfile如果使用 CMake,FindDoxygen模块提供了doxygen_add_docs函数,可以把文档目标接入构建系统(具体签名以所用 CMake 版本的文档为准):
find_package(Doxygen)
if(DOXYGEN_FOUND)
doxygen_add_docs(docs
include src python
COMMENT "Generating API documentation"
)
endif()这样可以用cmake --build build --target docs生成文档。注意doxygen_add_docs会使用 CMake 生成的配置,可能与手写的 Doxyfile 有差异;如果项目已经有精心维护的 Doxyfile,直接调用doxygen Doxyfile更可控。
常见坑点
1. 忘记把 .py 加入 FILE_PATTERNS
❌ 只配置了INPUT = python,但FILE_PATTERNS仍是默认的 C/C++ 扩展名,Python 文件被忽略。
✅ 显式写FILE_PATTERNS = *.h *.hpp *.cpp *.py。
2. 没有开启 RECURSIVE
❌INPUT = src,子目录里的文件没有出现在文档中。
✅RECURSIVE = YES,或用INPUT逐个列出子目录。
3. 注释和实体之间隔了空行
❌/** 说明 */空一行后才是函数声明,Doxygen 可能不关联。
✅ 注释紧贴声明;后置注释用///<。
4. 中文注释乱码
❌ 源码是 GBK,INPUT_ENCODING保持默认。
✅ 统一源码为 UTF-8,并设置INPUT_ENCODING = UTF-8。
5. 把 EXTRACT_ALL 当成「自动写文档」
❌ 设为YES后以为什么都不用写。
✅ 它只是让未注释的实体也出现在文档里;正式文档仍需要@brief、@param等。
6. 在 Python 里期待完整的类型信息
❌ 以为 Doxygen 能像 C++ 一样展示 Python 的类型注解和重载。
✅ 接受展示差异;复杂 Python 项目用 Sphinx,或 Sphinx + Breathe 组合。
7. HAVE_DOT 开了但没装 Graphviz
❌ 生成时大量警告,图全部缺失。
✅ 安装 Graphviz 并确认dot在PATH,或把HAVE_DOT设为NO。
8. 把生成目录纳入版本控制
❌ 把docs/html整个提交,仓库体积膨胀。
✅ 只提交 Doxyfile 和源注释,生成物由 CI 产出或本地生成。
总结
| 需求 | 配置/做法 |
|---|
| 扫描 C++ 与 Python | INPUT包含两者,FILE_PATTERNS加*.py |
| 递归子目录 | RECURSIVE = YES |
| 输出位置 | OUTPUT_DIRECTORY = docs |
| 只出 HTML | GENERATE_HTML = YES、GENERATE_LATEX = NO |
| 类图/调用图 | 安装 Graphviz,HAVE_DOT = YES |
| 中文注释 | 源码 UTF-8,INPUT_ENCODING = UTF-8 |
| Python 展示优化 | OPTIMIZE_OUTPUT_JAVA = YES(官方说明含 Python) |
| 混合项目 | 单 Doxyfile 或 C++/Python 分开生成 |
Doxygen 的价值在于「注释即文档」:只要在声明旁写好结构化注释,就能生成可导航的 API 手册。C++ 部分它支持得最成熟;Python 部分能用,但不要期待和 Sphinx 一样的体验。把配置项理解清楚、把注释写规范,比追求「一套工具解决所有语言」更重要。