引言
我们会创建静态库以及动态库之后,接下要分离我们的模块,既然工具包不仅仅是提供给 main 程序的,我们就没有别要它们放到一起了。
包就要规整
我们创建出另外一个包 utils,结构如下:
1 | test |
以上我们做了以下改动:
- 新建 utils 包,叫模块应该更加合理。
- 将 utils.c、utils.h 放到 utils 模块下。
- 同时,我们发现每个模块下都有一个 CMakeLists.txt 文件,这是必须的,如果说你的模块需要参与编译。
整理 CMakeLists.txt
既然每个包下有一个 CMakeLists.txt 文件,我们接下来要做的就是把根路径里的 CMakeLists.txt 内容提取到各自模块中的 CMakeLists.txt 里。我们先回顾一下根中 CMakeLists.txt 的内容:
1 | cmake_minimum_required(VERSION 3.21) |
我们要做的就是把 main 下相关的编译信息放到 src 包中的 CMakeLists.txt:
1 | set(MAIN_SOURCE |
然后把 utils 模块相关信息放到 utils 模块中的 CMakeLists.txt 文件中:
1 | set(UTILS_SOURCE |
最后我们根路径中的 CMakeLists.txt 中就剩下如下内容:
1 | cmake_minimum_required(VERSION 3.21) |
是不是精简了很多?而且各自模块的功能就要放到各自的模块,根包中的就只是单纯的做通用处理。到了这里我们还是没有办法启动我们的项目的,因为我们需要为根中的 CMakeLists.txt 指定它的子。本示例中,我们的子有两个,一个是 src 下的,一个是 utils 下的,所以我们要加进来:
1 | cmake_minimum_required(VERSION 3.21) |
9 行、10 行我们通过 add_subdirectory
指令,将 src、utils 两个模块加入进来。同时我们还要注意第 7 行路径改为:/build/utils,因为添加 utils 模块之后,就会在 build 里添加对应的包了,后续我们会讲解如何打包到指定位置,这样就不用修改我们的程序了。
安装你的库
由于编译之后,我们需要修改我们的库路径,所以这里我们指定我们的库安装路径。这样方便查找。
我们修改 utils 模块中的 CMakeLists.txt 内容:
1 | set(UTILS_SOURCE |
内容比较多,不过没有关系,我们一步一步分析:
- 5 行、6 行,我们这次同时生成动态库与静态库,由于变量明不能重复,所以动态库我们叫 utils-share。
- 我们生成库的时候,一般都会定义一个版本号,所以我们就用 file 指令读取当前路径下的 VERSION 文件中的版本号,并存于 version 变量中。
- 由于变量不能重名,导致动态库叫 utils-share,这显然不是我们想要的,所以我们需要给它重新命名,这也是第 10 行的作用。我们修改 utils-share 的属性,OUTPUT_NAME 用来指定动态库生成的名字;PUBLIC_HEADER 用来将我们的头文件暴露出来,毕竟库的使用方通过该头文件才能知道库提供的功能有哪些。VERSION 用于指定动态库的版本号。
- 我们在第 17 行安装我们的库,TARGETS 指定我们要安装的目标对象,这里是 utils 静态库,utils-share 动态库两个。接下来的 ARCHIVE 指定我们静态库安装路径,LIBRARY 指定我们动态库安装路径,PUBLIC_HEADER 指定我们头文件安装路径。
- 指令中的 LIB_INSTALL_DIR 以及 LIB_INCLUDE_DIR 则是在根路径定义的安装路径。
以上命令会生成三个库文件:libutils.a、libutils.1.0.0.dylib、libutils.dylib。其中 1.0.0 就是 VERSION 文件中定义的版本号,而 libutils.dylib 是一个软链,指向了 libutils.1.0.0.dylib。
最终根路径中的 CMakeLists.txt 内容如下:
1 | cmake_minimum_required(VERSION 3.21) |
- 6 行、7 行指定了静态库以及头文件的安装路径。
- 10 行指定我们的库文件路径。
- 11 行我们这里指定了头文件的安装路径。
- 13 行将我们找到的头文件包含进我们的程序中,有读者可能会有疑问,为什么不直接使用
include_directories
把头文件加进来,非要在用个find_path
呢?其实这里主要是介绍一下find_path
的用法,一方面是由于include_directories
的范围有点大,所以我们更推荐的做法是采用target_include_directories()
指令。除非该头文件是全局都需要使用的。这里我们就直接采用后者的方式,将头文件仅仅包括到当前编译文件中。
src 下的 CMakeLists.txt 为:
1 | set(MAIN_SOURCE |
6 行,我们将头文件编译到 test 目标对象中。这里有个 PRIVATE 关键字,相似的还有 PUBLIC、INTERFACE,那它们分别是什么意思呢?
- PRIVATE
私有,指被链接的库仅仅被目标使用,目标不对外暴露这个被链接的库的接口; - PUBLIC
公有,指被链接的库不仅被目标使用,目标还对外暴露这个被链接的库的接口; - INTERFACE
接口,指被链接的库不被目标使用,但目标对外暴露这个被链接的库的接口;
如果理解起来比较费劲,没有关系,在后续的文章中,我会单独开一篇,详细讲解这些特性。
- PRIVATE
7 行,将生成的库链接到 test 中。