3.2 检测Python库

NOTE:此示例代码可以在 https://github.com/devcafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-02 中找到,有一个C示例。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

可以使用Python工具来分析和操作程序的输出。然而,还有更强大的方法可以将解释语言(如Python)与编译语言(如C或C++)组合在一起使用。一种是扩展Python,通过编译成共享库的C或C++模块在这些类型上提供新类型和新功能,这是第9章的主题。另一种是将Python解释器嵌入到C或C++程序中。两种方法都需要下列条件:

  • Python解释器的工作版本
  • Python头文件Python.h的可用性
  • Python运行时库libpython

三个组件所使用的Python版本必须相同。我们已经演示了如何找到Python解释器;本示例中,我们将展示另外两种方式。

准备工作

我们将一个简单的Python代码,嵌入到C程序中,可以在Python文档页面上找到。源文件称为hello-embedded-python.c:

  1. #include <Python.h>
  2. int main(int argc, char *argv[]) {
  3. Py_SetProgramName(argv[0]); /* optional but recommended */
  4. Py_Initialize();
  5. PyRun_SimpleString("from time import time,ctime\n"
  6. "print 'Today is',ctime(time())\n");
  7. Py_Finalize();
  8. return 0;
  9. }

此代码将在程序中初始化Python解释器的实例,并使用Python的time模块,打印日期。

NOTE:嵌入代码可以在Python文档页面的 https://docs.python.org/2/extending/embedding.htmlhttps://docs.python.org/3/extending/embedding.html 中找到。

具体实施

以下是CMakeLists.txt中的步骤:

  1. 包含CMake最低版本、项目名称和所需语言:

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-02 LANGUAGES C)
  2. 制使用C99标准,这不严格要求与Python链接,但有时你可能需要对Python进行连接:

    1. set(CMAKE_C_STANDARD 99)
    2. set(CMAKE_C_EXTENSIONS OFF)
    3. set(CMAKE_C_STANDARD_REQUIRED ON)
  3. 找到Python解释器。这是一个REQUIRED依赖:

    1. find_package(PythonInterp REQUIRED)
  4. 找到Python头文件和库的模块,称为FindPythonLibs.cmake:

    1. find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
  5. 使用hello-embedded-python.c源文件,添加一个可执行目标:

    1. add_executable(hello-embedded-python hello-embedded-python.c)
  6. 可执行文件包含Python.h头文件。因此,这个目标的include目录必须包含Python的include目录,可以通过PYTHON_INCLUDE_DIRS变量进行指定:

    1. target_include_directories(hello-embedded-python
    2. PRIVATE
    3. ${PYTHON_INCLUDE_DIRS}
    4. )
  7. 最后,将可执行文件链接到Python库,通过PYTHON_LIBRARIES变量访问:

    1. target_link_libraries(hello-embedded-python
    2. PRIVATE
    3. ${PYTHON_LIBRARIES}
    4. )
  8. 现在,进行构建:

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. ...
    5. -- Found PythonInterp: /usr/bin/python (found version "3.6.5")
    6. -- Found PythonLibs: /usr/lib/libpython3.6m.so (found suitable exact version "3.6.5")
  9. 最后,执行构建,并运行可执行文件:

    1. $ cmake --build .
    2. $ ./hello-embedded-python
    3. Today is Thu Jun 7 22:26:02 2018

工作原理

FindPythonLibs.cmake模块将查找Python头文件和库的标准位置。由于,我们的项目需要这些依赖项,如果没有找到这些依赖项,将停止配置,并报出错误。

注意,我们显式地要求CMake检测安装的Python可执行文件。这是为了确保可执行文件、头文件和库都有一个匹配的版本。这对于不同版本,可能在运行时导致崩溃。我们通过FindPythonInterp.cmake中定义的PYTHON_VERSION_MAJORPYTHON_VERSION_MINOR来实现:

  1. find_package(PythonInterp REQUIRED)
  2. find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)

使用EXACT关键字,限制CMake检测特定的版本,在本例中是匹配的相应Python版本的包括文件和库。我们可以使用PYTHON_VERSION_STRING变量,进行更接近的匹配:

  1. find_package(PythonInterp REQUIRED)
  2. find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED)

更多信息

当Python不在标准安装目录中,我们如何确定Python头文件和库的位置是正确的?对于Python解释器,可以通过CLI的-D选项传递PYTHON_LIBRARYPYTHON_INCLUDE_DIR选项来强制CMake查找特定的目录。这些选项指定了以下内容:

  • PYTHON_LIBRARY:指向Python库的路径
  • PYTHON_INCLUDE_DIR:Python.h所在的路径

这样,就能获得所需的Python版本。

TIPS:有时需要将-D PYTHON_EXECUTABLE-D PYTHON_LIBRARY-D PYTHON_INCLUDE_DIR传递给CMake CLI,以便找到及定位相应的版本的组件。

要将Python解释器及其开发组件匹配为完全相同的版本可能非常困难,对于那些将它们安装在非标准位置或系统上安装了多个版本的情况尤其如此。CMake 3.12版本中增加了新的Python检测模块,旨在解决这个棘手的问题。我们CMakeLists.txt的检测部分也将简化为:

find_package(Python COMPONENTS Interpreter Development REQUIRED)

我们建议您阅读新模块的文档,地址是: https://cmake.org/cmake/help/v3.12/module/FindPython.html