📘
CMake Cookbook
  • Introduction
  • 前言
  • 第0章 配置环境
    • 0.1 获取代码
    • 0.2 Docker镜像
    • 0.3 安装必要的软件
    • 0.4 测试环境
    • 0.5 上报问题并提出改进建议
  • 第1章 从可执行文件到库
    • 1.1 将单个源文件编译为可执行文件
    • 1.2 切换生成器
    • 1.3 构建和链接静态库和动态库
    • 1.4 用条件句控制编译
    • 1.5 向用户显示选项
    • 1.6 指定编译器
    • 1.7 切换构建类型
    • 1.8 设置编译器选项
    • 1.9 为语言设定标准
    • 1.10 使用控制流
  • 第2章 检测环境
    • 2.1 检测操作系统
    • 2.2 处理与平台相关的源代码
    • 2.3 处理与编译器相关的源代码
    • 2.4 检测处理器体系结构
    • 2.5 检测处理器指令集
    • 2.6 为Eigen库使能向量化
  • 第3章 检测外部库和程序
    • 3.1 检测Python解释器
    • 3.2 检测Python库
    • 3.3 检测Python模块和包
    • 3.4 检测BLAS和LAPACK数学库
    • 3.5 检测OpenMP的并行环境
    • 3.6 检测MPI的并行环境
    • 3.7 检测Eigen库
    • 3.8 检测Boost库
    • 3.9 检测外部库:Ⅰ. 使用pkg-config
    • 3.10 检测外部库:Ⅱ. 自定义find模块
  • 第4章 创建和运行测试
    • 4.1 创建一个简单的单元测试
    • 4.2 使用Catch2库进行单元测试
    • 4.3 使用Google Test库进行单元测试
    • 4.4 使用Boost Test进行单元测试
    • 4.5 使用动态分析来检测内存缺陷
    • 4.6 预期测试失败
    • 4.7 使用超时测试运行时间过长的测试
    • 4.8 并行测试
    • 4.9 运行测试子集
    • 4.10 使用测试固件
  • 第5章 配置时和构建时的操作
    • 5.1 使用平台无关的文件操作
    • 5.2 配置时运行自定义命令
    • 5.3 构建时运行自定义命令:Ⅰ. 使用add_custom_command
    • 5.4 构建时运行自定义命令:Ⅱ. 使用add_custom_target
    • 5.5 构建时为特定目标运行自定义命令
    • 5.6 探究编译和链接命令
    • 5.7 探究编译器标志命令
    • 5.8 探究可执行命令
    • 5.9 使用生成器表达式微调配置和编译
  • 第6章 生成源码
    • 6.1 配置时生成源码
    • 6.2 使用Python在配置时生成源码
    • 6.3 构建时使用Python生成源码
    • 6.4 记录项目版本信息以便报告
    • 6.5 从文件中记录项目版本
    • 6.6 配置时记录Git Hash值
    • 6.7 构建时记录Git Hash值
  • 第7章 构建项目
    • 7.1 使用函数和宏重用代码
    • 7.2 将CMake源代码分成模块
    • 7.3 编写函数来测试和设置编译器标志
    • 7.4 用指定参数定义函数或宏
    • 7.5 重新定义函数和宏
    • 7.6 使用废弃函数、宏和变量
    • 7.7 add_subdirectory的限定范围
    • 7.8 使用target_sources避免全局变量
    • 7.9 组织Fortran项目
  • 第8章 超级构建模式
    • 8.1 使用超级构建模式
    • 8.2 使用超级构建管理依赖项:Ⅰ.Boost库
    • 8.3 使用超级构建管理依赖项:Ⅱ.FFTW库
    • 8.4 使用超级构建管理依赖项:Ⅲ.Google Test框架
    • 8.5 使用超级构建支持项目
  • 第9章 语言混合项目
    • 9.1 使用C/C++库构建Fortran项目
    • 9.2 使用Fortran库构建C/C++项目
    • 9.3 使用Cython构建C++和Python项目
    • 9.4 使用Boost.Python构建C++和Python项目
    • 9.5 使用pybind11构建C++和Python项目
    • 9.6 使用Python CFFI混合C,C++,Fortran和Python
  • 第10章 编写安装程序
    • 10.1 安装项目
    • 10.2 生成输出头文件
    • 10.3 输出目标
    • 10.4 安装超级构建
  • 第11章 打包项目
    • 11.1 生成源代码和二进制包
    • 11.2 通过PyPI发布使用CMake/pybind11构建的C++/Python项目
    • 11.3 通过PyPI发布使用CMake/CFFI构建C/Fortran/Python项目
    • 11.4 以Conda包的形式发布一个简单的项目
    • 11.5 将Conda包作为依赖项发布给项目
  • 第12章 构建文档
    • 12.1 使用Doxygen构建文档
    • 12.2 使用Sphinx构建文档
    • 12.3 结合Doxygen和Sphinx
  • 第13章 选择生成器和交叉编译
    • 13.1 使用CMake构建Visual Studio 2017项目
    • 13.2 交叉编译hello world示例
    • 13.3 使用OpenMP并行化交叉编译Windows二进制文件
  • 第14章 测试面板
    • 14.1 将测试部署到CDash
    • 14.2 CDash显示测试覆盖率
    • 14.3 使用AddressSanifier向CDash报告内存缺陷
    • 14.4 使用ThreadSaniiser向CDash报告数据争用
  • 第15章 使用CMake构建已有项目
    • 15.1 如何开始迁移项目
    • 15.2 生成文件并编写平台检查
    • 15.3 检测所需的链接和依赖关系
    • 15.4 复制编译标志
    • 15.5 移植测试
    • 15.6 移植安装目标
    • 15.7 进一步迁移的措施
    • 15.8 项目转换为CMake的常见问题
  • 第16章 可能感兴趣的书
    • 16.1 留下评论——让其他读者知道你的想法
Powered by GitBook
On this page
  • 准备工作
  • 具体实施
  • 具体实施
  • 更多信息

Was this helpful?

  1. 第6章 生成源码

6.3 构建时使用Python生成源码

Previous6.2 使用Python在配置时生成源码Next6.4 记录项目版本信息以便报告

Last updated 5 years ago

Was this helpful?

NOTE:此示例代码可以在 中找到,其中包含一个C++例子。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

构建时根据某些规则生成冗长和重复的代码,同时避免在源代码存储库中显式地跟踪生成的代码生成源代码,是开发人员工具箱中的一个重要工具,例如:根据检测到的平台或体系结构生成不同的源代码。或者,可以使用Python,根据配置时收集的输入,在构建时生成高效的C++代码。其他生成器解析器,比如:Flex ( )和Bison( );元对象编译器,如Qt的moc( );序列化框架,如谷歌的protobuf ( )。

准备工作

为了提供一个具体的例子,我们需要编写代码来验证一个数字是否是质数。现在有很多算法,例如:可以用埃拉托色尼的筛子(sieve of Eratosthenes)来分离质数和非质数。如果有很多验证数字,我们不希望对每一个数字都进行Eratosthenes筛选。我们想要做的是将所有质数一次制表,直到数字的上限,然后使用一个表查的方式,找来验证大量的数字。

本例中,将在编译时使用Python为查找表(质数向量)生成C++代码。当然,为了解决这个特殊的编程问题,我们还可以使用C++生成查询表,并且可以在运行时执行查询。

让我们从generate.py脚本开始。这个脚本接受两个命令行参数——一个整数范围和一个输出文件名:

"""
Generates C++ vector of prime numbers up to max_number
using sieve of Eratosthenes.
"""
import pathlib
import sys

# for simplicity we do not verify argument list
max_number = int(sys.argv[-2])
output_file_name = pathlib.Path(sys.argv[-1])

numbers = range(2, max_number + 1)
is_prime = {number: True for number in numbers}

for number in numbers:
  current_position = number
  if is_prime[current_position]:
    while current_position <= max_number:
      current_position += number
      is_prime[current_position] = False

primes = (number for number in numbers if is_prime[number])

code = """#pragma once

#include <vector>

const std::size_t max_number = {max_number};
std::vector<int> & primes() {{
  static std::vector<int> primes;
  {push_back}
  return primes;
}}
"""
push_back = '\n'.join([' primes.push_back({:d});'.format(x) for x in primes])
output_file_name.write_text(
code.format(max_number=max_number, push_back=push_back))

我们的目标是生成一个primes.hpp,并将其包含在下面的示例代码中:

#include "primes.hpp"

#include <iostream>
#include <vector>

int main() {
  std::cout << "all prime numbers up to " << max_number << ":";

  for (auto prime : primes())
      std::cout << " " << prime;

  std::cout << std::endl;

  return 0;
}

具体实施

下面是CMakeLists.txt命令的详解:

  1. 首先,定义项目并检测Python解释器:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-03 LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    find_package(PythonInterp QUIET REQUIRED)
  2. 将生成的代码放在${CMAKE_CURRENT_BINARY_DIR}/generate下,需要告诉CMake创建这个目录:

    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated)
  3. Python脚本要求质数的上限,使用下面的命令,我们可以设置一个默认值:

    set(MAX_NUMBER "100" CACHE STRING "Upper bound for primes")
  4. 接下来,定义一个自定义命令来生成头文件:

    add_custom_command(
      OUTPUT
          ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
      COMMAND
          ${PYTHON_EXECUTABLE} generate.py ${MAX_NUMBER}     ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
      WORKING_DIRECTORY
          ${CMAKE_CURRENT_SOURCE_DIR}
      DEPENDS
          generate.py
    )
  5. 最后,定义可执行文件及其目标,包括目录和依赖关系:

    add_executable(example "")
    target_sources(example
      PRIVATE
          example.cpp
          ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
      )
    target_include_directories(example
      PRIVATE
          ${CMAKE_CURRENT_BINARY_DIR}/generated
      )
  6. 准备测试:

    $ mkdir -p build
    $ cd build
    $ cmake ..
    $ cmake --build .
    $ ./example
    all prime numbers up to 100: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79

具体实施

为了生成头文件,我们定义了一个自定义命令,它执行generate.py脚本,并接受${MAX_NUMBER}和文件路径(${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp)作为参数:

add_custom_command(
  OUTPUT
      ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
  COMMAND
      ${PYTHON_EXECUTABLE} generate.py ${MAX_NUMBER} ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
  WORKING_DIRECTORY
      ${CMAKE_CURRENT_SOURCE_DIR}
  DEPENDS
      generate.py
  )

为了生成源代码,我们需要在可执行文件的定义中,使用target_sources很容易实现添加源代码作为依赖项:

target_sources(example
  PRIVATE
      example.cpp
      ${CMAKE_CURRENT_BINARY_DIR}/generated/primes.hpp
  )

前面的代码中,我们不需要定义新的目标。头文件将作为示例的依赖项生成,并在每次generate.py脚本更改时重新生成。如果代码生成脚本生成多个源文件,那么要将所有生成的文件列出,做为某些目标的依赖项。

更多信息

file(GLOB…)在配置时执行,而代码生成是在构建时发生的。因此可能需要一个间接操作,将file(GLOB…)命令放在一个单独的CMake脚本中,使用${CMAKE_COMMAND} -P执行该脚本,以便在构建时获得生成的文件列表。

我们提到所有的生成文件,都应该作为某个目标的依赖项。但是,我们可能不知道这个文件列表,因为它是由生成文件的脚本决定的,这取决于我们提供给配置的输入。这种情况下,我们可能会尝试使用file(GLOB…)将生成的文件收集到一个列表中(参见 )。

https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-6/recipe-03
https://github.com/westes/flex
https://www.gnu.org/software/bison/
http://doc.qt.io/qt5/moc.html
https://developers.google.com/protocol-buffers/
https://cmake.org/cmake/help/v3.5/command/file.html