CMake 秘籍(七)(1)

本文涉及的产品
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: CMake 秘籍(七)

第十三章:构建文档

在本章中,我们将涵盖以下食谱:

  • 使用 Doxygen 构建文档
  • 使用 Sphinx 构建文档
  • 结合 Doxygen 和 Sphinx

引言

文档在所有软件项目中都是必不可少的:对于用户,解释如何获取和构建代码,并说明如何有效地使用您的代码或库,对于开发者,描述库的内部细节,并帮助其他程序员参与并贡献于您的项目。本章将展示如何使用 CMake 构建代码文档,使用两个流行的框架:Doxygen 和 Sphinx。

使用 Doxygen 构建文档

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-12/recipe-01找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

Doxygen(www.doxygen.nl)是一个非常流行的源代码文档工具。您可以在代码中添加文档标签作为注释。运行 Doxygen 将提取这些注释并在 Doxyfile 配置文件中定义的格式中创建文档。Doxygen 可以输出 HTML、XML,甚至是 LaTeX 或 PDF。本食谱将向您展示如何使用 CMake 构建您的 Doxygen 文档。

准备就绪

我们将使用之前章节中介绍的message库的简化版本。源树组织如下:

.
├── cmake
│   └── UseDoxygenDoc.cmake
├── CMakeLists.txt
├── docs
│   ├── Doxyfile.in
│   └── front_page.md
└── src
    ├── CMakeLists.txt
    ├── hello-world.cpp
    ├── Message.cpp
    └── Message.hpp

我们的源代码仍然位于src子目录下,自定义 CMake 模块位于cmake子目录下。由于我们的重点是文档,我们删除了对 UUID 的依赖并简化了源代码。最显著的区别是头文件中的大量代码注释:

#pragma once
#include <iosfwd>
#include <string>
/*! \file Message.hpp */
/*! \class Message
 * \brief Forwards string to screen
 * \author Roberto Di Remigio
 * \date 2018
 */
class Message {
public:
  /*! \brief Constructor from a string
   * \param[in] m a message
   */
  Message(const std::string &m) : message_(m) {}
  /*! \brief Constructor from a character array
   * \param[in] m a message
   */
  Message(const char *m) : message_(std::string(m)) {}
  friend std::ostream &operator<<(std::ostream &os, Message &obj) {
    return obj.printObject(os);
  }
private:
  /*! The message to be forwarded to screen */
  std::string message_;
  /*! \brief Function to forward message to screen
   * \param[in, out] os output stream
   */
  std::ostream &printObject(std::ostream &os);
};

这些注释采用/*! */格式,并包含一些特殊标签,这些标签被 Doxygen 理解(参见www.stack.nl/~dimitri/doxygen/manual/docblocks.html)。

如何操作

首先,让我们讨论根目录中的CMakeLists.txt文件:

  1. 如您所熟悉,我们声明一个 C++11 项目,如下所示:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  1. 我们定义共享和静态库以及可执行文件的输出目录,如下所示:
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
  1. 我们将cmake子目录附加到CMAKE_MODULE_PATH。这是 CMake 找到我们的自定义模块所必需的:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
  1. 包含自定义模块UseDoxygenDoc.cmake。我们将在后面讨论其内容:
include(UseDoxygenDoc)
  1. 然后我们添加src子目录:
add_subdirectory(src)

src子目录中的CMakeLists.txt文件包含以下构建块:

  1. 我们添加一个message静态库,如下所示:
add_library(message STATIC
  Message.hpp
  Message.cpp
  )
  1. 然后我们添加一个可执行目标,hello-world
add_executable(hello-world hello-world.cpp)
  1. 然后,hello-world可执行文件应该链接到消息库:
target_link_libraries(hello-world
  PUBLIC
    message
  )

在根CMakeLists.txt文件的最后一节中,我们调用了add_doxygen_doc函数。这添加了一个新的docs目标,该目标将调用 Doxygen 来构建我们的文档:

add_doxygen_doc(
  BUILD_DIR
    ${CMAKE_CURRENT_BINARY_DIR}/_build
  DOXY_FILE
    ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in
  TARGET_NAME
    docs
  COMMENT
    "HTML documentation"
  )

最后,让我们看一下UseDoxygenDoc.cmake模块,其中定义了add_doxygen_doc函数:

  1. 我们找到DoxygenPerl可执行文件,如下所示:
find_package(Perl REQUIRED)
find_package(Doxygen REQUIRED)
  1. 然后,我们声明add_doxygen_doc函数。该函数理解单值参数:BUILD_DIRDOXY_FILETARGET_NAMECOMMENT。我们使用 CMake 的标准命令cmake_parse_arguments来解析这些参数:
function(add_doxygen_doc)
  set(options)
  set(oneValueArgs BUILD_DIR DOXY_FILE TARGET_NAME COMMENT)
  set(multiValueArgs)
  cmake_parse_arguments(DOXY_DOC
    "${options}"
    "${oneValueArgs}"
    "${multiValueArgs}"
    ${ARGN}
    )
  # ...
endfunction()
  1. Doxyfile包含构建文档所需的所有 Doxygen 设置。模板Doxyfile.in作为函数参数DOXY_FILE传递,并被解析到DOXY_DOC_DOXY_FILE变量中。我们按照以下方式配置模板文件Doxyfile.in
configure_file(
  ${DOXY_DOC_DOXY_FILE}
  ${DOXY_DOC_BUILD_DIR}/Doxyfile
  @ONLY
  )
  1. 然后,我们定义一个名为DOXY_DOC_TARGET_NAME的自定义目标,它将使用Doxyfile中的设置执行 Doxygen,并将结果输出到DOXY_DOC_BUILD_DIR
add_custom_target(${DOXY_DOC_TARGET_NAME}
  COMMAND
    ${DOXYGEN_EXECUTABLE} Doxyfile
  WORKING_DIRECTORY
    ${DOXY_DOC_BUILD_DIR}
  COMMENT
    "Building ${DOXY_DOC_COMMENT} with Doxygen"
  VERBATIM
  )
  1. 最终,会向用户打印一条状态消息:
message(STATUS "Added ${DOXY_DOC_TARGET_NAME} [Doxygen] target to build documentation")

我们可以像往常一样配置项目:

$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .

通过调用我们的自定义docs目标,可以构建文档:

$ cmake --build . --target docs

你会注意到,在构建树中会出现一个_build子目录。这包含 Doxygen 从你的源文件生成的 HTML 文档。使用你喜欢的浏览器打开index.html将显示 Doxygen 欢迎页面。

如果你导航到类列表,你可以例如浏览Message类的文档:

工作原理

CMake 默认不支持文档构建。但是,我们可以使用add_custom_target来执行任意操作,这是我们在本食谱中利用的机制。需要注意的是,我们需要确保系统上存在构建文档所需的工具(在本例中为 Doxygen 和 Perl)。

此外,请注意UseDoxygenDoc.cmake自定义模块仅执行以下操作:

  • 执行对 Doxygen 和 Perl 可执行文件的搜索
  • 定义一个函数

实际创建docs目标的操作留给了稍后调用add_doxygen_doc函数。这是一种“显式优于隐式”的模式,我们认为这是良好的 CMake 实践:不要使用模块包含来执行类似宏(或函数)的操作。

我们通过使用函数而不是宏来实现add_doxygen_doc,以限制变量定义的作用域和可能的副作用。在这种情况下,函数和宏都可以工作(并且会产生相同的结果),但我们建议除非需要修改父作用域中的变量,否则应优先使用函数而不是宏。

CMake 3.9 中添加了一个新的改进的FindDoxygen.cmake模块。实现了便利函数doxygen_add_docs,它将作为我们在本食谱中介绍的宏。有关更多详细信息,请查看在线文档cmake.org/cmake/help/v3.9/module/FindDoxygen.html

使用 Sphinx 构建文档

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-12/recipe-02找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

Sphinx 是一个 Python 程序,也是一个非常流行的文档系统(www.sphinx-doc.org)。当与 Python 项目一起使用时,它可以解析源文件中的所谓 docstrings,并自动为函数和类生成文档页面。然而,Sphinx 不仅限于 Python,还可以解析 reStructuredText、Markdown 纯文本文件,并生成 HTML、ePUB 或 PDF 文档。与在线 Read the Docs 服务(readthedocs.org)结合使用,它提供了一种快速开始编写和部署文档的绝佳方式。本食谱将向您展示如何使用 CMake 基于 Sphinx 构建文档。

准备工作

我们希望构建一个简单的网站来记录我们的消息库。源树现在看起来如下:

.
├── cmake
│   ├── FindSphinx.cmake
│   └── UseSphinxDoc.cmake
├── CMakeLists.txt
├── docs
│   ├── conf.py.in
│   └── index.rst
└── src
    ├── CMakeLists.txt
    ├── hello-world.cpp
    ├── Message.cpp
    └── Message.hpp

我们在cmake子目录中有一些自定义模块,docs子目录包含我们网站的主页,以纯文本 reStructuredText 格式,index.rst,以及一个 Python 模板文件,conf.py.in,用于 Sphinx 的设置。此文件可以使用 Sphinx 安装的一部分sphinx-quickstart实用程序自动生成。

如何操作

与之前的食谱相比,我们将修改根CMakeLists.txt文件,并实现一个函数(add_sphinx_doc):

  1. 在将cmake文件夹附加到CMAKE_MODULE_PATH之后,我们如下包含UseSphinxDoc.cmake自定义模块:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(UseSphinxDoc)
  1. UseSphinxDoc.cmake模块定义了add_sphinx_doc函数。我们使用关键字参数调用此函数,以设置我们的 Sphinx 文档构建。自定义文档目标将被称为docs
add_sphinx_doc(
  SOURCE_DIR
    ${CMAKE_CURRENT_SOURCE_DIR}/docs
  BUILD_DIR
    ${CMAKE_CURRENT_BINARY_DIR}/_build
  CACHE_DIR
    ${CMAKE_CURRENT_BINARY_DIR}/_doctrees
  HTML_DIR
    ${CMAKE_CURRENT_BINARY_DIR}/sphinx_html
  CONF_FILE
    ${CMAKE_CURRENT_SOURCE_DIR}/docs/conf.py.in
  TARGET_NAME
    docs
  COMMENT
    "HTML documentation"
  )

UseSphinxDoc.cmake模块遵循我们在前一个食谱中使用的相同“显式优于隐式”模式:

  1. 我们需要找到 Python 解释器和Sphinx可执行文件,如下所示:
find_package(PythonInterp REQUIRED)
find_package(Sphinx REQUIRED)
  1. 然后我们定义带有单值关键字参数的add_sphinx_doc函数。这些参数由cmake_parse_arguments命令解析:
function(add_sphinx_doc)
  set(options)
  set(oneValueArgs
    SOURCE_DIR
    BUILD_DIR
    CACHE_DIR
    HTML_DIR
    CONF_FILE
    TARGET_NAME
    COMMENT
    )
  set(multiValueArgs)
  cmake_parse_arguments(SPHINX_DOC
    "${options}"
    "${oneValueArgs}"
    "${multiValueArgs}"
    ${ARGN}
    )
  # ...
endfunction()
  1. 模板文件conf.py.in,作为CONF_FILE关键字参数传递,配置为在SPHINX_DOC_BUILD_DIR中的conf.py
configure_file(
  ${SPHINX_DOC_CONF_FILE}
  ${SPHINX_DOC_BUILD_DIR}/conf.py
  @ONLY
  )
  1. 我们添加了一个名为SPHINX_DOC_TARGET_NAME的自定义目标,以协调使用 Sphinx 构建文档:
add_custom_target(${SPHINX_DOC_TARGET_NAME}
  COMMAND
    ${SPHINX_EXECUTABLE}
       -q
       -b html
       -c ${SPHINX_DOC_BUILD_DIR}
       -d ${SPHINX_DOC_CACHE_DIR}
       ${SPHINX_DOC_SOURCE_DIR}
       ${SPHINX_DOC_HTML_DIR}
  COMMENT
    "Building ${SPHINX_DOC_COMMENT} with Sphinx"
  VERBATIM
  )
  1. 最后,我们向用户打印出一条状态消息:
message(STATUS "Added ${SPHINX_DOC_TARGET_NAME} [Sphinx] target to build documentation")
  1. 我们配置项目并构建docs目标:
$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build . --target docs

这将在构建树的SPHINX_DOC_HTML_DIR子目录中生成 HTML 文档。再次,您可以使用您喜欢的浏览器打开index.html并查看闪亮(但仍然稀疏)的文档:

它是如何工作的

再次,我们利用了add_custom_target的强大功能,向我们的构建系统添加了一个任意构建目标。在这种情况下,文档将使用 Sphinx 构建。由于 Sphinx 是一个可以与其他 Python 模块扩展的 Python 程序,因此docs目标将依赖于 Python 解释器。我们确保通过使用find_package来满足依赖关系。请注意,FindSphinx.cmake模块还不是标准的 CMake 模块;它的副本包含在项目源代码的cmake子目录下。

结合 Doxygen 和 Sphinx

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-12/recipe-03找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

我们有一个 C++项目,因此,Doxygen 是生成源代码文档的理想选择。然而,我们也希望发布面向用户的文档,例如解释我们的设计选择。我们更愿意使用 Sphinx 来实现这一点,因为生成的 HTML 也可以在移动设备上工作,而且我们可以将文档部署到 Read the Docs(readthedocs.org)。本食谱将说明如何使用 Breathe 插件(breathe.readthedocs.io)来桥接 Doxygen 和 Sphinx。

CMake 秘籍(七)(2)https://developer.aliyun.com/article/1525424

相关文章
|
2月前
|
编译器 Shell 开发工具
CMake 秘籍(八)(5)
CMake 秘籍(八)
22 2
|
2月前
|
编译器 开发工具 git
CMake 秘籍(八)(1)
CMake 秘籍(八)
13 1
|
2月前
|
Linux 编译器 C++
CMake 秘籍(七)(2)
CMake 秘籍(七)
29 1
|
2月前
|
消息中间件 Unix C语言
CMake 秘籍(二)(5)
CMake 秘籍(二)
27 1
|
2月前
|
编译器 Linux C++
CMake 秘籍(六)(5)
CMake 秘籍(六)
19 1
|
2月前
|
Linux C++ iOS开发
CMake 秘籍(三)(4)
CMake 秘籍(三)
19 1
|
2月前
|
测试技术 C++
CMake 秘籍(四)(5)
CMake 秘籍(四)
16 0
|
2月前
|
编译器 Linux C++
CMake 秘籍(二)(1)
CMake 秘籍(二)
23 0
|
2月前
|
编译器 Linux C++
CMake 秘籍(六)(2)
CMake 秘籍(六)
24 0
|
2月前
|
并行计算 Unix 编译器
CMake 秘籍(七)(5)
CMake 秘籍(七)
37 0

热门文章

最新文章

相关实验场景

更多