第1章:文档生成:集成Doxygen,使用Sphinx,CMake中调用文档生成工具
说实话,很多C++项目做得不错,但文档一塌糊涂。我见过太多团队,代码写得飞起,可新成员接手时只能对着源码发呆。文档这事儿,说白了就是「写的时候嫌麻烦,用的时候恨太少」。
我个人习惯是:把文档生成集成到构建系统里。这样每次编译时,文档自动更新,省心又省力。今天我们就聊聊怎么在CMake里搞定Doxygen和Sphinx。
为什么要在CMake里管文档?
你想想看,如果文档生成是独立步骤,很容易被遗忘。我有个项目,团队约定「每周五更新文档」,结果三个月后,文档还停留在第一版。
把文档生成塞进CMake,好处很明显:
- 自动化:构建时自动触发,不用记额外命令
- 一致性:所有开发者用同一套配置
- 可追溯:文档版本和代码版本天然绑定
核心思路:利用CMake的 add_custom_target 或 add_custom_command,把文档工具当作一个「构建目标」来管理。
集成Doxygen:从配置到生成
Doxygen是C++文档生成的老牌工具。我最早用Doxygen是十年前,那时候还在纠结要不要写注释。现在回头看,注释写得好,Doxygen就是你的救星。
第一步:准备Doxyfile
Doxygen的配置都在 Doxyfile 里。你可以手动生成:
doxygen -g Doxyfile
然后修改关键参数。我个人习惯这样配:
PROJECT_NAME = "MyProject"
PROJECT_BRIEF = "一个高性能计算库"
OUTPUT_DIRECTORY = docs/doxygen
INPUT = src include
RECURSIVE = YES
EXTRACT_ALL = YES
GENERATE_HTML = YES
GENERATE_LATEX = NO
嗯,这里要注意:EXTRACT_ALL 设为 YES 的话,即使没写注释的类也会被提取。对老项目迁移很友好。
第二步:在CMake中调用Doxygen
核心代码就这几行:
find_package(Doxygen REQUIRED)
if(DOXYGEN_FOUND)
add_custom_target(docs
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "生成Doxygen文档..."
)
endif()
然后运行:
cmake --build . --target docs
文档就出来了。简单吧?
小技巧:可以用 add_dependencies 把文档生成绑定到主目标上。比如 add_dependencies(my_lib docs),这样每次编译库时自动更新文档。
第三步:避坑指南
我曾经在一个跨平台项目里踩过坑——Windows上Doxygen路径带空格,导致命令执行失败。后来我加了个判断:
if(WIN32)
string(REPLACE " " "\\ " DOXYGEN_EXECUTABLE "${DOXYGEN_EXECUTABLE}")
endif()
另外,如果项目很大,Doxygen生成会很慢。我建议用 DOT_IMAGE_FORMAT = svg 和 HAVE_DOT = YES 来生成调用图,但别开 CALL_GRAPH,那玩意儿能跑半小时。
集成Sphinx:更适合API文档和教程
Sphinx本来是Python社区的,但配合 breathe 插件,可以读取Doxygen生成的XML,输出漂亮的HTML文档。说白了,Doxygen负责解析代码,Sphinx负责排版展示。
工作流程
- Doxygen生成XML格式的中间文件
- Sphinx + Breathe读取XML,生成最终文档
- CMake把这两步串起来
CMake配置示例
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
# 第一步:Doxygen生成XML
set(DOXYGEN_XML_DIR ${CMAKE_CURRENT_BINARY_DIR}/docs/xml)
set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
configure_file(${DOXYFILE_IN} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
add_custom_command(
OUTPUT ${DOXYGEN_XML_DIR}/index.xml
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "生成Doxygen XML..."
)
# 第二步:Sphinx生成HTML
add_custom_target(sphinx-docs
COMMAND ${SPHINX_EXECUTABLE} -b html
${CMAKE_CURRENT_SOURCE_DIR}/docs/sphinx
${CMAKE_CURRENT_BINARY_DIR}/docs/html
DEPENDS ${DOXYGEN_XML_DIR}/index.xml
COMMENT "生成Sphinx文档..."
)
这里用 configure_file 把CMake变量注入到Doxyfile里,比如输出路径。这样构建目录和源码目录就分开了,干净得很。
注意:Sphinx的 conf.py 里要配置好breathe:
extensions = ['breathe']
breathe_projects = {"MyProject": "build/docs/xml"}
breathe_default_project = "MyProject"
路径一定要对,否则breathe找不到XML文件。
知识体系总览
下面这张图,是我对文档集成流程的理解。你看一眼就明白了:
高级玩法:文档版本与条件控制
项目大了以后,你可能只想在特定分支或发布版本时生成文档。我常用的做法是加个选项:
option(BUILD_DOCS "构建文档" OFF)
if(BUILD_DOCS)
# 文档生成逻辑...
endif()
然后在CI里这样用:
cmake -DBUILD_DOCS=ON ..
cmake --build . --target sphinx-docs
这样日常开发时不会浪费时间生成文档,只有需要发布时才触发。
另一个技巧:用 set_target_properties 给文档目标加个 FOLDER 属性,在IDE里更好管理:
set_target_properties(docs PROPERTIES FOLDER "documentation")
总结一下
文档生成这件事,说白了就是「一次配置,长期受益」。我个人建议:
- 小项目:只用Doxygen就够了,简单直接
- 中大型项目:Doxygen + Sphinx + Breathe,文档质量上一个台阶
- 团队协作:把文档生成加到CI里,合并代码时自动更新
嗯,我见过最离谱的项目,文档目录里放了个「待补充.txt」。别笑,你可能也见过。把文档集成到构建系统里,至少能保证「有文档」,至于好不好看,那是下一步的事。