CMake 笔记 | [13] 检测python解释器和python库

警告
本文最后更新于 2024-01-28,文中内容可能已过时。

一、检测python解释器和python库

导言

python是一种非常流行的语言。许多项目用python编写的工具,从而将主程序和库打包在一起,或者在配置或构建过程中使用python脚本。这种情况下,确保运行时python解释器的依赖也需要得到满足。本篇将展示如何检测和使用python解释器。

除此之外,还有其他方法可以将解释语言(如python)与编译语言(如C或C++)组合在一起使用。一种是扩展python,通过编译成共享库的C或C++模块在这些类型上提供新类型和新功能。另一种是将python解释器嵌入到C或C++程序中。两种方法都需要下列条件:

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

二、检测python解释器

项目地址:

https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter3/01

CMakeLists.txt文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(python_interperter LANGUAGES NONE)

find_package(PythonInterp REQUIRED)

execute_process(
    COMMAND ${PYTHON_EXECUTABLE} "-c" "print('Hello, python interpreter!')"
    RESULT_VARIABLE RESULT_STATUS
    OUTPUT_VARIABLE RESULT_OUTPUT
    ERROR_QUIET
    OUTPUT_STRIP_TRAILING_WHITESPACE
)

# message(STATUS "RESULT_VARIABLE is: ${RESULT_STATUS}")
# message(STATUS "OUTPUT_VARIABLE is: ${RESULT_OUTPUT}")

include(CMakePrintHelpers)
cmake_print_variables(RESULT_STATUS RESULT_OUTPUT)
引用
1
find_package(PythonInterp REQUIRED)

使用find_package命令找到python解释器。

find_package是用于发现和设置包的CMake模块的命令。这些模块包含CMake命令,用于标识系统标准位置中的包。CMake模块文件称为Find<name>.cmake,当调用find_package(<name>)时,模块中的命令将会运行。

除了在系统上实际查找包模块之外,查找模块还会设置了一些有用的变量,反映实际找到了什么,也可以在自己的CMakeLists.txt中使用这些变量。对于python解释器,相关模块为FindPythonInterp.cmake附带的设置了一些CMake变量:

  • PYTHONINTERP_FOUND:是否找到解释器
  • PYTHON_EXECUTABLEpython解释器到可执行文件的路径
  • PYTHON_VERSION_STRINGpython解释器的完整版本信息
  • PYTHON_VERSION_MAJORpython解释器的主要版本号
  • PYTHON_VERSION_MINORpython解释器的次要版本号
  • PYTHON_VERSION_PATCHpython解释器的补丁版本号

可以强制CMake,查找特定版本的包。例如,要求python解释器的版本大于或等于2.7find_package(PythonInterp 2.7)

CMake有很多查找软件包的模块。建议在CMake在线文档中查询Find<package>.cmake模块,并在使用它们之前详细阅读它们的文档。find_package命令的文档可以参考 :

https://cmake.org/cmake/help/v3.5/command/find_ackage.html

引用
1
2
3
4
5
6
7
8
execute_process(
  COMMAND
      ${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
  RESULT_VARIABLE _status
  OUTPUT_VARIABLE _hello_world
  ERROR_QUIET
  OUTPUT_STRIP_TRAILING_WHITESPACE
  )

执行python命令并捕获它的输出和返回值。

输出

1
2
3
4
5
6
-- Found PythonInterp: /usr/bin/python3.8 (found version "3.8.10")
-- RESULT_VARIABLE is: 0
-- OUTPUT_VARIABLE is: Hello, python interpreter!
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter3/01/build

附录

软件包没有安装在标准位置时,CMake无法正确定位它们。用户可以使用-D参数传递相应的选项,告诉CMake查看特定的位置。python解释器可以使用以下配置:

1
$ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..

这将指定非标准/custom/location/python安装目录中的python可执行文件。

注意:每个包都是不同的,Find<package>.cmake模块试图提供统一的检测接口。当CMake无法找到模块包时,可以阅读相应检测模块的文档,以了解如何正确地使用CMake模块。可以在终端中直接浏览文档,可使用cmake --help-module FindPythonInterp查看。

除了检测包之外,我们还想提到一个便于打印变量的helper模块:

1
2
message(STATUS "RESULT_VARIABLE is: ${_status}")
message(STATUS "OUTPUT_VARIABLE is: ${_hello_world}")

使用以下工具进行调试:

1
2
include(CMakePrintHelpers)
cmake_print_variables(_status _hello_world)

将产生以下输出:

1
-- _status="0" ; _hello_world="Hello, world!"

三、检测python库

项目结构

1
2
3
.
├── CMakeLists.txt
└── hello_embedded_python.c

项目地址:

https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter3/02

CMakeLists.txt文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(link_python LANGUAGES C)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD_REQUIRED ON)

find_package(PythonInterp REQUIRED)

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

add_executable(hello_embedded_python hello_embedded_python.c)
target_include_directories(hello_embedded_python
  PRIVATE ${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(hello_embedded_python
  PRIVATE ${PYTHON_LIBRARIES}
)

为了确保可执行文件、头文件和库都有一个匹配的版本。这对于不同版本,可能在运行时导致崩溃。通过FindPythonInterp.cmake中定义的PYTHON_VERSION_MAJORPYTHON_VERSION_MINOR来实现:

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

可执行文件包含python.h头文件。因此,这个目标的include目录必须包含pythoninclude目录,可以通过PYTHON_INCLUDE_DIRS变量进行指定:

1
2
3
target_include_directories(hello_embedded_python
  PRIVATE ${PYTHON_INCLUDE_DIRS}
)

将可执行文件链接到python库,通过PYTHON_LIBRARIES变量访问:

1
2
3
target_link_libraries(hello_embedded_python
  PRIVATE ${PYTHON_LIBRARIES}
)

相关源码

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

附录

python不在标准安装目录中,如何确定python头文件和库的位置是正确的?

对于python解释器,可以通过-D选项传递PYTHON_LIBRARYPYTHON_INCLUDE_DIR选项来强制CMake查找特定的目录。这些选项指定了以下内容:

  • PYTHON_LIBRARY:指向python库的路径
  • PYTHON_INCLUDE_DIRpython.h所在的路径

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

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

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

1
find_package(Python COMPONENTS Interpreter Development REQUIRED)
Buy me a coffee~
支付宝
微信
0%