第1章:文档生成:集成Doxygen,使用Sphinx,CMake中调用文档生成工具

说实话,很多C++项目做得不错,但文档一塌糊涂。我见过太多团队,代码写得飞起,可新成员接手时只能对着源码发呆。文档这事儿,说白了就是「写的时候嫌麻烦,用的时候恨太少」。

我个人习惯是:把文档生成集成到构建系统里。这样每次编译时,文档自动更新,省心又省力。今天我们就聊聊怎么在CMake里搞定Doxygen和Sphinx。

为什么要在CMake里管文档?

你想想看,如果文档生成是独立步骤,很容易被遗忘。我有个项目,团队约定「每周五更新文档」,结果三个月后,文档还停留在第一版。

把文档生成塞进CMake,好处很明显:

  • 自动化:构建时自动触发,不用记额外命令
  • 一致性:所有开发者用同一套配置
  • 可追溯:文档版本和代码版本天然绑定

核心思路:利用CMake的 add_custom_targetadd_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 = svgHAVE_DOT = YES 来生成调用图,但别开 CALL_GRAPH,那玩意儿能跑半小时。

集成Sphinx:更适合API文档和教程

Sphinx本来是Python社区的,但配合 breathe 插件,可以读取Doxygen生成的XML,输出漂亮的HTML文档。说白了,Doxygen负责解析代码,Sphinx负责排版展示

工作流程

  1. Doxygen生成XML格式的中间文件
  2. Sphinx + Breathe读取XML,生成最终文档
  3. 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文件。

知识体系总览

下面这张图,是我对文档集成流程的理解。你看一眼就明白了:

CMake文档集成流程 C++ 源码 src/ include/ Doxygen 解析代码注释 XML 中间文件 build/docs/xml/ Sphinx + Breathe 读取XML生成文档 HTML 文档 build/docs/html/ CMake 构建系统 add_custom_target CMake 统一调度 Doxygen 和 Sphinx,实现文档自动化生成

高级玩法:文档版本与条件控制

项目大了以后,你可能只想在特定分支或发布版本时生成文档。我常用的做法是加个选项:

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」。别笑,你可能也见过。把文档集成到构建系统里,至少能保证「有文档」,至于好不好看,那是下一步的事。


公众号:蓝海资料掘金营,微信deep3321