目录
一. CMake概述
CMake 跨平台的开源构建工具。cmake可以根据CmakeLists.txt文件指定生成器而生成相应的构建系统文件,比如Makefile,Ninja,Visual Studio项目文件等。因此开发者只需编写一个 CMakeLists.txt 文件,就可以在不同的操作系统和编译器上构建项目。
简单来说CMake和make的区别就行,Cmake主要用于生成构建系统的文件,而Make则是实际执行构建的过程。并且Cmake也更加灵活,编写起来也更加容易。
CMake 也允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需 make 编译即可,所以可以把 CMake 看成一款自动生成 Makefile 的工具,其编译流程如下图
1.1 优点
- 跨平台
- 能够管理大型项目
- 简化编译构建过程和编译过程
- 可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能
1.2 使用
1.2.1 gcc静态库
- 编译源文件生成目标文件.o
gcc -c add.c div.c mult.c sub.c
- 创建静态库
ar rcs libexample.a add.o div.o mult.o sub.o
- 使用静态库
gcc main.c -o my_program -L. -lexample
1.2.2 gcc动态库
-fPIC
选项生成位置无关的代码,以便创建动态库。
gcc -fPIC -c add.c div.c mult.c sub.c
- 创建动态库
gcc -shared -o libexample.so add.o div.o mult.o sub.o
- 使用动态库:在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
gcc main.c -o my_program -L. -lexample
二.CMake 的使用
- CMake 支持大写、小写、混合大小写的命令。如果在编写 CMakeLists.txt 文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
2.1 注释
- CMake 使用 #[[ ]] 形式进行块注释。
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)
2.2 基础语法
2.2.1 根目录和当前目录
PROJECT_SOURCE_DIR和CMAKE_CURRENT_SOURCE_DIR:
- `PROJECT_SOURCE_DIR` 是指整个项目的根目录
- `CMAKE_CURRENT_SOURCE_DIR` 是指当前正在处理的 CMakeLists.txt 文件所在的目录。它们可以用来指定文件的路径,但使用时需要注意它们的含义和适用范围。
2.2.2 指定工程名
- PROJECT_NAME:可以获得project()指定的工程名称
- project()是用来指定一个新的工程,如果当前CMakeList文件设置了该值,那么PROJECT_SOURCE_DIR就是当前的目录的值,否则就是上一层包含project()目录的值
2.2.3 编译文件独立存放
- 我们可以在CMakeList.txt目录下创建一个build目录,然后编译时进入build目录,使用cmake .. 将生成的编译文件全部放在build目录下。
2.24 指定可执行文件程序名
# 样式1
add_executable(app add.c div.c main.c mult.c sub.c)
# 样式2
add_executable(app add.c;div.c;main.c;mult.c;sub.c)
2.3 set
2.3.1 set基础用法
- 方式1: 各个源文件之间使用空格间隔
set(SRC_LIST add.c div.c main.c mult.c sub.c)
- 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app ${SRC_LIST})
2.3.2 指定使用的 C++ 标准
#增加-std=11
set(CMAKE_CXX_STANDARD 11)
#或者还有一种方法 在执行cmake命令的时候使用
cmake xxxx.txt -DCMAKE_CXX_STANDARD=11
2.3.4 指定输出路径
- 这里的set设置变量时可以使用相对路径
set(mydirectory /home/Linux/project)
set(EXECUTABLE_OUTPUT_PATH ${mydirectory}/bin)
#如果此处指定可执行程序生成路径相对路径 ./xxx/xxx,那么这个路径对应的就是 makefile 文件所在的那个目录而不是CMakeList.txt文件.
2.4 搜索文件
- aux_source_directory: 查找某个路径下的所有源文件
- aux_source_directory(< dir > < variable >)
#dir:要搜索的目录
#variable:将从 dir 目录下搜索到的源文件列表存储到该变量中
cmake_minimum_required(VERSION 3.0)
PROJECT(TEST)
include_directories(${PROJECT_SOURCE_DIR}/include)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/SRC src_list)
add_executable(${PROJECT_NAME} ${SRC_LIST})
- FILE: 和aux_source_directory作用一样。但file的功能更强大
- file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
- GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
- GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
- 搜索当前目录的 src 目录下所有的源文件,并存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
#CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。
2.4.1包含头文件
- include_directories` ([`AFTER|BEFORE`] [`SYSTEM`] dir1 [dir2 ...])**
将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径。
- 命令解析
- 默认情况下,`include_directories`命令会将目录添加到列表最后,可以通过命令设置`CMAKE_INCLUDE_DIRECTORIES_BEFORE`变量为`ON`来改变它默认行为,将目录添加到列表前面。也可以在每次调用`include_directories`命令时使用`AFTER`或`BEFORE`选项来指定是添加到列表的前面或者后面。
- 如果使用`SYSTEM`选项,会把指定目录当成系统的搜索目录。该命令作用范围只在当前的CMakeLists.txt以及子的CMakeList.txt
注意: 如果使用命令 SYSTEM
选项用于将指定目录视为系统搜索目录。那么目录下的头文件将被视为系统头文件,而不会被认为是普通的用户自定义头文件。这可以避免某些警告或冲突,特别是当您的项目与系统提供的头文件具有相同的名称时。
cmake_minimum_required(VERSION 3.18.2)
project(include_directories_test)
include_directories(sub)
include_directories(sub2) #默认将sub2添加到列表最后
include_directories(BEFORE sub3) #可以临时改变行为,添加到列表最前面
get_property(dirs DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
message(">>> include_dirs=${dirs}") #打印一下目录情况
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON) #改变默认行为,默认添加到列表前面
include_directories(sub4)
include_directories(AFTER sub5) #可以临时改变行为,添加到列表的最后
get_property(dirs DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
message(">>> SET DEFAULT TO BEFORE, include_dirs=${dirs}")
三. 制作库和链接库
3.1 静态和动态库生成
-
静态库名字分为三部分:lib+ 库名字 +.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
add_library(库名称 STATIC 源文件1 [源文件2] ...)
├── build
├── CMakeLists.txt
├── include # 头文件目录
│ └── head.h
├── main.cpp # 用于测试的源文件
└── src # 源文件目录
├── add.cpp
├── div.cpp
├── mult.cpp
└── sub.cpp
根据上面的目录结构,可以这样编写 CMakeLists.txt 文件:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})
# 动态库制作
# add_library(calc SHARED ${SRC_LIST})
3.1.2 指定库的输出路径
- 动态库方式
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})
由于在 Linux 下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用 EXECUTABLE_OUTPUT_PATH 宏了,而应该使用 LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。
- 静态库方式:这种方式也适用于动态库
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})
3.2静态库链接
├── build
├── CMakeLists.txt
├── include
│ └── head.h
├── lib
│ └── libcalc.a # 制作出的静态库的名字
└── src
└── main.cpp
3.2.1 链接静态库
link_libraries(<static lib> [<static lib>...])
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
#包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
#需要注意的是,link_libraries()函数的参数是针对整个项目的,因此如果在同一个项目中有多个目标,它们都会使用相同的链接库列表。如果要为特定目标指定链接库,可以使用target_link_libraries()函数。
add_executable(app ${SRC_LIST})
3.3 动态库链接
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
- target:指定要加载动态库的文件的名字
- PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为 PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
PUBLIC:在 public 后面的库会被 Link 到前面的 target 中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在 private 后面的库仅被 link 到前面的 target 中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在 interface 后面引入的库不会被链接到前面的 target 中,只会导出符号。
3.3.1动态库之间的相互链接
target_link_libraries(A B C)
target_link_libraries(D A)
第一行将target_link_libraries(A B C)库 B 和 C 添加为目标 A 的依赖项。这意味着当构建目标 A 时,它将链接到库 B 和 C。
第二行将target_link_libraries(D A)目标 A 添加为目标 D 的依赖项。这意味着当构建目标 D 时,它将首先构建目标 A(如果尚未构建),然后链接到其输出。
所以,这两行的整体效果是:
目标 A 依赖库 B 和 C。
目标 D 依赖于目标 A,后者又依赖于库 B 和 C。
构建目标 D 时,构建系统将确保以正确的顺序构建和链接所有依赖项。具体来说,它将首先构建目标 A(如果尚未构建),然后链接到库 B 和 C,最后链接到目标 A 的输出。
3.3.1 链接系统动态库
cmake 中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)
- app: 对应的是最终生成的可执行程序的名字
- pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为 libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。
3.3.2 链接第三方动态库
cmake_minimum_required(VERSION 3.0)
project(CALC)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/SHARE_TEST)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/lib)
add_executable(app ${SRC_LIST})
TARGET_LINK_LIBRARIES(app calc)
3.4 优缺点
3.4.1 静态库
优点:
- 静态库被打包到应用程序中加载速度快
- 发布程序无需提供静态库,移植方便
缺点:
- 相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存
- 库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。
3.4.1 动态库
优点:
- 可实现不同进程间的资源共享
- 动态库升级简单,只需要替换库文件,无需重新编译应用程序
- 程序猿可以控制何时加载动态库,不调用库函数动态库不会被加载
缺点:
- 加载速度比静态库慢,以现在计算机的性能可以忽略
- 发布程序需要提供依赖的动态库
四 日志
- 在 CMake 中可以用用户显示一条消息,该命令的名字为 message:
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 警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")