CMake

这里画一个图直接直接展示一下CMake和Makefile的使用过程

1
g++ *.cpp -o app  //直接编译    

使用#注释

块注释#[[ ]]

camke_minimum_required:指定使用的cmake的最低版本

project:定义工程名称

add_executable:定义工程会生成一个可执行程序 add_executable(可执行程序名称 源文件名称(空格或者分号隔离源文件))

cmake CMakeLists.txt:文件所在路径

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.15)
project(test)
add_executable(app, add.cpp div.cpp mult.cpp main.cpp sub.cpp)

#如果CMakeLists.txt文件就在当前目录下,直接执行以下命令
cmake .
make
#就会出现app可执行文件

生成了很多其他文件,我们可以把这些临时文件放在一个文件夹里,一般为build文件夹。创建build文件后,cd进build文件,执行camke ..,则cmake执行后生成的文件都放在了build文件夹里。

set的使用

set是为了简化add_executable(app, add.cpp div.cpp mult.cpp main.cpp sub.cpp)的编写,要不然这样和直接编译感觉没啥区别。set给一个变量赋值都是字符串类型,这些文件的名字会作为一个字符串存在变量里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# set指令的语法是:
# [] 中的参数为可选项,如不需要可以不写
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

# VAR:变量名
# VALUE:变量值

# 方式一:各个源文件之间使用空格间隔
set(SRC_LIST add.c div.c main.c mult.c sub.c)

# 方式二:各个源文件之间使用;间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)

add_executable(app ${SRC_LIST})

取变量值必须以这种方式。

1
2
# "$ + {变量名}"
${SRC_LIST}

还可以通过set设置使用C++的标准(C++11,C++17)。

1
2
# 正常编译选定c++标准
g++ *.cpp -std=c++11 -o app
1
2
3
4
5
6
7
8
9
10
11
# 使用camke指定c++标准,对应有一个宏叫做DCMAKE_Cxx_STANDARD
# 由两种方式指定C++标准
# 第一种:在CMakeLists.txt中通过set命令指定
set(CAMKE_CXX_STANDARD 11) # 等价于增加 -std=c++11
set(CAMKE_CXX_STANDARD 14)
set(CAMKE_CXX_STANDARD 17)

# 第二种:在执行camke命令的时候制定出这个宏的值
camke CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

set还可以指定输出路径,建议使用绝对路径,如果这个路径中的子目录不存在,会自动生成,无需自己手动创建。

1
2
3
4
5
# 在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令设置
set(HOME /home/zcl/linux/soft)
set(EXECUTABLE_UOTPUT_PATH ${HOME}/bin)
# 第一行:定义一个变量用于存储一个绝对路径
# 第二行:将拼接好的路径值设置给EXECUTABLE_OUTPUT_PATH宏

搜索路径

我们可以看到我们上面所讲的使用set并没有解决要将所有源文件名字写出来的本质问题。为了解决这个问题,这里我们讲一下通过搜索某个目录下的文件来引入源文件的方法。CMake给我们提供了两种方法来搜索文件,aux_source_directory命令或者file命令。

方式一

在CMake中使用aux_source_directory命令可以查找某个路径下的所有源文件。

1
2
3
4
5
6
7
8
9
10
# 命令格式
aux_source_directory(<dir> <variable>)
# dir:要搜索的目录
# variable:将从dir目录下搜索到的源文件列表存储到该变量中
# 这里介绍一个宏,PROJECT_SOURCE_DIR宏代表的就是我们在camke命令后携带的那个路径。比如camke ..,PROJECT_SORCE_DIR宏代表的就是`..`
# 再介绍另外一个宏,CMAKE_CURRENT_SOURCE_DIR宏代表的就是当前CMakelists.txt文件所在的路径
# 下面进入实操,比如搜索当前CMakelists.txt文件所在目录下的src目录
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)
add_executable(app ${SRC_LIST})

方式二

通过file命令来搜索出所有需要的源文件

1
2
3
4
5
6
7
8
9
10
# 命令格式
file(GLOB 变量名 要搜索的文件路径和文件类型)
file(GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
# GLOB:将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中
# GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
# 比如:搜索当前目录的src目录下所有的源文件,并将其存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CAMKE_CURRENT_SOURCE_DIR}/include/*.h)
# 注:关于要搜索的文件路径和类型可以加双引号,也可不加
file(GLOB MAIN_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

搜索头文件路径

在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能找到这些头文件。在CMake里搜索头文件的命令也很简单

1
2
3
4
include_directories(headpath)
# headpath就是头文件路径,这里建议写绝对路径
# 实操
include_directories(${PROJECT_SOURCE_DIR}/include)

通过CMake制作库文件

有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,下面来讲解在cmake中生成这两类库文件的方法。

制作静态库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 在cmake中,如果要制作静态库,需要使用的命令如下:
add_library(库名称 STATIC 源文件1 [源文件2] ...)
# 注:在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
# 如果是动态库,最终生成的库的名字就是:libXXX.so(Linux),libXXX.dll(Windows);
# 如果是静态库,最终生成的库的名字就是:libXXX.a(Linux),libXXX.lib(Windows);
# 下面有一个目录,需要将src目录中的源文件编译成静态库,然后再使用:
.
├── 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})
# 注:STATIC对应的是静态库,SHARED对应的是动态库

制作动态库

1
2
3
4
5
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 SHARED ${SRC_LIST})

指定输出路径

对于生成的库文件来说和可执行程序一样都可以指定输出路径。这里使用LIBRARY_OUTPUT_PATH宏,这个宏对应静态库文件和动态库文件都适用。

1
2
3
4
5
6
7
8
9
10
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})

包含库文件

在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake中也为我们提供了相关的加载动态库的命令。

链接静态库

1
2
3
4
5
6
src
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 测试目录结构
$ tree
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   └── libcalc.a # 制作出的静态库的名字
└── src
└── main.cpp

4 directories, 4 files

在cmake中,链接静态库的命令如下:

1
2
3
link_libraries(<static lib> [<static lib>...])
# 参数1:指定出要链接的静态库的名字,可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
# 参数2-N:要链接的其它静态库的名字

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

1
link_directories(<lib path>)

这样,修改之后的CMakeLists.txt文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径 添加了这行代码,就可以根据参数指定的路径找到这个静态库了。
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

链接动态库

target不知道这个符号是来自它链接的多个库中的哪一个库,它只知道有这么一个库。

FATAL_ERROR:CMake 错误, 终止所有处理过程(CMake在生成”message to display”这条消息之后就不在执行了,直接中断)

CMake在底层管理的时候会将子字符串通过分号隔开,但通过message打印变量值的时候,看不到这个分号。这个分号有助于cmake进行字符串删除操作。只能删除组成变量的子串。比如一开始SRC=“A123”,后来apeend了“456”, “789”,如果没有分号,你可能可以删除”345”,但有了分号后,就删除不了了,你只能删除组成SRC的完整的子串,比如”A123”, “456”等。

注意,存储列表长度的output variable依旧是一个字符串类型。