CMake保姆级编程
CMake保姆级编程
文档来源:爱编程的大丙
ubuntu
下查看cmake
版本:
1 | # 终端输入 |
一、基础
1.源文件与CmakeLists.txt
均在一个目录下
文件下的目录结构:
1 | $ tree |
所有源码全部放在一个文件夹下,在该目录下编写CmakeLists.txt
:
1 | cmake_minimum_required(VERSION 3.16) |
cmake_minimum_required
:指定使用的cmake
的最低版本project
:定义工程名称add_executable
:定义工程会生成一个可执行程序
1 | add_executable(可执行程序名 源文件名称) |
在CMakeLists.txt
同级目录下终端打开输入
1 | cmake . |
当执行cmake
命令之后,CMakeLists.txt
中的命令就会被执行,所以一定要注意给 cmake
命令指定路径的时候一定不能出错
执行命令之后,看一下源文件所在目录中是否多了一些文件:
1 | $ tree -L 1 |
注意: 可以通过创建一个build
目录,在build
目录与CmakeLists.txt
文件同级,在build目录下终端输入cmake ..
,则cmake
产生的中间文件均会放在build
目录下
我们可以看到在对应的目录下生成了一个 makefile
文件,此时再执行 make
命令,就可以对项目进行构建得到所需的可执行程序了
1 | make |
1 | $ make |
2.定义变量set
假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在 cmake
里定义变量需要使用 set
1 | # SET 指令的语法是: |
- VAR:变量名
- VALUE:变量值
1 | # 如下实例 |
(1)使用c++标准
在编写 C++ 程序的时候,可能会用到 C++11、C++14、C++17、C++20
等新特性,那么就需要在编译的时候在编译命令中制定出要使用哪个标准,终端命令行直接输入:
1 | $ g++ *.cpp -std=c++11 -o app |
上面的例子中通过参数 -std=c++11
指定出要使用c++11
标准编译程序,C++
标准对应有一宏叫做 DCMAKE_CXX_STANDARD
。在 CMake
中想要指定 C++
标准有两种方式:
- 在
CMakeLists.txt
中通过set
命令指定
1 | #增加-std=c++11 |
- 在执行
cmake
命令的时候指定出这个宏的值
1 | #增加-std=c++11 |
示例:
1 | # c99 标准 CMakeLists.txt 在上一级目录下 |
(2)指定输出的路径
在 CMake
中指定可执行程序输出的路径,也对应一个宏,叫做 EXECUTABLE_OUTPUT_PATH
,它的值还是通过 set
命令进行设置:
1 | set(HOME /home/zxz/Proj/CLionProj/Linux_C/Point/bin) |
- 第一行:定义一个变量用于存储一个绝对路径(也可以创建相对路径,
bin,build
目录与CMakeLists.txt
同级,在build目录下执行cmake ..
) - 第二行:将拼接好的路径值设置给 EXECUTABLE_OUTPUT_PATH 宏
- 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建
由于可执行程序是基于 cmake
命令生成的 makefile 文件然后再执行 make
命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx
,那么这个路径中的./
对应的就是makefile
文件所在的那个目录。
3.搜索文件
如果一个项目里边的源文件很多,在编写CMakeLists.txt
文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在CMake
中为我们提供了搜索文件的命令,可以使用aux_source_directory
命令或者 file
命令。
(1)aux_source_directory
1 | aux_source_directory(< dir > < variable >) |
dir
:要搜索的目录variable
:将从dir
目录下搜索到的源文件列表存储到该变量中
实例项目:
项目目录:
1 | (base) zxz@zxz-B660M-GAMING-X-AX-DDR4:~/Proj/CLionProj/CMake_Demo/De2$ tree |
head.h
1 |
|
a.cpp
1 |
|
b.cpp
1 |
|
main.cpp
1 |
|
CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
注意:
${PROJECT_SOURCE_DIR}
是指最近包含PROJECT()
语句的CMakeLists.txt
所在的目录,如上的CMakeLists.txt
中不包含了PROJECT(De2)
语句,而CMakeLists.txt
所在的目录是~/Proj/CLionProj/CMake_Demo/De2
${CMAKE_CURRENT_SOURCE_DIR}
这是cmake
当前正在处理的源目录的完整路径,在该程序中,在build
目录下终端输入cmake ..
处理的是build
的上级目录下的CMakeLists.txt
文件,则此处的${CMAKE_CURRENT_SOURCE_DIR}
也是和而CMakeLists.txt
所在的目录一致
在build目录下终端输入:
1 | $ cmake .. |
在bin
目录下可以找到可执行文件De2
(2)file
1 | file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型) |
GLOB
: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中GLOB_RECURSE
:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
搜索当前目录的 src 目录下所有的源文件,并存储到变量中
1 | file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) |
CMAKE_CURRENT_SOURCE_DIR
宏表示当前访问的CMakeLists.txt
文件所在的路径关于要搜索的文件路径和类型可加双引号,也可不加:
1 | file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h") |
4.包含头文件
在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake
中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是 include_directories
:
1 | include_directories(headpath) |
实例项目
项目目录结构:
1 | (base) zxz@zxz-B660M-GAMING-X-AX-DDR4:~/Proj/CLionProj/CMake_Demo/De2$ tree |
需要在CMakeLists.txt
中添加语句,指定头文件路径
1 | # 包含头文件 |
${PROJECT_SOURCE_DIR}
是指最近包含PROJECT()
语句的CMakeLists.txt
所在的目录
5.制作动态库或静态库
有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,下面来讲解在cmake
中生成这两类库文件的方法
(1)制作静态库
在cmake
中,如果要制作静态库,需要使用的命令如下,关键字STATIC
1 | add_library(库名称 STATIC 源文件1 [源文件2] ...) |
在Linux中,静态库名字分为三部分:lib
+库名字
+.a
,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充
实例项目:将src
中的源文件编译成静态库
项目目录结构:
1 | (base) zxz@zxz-B660M-GAMING-X-AX-DDR4:~/Proj/CLionProj/CMake_Demo/De4$ tree |
CMakeLists.txt
1 | # 包含头文件 |
这样最终就会生成对应的静态库文件libcalc.a
(2)制作动态库
在cmake
中,如果要制作动态库,需要使用的命令如下,关键字SHARED
1 | add_library(库名称 SHARED 源文件1 [源文件2] ...) |
在Linux中,动态库名字分为三部分:lib
+库名字
+.so
,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充
CMakeLists.txt
1 | # 包含头文件 |
(3)指定输出路径
方式1-适用动态库
对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的
,所以可以按照生成可执行程序的方式去指定它生成的目录:
1 | # 包含头文件 |
通过set
命令给EXECUTABLE_OUTPUT_PATH
宏设置了一个路径,这个路径就是可执行文件生成的路径
方式2-动静态库均适用(一般用这个)
由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH
宏了,而应该使用LIBRARY_OUTPUT_PATH
,这个宏对应静态库文件和动态库文件都适用
1 | # 包含头文件 |
6.包含库文件
在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake
中也为我们提供了相关的加载动态库的命令
(1)链接静态库
项目目录结构:
1 | (base) zxz@zxz-B660M-GAMING-X-AX-DDR4:~/Proj/CLionProj/CMake_Demo/De4$ tree |
将src
目录下的a.cpp
、b.cpp
编译成静态库文件
链接静态库命令如下:
1 | link_libraries(<static lib> [<static lib>...]) |
- 参数1:指定出要链接的静态库的名字
- 可以是全名
libxxx.a
- 也可以是掐头(
lib
)去尾(.a
)之后的名字xxx
- 可以是全名
- 参数2-N:要链接的其它静态库的名字
或者
1 | # 这种方式需要 放到 add_executable(De3 main.cpp) 之后 |
参数2:指定出要链接的静态库的名字
可以是全名
libxxx.a
也可以是掐头(
lib
)去尾(.a
)之后的名字xxx
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:
1 | link_directories(<lib path>) |
CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
在build
目录下打开终端,输入
1 | $ cmake .. |
此时的项目目录结构
1 | $ tree |
(2)链接动态库
在程序编写过程中,除了在项目中引入静态库,好多时候也会使用一些标准的或者第三方提供的一些动态库,关于动态库的制作、使用以及在内存中的加载方式和静态库都是不同的
在CMake
中链接动态库
1 | target_link_libraries( |
target
:指定要加载动态库的文件的名字- 该文件可能是一个源文件
- 该文件可能是一个动态库文件
- 该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE
:动态库的访问权限,默认为PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可
动态库的链接具有传递性,如果动态库 A 链接了动态库 B、C,动态库 D 链接了动态库 A,此时动态库 D 相当于也链接了动态库 B、C,并可以使用动态库 B、C 中定义的方法
1
2target_link_libraries(A B C)
target_link_libraries(D A)PUBLIC
:在public
后面的库会被 Link 到前面的target
中,并且里面的符号也会被导出,提供给第三方使用PRIVATE
:在private
后面的库仅被 link 到前面的target
中,并且终结掉,第三方不能感知你调了啥库INTERFACE
:在interface
后面引入的库不会被链接到前面的target
中,只会导出符号
区别
动态库的链接和静态库是完全不同的:
- 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了
- 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
- 静态库的扩展名一般为
.a
或.lib
;动态库的扩展名一般为.so
或.dll
- 动态库(共享库)在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。(缺少动态库生成的可执行文件无法运行)
因此,在cmake
中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:
1 | cmake_minimum_required(VERSION 3.16) |
在 target_link_libraries(De5 do)
中:
app
: 对应的是最终生成的可执行程序的名字do
:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为 libdo.so,在指定的时候一般会掐头(lib)去尾(.so)。
在build
目录下终端输入
1 | $ cmake .. |
此时的项目目录结构
1 | . |
(3)链接第三方库
可以同时链接 自己编写的动态库以及第三方库
1 | target_link_libraries(De5 pthread) |
上面的可执行文件De5
链接了第三方的动态库 pthread
线程库
但是当我们运行可执行文件时 ./De5
,可能回报错,具体情况百度即可
有一些情况是因为我们没有将链接的动态库路径告知我们的程序,下面的语句可以在CMake
中导入动态(静态)库路径
1 | link_directories(path) |
7.日志
在CMakeLists.txt
中可以添加消息(日志)输出语句,在cmake
是会输出在控制台
1 | message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...) |
(无)
:重要消息STATUS
:非重要消息WARNING:CMake
警告,会继续执行AUTHOR_WARNING:CMake
警告 (dev
), 会继续执行SEND_ERROR
:CMake
错误,继续执行,但是会跳过生成的步骤FATAL_ERROR
:CMake
错误,终止所有处理过程
CMake
的命令行工具会在stdout
上显示 STATUS
消息,在 stderr
上显示其他所有消息。CMake
的 GUI 会在它的 log 区域显示所有消息。
CMake
警告和错误消息的文本显示使用的是一种简单的标记语言,文本没有缩进,段落之间以新行做为分隔符
1 | # 输出一般日志信息 |
8.变量操作
有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过 file
命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用 set
命令也可以使用 list
命令
(1)使用set进行拼接
使用set
对变量名进行拼接,如下语句
1 | set(变量名1 ${变量名1} ${变量名2} ...) |
关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖
1 | file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp) |
(2)使用list进行拼接
使用list
对变量名进行拼接,如下语句
1 | list(APPEND <list> [<element> ...]) |
list
命令的功能比set
要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND
表示进行数据追加,后边的参数和 set
就一样了
1 | file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp) |
在CMake
中,使用 set
命令可以创建一个 list
。set(var a b c d e)
命令将会创建一个 list a b c d e
,但是最终打印变量值的时候得到的是 abcde
1 | set(tmp1 a b c d e) |
控制台输出
1 | abcde |
(3)list字符串移除
我们在通过 file 搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如
1 | $ tree |
在当前这么目录有五个源文件,其中 main.cpp
是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要 a.cpp
、b.cpp
这两个源文件就可以了。此时,就需要将main.cpp
搜索到的数据中剔除出去,想要实现这个功能,也可以使用 list
1 | list(REMOVE_ITEM <list> <value> [<value> ...]) |
通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了 REMOVE_ITEM
1 | file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp) |
移除的文件的名字指定给list
就可以了。但是一定要注意通过 file
命令搜索源文件的时候得到的是文件的绝对路径(在 list
中每个文件对应的路径都是一个 item
,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否则移除操作不会成功
(4)list的其他操作
获取list
列表的长度、读取列表中指定索引的的元素,可以指定多个索引、将列表中的元素用连接符(字符串)连接起来组成一个字符串等…
9.宏定义
在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示
1 |
|
上述代码:
1 | #ifdef DEBUG |
对 DEBU
G 宏进行了判断,如果该宏被定义了,那么就会进行日志输出,如果没有定义这个宏,该段代码就相当于被注释掉了,因此最终无法看到日志输出(上述代码中并没有定义这个宏)
输出为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在 gcc/g++
命令中去指定,如下
1 | $ gcc test.c -DDEBUG -o app |
在 gcc/g++
命令中通过参数 -D 指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为 DEBUG
在CMakeLists.txt
中可以进行类似的定义
1 | add_definitions(-D宏名称) |
如下:
1 | cmake_minimum_required(VERSION 3.16) |
这样定义宏之后,上面的c++
代码中的注释部分会在编译的时候 在日志中输出
10.预定义宏
宏 | 功能 |
---|---|
PROJECT_SOURCE_DIR | 使用 cmake 命令后紧跟的目录,一般是工程的根目录 |
PROJECT_BINARY_DIR | 执行 cmake 命令的目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
PROJECT_NAME | 返回通过 PROJECT 指令定义的项目名称 |
CMAKE_BINARY_DIR | 项目实际构建路径,假设在 build 目录进行的构建,那么得到的就是这个目录的路径 |
11.关键词
(1)PROJECT()
可以用来指定工程的名字和支持的语言,默认支持所有语言
PROJECT (HELLO)
指定了工程的名字,并且支持所有语言—建议
PROJECT (HELLO CXX)
指定了工程的名字,并且支持语言是C++
PROJECT (HELLO C CXX)
指定了工程的名字,并且支持语言是C和C++
该指定隐式定义了两个CMAKE
的变量
<projectname>_BINARY_DIR
,本例中是 HELLO_BINARY_DIR
<projectname>_SOURCE_DIR
,本例中是 HELLO_SOURCE_DIR
MESSAGE关键字就可以直接使用者两个变量,都指向当前的工作目录
注意:如果改了工程名,这两个变量名也会改变,但是CMAKE
给出了解决方案,又定义两个预定义变量:PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
,这两个变量和HELLO_BINARY_DIR
,HELLO_SOURCE_DIR
是一致的。所以改了工程名也没有关系
(2)MESSAGE()
向终端输出用户自定义的信息
主要包含三种信息:
SEND_ERROR
,产生错误,生成过程被跳过。SATUS
,输出前缀为—的信息。FATAL_ERROR
,立即终止所有cmake
过程.
(3)add_subdirecroty指令
1 | ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
- 这个指令用于向当前工程添加存放源文件的子目录
(src)
,并可以指定中间二进制和目标二进制存放的位置(bin)
EXCLUDE_FROM_ALL
函数是将写的目录从编译中排除,如程序中的exampleADD_SUBDIRECTORY(src bin)
- 将
src
子目录加入工程并指定编译输出(包含编译中间结果)路径为bin
目录 - 如果不进行
bin
目录的指定,那么编译结果(包括中间结果)都将存放在build/src
目录
- 将
(4)link_directories
向工程添加多个特定的库文件搜索路径
1 | link_directories(dir1 dir2 ...) |
(5)add_compile_options
添加编译参数
1 | add_executable(exename source1 source2 ... sourceN) |
(6)CMAKE_C_FLAGS
gcc
编译选项
(7)CMAKE_CXX_FLAGS
g++编译选项
1 | # 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11 |
(8)CMAKE_BUILD_TYPE
编译类型(Debug, Release)
1 | # 设定编译类型为debug,调试时需要选择debug |
12.更改二进制保存路径
SET
指令重新定义 EXECUTABLE_OUTPUT_PATH
和 LIBRARY_OUTPUT_PATH
变量 来指定最终的目标二进制的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
哪里要改变目标存放路径,就在哪里加入上述的定义,所以应该在src
下的CMakeLists.txt
下写
13.安装
- 一种是从代码编译后直接
make install
安装 - 一种是打包时的指定 目录安装
- 简单的可以这样指定目录:
make install DESTDIR=/tmp/test
- 稍微复杂一点可以这样指定目录:
./configure –prefix=/usr
- 简单的可以这样指定目录:
二、嵌套CMAKE
如果项目很大,或者项目中有很多的源码目录,在通过 CMake
管理项目的时候如果只使用一个 CMakeLists.txt
,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt
文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护
项目目录结构:
1 | $ tree |
1.预备
(1)节点关系
Linux
的目录是树状结构,所以嵌套的 CMake
也是一个树状结构,最顶层的 CMakeLists.txt
是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt
文件变量作用域的一些信息:
- 根节点
CMakeLists.txt
中的变量全局有效 - 父节点
CMakeLists.txt
中的变量可以在子节点中使用 - 子节点
CMakeLists.txt
中的变量只能在当前节点中使用
(2)添加子目录
在CMake
中父子节点之间的关系是如何建立的,这里需要用到一个 CMake
命令:
1 | add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
source_dir
:指定了CMakeLists.txt
源文件和代码文件的位置,其实就是指定子目录binary_dir
:指定了输出文件的路径,一般不需要指定,忽略即可EXCLUDE_FROM_ALL
:在子路径下的目标默认不会被包含到父路径的ALL
目标里,并且也会被排除在IDE
工程文件之外。用户必须显式构建在子路径下的目标
2.实施
在上面的目录中我们要做如下事情:
通过
test1
目录中的测试文件进行计算器相关的测试通过
test2
目录中的测试文件进行排序相关的测试
现在相当于是要进行模块化测试,对于 calc
和 sort
目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。
(1)根目录
根目录(最外层的)中的 CMakeLists.txt
文件内容如下
1 | cmake_minimum_required(VERSION 3.16) |
在根节点对应的文件中主要做了两件事情:定义全局变量
和添加子目录
- 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的
CMakeLists.txt
文件的可读性和可维护性,避免冗余并降低出差的概率 - 一共添加了四个子目录,每个子目录中都有一个
CMakeLists.txt
文件,这样它们的父子关系就被确定下来了
(2)calc目录
calc
目录中的CMakeLists.txt
文件内容如下
1 | cmake_minimum_required(VERSION 3.16) |
file(GLOB SRC ./)
可以使用aux_source_directory(./ SRC)
进行代替,搜索当前目录(calc
)下的所有源文件,并且存储至 SRC变量中include_directories(${HEAD_PATH})
,包含头文件路径,HEAD_PATH
是在根节点(最外层的CMakeLists.txt
)文件中定义的set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
中重新定义生成的库文件存放在的位置LIBRARY_OUTPUT_PATH
,该程序中是生成静态库,LIB_PATH
是在根节点(最外层的CMakeLists.txt
)文件中定义的add_library(${CALC_LIB} STATIC ${SRC})
:生成静态库,静态库名字CALC_LIB
是在根节点文件中定义的
(3)sort目录
sort
目录中的 CMakeLists.txt
文件内容如下:
1 | cmake_minimum_required(VERSION 3.16) |
add_library(${SORT_LIB} SHARED ${SRC})
:生成动态库,动态库名字SORT_LIB
是在根节点文件中定义的注意: 在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库
(4)test1目录
test1
目录中的CMakeLists.txt
文件内容如下
1 | cmake_minimum_required(VERSION 3.16) |
link_libraries(${CALC_LIB})
链接静态库,其实对于静态库的链接也可以target_link_libraries(可执行文件名 静态库名)
当然需要放到add_executable(${APP_NAME_1} ${SRC})
之后add_executable
:生成可执行程序,APP_NAME_1
变量是在根节点文件中定义的set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
重新定义目标二进制可执行文件的存放位置
注意: 此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了
(5)test2目录
test2
目录中的 CMakeLists.txt
文件内容如下
1 | cmake_minimum_required(VERSION 3.16) |
link_directorie
s:指定可执行程序要链接的动态库的路径,LIB_PATH
变量是在根节点文件中定义的add_executable
:生成可执行程序,APP_NAME_2
变量是在根节点文件中定义的target_link_libraries
:指定可执行程序要链接的动态库的名字
注意: 在生成可执行程序的时候,动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存,只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,且多个进程可以共用内存中的同一个动态库,所以动态库又叫共享库