runsisi's

technical notes

指定 Python 版本进行 CMake 工程构建

2019-08-28 runsisi#cpp#cmake#python

当系统上存在多个 Python 解释器时,CMake 的 FindPythonInterp 模块需要指定额外的参数才能找到正确的(或者说想要的) Python 解释器。

搜索路径

如果要使用非 /usr/local, /usr 目录下的 Python 解释器,则需要修改 cmake 的搜索路径 CMAKE_PREFIX_PATH(默认为空):

set(CMAKE_PREFIX_PATH "/opt/runsisi/python3")

或:

list(APPEND CMAKE_PREFIX_PATH "/opt/runsisi/python3")

CMAKE_PREFIX_PATH 指定的路径比默认的 /usr/local, /usr 具有更高的优先级,如果 CMAKE_PREFIX_PATH 指定的路径下没有搜索到,则会继续搜索默认路径。

可以为 CMAKE_PREFIX_PATH 增加多个搜索路径(以分号分隔,在前的具有更高的优先级):

set(CMAKE_PREFIX_PATH "/opt/runsisi/python3;/home/runsisi;/usr;/opt/rh/python")

或:

list(APPEND CMAKE_PREFIX_PATH "/home/runsisi;/opt/runsisi/python3;/opt/rh/python;/usr/local")

默认版本

默认情况下,FindPythonInterp 会调用 find_program 命令逐个搜索 python python3.7 .. python3.0 python2.7 .. python2.0 python1.6 python1.5,而实际上第一个名字 python 在 Ubuntu18.04 和 CentOS7.x 上都是指向 python2.7,因此 FindPythonInterp 总是找到 Python2.7。

可用通过定义 Python_ADDITIONAL_VERSIONS 指定优先搜索的版本,此时 FindPythonInterp 会先搜索 Python_ADDITIONAL_VERSIONS 指定的版本,然后才是上面提到的那一大串,pybind11 就是这样处理的,因此在同时装有 Python2 和 Python3 的环境上,会优先选择 Python3:

set(Python_ADDITIONAL_VERSIONS 3.7 3.6 3.5 3.4)

指定版本

显然上面的查找逻辑有点傻,FindPythonInterp 还支持一些额外的变量以控制它的行为:

$ cmake .. -DPythonInterp_FIND_VERSION=3 -DPythonInterp_FIND_VERSION_MAJOR=3
$ cmake .. -DPythonInterp_FIND_VERSION=3.6 -DPythonInterp_FIND_VERSION_MAJOR=3
$ cmake .. -DPythonInterp_FIND_VERSION=3.7.4 -DPythonInterp_FIND_VERSION_MAJOR=3
$ cmake .. -DPythonInterp_FIND_VERSION=2 -DPythonInterp_FIND_VERSION_MAJOR=2
$ cmake .. -DPythonInterp_FIND_VERSION=2.7 -DPythonInterp_FIND_VERSION_MAJOR=2
$ cmake .. -DPythonInterp_FIND_VERSION=2.7.15 -DPythonInterp_FIND_VERSION_MAJOR=2

注意:如果要像上面一样尝试使用不同的变量组合,在每次执行 cmake 之前需要删除上一次 cmake 执行生成的中间文件。

PythonInterp_FIND_VERSION 屏蔽 cmake 默认搜索逻辑,并指定要求的最小版本号;

PythonInterp_FIND_VERSION_MAJOR 指定 Python 主版本号,当前就 1, 2 或者 3,不过这里有 bug,如果指定一个 find_program 找不到的版本号,此时会继续找 python,从而总是会找到 Python2;

PythonInterp_FIND_VERSION_MAJOR 在 1 的情况下,会搜索 1.6 1.5 版本,在 2 的情况下,会搜索 2.7 .. 2.0 版本,而在 3 的情况下,会搜索 3.7 .. 3.0 版本;

如果在不同的路径下有两个版本号都满足要求,此时可以通过控制 CMAKE_PREFIX_PATH 的顺序选择合适的版本;

如果在相同路径下有两个版本号都满足要求,但想要指定一个更低的版本,则 FindPythonInterp 无法实现,因为它总是先从最大的次版本号开始往下搜索,不过此时可以通过打开 PythonInterp_FIND_VERSION_EXACT(默认关闭)来确认是否找了想要的版本:

$ cmake .. -DPythonInterp_FIND_VERSION=3.6.1 -DPythonInterp_FIND_VERSION_MAJOR=3 -DPythonInterp_FIND_VERSION_EXACT=ON

确认版本

cmake 生成的 CmakeCache.txt 文件中可以找到 Python 相关的定义:

//Path to a program.
PYTHON_EXECUTABLE:FILEPATH=/opt/runsisi/python3/bin/python3

//Path to a library.
PYTHON_LIBRARY:FILEPATH=/opt/runsisi/python3/lib/libpython3.6d.so

//No help, variable specified on the command line.
PythonInterp_FIND_VERSION:UNINITIALIZED=3.6

//No help, variable specified on the command line.
PythonInterp_FIND_VERSION_MAJOR:UNINITIALIZED=3

注意事项

CMAKE_PREFIX_PATH 可以通过环境变量导出或在 CMakeList.txt 文件或命令行上以变量的形式定义,如果通过环境变量导出,对于 FindPythonInterp 模块而言,在 PATH 上加上搜索路径与在 CMAKE_PREFIX_PATH 上加上搜索路径效果是一致的(因为内部的实现逻辑是查找 python 可执行程序并获取到相关信息),显然此时 Linux 下多个路径的分隔符就不能是分号了,而必须是冒号,而 Windows 下仍然是分号。

新版本的 CMake(3.12+)推荐使用新的 FindPython3FindPython2 或者 FindPython 模块,这里提到的变量定义并不适用于这些模块。