惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
爱范儿
爱范儿
小众软件
小众软件
K
Kaspersky official blog
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
V
Vulnerabilities – Threatpost
博客园_首页
Microsoft Security Blog
Microsoft Security Blog
C
Cybersecurity and Infrastructure Security Agency CISA
V
V2EX
C
Check Point Blog
S
Schneier on Security
P
Palo Alto Networks Blog
IT之家
IT之家
GbyAI
GbyAI
T
Threat Research - Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Apple Machine Learning Research
Apple Machine Learning Research
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
Project Zero
Project Zero
Y
Y Combinator Blog
V
Visual Studio Blog
Simon Willison's Weblog
Simon Willison's Weblog
T
Threatpost
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
S
Securelist
C
CERT Recently Published Vulnerability Notes
A
Arctic Wolf
M
MIT News - Artificial intelligence
人人都是产品经理
人人都是产品经理

博客园 - DoubleLi

Git 自动添加 git 子模块(.gitmodules) 如何在gitlab中使用子模块 聊聊git push到远程服务器出现RPC failed问题 Qt Create多核编译配置 C++程序的发布部署方式及缺失依赖库dll的解决方法 VS+QT生成的exe文件所依赖的dll文件和路径 vs如何发布exe并附带动态链接库dll 详解 Visual Studio、MSVC、编译器版本,看完就不再困惑啦! MobaXterm使sftp目录与terminal目录同步 从GPS/北斗模块中获取经纬度 Loguru:Python 日志终极解决方案 VsCode C++ namespace has no member错误 VS Code 提示 namespace “std“ has no member “***“ 解决方法 windows平台中使用vscode远程连接linux进行c++开发配置教程(内容详细适合小白)-2021-3-30 vscode C++ 自动补全失效 从零开始的vscode安装及环境配置教程(C/C++)(Windows系统) VScode 完全卸载并清除原有配置 vscode中 “std“ 没有成员 “round“C/C++(135) vscode 提示 namespace “std“ has no member “mutex“ 的解决方法
CMake应用:模块化及库依赖
DoubleLi · 2025-04-10 · via 博客园 - DoubleLi
当项目比较大的时候,往往需要将代码划分为几个模块,可能还会分离出部分通用模块,在多个项目之间同时使用;当然,也可能是依赖开源的第三方库,在项目中包含第三方源代码或者编译好的库文件。本文将会介绍CMake中如何模块化地执行编译,以及指定目标对相应库文件的依赖。

在上一篇文章中,笔者介绍了一个比较完备的CMakeists.txt该如何书写。往期文章可以查看专栏:CMake实践应用专题,上一篇文章链接如下:

但是上一篇文章介绍的CMakeLists.txt一般是在项目初期的样子,随着项目代码原来越多,或者功能越来越多,代码可能会分化出不同的功能模块,并且有一些可能是多个项目通用的模块,这时为了更好地管理各个模块,可以为每个模块都编写一个CMakeLists.txt文件,然后在父级目录中对不同编译目标按需添加依赖。

本文着重介绍下面的内容:

  1. 模块化管理构建系统(add_subdirectory)
  2. 导入编译好的目标文件
  3. 添加库依赖

一 模块化构建

在前面的文章中介绍过,CMakeLists.txt是定义一个目录(Source Tree)的构建系统的,所以对于模块化构建,其实就是分别为每一个子模块目录编写一个CMakeLists.txt,在其父目录中“导入”子目录的构建系统生成对应的目标,以便在父目录中使用。

下面仍以开源项目:https://gitee.com/RealCoolEngineer/cmake-template为例,基于上一篇文章的状态进行修改,本文对应的commit id为:4bfb85b

假设项目目录结构如下:

./cmake-template
├── CMakeLists.txt
├── src
│   └── c
│       ├── cmake_template_version.h
│       ├── cmake_template_version.h.in
│       ├── main.c
│       └── math
│           ├── add.c
│           ├── add.h
│           ├── minus.c
│           └── minus.h
└── test
    └── c
        ├── test_add.c
        └── test_minus.c

现在的编译任务为:

  1. 将math目录视为子模块,为其单独定义构建系统
  2. 整个项目依赖math模块的编译结果,生成其他目标文件

1 定义子目录的构建系统

只要是定义目录的构建系统,都是在此目录下创建一个CMakeLists.txt文件,其结构和语法在上一篇文章已经介绍的比较详细。

因为主要进行模块的编译工作,所以一般只需要编译构建库文件(静态库或者动态库),以及针对该库对外提供接口的一些单元测试即可,所以可以写的比较简单一些。

src/math目录下新建CMakeLists.txt文件,内容如下:

cmake_minimum_required(VERSION 3.12)
project(CMakeTemplateMath VERSION 0.0.1 LANGUAGES C CXX)

aux_source_directory(. MATH_SRC)
message("MATH_SRC: ${MATH_SRC}")

add_library(math STATIC ${MATH_SRC})

如上代码所示,对于子目录(模块),一般也有自己的project命令,同时如果有需要,也可以指定自己的版本号。

这里使用了一个此前没有提到的命令:aux_source_directory,该命令可以搜索指定目录(第一个参数)下的所有源文件,将源文件的列表保存到指定的变量(第二个参数)。

2 包含子目录

通过命令add_subdirectory包含一个子目录的构建系统,其命令格式如下:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

其中source_dir就是要包含的目标目录,该目录下必须存在一个CMakeLists.txt文件,一般为相对于当前CMakeLists.txt的目录路径,当然也可以是绝对路径;

binary_dir是可选的参数,用于指定子构建系统输出文件的路径,相对于当前的Binary tree,同样也可以是绝对路径。 一般情况下,source_dir是当前目录的子目录,那么binary_dir的值为不做任何相对路径展开的source_dir;但是如果source_dir不是当前目录的子目录,则必须指定binary_dir,这样CMake才知道要将子构建系统的相关文件生成在哪个目录下。

如果指定了EXCLUDE_FROM_ALL选项,在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。但是,如果在父级项目显式声明依赖子目录的目标文件,那么对应的目标文件还是会被构建以满足父级项目的依赖需求。

综上,可以修改cmake-template项目根目录下的CMakeLists.txt文件,将原来的如下内容:

# Build math lib
add_library(math STATIC ${MATH_LIB_SRC})

修改为:

add_subdirectory(src/c/math)

构建的静态库的名字依旧是math,所以在编译demo目标时,链接的库的名字不用修改:

# Build demo executable
add_executable(demo src/c/main.c)
target_link_libraries(demo math)

此时构建和编译的命令没有任何改变:

➜ cmake-template # cmake -B cmake-build
➜ cmake-template # cmake --build cmake-build

上面的命令指定父项目的生成路径(Binary tree)为cmake-build,那么子模块(math)的生成路径为cmake-build/src/c/math,也就是说binary_dirsrc/c/math,等同于source_dir

二 导入编译好的目标文件

在前面介绍的命令add_subdirectory其实是相当于通过源文件来构建项目所依赖的目标文件,但是CMake也可以通过命令来导入已经编译好的目标文件。

1 导入库文件

使用add_library命令,通过指定IMPORTED选项表明这是一个导入的库文件,通过设置其属性指明其路径:

add_library(math STATIC IMPORTED)
set_property(TARGET math PROPERTY
             IMPORTED_LOCATION "./lib/libmath.a")

对于库文件的路径,也可以使用find_library命令来查找,比如在lib目录下查找math的Realse和Debug版本:

find_library(LIB_MATH_DEBUG mathd HINTS "./lib")
find_library(LIB_MATH_RELEASE math HINTS "./lib")

对于不同的编译类型,可以通过IMPORTED_LOCATION_<CONFIG>来指明不同编译类型对应的库文件路径:

add_library(math STATIC IMPORTED GLOBAL)
set_target_properties(math PROPERTIES
  IMPORTED_LOCATION "${LIB_MATH_RELEASE}"
  IMPORTED_LOCATION_DEBUG "${LIB_MATH_DEBUG}"
  IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
)

导入成功以后,就可以将该库链接到其他目标上,但是导入的目标不可以被install

这里以导入静态库为例,导入动态库或其他类型也是类似的操作,只需要将文件类型STATIC修改成对应的文件类型即可。

2 导入可执行文件

这个不是那么常用,为了文章完整性,顺便提一下。是和导入库文件类似的:

add_executable(demo IMPORTED)
set_property(TARGET demo PROPERTY
             IMPORTED_LOCATION "./bin/demo")

三 库依赖

这里主要着重介绍一下target_link_libraries命令的几个关键字:

  1. PRIVATE
  2. INTERFACE
  3. PUBLIC

这三个关键字的主要作用是指定的是目标文件依赖项的使用范围(scope),所以可以专门了解一下。

假设某个项目中存在两个动态链接库:动态链接库liball.so、动态链接库libsub.so

对于PRIVATE关键字,使用的情形为:liball.so使用libsub.so,但是liball.so不对外暴露libsub.so的接口:

target_link_libraries(all PRIVATE sub)
target_include_directories(all PRIVATE sub)

对于INTERFACE关键字,使用的情形为:liball.so没有使用libsub.so,但是liball.so对外暴露libsub.so的接口,也就是liball.so的头文件包含了libsub.so的头文件,在其它目标使用liball.so的功能的时候,可能必须要使用libsub.so的功能:

target_link_libraries(all INTERFACE sub)
target_include_directories(all INTERFACE sub)

对于PUBLIC关键字(PUBLIC=PRIVATE+INTERFACE),使用的情形为:liball.so使用libsub.so,并且liball.so对外暴露libsub.so的接口:

target_link_libraries(all PUBLIC sub)
target_include_directories(all PUBLIC sub)

这里的内容可以有个大概了解即可,随着后续深入使用,自然会水到渠成。