5.2 配置时运行自定义命令
NOTE:此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-05/recipe-02 中找到。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。
运行CMake生成构建系统,从而指定原生构建工具必须执行哪些命令,以及按照什么顺序执行。我们已经了解了CMake如何在配置时运行许多子任务,以便找到工作的编译器和必要的依赖项。本示例中,我们将讨论如何使用execute_process
命令在配置时运行定制化命令。
具体实施
第3章第3节中,我们已经展示了execute_process
查找Python模块NumPy时的用法。本例中,我们将使用execute_process
命令来确定,是否存在特定的Python模块(本例中为Python CFFI),如果存在,我们在进行版本确定:
对于这个简单的例子,不需要语言支持:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02 LANGUAGES NONE)
我们要求Python解释器执行一个简短的代码片段,因此,需要使用
find_package
来查找解释器:find_package(PythonInterp REQUIRED)
然后,调用
execute_process
来运行一个简短的Python代码段;下一节中,我们将更详细地讨论这个命令:# this is set as variable to prepare
# for abstraction using loops or functions
set(_module_name "cffi")
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
OUTPUT_VARIABLE _stdout
ERROR_VARIABLE _stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
然后,打印结果:
if(_stderr MATCHES "ModuleNotFoundError")
message(STATUS "Module ${_module_name} not found")
else()
message(STATUS "Found module ${_module_name} v${_stdout}")
endif()
下面是一个配置示例(假设Python CFFI包安装在相应的Python环境中):
$ mkdir -p build
$ cd build
$ cmake ..
-- Found PythonInterp: /home/user/cmake-cookbook/chapter-05/recipe-02/example/venv/bin/python (found version "3.6.5")
-- Found module cffi v1.11.5
工作原理
execute_process
命令将从当前正在执行的CMake进程中派生一个或多个子进程,从而提供了在配置项目时运行任意命令的方法。可以在一次调用execute_process
时执行多个命令。但请注意,每个命令的输出将通过管道传输到下一个命令中。该命令接受多个参数:
- WORKING_DIRECTORY,指定应该在哪个目录中执行命令。
- RESULT_VARIABLE将包含进程运行的结果。这要么是一个整数,表示执行成功,要么是一个带有错误条件的字符串。
- OUTPUT_VARIABLE和ERROR_VARIABLE将包含执行命令的标准输出和标准错误。由于命令的输出是通过管道传输的,因此只有最后一个命令的标准输出才会保存到OUTPUT_VARIABLE中。
- INPUT_FILE指定标准输入重定向的文件名
- OUTPUT_FILE指定标准输出重定向的文件名
- ERROR_FILE指定标准错误输出重定向的文件名
- 设置OUTPUT_QUIET和ERROR_QUIET后,CMake将静默地忽略标准输出和标准错误。
- 设置OUTPUT_STRIP_TRAILING_WHITESPACE,可以删除运行命令的标准输出中的任何尾随空格
- 设置ERROR_STRIP_TRAILING_WHITESPACE,可以删除运行命令的错误输出中的任何尾随空格。
有了这些了解这些参数,回到我们的例子当中:
set(_module_name "cffi")
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
OUTPUT_VARIABLE _stdout
ERROR_VARIABLE _stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
if(_stderr MATCHES "ModuleNotFoundError")
message(STATUS "Module ${_module_name} not found")
else()
message(STATUS "Found module ${_module_name} v${_stdout}")
endif()
该命令检查python -c "import cffi; print(cffi.__version__)"
的输出。如果没有找到模块,_stderr
将包含ModuleNotFoundError
,我们将在if语句中对其进行检查。本例中,我们将打印Module cffi not found
。如果导入成功,Python代码将打印模块的版本,该模块通过管道输入_stdout
,这样就可以打印如下内容:
message(STATUS "Found module ${_module_name} v${_stdout}")
更多信息
本例中,只打印了结果,但实际项目中,可以警告、中止配置,或者设置可以查询的变量,来切换某些配置选项。
代码示例会扩展到多个Python模块(如Cython),以避免代码重复。一种选择是使用foreach
循环模块名,另一种方法是将代码封装为函数或宏。我们将在第7章中讨论这些封装。
第9章中,我们将使用Python CFFI和Cython。现在的示例,可以作为有用的、可重用的代码片段,来检测这些包是否存在。