💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 6.2 使用Python在配置时生成源码 **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-6/recipe-02 中找到,其中包含一个Fortran/C例子。该示例在CMake 3.10版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows(使用MSYS Makefile)上进行过测试。* 本示例中,我们将再次从模板`print_info.c.in`生成`print_info.c`。但这一次,将假设CMake函数`configure_file()`没有创建源文件,然后使用Python脚本模拟这个过程。当然,对于实际的项目,我们可能更倾向于使用`configure_file()`,但有时使用Python生成源代码的需要时,我们也应该知道如何应对。 这个示例有严重的限制,不能完全模拟`configure_file()`。我们在这里介绍的方法,不能生成一个自动依赖项,该依赖项将在构建时重新生成`print_info.c`。换句话说,如果在配置之后删除生成的`print_info.c`,则不会重新生成该文件,构建也会失败。要正确地模拟`configure_file()`,需要使用`add_custom_command()`和`add_custom_target()`。我们将在第3节中使用它们,来克服这个限制。 这个示例中,我们将使用一个简单的Python脚本。这个脚本将读取`print_info.c.in`。用从CMake传递给Python脚本的参数替换文件中的占位符。对于更复杂的模板,我们建议使用外部工具,比如Jinja(参见http://jinja.pocoo.org )。 ```python def configure_file(input_file, output_file, vars_dict): with input_file.open('r') as f: template = f.read() for var in vars_dict: template = template.replace('@' + var + '@', vars_dict[var]) with output_file.open('w') as f: f.write(template) ``` 这个函数读取一个输入文件,遍历` vars_dict`变量中的目录,并用对应的值替换`@key@`,再将结果写入输出文件。这里的键值对,将由CMake提供。 ## 准备工作 `print_info.c.in`和`example.f90`与之前的示例相同。此外,我们将使用Python脚本`configurator.py`,它提供了一个函数: ```python def configure_file(input_file, output_file, vars_dict): with input_file.open('r') as f: template = f.read() for var in vars_dict: template = template.replace('@' + var + '@', vars_dict[var]) with output_file.open('w') as f: f.write(template) ``` 该函数读取输入文件,遍历`vars_dict`字典的所有键,用对应的值替换模式`@key@`,并将结果写入输出文件(键值由CMake提供)。 ## 具体实施 与前面的示例类似,我们需要配置一个模板文件,但这一次,使用Python脚本模拟`configure_file()`函数。我们保持CMakeLists.txt基本不变,并提供一组命令进行替换操作`configure_file(print_info.c.in print_info.c @ONLY)`,接下来将逐步介绍这些命令: 1. 首先,构造一个变量`_config_script`,它将包含一个Python脚本,稍后我们将执行这个脚本: ```cmake set(_config_script " from pathlib import Path source_dir = Path('${CMAKE_CURRENT_SOURCE_DIR}') binary_dir = Path('${CMAKE_CURRENT_BINARY_DIR}') input_file = source_dir / 'print_info.c.in' output_file = binary_dir / 'print_info.c' import sys sys.path.insert(0, str(source_dir)) from configurator import configure_file vars_dict = { '_user_name': '${_user_name}', '_host_name': '${_host_name}', '_fqdn': '${_fqdn}', '_processor_name': '${_processor_name}', '_processor_description': '${_processor_description}', '_os_name': '${_os_name}', '_os_release': '${_os_release}', '_os_version': '${_os_version}', '_os_platform': '${_os_platform}', '_configuration_time': '${_configuration_time}', 'CMAKE_VERSION': '${CMAKE_VERSION}', 'CMAKE_GENERATOR': '${CMAKE_GENERATOR}', 'CMAKE_Fortran_COMPILER': '${CMAKE_Fortran_COMPILER}', 'CMAKE_C_COMPILER': '${CMAKE_C_COMPILER}', } configure_file(input_file, output_file, vars_dict) ") ``` 2. 使用`find_package`让CMake使用Python解释器: ```cmake find_package(PythonInterp QUIET REQUIRED) ``` 3. 如果找到Python解释器,则可以在CMake中执行`_config_script`,并生成`print_info.c`文件: ```cmake execute_process( COMMAND ${PYTHON_EXECUTABLE} "-c" ${_config_script} ) ``` 4. 之后,定义可执行目标和依赖项,这与前一个示例相同。所以,得到的输出没有变化。 ## 工作原理 回顾一下对CMakeLists.txt的更改。 我们执行了一个Python脚本生成`print_info.c`。运行Python脚本前,首先检测Python解释器,并构造Python脚本。Python脚本导入`configure_file`函数,我们在`configurator.py`中定义了这个函数。为它提供用于读写的文件位置,并将其值作为键值对。 此示例展示了生成配置的另一种方法,将生成任务委托给外部脚本,可以将配置报告编译成可执行文件,甚至库目标。我们在前面的配置中认为的第一种方法更简洁,但是使用本示例中提供的方法,我们可以灵活地使用Python(或其他语言),实现任何在配置时间所需的步骤。使用当前方法,我们可以通过脚本的方式执行类似`cmake_host_system_information()`的操作。 但要记住,这种方法也有其局限性,它不能在构建时重新生成`print_info.c`的自动依赖项。下一个示例中,我们应对这个挑战。 ## 更多信息 我们可以使用`get_cmake_property(_vars VARIABLES)`来获得所有变量的列表,而不是显式地构造`vars_dict`(这感觉有点重复),并且可以遍历`_vars`的所有元素来访问它们的值: ```cmake get_cmake_property(_vars VARIABLES) foreach(_var IN ITEMS ${_vars}) message("variable ${_var} has the value ${${_var}}") endforeach() ``` 使用这种方法,可以隐式地构建`vars_dict`。但是,必须注意转义包含字符的值,例如:`;`, Python会将其解析为一条指令的末尾。