Basic 08-third-party-library
# 介绍
几乎所有的 CPP 工程都会用到第三方库,头文件和程序 (third party libraries, head files and program),CMake 提供了函数 find_package() 从 CMAKE_MODULE_PATH 中查找第三方库对应的 CMake 模块。
其中在 Linux 中默认的搜索路径为 /usr/share/cmake**/Modules ,其中在我的 Ubuntu 20.04 上的路径为 /usr/share/cmake-3.16/Modules ,在该文件夹中 CMake 模块一般命名为 FindXXX.cmake 。
比如在我的系统上,使用 ls | grep Find | wc -l 命令可以发现有 179 个第三方库。
本例的目录🌲:
├── CMakeLists.txt
├── main.cpp
2
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
# Set the project name
project (third_party_include)
# find a boost install with the libraries filesystem and system
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
# check if boost was found
if(Boost_FOUND)
message ("boost found")
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
# Add an executable
add_executable(third_party_include main.cpp)
# link against the boost libraries
target_link_libraries( third_party_include
PRIVATE
Boost::filesystem
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 运行本例子前,需要安装 Boost libraries
sudo apt-get install libboost-all-dev
# 概念解释
# 查找一个第三方库模块
find_package() 函数将会从 CMAKE_MODULE_PATH 中查找命名为 FindXXX.cmake 的 CMake 模块。
确定的 find_package() 函数格式将取决于你要搜索的库,这里以搜索 boost library 为例:
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
- Boost: 需要搜索的库的名字,这部分将被用于确定对应的 CMake 模块的格式
FindBoost.cmake - 1.46.1: 查找的 Boost 库的最低版本
- REQUIRED: 告诉 CMake 这个模块是必要的,如果找不到就告知用户并返回失败
- COMPONENTS: 查看这个库中是否有这两个组成部分,如果没有并且使用了
REQUIRED,那就应该告知用户并返回搜索失败的信息。
# 检查这个库是否被找到
当调用 find_package() 函数找到要找的库时,几乎所有的第三方库的 CMake 都会设置一个变量 XXX_FOUND,用来表明这个库是已经被找到。
在这个例子中这个变量为 Boost_FOUND,可以在 CMakeLists.txt 中加入如下的判断语句:
if(Boost_FOUND)
message ("boost found")
include_directories(${Boost_INCLUDE_DIRS})
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
2
3
4
5
6
# 设置其他变量
和变量 XXX_FOUND 的设置类似,当一个库被找到后,库对应的 CMake 模块会导出一些和库有关的变量。比如在这个例子中被会设置 boost 的头文件位置到变量 Boost_INCLUDE_DIRS 中。
# 导出目标
当我们使用 find_package() 找到对应库的 CMake Module ( FindXXX.cmake ) 后,就可以将 FindXXX.cmake 模块中导出的只读目标别名 (read-only target name) 在 CMakeLists.txt 中进行使用。
为了包含 boost 库中的子库 boost.filesystem 到 CMakeLists.txt 文件,可以使用:
target_link_libraries( imported_targets
PRIVATE
Boost::filesystem
)
2
3
4
这将会自动链接 Boost::filesystem and Boost::system libraries,并且将 Boost 头文件目录也放到目标 imported_targets 的包含目录属性中。
# 设置目标别名和导入目标
大多数的库对应的 CMake Module ( FindXXX.cmake ) 都会设置给目标设置别名 (ALIAS targets):
比如从 CMake version3.5 开始,Boost 模块就支持了这个特性。和之前我们在 04-shared-library 中为共享库设置目标别名并在链接时使用别名一样,我们可以在 target_link_libraries 中直接使用第三方库的别名,这样做的好处是不仅能指定需要链接的库,而且还能把该库引用的头文件传递给 target。
通常 Boost CMake library targets 的名字中来自于库的名字本身,比如库 Boost.system 对应的 target 名称为 Boost::system. 具体可以看 the FindBoost docs' section on the imported targets (opens new window) 中说到 Boost::<component>。
比如如果你使用 find_package() 函数找到了第三方库 Boost,你就可以像下面这种方式使用 Boost 的 subsystem:
- Boost::boost 只有头文件的库
- Boost::system Boost 中的 system library
- Boost::filesystem Boost 中的 filesystem library
提示
假设你只想在你的 target 中使用 header only target (本例中的 Boost::boost ) 添加一个头文件,你应该怎么做?
正常情况下,你可以直接使用 target_link_libraries(target PRIVATE Boost::boost) 即可。
当然这里情况比较特殊,应为 Boost 的头文件在 /usr/include 文件夹下,GCC 会默认搜索该文件夹下的头文件。
参考 How to specify header-only boost library for CMake? (opens new window)
这些 Boost 中的库目标就如同你自己构建的库目标一样,这些目标也会有很多属性,比如 INCLUDE_DIRECTORIES , INTERFACE_INCLUDE_DIRECTORIES , COMPILE_DEFINITIONS , INTERFACE_COMPILE_DEFINITIONS 等等。
所以当你链接 Boost::filesystem 时,将会自动导入依赖关系 Boost::boost 和 Boost::system 。
- 关于
Boost::filesystem会自动导入Boost::system在手册中也说到了 the FindBoost docs' section on the imported targets (opens new window)
为了链接你找到的目标,比如本例中的 Boost::filesystem ,你可以使用:
target_link_libraries(third_party_include
PRIVATE
Boost::filesystem
)
2
3
4
# 没有别名的目标
虽然大部分的 modern library 都可以使用目标别名进行链接,但是不是所有的第三方库都支持这一功能。当一个第三方库没有别名,无法直接使用目标别名进行链接时,你可以查看下面这些变量是否是可用的:
xxx_INCLUDE_DIRS: 该变量指向了库的源文件的包含文件 ( inlude dirctory ) 的目录
xxx_LIBRARY: 该变量指向了库的路径
如果这些变量都是可用的,那么类似于 Basic 03-static-library 我可以手动的为当前我们需要编译的目标添加包含目录和链接库:
# Include the boost headers
target_include_directories( third_party_include
PRIVATE ${Boost_INCLUDE_DIRS}
)
# link against the boost libraries
target_link_libraries( third_party_include
PRIVATE
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
)
2
3
4
5
6
7
8
9
10
11
所以本例的 CMakeLists.txt 的另一种写法是:
cmake_minimum_required(VERSION 3.5)
# Set the project name
project (third_party_include)
# find a boost install with the libraries filesystem and system
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
# check if boost was found
if(Boost_FOUND)
message ("boost found")
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
# Add an executable
add_executable(third_party_include main.cpp)
# Include the boost headers
target_include_directories( third_party_include
PRIVATE ${Boost_INCLUDE_DIRS}
)
# link against the boost libraries
target_link_libraries( third_party_include
PRIVATE
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
)
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
# 构建本例
太简单了就不说了。。。。和之前一样。
# 一些解释
这个练习对像我一样的菜鸡来说,还是很有疑惑性的,在我阅读过程中我发现以下几个问题?
- 我们在 main.cpp 中使用了
Boost::shared_ptr,查阅资料后我们可以发现,它是Boost::smart_ptr中的一个模版类,那为什么我们不要添加Boost::smart_ptr在target_link_libraries中呢?
我对这个问题其实疑惑了很久,还去 stackoverflow 上提出了这个问题 Why don't I need add boost::shared_ptr to target_link_libraries when linking to Boost::filesystem? (opens new window)。
通过大神的解答,我了解到: Boost::smart_ptr 是一个 head only library,也就是说它没有对应的静态库或者共享库。我们可以从 Which Boost libraries are header-only? (opens new window) 这个解答中可以看到哪些 Boost library 是需要构建动态库或者静态库的。
因为它是一个 header-only library 所以在预编译阶段就会展开到 main.cpp 文件中,之后不需要再进行链接。我们只需要指定 Boost::smart_ptr 所在的目录给告诉编译器要从哪个地方查找这个头文件就好。
而恰好 target_link_libraries(third_party_include PRIVATE Boost::filesystem) 的作用是可以把包含目录传递到当前目标 main.cpp 的 INCLUDE_DIRECTORIES 属性中;而且其实 /usr/include 是 gcc 默认会查找的头文件目录,就算是 target_link_libraries 不传递都没问题。
在我的系统包含 Boost 的目录是 /usr/include ,我们可以通过执行 make 构建过程中编译 main.cpp 的系统调用过程命令
strace /usr/lib/ccache/c++ -DBOOST_ALL_NO_LIB -DBOOST_FILESYSTEM_DYN_LINK -o main.cpp.o -c /home/tartarus/cmake/cmake-examples/01-basic/H-third-party-library/main.cpp
观察到 Boost 在 /usr/include 目录。
注意
值得注意的是,因为包含目录是 /usr/include ,所以 main.cpp 中指定需要使用的 boost 头文件时需要使用 /boost/... 指定要包含的头文件。
而且作者的一句话其实也说明了这一点:
As with your own targets, these targets include their dependencies, so linking against
Boost::filesystemwill automatically addBoost::boostandBoost::systemdependencies.