> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/5.0-chinese/5.2-chinese.md).

# 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. 对于这个简单的例子，不需要语言支持:

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

   ```
   find_package(PythonInterp REQUIRED)
   ```
3. 然后，调用`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
     )
   ```
4. 然后，打印结果：

   ```
   if(_stderr MATCHES "ModuleNotFoundError")
       message(STATUS "Module ${_module_name} not found")
   else()
       message(STATUS "Found module ${_module_name} v${_stdout}")
   endif()
   ```
5. 下面是一个配置示例(假设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。现在的示例，可以作为有用的、可重用的代码片段，来检测这些包是否存在。
