目录
我们承担ROS,FastDDS等通信中间件,C++,cmake等技术的项目开发和专业指导和培训,有10年+相关工作经验,质量有保证,如有需要请私信联系。
本文主要记录通过CMake中的find家族命令加载外部依赖,通过execute_process
执行外部程序,以及相关CMake示例代码,了解CMake时怎样加载和运行外部对象。
find族命令
find_file
:在相应路径下查找命名文件
find_library
:查找一个库文件
find_package
:从外部项目查找和加载设置
find_path
:查找包含指定文件的目录
find_program
:找到一个可执行程序
find_package
用于发现和设置包的CMake模块的命令。这些模块包含CMake命令,用于标识系统标准位置中的包。CMake模块文件称为Find<name>.cmake
,当调用find_package(<name>)
时,模块中的命令将会运行。
基本签名
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
<PackageName>_FOUND
会被设置用来表示该package有没有找到。
- 可选参数说明:
QUIET
:不会有信息输出,如果没有加上关键字参数REQUIRED,包没有被发现,也不会有信息输出REQUIRED
:包没有查找到,会输出一条错误信息并停止编译version
:版本参数要求查找到的包版本要符合EXACT
:要求版本必须匹配。COMPONENTS
:要求的组件列表可以在该参数之后指定。OPTIONAL_COMPONENTS
:额外可选的组件可以在该参数之后指定TODO
- 这个命令分为Module模式和Config模式。如果没有给关键字参数MODULE,module没有被发现将自动回退到Config模式。
- MODULE模式下,cmake会查找
Find<PackageName>.cmake
,优先在CMAKE_MODULE_PATH
路径中查找。查找模块还会设置一些有用的变量,反映实际找到了什么,可以在CMakeLists.txt中使用这些变量,这些变量为:<PackageName>_FOUND
是否发现该库<PackageName>_INCLUDE_DIR
or<PackageName>_INCLUDES
头文件目录<PackageName>_LIBRARY
或<PackageName>_LIBRARIES
库文件
- 如果没有指定可选的关键字参数MODULE,cmake将首先使用Module模式,如果没找到才会使用Config模式去查找。可以通过将变量
CMAKE_FIND_PACKAGE_PREFER_CONFIG
设置为TRUE来直接使用Config模式。在Config模式下,CMake会搜索名为<package>Config.cmake
和<package>-config.cmake
文件
- MODULE模式下,cmake会查找
查找路径
cmake构造了以下查找路径:
<prefix>/ (W)
<prefix>/(cmake|CMake)/ (W)
<prefix>/<name>*/ (W)
<prefix>/<name>*/(cmake|CMake)/ (W)
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (W/U)
find_program
find_program (
<VAR>
name | NAMES name1 [name2 ...] [NAMES_PER_DIR] # 指定一个或多个可能的名字
[HINTS path1 [path2 ... ENV var]]
[PATHS path1 [path2 ... ENV var]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[DOC "cache documentation string"]
[REQUIRED]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)
发现程序的命令。VAR是一个缓存变量,用于保存查找到的结果
execute_process
execute_process(COMMAND <cmd1> [<arguments>] # 要启用的子进程
[COMMAND <cmd2> [<arguments>]]...
[WORKING_DIRECTORY <directory>] # 子进程的目录
[TIMEOUT <seconds>] # 子进程超时时长,超时后没结束的子进程将被终止,RESULT_VARIABLE会被设置为"timeout"
[RESULT_VARIABLE <variable>] # 子进程的执行结果,是一个整数的错误码或者描述错误信息的字符串。子进程执行成功返回0?
[RESULTS_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>] # 存储标准输出的内容
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ECHO <where>]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <name>]
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
[COMMAND_ERROR_IS_FATAL <ANY|LAST>])
将从当前正在执行的CMake进程中派生一个或多个子进程,从而提供了在配置项目时运行任意命令的方法。可以在一次调用execute_process
时执行多个命令,但注意,每个命令的输出将通过管道传输到下一个命令中。
检测外部程序
CMake官方为用户定义了很多外部依赖包。
检测并执行外部程序
- 查找python并使用python解释器执行
find_package(Python3 REQUIRED)
execute_process(
COMMAND ${Python3_EXECUTABLE} "-c" "print('hello, world')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
include(CMakePrintHelpers)
cmake_print_variables(_status _hello_world)
输出如下:
- 查找python库并在C代码中调用python
在我的Ubuntu中路径为/usr/share/cmake-3.22/Modules/FindPythonLibs.cmake。以下示例在C++程序中调用python库函数链接编译,用python解释器执行一条打印:
find_package(Python3 COMPONENTS Interpreter Development)
add_executable(hello-embedded-python hello-embedded-python.cpp)
target_include_directories(
hello-embedded-python
PRIVATE
${Python3_INCLUDE_DIRS}
)
target_link_libraries(
hello-embedded-python
PRIVATE
${Python3_LIBRARIES}
)
C代码为
#include <Python.h>
int main(int argc, char_t *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;
}
通过 find_package(<name>)
命令查找包,除了查了包之外还会设置一些有用的变量,直接在CMakeLists.txt中使用这些变量。
软件包没有安装在标准位置时,CMake无法正确定位,用户可以使用-D
参数传递相应的选项,告诉CMake查看特定的位置。如:cmake -D Python3_EXECUTABLE=/custom/location/python
FetchContent
FetchContent是CMake中的一个模块,它提供了一种在构建过程中下载和填充项目源代码的方法。FetchContent模块在CMake 3.11版本中首次引入,它的主要应用场景是下载和使用项目依赖项。
FetchContent的工作原理是,你可以在CMake脚本中定义需要下载的内容的详细信息(包括URL,GIT仓库,文件哈希等),然后FetchContent会在构建过程中下载这些文件,并将它们加入到你的项目源码中。
函数
FetchContent主要有四个函数:
FetchContent_Declare
:用于声明要下载内容的详细信息。
FetchContent_GetProperties
:用于查询已下载内容的详细信息。
FetchContent_Populate
:用于实际下载和填充内容。这通常在配置期间完成,这样生成的构建文件可以包含下载的内容。
FetchContent_MakeAvailable
:这是CMake 3.14版本中添加的新功能,它同时执行FetchContent_GetProperties
和FetchContent_Populate
。
构建时下载googletest为例
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG origin/main
)
FetchContent_MakeAvailable(googletest)
在链接时可以直接使用gtest_main等库了
add_executable(${TARGET_NAME})
target_link_libraries(
${TARGET_NAME}
PUBLIC
gtest_main
)
具体使用googletest构建UT的示例参考CMake之创建和运行测试
ExternalProject
官方文档
这个模块可以在当前系统中下载,配置,构建并安装其他的CMake项目或者其他构建系统的项目。
示例:
include(ExternalProject)
ExternalProject_Add(foobar
GIT_REPOSITORY git@github.com:FooCo/FooBar.git
GIT_TAG origin/release/1.2.3
)
ExternalProject_Add 和 FetchContent比较
ExternalProject_Add
和 FetchContent
都是 CMake 中用于处理外部依赖的模块,但是它们的工作方式有一些不同。
ExternalProject_Add
:该命令允许在构建过程中下载,配置,构建并安装外部项目。它在主构建过程的一个固定点(经常是构建过程的一开始)下载和安装外部项目,然后在主项目中使用find_package或者其他方式来查找和使用这些已经安装的外部项目。
FetchContent
:这是一个相对较新的命令,首次出现在 CMake 3.11 中,它允许在主构建系统的配置阶段下载外部内容。然后可以使用add_subdirectory命令将这些下载的源代码作为主项目的一部分进行编译和链接。这意味着不需要预先安装外部依赖,也不需要使用find_package或其他方式来查找这些依赖。
总的来说,FetchContent
提供了一种更简洁、直接的方式来处理外部依赖,它可以使项目结构更简单,构建过程更快。而 ExternalProject_Add
提供了更多的灵活性,例如可以处理更复杂的构建和安装过程。可以根据项目的具体需求来选择使用哪一个。