返回介绍

6.3 构建时使用 Python 生成源码

发布于 2025-05-06 21:45:57 字数 5462 浏览 0 评论 0 收藏

NOTE : 此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-6/recipe-03 中找到,其中包含一个 C++例子。该示例在 CMake 3.5 版(或更高版本) 中是有效的,并且已经在 GNU/Linux、macOS 和 Windows 上进行过测试。

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

准备工作

为了提供一个具体的例子,我们需要编写代码来验证一个数字是否是质数。现在有很多算法,例如:可以用埃拉托色尼的筛子(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…) 将生成的文件收集到一个列表中(参见 https://cmake.org/cmake/help/v3.5/command/file.html )。

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

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。