Learning CMake Cookbook Chapter03 Part01
本部分与python相关~
检测python解释器
这部分直接在CmakeLists.txt中进行python代码的嵌入,没有C/C++相关的源码,如下所示:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES NONE)
# 这个命令要我们找到python解释器, 即:PythonInterp
find_package(PythonInterp REQUIRED)
# PYTHONINTERP_FOUND:是否找到解释器
# PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
# PYTHON_VERSION_STRING:Python解释器的完整版本信息
# PYTHON_VERSION_MAJOR:Python解释器的主要版本号
# PYTHON_VERSION_MINOR:Python解释器的次要版本号
# PYTHON_VERSION_PATCH:Python解释器的补丁版本号
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}")
include(CMakePrintHelpers)
cmake_print_variables(_status _hello_world)
在写这个代码块的时候难免想到一些问题:
- find_package() 这个命令是通过调用 Find<pack_name>.cmake这个文件来间接实现的,那么这个文件到底在哪(我们可以先不管它做了什么)?
- 以上的文中提到:如果不是“系统标准位置”的包,直接使用find_package()会导致无法找到其目录。那么对于我们自定义安装的包,如何使用find_package()来找到它呢?
- 在这个文件中,我们通过嵌入一条python语句来实现其对应语句的功能,那么随之而来想到的就是:我们可不可以通过这种方式来内嵌一个完整的python文件来执行呢?
Find<pack_name>.cmake文件在哪?
如果cmake是默认安装,则其对应的Find<pack_name>.cmake文件都会被保存在 /usr/share/cmake-3.16/Modules 路径下,或是 usr/local/share/cmake-3.16/Modules 路径下。 另外,执行find_package()命令会优先从项目根目录(与CMakeList.txt同级目录)下的Module文件夹(如果有的话)中寻找对应的Find<pack_name>.cmake并执行,其次才会从刚刚提到的默认目录下进行搜索并执行~。
如何使用find_package()命令找不在“系统标准位置”的包?
我们知道,其实find_package()对应执行的Find<pack_name>.cmake是初始化了一些变量。比如以上提到的:
# PYTHONINTERP_FOUND:是否找到解释器
# PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
# PYTHON_VERSION_STRING:Python解释器的完整版本信息
# PYTHON_VERSION_MAJOR:Python解释器的主要版本号
# PYTHON_VERSION_MINOR:Python解释器的次要版本号
# PYTHON_VERSION_PATCH:Python解释器的补丁版本号
我们可以通过直接给cmake传入这些参数来解决问题,从而不必使用find_package()命令,如下指定python解释器的位置(其实就是python命令可执行文件的位置):
$ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..
但是这样显然不是我想要的答案,因为我们相当于绕开了find_package()指令,并没有执行Find<pack_name>.cmake这个文件。我想得到的解决是,如何引导Find<pack_name>.cmake到对应的自定义库安装位置,从而让文件自动设置这些变量???(pending:2022-04-26)
嵌入执行一个python脚本文件而不是一条单一的python语句
比较简单,修改上述对应部分如下:
set(Py_File_Path "/home/ctrtemp/Documents/VS_Code_Prj/Learning/Learning_Cmake/Own_Learning/chapter-03/recipe-01/example/test.py")
execute_process(
COMMAND
${PYTHON_EXECUTABLE} ${Py_File_Path}
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
在build文件夹下执行cmake … 将会得到和以上相同的结果
但我仍然有问题:这其实并不是在C++中内嵌了python代码,而是在代码编译阶段执行了python脚本文件,和我预想的“使用C++调用执行python”还有一定的偏差!(Pending:2022-04-26)
检测python库
我们先来看一下源文件的做法:
#include <Python.h>
int main(int argc, char *argv[]) {
Py_SetProgramName(argv[0]);
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
Py_Finalize();
return 0;
}
其大概意思就是通过给PyRun_SimpleString()函数传一个字符串格式的python语句,并将其结果输出。注意一个点:这里的Python.h在编辑器(我的是VScode)中是会直接报错的。警告说找不到这个文件,但是执行cmak语句却可以生成对应的可执行文件,这是如何做到的呢?
其实很多工程文件都是如此!以下几点必须明确:
- 在代码编辑器中,是否给出这种警报一般是编辑器检索系统环境变量中的路径,如果头文件没有出现在环境变量指定的路径下,则会报错说找不到(一般对于C/C++来说,头文件对应的环境变量大多为:C_Path / C_INCLUDE_PATH / CPLUS_INCLUDE_PATH)
- 一般工程需要很多的依赖文件,包括头文件以及库文件,如果用户想部署工程,并通过当场修改环境变量以适应工程的话是非常繁琐的,并且将会使得整个系统变量变得繁杂(就因为一个工程做很大修改既麻烦又造成了污染!)
- 所以一般是通过编辑CMakeLists.txt的方式,动态地为工程指定其对应的依赖文件的路径,相当于为工程进行“定制”。
于是有以下的CMakeList.txt部分代码:
cmake_minimum_required(VERSION 3.5 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)
message(STATUS "${PYTHON_VERSION_MAJOR}")
message(STATUS "${PYTHON_VERSION_MINOR}")
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
add_executable(hello-embedded-python hello-embedded-python.c)
message(STATUS "${PYTHON_INCLUDE_DIRS}")
message(STATUS "${PYTHON_LIBRARIES}")
target_include_directories(hello-embedded-python
PRIVATE
${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(hello-embedded-python
PRIVATE
${PYTHON_LIBRARIES}
)
以上代码尤其注意关于message对应的语句,输出依赖文件对应的位置,显然,这些变量是被find_package()找到并设置的。同样的,在头文件以及库文件对应的那几个环境变量中,没有包含其对应的路径,这也正是之前在代码编辑器中报错的原因。也正因为是我们通过CmakeLists.txt设置了对应的变量,才使得我们可以在编译的过程中索引到对应依赖文件,成功生成并执行可执行文件。
检测python模块和包
之前提到的是在CMakeLists.txt中嵌入了python语句或者文件并执行,但并没有满足我们的实际需求:我们实际想做的是在C/C++中直接内嵌python代码或用C/C++调用python脚本文件执行。这一节就是解决我们之前的需求的,在C/C++中内嵌执行python。
首先我们来看C++代码部分,基本的代码已经做了相对应的注释说明:
#include <python3.8/Python.h>
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) {
fprintf(stderr, "Usage: pure-embedding pythonfile funcname [args]\n");
return 1;
}
Py_Initialize();
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append(\".\")");
pName = PyUnicode_DecodeFSDefault(argv[1]);
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyLong_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
} else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr, "Call failed\n");
return 1;
}
} else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
} else {
PyErr_Print();
fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
return 1;
}
Py_Finalize();
return 0;
}
执行时输入的参数如下:
$ ./pure-embedding use_numpy print_ones 2 3
之后来看CMake_Lists.txt文件:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-03 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
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
)
if(NOT _numpy_status)
set(NumPy ${_numpy_location} CACHE STRING "Location of NumPy")
endif()
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import numpy; print(numpy.__version__)"
OUTPUT_VARIABLE _numpy_version
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "curren numpy version = ${_numpy_version}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)
add_executable(pure-embedding "")
target_sources(pure-embedding
PRIVATE
Py${PYTHON_VERSION_MAJOR}-pure-embedding.cpp
)
target_include_directories(pure-embedding
PRIVATE
${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(pure-embedding
PRIVATE
${PYTHON_LIBRARIES}
)
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
)
# make sure building pure-embedding triggers the above
# custom command
target_sources(pure-embedding
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
)
首先这一部分的重点在于我们要理解add_custom_command()这个语句:
- 语句允许我们在代码中执行某些用户自定义的命令,相当于直接在命令行执行语句块中的一些命令
- 关于参数方面:COMMAND后面加一个所要输出的命令
- 之后的参数比较繁杂这里暂时pending
(Pending 2022-04-26) 感觉这里要较长期间的pending了,遇到一个真正需要C/C++嵌入python代码的应用时,用到之后才能更快更好的进行理解提高。
|