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),如果存在,我们在进行版本确定:

  1. 对于这个简单的例子,不需要语言支持:

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-02 LANGUAGES NONE)
  2. 我们要求Python解释器执行一个简短的代码片段,因此,需要使用find_package来查找解释器:

    1. find_package(PythonInterp REQUIRED)
  3. 然后,调用execute_process来运行一个简短的Python代码段;下一节中,我们将更详细地讨论这个命令:

    1. # this is set as variable to prepare
    2. # for abstraction using loops or functions
    3. set(_module_name "cffi")
    4. execute_process(
    5. COMMAND
    6. ${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
    7. OUTPUT_VARIABLE _stdout
    8. ERROR_VARIABLE _stderr
    9. OUTPUT_STRIP_TRAILING_WHITESPACE
    10. ERROR_STRIP_TRAILING_WHITESPACE
    11. )
  4. 然后,打印结果:

    1. if(_stderr MATCHES "ModuleNotFoundError")
    2. message(STATUS "Module ${_module_name} not found")
    3. else()
    4. message(STATUS "Found module ${_module_name} v${_stdout}")
    5. endif()
  5. 下面是一个配置示例(假设Python CFFI包安装在相应的Python环境中):

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. -- Found PythonInterp: /home/user/cmake-cookbook/chapter-05/recipe-02/example/venv/bin/python (found version "3.6.5")
    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,可以删除运行命令的错误输出中的任何尾随空格。

有了这些了解这些参数,回到我们的例子当中:

  1. set(_module_name "cffi")
  2. execute_process(
  3. COMMAND
  4. ${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
  5. OUTPUT_VARIABLE _stdout
  6. ERROR_VARIABLE _stderr
  7. OUTPUT_STRIP_TRAILING_WHITESPACE
  8. ERROR_STRIP_TRAILING_WHITESPACE
  9. )
  10. if(_stderr MATCHES "ModuleNotFoundError")
  11. message(STATUS "Module ${_module_name} not found")
  12. else()
  13. message(STATUS "Found module ${_module_name} v${_stdout}")
  14. endif()

该命令检查python -c "import cffi; print(cffi.__version__)"的输出。如果没有找到模块,_stderr将包含ModuleNotFoundError,我们将在if语句中对其进行检查。本例中,我们将打印Module cffi not found。如果导入成功,Python代码将打印模块的版本,该模块通过管道输入_stdout,这样就可以打印如下内容:

  1. message(STATUS "Found module ${_module_name} v${_stdout}")

更多信息

本例中,只打印了结果,但实际项目中,可以警告、中止配置,或者设置可以查询的变量,来切换某些配置选项。

代码示例会扩展到多个Python模块(如Cython),以避免代码重复。一种选择是使用foreach循环模块名,另一种方法是将代码封装为函数或宏。我们将在第7章中讨论这些封装。

第9章中,我们将使用Python CFFI和Cython。现在的示例,可以作为有用的、可重用的代码片段,来检测这些包是否存在。