Chapter 3

在学习的过程中总结的关于python路径的一些问题

ubuntu中自带了python,我的ubuntu版本是20.04,python版本是3.8,但是不像自己编译安装一样,ubuntu把这个python安装到了很多的地方。

首先,默认是没有安装pip的,可以通过sudo apt install python3-pip来安装。

然后,再使用pip来安装python的一些模块的时候,这些模块被安装到的路径是由USER_SITE变量来决定的。可以通过python3 -m site来查看这些信息。比如我的就是

1
2
3
4
5
6
7
8
9
10
11
12
13
~ ❯ python3 -m site               
sys.path = [
'/home/lushuangning',
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/python3.8/lib-dynload',
'/home/lushuangning/.local/lib/python3.8/site-packages',
'/usr/local/lib/python3.8/dist-packages',
'/usr/lib/python3/dist-packages',
]
USER_BASE: '/home/lushuangning/.local' (exists)
USER_SITE: '/home/lushuangning/.local/lib/python3.8/site-packages' (exists)
ENABLE_USER_SITE: True

因此,如果使用pip install numpy的话,numpy包会被安装在/home/lushuangning/.local/lib/python3.8/site-packages下面,而不是安装在python自带的模块/usr/lib/python3/dist-packages下,想要更改可以参考ubuntu下更改pip安装的模块路径

另外,python的include目录在/usr/include/python3.8,这也是下面PYTHON_INCLUDE_DIR指向的目录,而PYTHON_LIBRARY指向的是/usr/lib/python3.8/config-3.8-x86_64-linux-gnu/libpython3.8.so

chpater 3.1 检测 Python 解释器

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES NONE)

find_package(PythonInterp REQUIRED)
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)

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

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

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

  • PYTHONINTERP_FOUND:是否找到解释器
  • PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
  • PYTHON_VERSION_STRING:Python解释器的完整版本信息
  • PYTHON_VERSION_MAJOR:Python解释器的主要版本号
  • PYTHON_VERSION_MINOR :Python解释器的次要版本号
  • PYTHON_VERSION_PATCH:Python解释器的补丁版本号

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

可以强制满足依赖关系:

1
find_package(PythonInterp REQUIRED)

TIPS:CMake有很多查找软件包的模块。我们建议在CMake在线文档中查询Find<package>.cmake模块,并在使用它们之前详细阅读它们的文档。find_package命令的文档可以参考 https://cmake.org/cmake/help/latest/command/find_package.html 另外一个方法是浏览 https://github.com/Kitware/CMake/tree/master/Modules 中的CMake模块源代码——它们记录了模块使用的变量,以及模块可以在CMakeLists.txt中使用的变量。

软件包没有安装在标准位置时,CMake无法正确定位它们。尤其是,对于Python解释器来说,使用Anaconda的用户可能创建了多个Python环境。用户可以使用CLI的-D参数传递相应的选项,告诉CMake查看特定的位置。Python解释器可以使用以下配置:

1
$ cmake -D PYTHON_EXECUTABLE=/media/lushuangning/mount_disk/software/anaconda3/envs/detectron2/bin/python ..

chapter 3.2 检测python库

勘误

首先是hello-embedded-python.c里,有一行代码是

1
2
PyRun_SimpleString("from time import time,ctime\n"
"print 'Today is',ctime(time())\n");

这样在python3下会报错的,应该修改为

1
2
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is',ctime(time()))\n");

个人猜测原书和翻译上可能用的是python2中的print方法,一时没有改过来。完整的代码是

hello-embedded-python.c

1
2
3
4
5
6
7
8
9
10
#include <Python.h>

int main(int argc, char *argv[]) {
Py_SetProgramName(argv[0]); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is',ctime(time()))\n");
Py_Finalize();
return 0;
}

其次,如果是用的Anaconda,那么直接cmake --build .也是会报错的,需要先指定PYTHONHOMEPYTHON_EXECUTABLE的路径,正确的流程是

1
2
3
4
mkdir build && cd build
cmake -D PYTHONHOME=/media/lushuangning/mount_disk/software/anaconda3/envs/detectron2 -D PYTHON_EXECUTABLE=/media/lushuangning/mount_disk/software/anaconda3/envs/detectron2/bin/python ..
cmake --build .
./hello-embedded-python

完整的CmakeLists.txt代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(recipe-02 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}
)

message(STATUS "PYTHONHOME is " ${PYTHONHOME})

注:find_package(PythonInterp REQUIRED)已在Cmake 3.12以后的版本中废弃,替代的方式为find_package(Python COMPONENTS Interpreter Development)

chapter 3.3 检测Python模块和包

本示例演示如何通过Cmake找到Python的Numpy模块

1
2
3
4
5
6
7
8
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import re, numpy; print(re.compile('/__init__.py.*').sub('',numpy.__file__))"
RESULT_VARIABLE _numpy_status
OUTPUT_VARIABLE _numpy_location
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)

新命令execute_process将作为子进程执行一个或多个命令。最后,子进程返回值将作为参数,传递给RESULT_VARIABLE,而管道标准输出和标准错误的内容将被保存到变量作为参数传递给OUTPUT_VARIABLEERROR_VARIABLEexecute_process可以执行任何操作,并使用它们的结果来推断系统配置。本例中,用它来确保NumPy可用,然后获得模块版本。

1
2
3
4
5
6
7
include(FindPackageHandleStandardArgs)

find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)

新命令find_package_handle_standard_args提供了用于处理与查找相关程序和库的标准工具。引用此命令时,可以正确的处理与版本相关的选项(REQUIREDEXACT),而无需更多的CMake代码。稍后将介绍QUIETCOMPONENTS选项。

所有必需的变量都设置为有效的文件路径(NumPy)后,发送到模块(NumPy_FOUND)。它还将版本保存在可传递的版本变量(_numpy_version)中并打印:

1
-- Found NumPy: /usr/lib/python3.6/site-packages/numpy (found version "1.14.3")
1
2
3
4
5
6
7
8
9
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
)

新命令add_custom_command,将在第5章详细讨论。

完整的执行命令及结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~/CodeHub/CMake/MyCmakeExample/chapter3/3.3/build main ?1                      
❯ cmake -D PYTHON_INCLUDE_DIR=/usr/include/python3.8 -D PYTHON_LIBRARY=/usr/lib/python3.8/config-3.8-x86_64-linux-gnu/libpython3.8.so ..
-- The CXX compiler identification is GNU 9.4.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonInterp: /usr/bin/python3.8 (found version "3.8.10")
-- Found PythonLibs: /usr/lib/python3.8/config-3.8-x86_64-linux-gnu/libpython3.8.so (found suitable exact version "3.8.10")
-- Found NumPy: /home/lushuangning/.local/lib/python3.8/site-packages/numpy (found version "1.22.3")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lushuangning/CodeHub/CMake/MyCmakeExample/chapter3/3.3/build

chapter 3.4 检测BLAS和LAPACK数学库

这篇示例需要提前安装gfortran,BLAS库,LAPACK库,安装命令为sudo apt install gfortran libblas-dev liblapack-dev liblapack-doc

我们首先明确,这个系列的博客是在学习CMake的用法,而不是Python、Fortran或者其他什么语言的用法,注意不要本末倒置。 这里我们把用到的文件从对应的github上下载下来即可,重点关注的是CMakeLists.txt文件,而这其中,构建过程的重点代码又如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
add_library(math "")

target_sources(math
PRIVATE
CxxBLAS.cpp
CxxLAPACK.cpp
)

target_include_directories(math
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(math
PUBLIC
${LAPACK_LIBRARIES}
)

add_executable(linear-algebra "")

target_sources(linear-algebra
PRIVATE
linear-algebra.cpp
)

target_link_libraries(linear-algebra
PRIVATE
math
)

理清楚这个顺序很重要。首先,add_library生成一个math的链接文件,该文件依赖的cpp文件由target_sources指定,包含的头文件由target_include_directories指定,需要用到的库由target_link_libraries指定。

然后,可执行文件linear-algebra需要的cpp文件由target_sources指定,需要用到的库即是上一步生成的链接文件math,由target_link_libraries指定。

chapter 3.5-3.6

chapter 3.5和3.6需要用到OpenMP,但是其安装较为繁琐,而且我目前的技术栈用不到它。还是那句话,我们当前的目的是学习CMake,这两节的CMakeLists.txt很简单,并且没有出现新的CMake命令,因此选择跳过这两节。

chapter 3.7-3.8

这两节里面出现了一个很重要的知识点,即find_package查找的两种方式。CMake的官方文档中也给出了这两种方式的说明:https://cmake.org/cmake/help/latest/command/find_package.html

为此,不惜直接将官网上的这两段重点复制过来


Module mode

In this mode, CMake searches for a file called Find<PackageName>.cmake, looking first in the locations listed in the CMAKE_MODULE_PATH, then among the Find Modules provided by the CMake installation. If the file is found, it is read and processed by CMake. It is responsible for finding the package, checking the version, and producing any needed messages. Some Find modules provide limited or no support for versioning; check the Find module’s documentation.

The Find<PackageName>.cmake file is not typically provided by the package itself. Rather, it is normally provided by something external to the package, such as the operating system, CMake itself, or even the project from which the find_package() command was called. Being externally provided, Find Modules tend to be heuristic in nature and are susceptible to becoming out-of-date. They typically search for certain libraries, files and other package artifacts.

Module mode is only supported by the basic command signature.

Config mode

In this mode, CMake searches for a file called <lowercasePackageName>-config.cmake or <PackageName>Config.cmake. It will also look for <lowercasePackageName>-config-version.cmake or <PackageName>ConfigVersion.cmake if version details were specified (see Config Mode Version Selection for an explanation of how these separate version files are used).

In config mode, the command can be given a list of names to search for as package names. The locations where CMake searches for the config and version files is considerably more complicated than for Module mode (see Config Mode Search Procedure).

The config and version files are typically installed as part of the package, so they tend to be more reliable than Find modules. They usually contain direct knowledge of the package contents, so no searching or heuristics are needed within the config or version files themselves.

Config mode is supported by both the basic and full command signatures.


找不到依赖是CMake报错的重灾区,了解CMake如何找到依赖是非常重要的,可参考深入理解CMake(3):find_package()的使用对这两种模式有一个大致的了解。