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 )。
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
变量中的目录,并用对应的值替换 @ [email protected]
,再将结果写入输出文件。这里的键值对,将由 CMake 提供。
准备工作
print_info.c.in
和 example.f90
与之前的示例相同。此外,我们将使用 Python 脚本 configurator.py
,它提供了一个函数:
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
字典的所有键,用对应的值替换模式 @ [email protected]
,并将结果写入输出文件(键值由 CMake 提供)。
具体实施
与前面的示例类似,我们需要配置一个模板文件,但这一次,使用 Python 脚本模拟 configure_file()
函数。我们保持 CMakeLists.txt 基本不变,并提供一组命令进行替换操作 configure_file(print_info.c.in print_info.c @ONLY)
,接下来将逐步介绍这些命令:
- 首先,构造一个变量
_config_script
,它将包含一个 Python 脚本,稍后我们将执行这个脚本: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) ")
- 使用
find_package
让 CMake 使用 Python 解释器:find_package(PythonInterp QUIET REQUIRED)
- 如果找到 Python 解释器,则可以在 CMake 中执行
_config_script
,并生成print_info.c
文件:execute_process( COMMAND ${PYTHON_EXECUTABLE} "-c" ${_config_script} )
- 之后,定义可执行目标和依赖项,这与前一个示例相同。所以,得到的输出没有变化。
工作原理
回顾一下对 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
的所有元素来访问它们的值:
get_cmake_property(_vars VARIABLES) foreach(_var IN ITEMS ${_vars}) message("variable ${_var} has the value ${${_var}}") endforeach()
使用这种方法,可以隐式地构建 vars_dict
。但是,必须注意转义包含字符的值,例如: ;
, Python 会将其解析为一条指令的末尾。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论