ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 6.3 构建时使用Python生成源码 **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`脚本开始。这个脚本接受两个命令行参数——一个整数范围和一个输出文件名: ```python """ 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`,并将其包含在下面的示例代码中: ```c++ #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 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创建这个目录: ```cmake file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated) ``` 3. Python脚本要求质数的上限,使用下面的命令,我们可以设置一个默认值: ```cmake set(MAX_NUMBER "100" CACHE STRING "Upper bound for primes") ``` 4. 接下来,定义一个自定义命令来生成头文件: ```cmake 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. 最后,定义可执行文件及其目标,包括目录和依赖关系: ```cmake 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. 准备测试: ```shell $ 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`)作为参数: ```cmake 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`很容易实现添加源代码作为依赖项: ```cmake 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`执行该脚本,以便在构建时获得生成的文件列表。