文章目录
- 一、补充知识
-
- [1. install](#1. install)
-
- [1.1 基础用法](#1.1 基础用法)
- [1.2 include(GNUInstallDirs) 的作用](#1.2 include(GNUInstallDirs) 的作用)
- [二、静态库 的安装](#二、静态库 的安装)
-
- [1. 最终想要达到的效果](#1. 最终想要达到的效果)
- [2. 构建-编译链接-安装 静态库所需的CMake结构](#2. 构建-编译链接-安装 静态库所需的CMake结构)
- [3. 构建-编译链接-安装 的结果](#3. 构建-编译链接-安装 的结果)
- 三、如何使用安装好的静态库
-
- [1. find_package](#1. find_package)
-
- [1.1 find_package函数的介绍](#1.1 find_package函数的介绍)
- [1.2 find_package 的两种查找模式(含 默认查找模式 的介绍)](#1.2 find_package 的两种查找模式(含 默认查找模式 的介绍))
- [2. 使用 刚安装好的静态库](#2. 使用 刚安装好的静态库)
- [四、动态库 的安装](#四、动态库 的安装)
-
- [1. 构建-编译链接-安装 动态库所需的CMake结构](#1. 构建-编译链接-安装 动态库所需的CMake结构)
- [2. 构建-编译链接-安装 的结果](#2. 构建-编译链接-安装 的结果)
- 五、如何使用安装好的动态库
-
- [1. 分别与 动态库 和 静态库 链接生成的可执行程序 的区别](#1. 分别与 动态库 和 静态库 链接生成的可执行程序 的区别)
- [2. 动态库的搜索路径](#2. 动态库的搜索路径)
- [3. 使用 刚安装好的动态库](#3. 使用 刚安装好的动态库)
- [4. CMake 的 RPATH 处理机制(解释 "3." 的结尾提出的问题)](#4. CMake 的 RPATH 处理机制(解释 “3.” 的结尾提出的问题))
一、补充知识
1. install
1.1 基础用法
变量 CMAKE_INSTALL_PREFIX 中设置了默认安装路径。Linux下,CMAKE_INSTALL_PREFIX默认值是:/usr/local
install设置 的安装路径 相对于 CMAKE_INSTALL_PREFIX中路径
- 安装目标文件(可执行程序、库)
(1) 语法一:
bash
install(TARGETS <目标名称>... # 多个目标(含 可执行程序、库)
RUNTIME DESTINATION <目录> # 可执行文件(.exe, 无后缀)
LIBRARY DESTINATION <目录> # 动态库(.so, .dll)
ARCHIVE DESTINATION <目录> # 静态库(.a, .lib)
)
示例:
bash
add_executable(myapp main.cpp)
add_library(Mylib SHARED mylib.cpp)
add_library(MyMath STATIC mymath.cpp)
# 安装可执行程序到 bin,动态库到 lib,静态库到 static_lib
install(TARGETS myapp Mylib MyMath
RUNTIME DESTINATION bin # /usr/local/bin
LIBRARY DESTINATION lib # /usr/local/lib
ARCHIVE DESTINATION static_lib # /usr/local/static_lib
)
(2) 语法二:
bash
install(TARGETS <目标名称> # 指定 同类型目标
DESTINATION <目录>
)
示例:
bash
add_executable(myapp main.cpp)
add_executable(test test.cpp)
add_library(Mylib SHARED mylib.cpp)
add_library(MyMath STATIC mymath.cpp)
# 安装可执行程序到 bin
install(TARGETS myapp test
DESTINATION lib
)
# 安装动态库到 lib
install(TARGETS Mylib
DESTINATION bin
)
# 安装静态库到 static_lib
install(TARGETS MyMath
DESTINATION static_lib
)
- 安装普通文件(头文件、配置文件等)
语法:
bash
install(FILES <文件列表> DESTINATION <目录>)
install(PROGRAMS <脚本文件> DESTINATION <目录>) # 自动添加可执行权限
示例:
bash
# 安装头文件到 include
install(FILES utils.h config.h
DESTINATION include
)
# 安装配置文件到 etc,并设置权限
install(FILES app.conf
DESTINATION etc
PERMISSIONS OWNER_READ OWNER_WRITE
)
# 安装脚本到 bin(自动设为可执行)
install(PROGRAMS setup.sh
DESTINATION bin
)
- 安装目录下所有文件(递归复制)(一般用于头文件安装)
语法:
bash
install(DIRECTORY <目录路径>
DESTINATION <目录>
[FILES_MATCHING PATTERN "*.h"] # 只安装匹配文件
[PATTERN ".git" EXCLUDE] # 排除特定目录
)
示例:
bash
# 安装整个目录路径下文件 到 /usr/local/include
install(DIRECTORY src/include/
DESTINATION include
)
# 只安装指定目录路径下的 .h 文件,排除 .git 目录
install(DIRECTORY src/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN ".git" EXCLUDE
)
1.2 include(GNUInstallDirs) 的作用
GNUInstallDirs是CMake的一个模块,主要作用是提供一组预定义的变量,这些变量代表了符合GNU标准的安装目录路径
- 关键变量列表与默认值
| 变量名 | 用途 | 典型默认值 |
|---|---|---|
| CMAKE_INSTALL_BINDIR | 用户可执行文件(如 .exe) | bin |
| CMAKE_INSTALL_LIBDIR | 库文件(如 .so、.dll) | lib 或 lib64 |
| CMAKE_INSTALL_INCLUDEDIR | C/C++ 头文件 | include |
| CMAKE_INSTALL_DATADIR | 架构无关数据(如配置文件) | share |
| CMAKE_INSTALL_SYSCONFDIR | 系统配置文件 | etc |
所有路径均相对于 CMAKE_INSTALL_PREFIX(如未指定,默认为 /usr/local)。
include(GNUInstallDirs) 是 CMake 中用于标准化项目安装路径的核心命令,它打开GNUInstallDirs模块,通过此模块中的预定义变量,确保软件安装目录遵循 GNU 标准,提升跨平台兼容性和可维护性
- 使用示例
bash
add_library(MyMath STATIC mymath.cpp)
include(GNUInstallDirs) # 引入模块
# 安装可执行文件到标准 bin 目录
install(TARGETS MyMath
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# 安装头文件到标准 include 目录(避免冲突)
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
二、静态库 的安装
1. 最终想要达到的效果

2. 构建-编译链接-安装 静态库所需的CMake结构
- Step 0: 目录结构
bash
CMAKE_TEST
├── build
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt # 重点
├── Config.cmake.in
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
- Step 1: CMAKE_TEST/CMakeLists.txt
bash
# 设置最低版本号
cmake_minimum_required(VERSION 3.18)
# 设置项目名称
project(InstallMyMath
LANGUAGES CXX
)
# 执行子目录的 CMakeLists.txt
add_subdirectory(my_lib)
- Step 2: CMAKE_TEST/my_lib/CMakeLists.txt
bash
############################## 构建阶段设置 ###############################
# 1 收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath STATIC ${SRC_LISTS})
# 3 设置库的使⽤要求,也就是下游消费者必须包含的头⽂件搜索路径
target_include_directories(MyMath INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # include
)
# 4 设置库的默认输出路径
set_target_properties(MyMath PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
############################## 安装阶段设置 ###############################
# 5 安装我们的静态库
include(GNUInstallDirs)
install(TARGETS MyMath
EXPORT MyMathTargets # 将目标 MyMath的导出信息 关联到名为 MyMathTargets的导出目标集合
DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
)
# 6 安装头⽂件
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math # /usr/local/include/math/math.h
FILES_MATCHING PATTERN "*.h"
)
# 7 安装导出⽬标集合 到 构建树
export(EXPORT MyMathTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake # 生成的导出文件放在构建目录
)
# 8 安装导出⽬标集合 到 安装树
install(EXPORT MyMathTargets
FILE MyMathTargets.cmake
NAMESPACE MyMath:: # MyMath::MyMath(命名空间)
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath # /usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)
# 9 ⽣成find_package 需要的 配置⽂件
include(CMakePackageConfigHelpers) # 包含辅助函数(帮助用户将模板文件 转换成 配置文件)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in # 输入 模板文件(自己创建)
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake # 生成的配置文件
INSTALL_DESTINATION "lib/cmake/MyMath" # 配置文件最终安装位置
)
# 10 安装配置⽂件到cmake 标准的安装路径
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
DESTINATION "lib/cmake/MyMath"
)
导出目标集合的作用:记录库的安装路径 及其 目标属性(依赖关系)(可以同时记录多个库的安装路径 及其 依赖关系)
为什么要在第 5 步(安装静态库的相关设置),将 静态库目标MyMath 导出到 导出目标集合MyMathTargets?
- 在第5步之前,静态库目标MyMath 的依赖关系(如 头文件路径等)都已设置完毕
- 在第5步,设置好 静态库目标MyMath 的安装路径
- 到第5步,静态库目标MyMath 的安装路径 及其 目标属性(依赖关系)都已明确,所以在这一步将 静态库目标MyMath 导出到 导出目标集合MyMathTargets
根据 导出目标集合MyMathTargets 中记录的库的安装路径 及其 依赖关系 生成 MyMathTargets.cmake脚本文件
- Step 3: CMAKE_TEST/my_lib/Config.cmake.in
bash
@PACKAGE_INIT@ # 初始化(无需理解作用)
include(${CMAKE_CURRENT_LIST_DIR}/MyMathTargets.cmake) # 配置文件 MyMathConfig.cmake 也会包含 这条代码
Step 2 的 第9步:将 模板文件Config.cmake.in 转换成 配置文件MyMathConfig.cmake
3. 构建-编译链接-安装 的结果
- 安装结果符合预期

- 验证了 配置文件MyMathConfig.cmake 中打开了同目录下的 脚本文件MyMathTargets.cmake

三、如何使用安装好的静态库
1. find_package
1.1 find_package函数的介绍
- 函数作用
find_package 命令的主要目的是定位一个外部项目(库、工具集等) 并将其导入到当前的 CMake 构建中。成功找到包后,它会定义一些变量(如 < PackageName >_FOUND, < PackageName >_INCLUDE_DIRS, < PackageName >_LIBRARIES)和可能导入目标(< PackageName >::< Component >),使得你的项目能够方便地使用该包提供的头文件和库。
- 函数形式:
bash
find_package(<PackageName>
[version]
[EXACT]
[QUIET]
[MODULE / CONFIG]
[REQUIRED]
)
- 参数解释:
| 参数 | 含义 | 示例 |
|---|---|---|
| < PackageName > | 要查找的包的名称 | CMake 会基于这个名称去寻找对应的 Find< PackageName >.cmake 模块(Module 模式)或 < PackageName >Config.cmake / < PackageName >-config.cmake 文件(Config 模式) |
| [version] (可选) | 指定需要的包的最低版本号 | find_package(OpenCV 4.5) 要求至少 OpenCV 4.5 版本 |
| [EXACT] (可选) | 要求找到的包版本必须精确匹配指定的 [version] | find_package(OpenCV 4.5.0 EXACT) 要求必须是 OpenCV 4.5.0 版本,4.5.1 或 4.4.0 都不行。 |
| [QUIET] (可选) | 如果指定,当查找失败时,CMake 不会输出任何警告或错误消息 | find_package(OptionalLib QUIET) 如果找不到 OptionalLib,CMake 不会报错 |
| [MODULE / CONFIG] (可选) | 强制仅用模块模式 / 强制仅用配置模式 | - |
| [REQUIRED] (可选) | 若找不到包,终止配置过程并报错 | - |
1.2 find_package 的两种查找模式(含 默认查找模式 的介绍)
在 CMake 中,通常使用 find_package() 命令来查找和使用外部依赖库。他有两种查找模式(模块模式 和 配置模式):
- 模块模式(Module Mode): find_package( < PackageName > MODULE )
CMake 执行module模式大概分为以下3步:
- 查找模块文件:
搜索 Find < PackageName >.cmake 文件
顺序: CMAKE_MODULE_PATH → CMake 内置模块(/usr/local/share/cmake/Modules/)
- 执行模块文件:
模块文件通常会:
查找库文件( find_library() )
查找头文件( find_path() )
检查版本( find_package_handle_standard_args() )
定义导入目标( add_library(... IMPORTED) )
- 设置输出变量:
常见变量:
MyPackage_FOUND :是否找到包
MyPackage_INCLUDE_DIRS :头文件路径
MyPackage_LIBRARIES :库文件路径
MyPackage_VERSION :包版本
- 配置模式(Config Mode): find_package( < PackageName > CONFIG )
CMake 执行Config模式大概分为以下2步:
- 查找配置文件:
搜索 < PackageName >Config.cmake 或 < PackageName >-config.cmake
搜索路径: CMAKE_PREFIX_PATH → 标准系统路径(/usr/local/lib/cmake/) → /usr/local/lib/cmake/< PackageName >/
- 执行配置文件:
配置文件中会包含脚本文件 < PackageName >Targets.cmake,该脚本文件 通常由包开发者提供,会:
定义导入目标(如 PackageName::PackageName)
设置依赖关系(头文件路径 和 库文件路径属性)
提供版本信息
当find_package未显式指定查找模式时,它的默认搜索模式 取决于 CMake 版本:
- CMake < 3.24:默认优先使用模块模式(Module Mode)
find_package 会先查找 Find< PackageName >.cmake 文件(模块模式),若未找到则回退到配置模式(搜索 < PackageName >Config.cmake 或 < PackageName >-config.cmake)- CMake ≥ 3.24:默认优先使用配置模式(Config Mode)
若未显式指定模式,会优先搜索配置模式的文件,失败后再尝试模块模式
2. 使用 刚安装好的静态库
- Step 0: 目录结构
bash
test_MyMath/
├── build
├── CMakeLists.txt
└── main.cpp
- Step 1: test_MyMath/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.10)
project(MyMathApp)
# 查找 MyMath 库
find_package(MyMath CONFIG REQUIRED)
# 添加可执⾏⽂件
add_executable(main main.cpp)
# 添加依赖关系
target_link_libraries(main PRIVATE MyMath::MyMath)
find_package(MyMath CONFIG REQUIRED)
find_package() 命令采用 配置模式 搜索 /usr/local/lib/cmake/MyMath 下名为 MyMathConfig.cmake 的配置文件
- Step 2: test_MyMath/main.cpp
cpp
#include <iostream>
#include "math/math.h"
int main()
{
std::cout << "add(3,4) = " << add(3, 4) << std::endl;
std::cout << "sub(3,4) = " << sub(3, 4) << std::endl;
}
- 进入构建目录,执行构建-编译链接命令,形成可执行文件main

- main程序正常执行

四、动态库 的安装
1. 构建-编译链接-安装 动态库所需的CMake结构
动态库的安装过程 和 静态库安装过程的区别:
(1)编译过程:添加编译选项 "-fPIC" (位置无关码)
(2)动态库 需要指定VERSION 和 SOVERSION版本
- Step 0: 目录结构
bash
install_shared_mymath
├── build
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt # 重点
├── Config.cmake.in
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
- Step 1: CMAKE_TEST/CMakeLists.txt
bash
# 设置最低版本号
cmake_minimum_required(VERSION 3.18)
# 设置项目名称
project(InstallMyMath
LANGUAGES CXX
)
# 执行子目录的 CMakeLists.txt
add_subdirectory(my_lib)
- Step 2: CMAKE_TEST/my_lib/CMakeLists.txt
bash
############################## 构建阶段设置 ###############################
# 1 收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath SHARED ${SRC_LISTS})
# 3 设置库的使⽤要求,也就是下游消费者必须包含的头⽂件搜索路径
target_include_directories(MyMath INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # include
)
# 4 设置库的默认输出路径 及 输出名和版本号
set_target_properties(MyMath PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
OUTPUT_NAME MyMath
VERSION 1.2.3
SOVERSION 20
)
# 5 设置库目标的编译选项
set_target_properties(MyMath PROPERTIES
COMPILE_OPTIONS "-fPIC"
)
############################## 安装阶段设置 ###############################
# 6 安装我们的动态库
include(GNUInstallDirs)
install(TARGETS MyMath
EXPORT MyMathTargets # 将目标 MyMath的导出信息 关联到名为 MyMathTargets的导出目标集合
DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
)
# 7 安装头⽂件
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math # /usr/local/include/math/math.h
FILES_MATCHING PATTERN "*.h"
)
# 8 安装导出⽬标集合 到 构建树
export(EXPORT MyMathTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake # 生成的导出文件放在构建目录
)
# 9 安装导出⽬标集合 到 安装树
install(EXPORT MyMathTargets
FILE MyMathTargets.cmake
NAMESPACE MyMath:: # MyMath::MyMath(命名空间)
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath # /usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)
# 10 ⽣成find_package 需要的 配置⽂件
include(CMakePackageConfigHelpers) # 包含辅助函数(帮助用户将模板文件 转换成 配置文件)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in # 输入 模板文件(自己创建)
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake # 生成的配置文件
INSTALL_DESTINATION "lib/cmake/MyMath" # 配置文件最终安装位置
)
# 11 安装配置⽂件到cmake 标准的安装路径
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
DESTINATION "lib/cmake/MyMath"
)
- Step 3: CMAKE_TEST/my_lib/Config.cmake.in
bash
@PACKAGE_INIT@ # 初始化(无需理解作用)
include(${CMAKE_CURRENT_LIST_DIR}/MyMathTargets.cmake) # 配置文件 MyMathConfig.cmake 也会包含 这条代码
2. 构建-编译链接-安装 的结果
- 构建-编译链接 过程

- 安装结果符合预期

- VERSION:记录库的完整构建版本(如 1.2.3)
- SOVERSION(SONAME):记录库的ABI兼容性版本。程序编译时记录 SONAME。 程序运行时通过 SOVERSION 自动加载兼容的最新库(如 libMyMath.so.20 → libMyMath.so.1.2.3),避免因版本冲突导致崩溃。
五、如何使用安装好的动态库
1. 分别与 动态库 和 静态库 链接生成的可执行程序 的区别
目标文件 与 静态库链接时,链接器提取静态库中所需 .o,拷贝+链接进最终可执行文件。可执行文件可以直接运行,不再依赖静态库。
目标文件 与 动态库链接时,最终可执行文件 仅记录与动态库的依赖关系 和 符号信息。可执行文件 运行时,必须通过加载器找到所有依赖的 .so 文件,才可以顺利执行。
2. 动态库的搜索路径
加载器(动态链接器)按顺序在以下几个地方查找:
- LD_LIBRARY_PATH 环境变量中列出的目录
- ELF 文件的 DT_RUNPATH 中记录的路径(链接时设置)
- 系统默认路径 /lib、/usr/lib ...
3. 使用 刚安装好的动态库

- Step 0: 目录结构
bash
test_MyMath/
├── build
├── CMakeLists.txt
└── main.cpp
- Step 1: test_MyMath/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.10)
project(MyMathApp)
# 查找 MyMath 库
find_package(MyMath CONFIG REQUIRED)
# 添加可执⾏⽂件
add_executable(main main.cpp)
# 添加依赖关系
target_link_libraries(main PRIVATE MyMath::MyMath)
- Step 2: test_MyMath/main.cpp
cpp
#include <iostream>
#include "math/math.h"
int main()
{
std::cout << "add(3,4) = " << add(3, 4) << std::endl;
std::cout << "sub(3,4) = " << sub(3, 4) << std::endl;
}
- 构建-编译链接,生成可执行程序main

- 可执行程序main 成功执行

问题:
可执行程序main 依赖 动态库libMyMath.so.20
所以 main运行时,必须 先找到并加载动态库libMyMath.so.20,自己才能顺利执行
那么 main在运行时是如何找到 自己依赖的动态库的呢?
4. CMake 的 RPATH 处理机制(解释 "3." 的结尾提出的问题)
- 使用 readelf -d 命令查看 ELF文件(可执行文件 就是 ELF格式)的 .dynamic 段

以上,我们观察到 main的 DT_RUNPATH 中记录了 /usr/local/lib,这正是 动态库libMyMath.so.20 的路径, 所以main在运行时是能找到 自己依赖的动态库的!
CMake 的默认 RPATH 行为
- CMake 的默认策略:
- 当链接位于非标准路径(非 /usr/lib, /lib)的 动态库时,CMake 默认会自动添加链接库的路径到 RPATH/RUNPATH
在我们的项目中:
bash
find_package(MyMath REQUIRED CONFIG)
target_link_libraries(main PRIVATE MyMath::MyMath)
CMake 检测到 导入目标MyMath::MyMath 中记录的动态库libMyMath.so.20 位于 /usr/local/lib
因为 /usr/local/lib 不是标准系统库路径,CMake 自动将其添加到可执行文件的 RPATH/RUNPATH
- 标准与非标准库路径
| 路径类型 | 示例 | CMake 是否自动添加 RPATH |
|---|---|---|
| 标准系统路径 | /usr/lib, /lib | ❌ 否 |
| 非标准路径 | /usr/local/lib, /opt/mylib | ✅ 是 |
| 相对路径 | .../mylib | ✅ 是 |
- CMake 的RPATH行为 可以被主动禁用
bash
set(CMAKE_SKIP_RPATH TRUE)
// 默认情况下,CMAKE_SKIP_RPATH 被设置为FALSE,CMake 的RPATH行为处于开启状态
- 试试主动禁用 CMake 的RPATH行为 的效果:

- 生成的main文件中不记录DT_RUNPATH

- main找不到依赖的动态库libMyMath.so.20,无法顺利执行




