【CMake】检测外部库和程序
find_package
是用于发现和设置包的CMake模块的命令。这些模块包含CMake命令,用于标识系统标准位置中的包。CMake模块文件称为Find<name>.cmake
,当调用find_package(<name>)
时,模块中的命令将会运行。
除了在系统上实际查找包模块之外,查找模块还会设置了一些有用的变量,反映实际找到了什么,也可以在自己的CMakeLists.txt
中使用这些变量。
对于Python解释器,相关模块为FindPythonInterp.cmake(位于<CMake安装路径>/share/cmake-<版本号>/Modules/)
附带的设置了一些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)。
如果在查找位置中没有找到适合Python解释器的可执行文件,CMake将中止配置。
1、Cmake检测python解释器:
1 cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 2 project(recipe-01 LANGUAGES NONE) 3 4 find_package(PythonInterp REQUIRED) 5 6 execute_process( 7 COMMAND 8 ${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')" 9 RESULT_VARIABLE _status 10 OUTPUT_VARIABLE _hello_world 11 ERROR_QUIET 12 OUTPUT_STRIP_TRAILING_WHITESPACE 13 ) 14 15 message(STATUS "RESULT_VARIABLE is: ${_status}") 16 message(STATUS "OUTPUT_VARIABLE is: ${_hello_world}")
2、CMake检测python库:
C++代码:
1 #include <Python.h> 2 #include <sstream> 3 4 int main(int argc, char *argv[]) 5 { 6 7 std::wstringstream wss; 8 wss << argv[0]; 9 Py_SetProgramName(wss.str().c_str()); 10 Py_Initialize(); 11 PyRun_SimpleString("from time import time,ctime\n" 12 "print('Today is',ctime(time()))\n"); 13 Py_Finalize(); 14 return 0; 15 }
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 2 project(CmakeTest LANGUAGES CXX) 3 4 find_package(PythonInterp REQUIRED) 5 find_package(Python COMPONENTS Interpreter Development REQUIRED) 6 7 add_executable(${PROJECT_NAME} main.cpp) 8 9 message(STATUS "Python Include: ${Python_INCLUDE_DIRS}") 10 target_include_directories(${PROJECT_NAME} 11 PRIVATE 12 ${Python_INCLUDE_DIRS} 13 ) 14 15 target_link_libraries(${PROJECT_NAME} 16 PRIVATE 17 ${Python_LIBRARIES} 18 )
3、CMake检测python的模块和包
python代码:
1 # @file use_numpy.py 2 import numpy as np 3 4 # 构建rows * cols的矩阵 5 def print_ones(rows, cols): 6 A = np.ones(shape=(rows, cols), dtype=float) 7 print(A) 8 # we return the number of elements to verify 9 # that the C++ code is able to receive return values 10 num_elements = rows*cols 11 return(num_elements)
C++代码:(参考:https://docs.python.org/2/extending/embedding.html#pure-embedded)
1 /** 2 * @file main.cpp 3 */ 4 #include <Python.h> 5 int main(int argc, char *argv[]) 6 { 7 PyObject *pName, *pModule, *pDict, *pFunc; 8 PyObject *pArgs, *pValue; 9 int i; 10 if (argc < 3) 11 { 12 fprintf(stderr, "Usage: %s pythonfile funcname [args]\n", argv[0]); 13 return 1; 14 } 15 Py_Initialize(); 16 PyRun_SimpleString("import sys"); 17 PyRun_SimpleString("sys.path.append(\".\")"); 18 pName = PyUnicode_DecodeFSDefault(argv[1]); 19 /* Error checking of pName left out */ 20 pModule = PyImport_Import(pName); 21 Py_DECREF(pName); 22 if (pModule != NULL) 23 { 24 pFunc = PyObject_GetAttrString(pModule, argv[2]); 25 /* pFunc is a new reference */ 26 if (pFunc && PyCallable_Check(pFunc)) 27 { 28 pArgs = PyTuple_New(argc - 3); 29 for (i = 0; i < argc - 3; ++i) 30 { 31 pValue = PyLong_FromLong(atoi(argv[i + 3])); 32 if (!pValue) 33 { 34 Py_DECREF(pArgs); 35 Py_DECREF(pModule); 36 fprintf(stderr, "Cannot convert argument\n"); 37 return 1; 38 } 39 /* pValue reference stolen here: */ 40 PyTuple_SetItem(pArgs, i, pValue); 41 } 42 pValue = PyObject_CallObject(pFunc, pArgs); 43 Py_DECREF(pArgs); 44 if (pValue != NULL) 45 { 46 printf("Result of call: %ld\n", PyLong_AsLong(pValue)); 47 Py_DECREF(pValue); 48 } 49 else 50 { 51 Py_DECREF(pFunc); 52 Py_DECREF(pModule); 53 PyErr_Print(); 54 fprintf(stderr, "Call failed\n"); 55 return 1; 56 } 57 } 58 else 59 { 60 if (PyErr_Occurred()) 61 PyErr_Print(); 62 fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]); 63 } 64 Py_XDECREF(pFunc); 65 Py_DECREF(pModule); 66 } 67 else 68 { 69 PyErr_Print(); 70 fprintf(stderr, "Failed to load \"%s\"\n", argv[1]); 71 return 1; 72 } 73 Py_Finalize(); 74 return 0; 75 }
CMakeLists.txt内容:
1 cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 2 project(CmakeTest LANGUAGES CXX) 3 4 set(CMAKE_CXX_STANDARD 11) 5 set(CMAKE_CXX_EXTENSIONS OFF) 6 set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 8 find_package(PythonInterp REQUIRED) 9 find_package(Python COMPONENTS Interpreter Development REQUIRED) 10 11 message(STATUS "Python Include: ${Python_INCLUDE_DIRS}") 12 13 # execute_process将作为通过子进程执行一个或多个命令。最后,子进程返回值将保存到变量作为参数,传递给RESULT_VARIABLE, 14 # 而管道标准输出和标准错误的内容将被保存到变量作为参数传递给OUTPUT_VARIABLE和ERROR_VARIABLE。 15 # execute_process可以执行任何操作,并使用它们的结果来推断系统配置。本例中,用它来确保NumPy可用,然后获得模块版本。 16 execute_process( 17 COMMAND 18 ${PYTHON_EXECUTABLE} "-c" "import re, numpy; print(re.compile('/__init__.py.*').sub('',numpy.__file__))" 19 RESULT_VARIABLE _numpy_status 20 OUTPUT_VARIABLE _numpy_location 21 ERROR_QUIET 22 OUTPUT_STRIP_TRAILING_WHITESPACE 23 ) 24 25 if(NOT _numpy_status) 26 set(NumPy ${_numpy_location} CACHE STRING "Location of NumPy") 27 endif() 28 29 execute_process( 30 COMMAND 31 ${PYTHON_EXECUTABLE} "-c" "import numpy; print(numpy.__version__)" 32 OUTPUT_VARIABLE _numpy_version 33 ERROR_QUIET 34 OUTPUT_STRIP_TRAILING_WHITESPACE 35 ) 36 37 # find_package_handle_standard_args提供了,用于处理与查找相关程序和库的标准工具。 38 # 所有必需的变量都设置为有效的文件路径(NumPy)后,发送到模块(NumPy_FOUND)。 39 # 它还将版本保存在可传递的版本变量(_numpy_version)中并打印: 40 # 目前的示例中,没有进一步使用这些变量。如果返回NumPy_FOUND为FALSE,则停止配置。 41 include(FindPackageHandleStandardArgs) 42 find_package_handle_standard_args(NumPy 43 FOUND_VAR NumPy_FOUND 44 REQUIRED_VARS NumPy 45 VERSION_VAR _numpy_version 46 ) 47 48 add_executable(${PROJECT_NAME} "") 49 50 target_sources(${PROJECT_NAME} 51 PRIVATE 52 main.cpp 53 ) 54 target_include_directories(${PROJECT_NAME} 55 PRIVATE 56 ${Python_INCLUDE_DIRS} 57 ) 58 target_link_libraries(${PROJECT_NAME} 59 PRIVATE 60 ${Python_LIBRARIES} 61 ) 62 63 # 将use_numpy.py复制到build目录 64 # 选择使用add_custom_command,而不是file(COPY ...)来确保文件在每次更改时都会被复制,而不仅仅是第一次运行配置时 65 add_custom_command( 66 OUTPUT 67 ${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py 68 COMMAND 69 ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py 70 ${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py 71 DEPENDS 72 ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py 73 ) 74 75 # target_sources命令,它将依赖项添加到${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py;这样做是为了确保构建目标,能够触发之前的命令。 76 target_sources(${PROJECT_NAME} 77 PRIVATE 78 ${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py 79 )