Cmake代码操作详解

Cmake代码操作详解

1.基础CMakeLists编写操作

1.1.初始条件

CMake
 |-study_project
     |-add.c
     |-add.h
     |-main.c
     |-CMakeLists.txt

1.2.CMakeLists中的基础编写

CMake
cmake_minimum_required(VERSION 3.15
project(study_project)
add_executable(kzxcx add.c add.h main.c)

cmake_minimum_required:指定使用的Cmake的最低版本,代码中我们指定的版本是3。15

注:可选,非必须,如果不加可能会有警告

project:定义工程名称

add_executable:定义工程会生成一个可执行程序

CMake
add_executable(可执行程序名 源文件名称)

当有多个文件时,有两种编写方案

CMake
#样式1
add_executable(kzxcx add.c add.h main.c)
#样式2
add_executable(kzxcx add.c;add.h;main.c)

1.3.执行CMake命令操作详解

CMake
 |-study_project
     |-add.c
     |-add.h
     |-main.c
     |-CMakeLists.txt

将CMakeLists.txt文件编写好了之后,就可以执行cmake命令了。

CMake
# cmake 命令原型
cmake CMakeLists.txt文件所在目录(用 . 来表示)
# .表示当前目录,..表示上一级目录

当前路径:~/study_project$

CMake
# 已经在CMakeLists所在路径中
cmake .
# 生成的文件也在该目录(src)

但是生成的一系列文件会混淆当前目录的文件,所以需要创建一个对应的目录,将生成的文件存入该目录

操作:mkdir build

Plain Text
  |-study_project
     |--build
        |
     |-add.c
     |-add.h
     |-main.c
     |-CMakeLists.txt

然后我们进入build目录进行CMakeLists的编译生成

操作:cd build

当前路径:~/study_project/build/$

所以相对于build的目录,CMakeLists在上一级目录

CMake
# CMakeLists所在目录在上一级
cmake ..
# 生成的文件在该目录(build)

在build的目录中,只要Makefile文件生成了,就没什么问题了

最后我们要通过Makefile文件生成对应的可执行程序

当前路径:~/study_project/build/$

CMake
# 通过Makefile生成可执行程序
make

于是当前列表就会有一个可执行程序kzxcx(add_executable中的可执行程序名)

操作:ls 

CMake
kzxcx CMakeCache.txt CMakeFiles Makefile cmake_install_cmake

运行:./ + 可执行程序

操作:./kzxcx

2.set的使用

2.1.初始条件

CMake
 |-study_project
     |--build
        |-
     |-add.c
     |-add.h
     |-main.c
     |-CMakeLists.txt

2.2.定义变量

在“1.基础CMakeLists编写操作”中,假设3个源文件需要反复被使用,用起来会比较麻烦,此时我们需要定义一个变量,将文件名对应的字符储存起来,在CMake中定义变量需使用set

CMake
# set 指令的语法是:
# []中的参数为可选项,如不需要可以不写
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

VAR:变量名

VALUE:变量值

第三个:一般用不到

变量值之间的间隔同样是两种方法:1.空格隔开 2.‘ ;’号隔开2.

具体使用:

CMake
cmake_minimum_required(VERSION 3.15
project(study_project)
set(SUM add.c add.h main.c)  #相当于将add.c,add.h,main.c 存入变量sum中
add_executable(kzxcx ${SUM}) #调用sum中的元素需要使用 ${}取变量的值

2.3.指定使用的C++标准

在实际C++使用中,我们可能会用到C++11,C++14,C++17,C++20等新特性,像auto的是C++11中的新特性,不指定C++11的标准的话,编译器会默认使用C++98的标准,导致编译出错

决定C++版本的宏是CMAKE_CXX_STANDARD

2.3.1.CMakeLists中通过set命令指定

CMake
# 增加-std=C++11
set(CMAKE_CXX_STANDARD 11)
# 增加-std=C++14
set(CMAKE_CXX_STANDARD 14)
# 增加-std=C++17
set(CMAKE_CXX_STANDARD 17)

2.3.2.在执行CMake命令的时候指定这个宏的值

其中‘-D’是指定宏的值

CMake
# 增加-std=C++11
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11

2.4.指定输出的路径

在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH

CMake
set(HOME /home)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin}

第一行:定义一个变量用于储存一个路径

第二行:将拼接好的路径设置给EXECUTABLE_OUTPUT_PATH

2.5.set的使用(综合上面基础)

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中
set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
add_executable(kzxcx ${SUM})  # 调用sum中的元素需要使用 ${}取变量的值

3.搜索文件

当一个项目有很多个文件时,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,就算是set的也不太现实,所以CMake提供了搜索文件的命令,可以使用aux_source_directory命令或者file命令

3.1.方式1

在CMake中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命名格式为:

CMake
aux_source_directory(<dir> <variable>)

dir:要搜索的目录

variable:将从dir目录下搜索到的源文件(.c,.cpp)列表储存到该变量(<variable>)中

3.2.方式2

在CMake中使用file

CMake
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)

GLOB:(搜索当前目录)将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其储存到变量

GLOB_RECURSE:(搜索当前目录以及它的子目录)递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中

事例:

CMake
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找src目录下的所以cpp文件并存入MAIN_SRC
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)#  找include目录下的.h文件并存入MAIN_HEAD

CMAKE_CURRENT_SOURCE_DIR宏表示当前访问的CMakeLists.txt文件所在路径

注:要搜索的文件路径可加双引号,也可以不加

3.3.初始条件

CMake
 |-study_project
     |--build
        |-
     |-add.c
     |-add.h
     |-main.c
     |-CMakeLists.txt

3.4.搜索文件的使用(综合上面)

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中

aux_source_directory(${PROJECT_SOURCE_DIR} SRC) # 搜索当前CMakeLists的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)# 找到CMakeLists的目录下的所有cpp文件并存入MAIN_SRC

set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
add_executable(kzxcx ${SRC})  # 调用SRC中的元素需要使用 ${}取变量的值

PROJECT_SOURCE_DIR :是指执行cmake命令时后面携带的路径,该路径与CMakeLists一致,所以在搜索时后面不添加路径,与CMAKE_CURRENT_SOURCE_DIR差不多

CMAKE_CURRENT_SOURCE_DIR :当前CMakeLists的路径

file中的GLOB:搜索当前目录

file中的GLOB_RECURSE:搜索当前目录以及它的子目录

4.(包含头文件)指定头文件路径

4.1.初始条件

操作:mkdir src include

并将代码存入src中,头文件存入include,这样项目study_project就有三个子目录,include,src,build

CMake
|-study_project
    |--build
       |-
    |--include
       |-add.h
    |--src
       |-add.c
       |-main.c
    |-CMakeLists.txt

现在CMakeLists.txt在src外,需要进行调整

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中

# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC
# set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)

set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
add_executable(kzxcx ${SRC})  # 调用SRC中的元素需要使用 ${}取变量的值

由于现在add.h头文件不与代码在同一个目录,add.c代码的头文件(add.h)在查找路径的时候查找不到,编译运行时会发生错误。这时我们需要将源文件的头文件路径指定出来,这样才能保证编译过程中编译器能找到这些头文件。

在CMake设置要包含的目录,通过include_directories就行。例:

CMake
include_directories(headpath)

4.2.包含头文件路径使用(综合上面)

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中

# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC
# set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录

set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
add_executable(kzxcx ${SRC})  # 调用SRC中的元素需要使用 ${}取变量的值

5.制作库文件

5.1.制作动态库

动态库:在程序编译时,并不会被放到连接的目标代码中,而是在程序运行时载入,程序运行时还需动态库。

动态库也叫共享库,目的是减少目标文件的大小

CMake
add_library(库名称 SHARED 源文件1 [源文件2] ...)

动态库分为三部分:lib + 库名字 + .so,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。

 当前初始条件

CMake
|-build
|-CMakeLists.txt
|--include
   |-head.h
|-main.cpp
|--src
   |-add.cpp
   |-div.cpp
   |-mult.cpp
   |-sub.cpp

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中
# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC
# set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录
# set(EXECUTABLE_OUTPUT_PATH  /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
# add_executable(kzxcx ${SRC})  # 调用SRC中的元素需要使用 ${}取变量的值

add_library(calc SHARED ${SRC})

经过cmake生成Makefile再经过make后最后生成的是libcalc.so

动态库有可执行权限

5.2.制作静态库

静态库:静态库在程序编译时,会被连接到目标代码中,程序运行时,将不再需要静态库

CMake
add_library(库名称 STATIC 源文件1 [源文件2] ...)

静态库分为三部分:lib + 库名字 + .a,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。

静态库制作与动态库相似,只要把SHARED改为STATIC就行,最后生成的文件时的文件是libcalc.a

静态库没有可执行权限

5.3.指定库的生成路径

通过set命令给EXECUTABLE_OUT_PATH宏设置一个路径,这个路径就是可执行文件生成的路径

EXECUTABLE_OUT_PLAIN宏:这个宏是可执行文件的路径,静态库不行

LIBRARY_OUTPUT_PATH宏:这个静态和动态都通用

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中
# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC
# set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录
# set(EXECUTABLE_OUTPUT_PATH  /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径
# add_executable(kzxcx ${SRC})  # 调用SRC中的元素需要使用 ${}取变量的值
set(LIABRARY_OUTPUT_PATH home/hafisdf/fhadsh/) # 如果右边的路径不存在,会将路径先生成出来
add_library(calc SHARED ${SRC})

5.4.总结

动态库文件和静态库文件本质都是二进制文件,一个有可执行权限,一个没有,一个是程序运行时共享,一个是编译时存入,一个节省文件空间,一个运行不需要库。

6.在程序中链接库

6.1.链接静态库

当前初始条件

CMake
|-build
|-CMakeLists.txt
|--include
   |-head.h
|-main.cpp
|--lib1
   |-libcalc.a #
静态库
|--lib2
   |-libcalc.so #
动态库

链接静态库

CMake
link_libraries(<static lib> [stdtic lib> ...])

参数1:可以是全名libxxx.a

也可以是xxx

参数2-N:要链接其他库的名称

自己制作或者是第三方制作的链接库可能出现静态库找不到的情况,我们可以将静态库的路径也指定出来

CMake
link_directories(<lib path>) # 指定库的路径

修改后的CMakeLists文件内容如下:

CMake
cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本
project(study_project) #项目名称
# set(SUM add.c add.h main.c)  # 相当于将add.c,add.h,main.c 存入变量sum中
# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC
# set(CMAKE_CXX_STANDARD 11)  # C++标准赋值(声明)
include_directories(${PROJECT_SOURCE_DIR}/include) # 指定头文件的目录
link_libraries(calc) # 链接静态库
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib1) # 把静态库路径显示出来
add_executable(kzxcx ${SRC})

怎么把库文件的代码也追加到SRC中?就是经过link_libraries链接,在生成可执行文件时,会把源文件和库文件放在一起最终生成可执行程序。使用静态库最终与源文件都会被打包到可执行程序里面去。

6.2.链接动态库

cmake中链接动态库的命令如下:

CMake
target_link_libraries(
    <target>
    <PRIVATE [PUBLIC]INTERFACE> <item>..
    [<PRIVATE [PUBLIC]INTERFACE> <item>..]..)

targe:指要要加载动态链接库的文件名字

该文件可能是一个源文件

该文件可能是一个动态库文件

该文件可能是一个可执行文件

PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC

如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可

动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。

CMake
target_link_libraries(A B C)
target_link_libraries(D A)

假如A,B,C,D都是公共的动态库,那么调用D库时就可以用D,A,B,C的库,B,C会链接到A,A会链接到D

假如B,C是私有的动态库,那么调用A(B,C只会传递一次)的可以调用B,C,调用D的时候不能调用B,C

PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。

PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库

INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

动态库的链接和静态库是完全不同的

静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动静态库就被加载到内存中了。

动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后

CMake
cmake_minimum_required(VERSION 3.0)
project(study_project)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include) #
添加头文件路径
# 声明链接库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib2)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

在target_link_libraries(app pthread)中:

app: 对应的是最终生成的可执行程序的名字

pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。

6.3.总结使用

CMake
$ tree
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h            #
动态库对应的头文件
├── lib
│   └── libcalc.so        # 自己制作的动态库文件
└── main.cpp              # 测试用的源文件

假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(app ${SRC_LIST})
target_link_libraries(app pthread calc)

在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:

CMake
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置,这需要link_directories(path)

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
#
指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)

7.cmake日志输出

在CMake中可以用用户显示一条消息,该命令的名字为message

CMake
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警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符

CMake
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

8.变量操作

8.1 追加

使用set拼接

CMake
set(变量名1 ${变量名1} ${变量名2} ...)

例:

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
#
追加(拼接)
set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

使用list拼接

CMake
list(APPEND <list> [<element> ...])

list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了。

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
#
追加(拼接)
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde

CMake
set(tmp1 a;b;c;d;e)
set(tmp2 a b c d e)
message(${tmp1})
message(${tmp2})

结果:

CMake
abcde
abcde

8.2 字符串移除

我们在通过file搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:

CMake
$ tree
.
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

0 directories, 5 files

在当前这个目录有五个源文件,其中main.cpp是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要add.cppdiv.cpmult.cppsub.cpp这四个源文件就可以了。此时,就需要将main.cpp从搜索到的数据中剔除出去,想要实现这个功能,也可以使用list

CMake
list(REMOVE_ITEM <list> <value> [<value> ...])

通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了REMOVE_ITEM

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
#
移除前日志
message(STATUS "message: ${SRC_1}")
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)
# 移除后日志
message(STATUS "message: ${SRC_1}")

可以看到,在8把将要移除的文件的名字指定给list就可以了。但是一定要注意通过 file 命令搜索源文件的时候得到的是文件的绝对路径(在list中每个文件对应的路径都是一个item,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否是移除操作不会成功.

list命令还有其它功能

1.获取 list 的长度。

CMake
list(LENGTH <list> <output variable>)

LENGTH:子命令LENGTH用于读取列表长度

<list>:当前操作的列表

<output variable>:新创建的变量,用于存储列表的长度。

2.读取列表中指定索引的的元素,可以指定多个索引

CMake
list(GET <list> <element index> [<element index> ...] <output variable>)

<list>:当前操作的列表

<element index>:列表元素的索引

从0开始编号,索引0的元素为列表中的第一个元素;

索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推

当索引(不管是正还是负)超过列表的长度,运行会报错

<output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。

3.将列表中的元素用连接符(字符串)连接起来组成一个字符串

CMake
list (JOIN <list> <glue> <output variable>)

<list>:当前操作的列表

<glue>:指定的连接符(字符串)

<output variable>:新创建的变量,存储返回的字符串

4.查找列表是否存在指定的元素,若果未找到,返回-1

CMake
list(FIND <list> <value> <output variable>)

<list>:当前操作的列表

<value>:需要在列表中搜索的元素

<output variable>:新创建的变量

如果列表<list>中存在<value>,那么返回<value>在列表中的索引

如果未找到则返回-1。

5.将元素追加到列表中

CMake
list (APPEND <list> [<element> ...])

6.在list中指定的位置插入若干元素

CMake
list(INSERT <list> <element_index> <element> [<element> ...])

7.将元素插入到列表的0索引位置

CMake
list (PREPEND <list> [<element> ...])

8.将列表中最后元素移除

CMake
list (POP_BACK <list> [<out-var>...])

9.将列表中第一个元素移除

CMake
list (POP_FRONT <list> [<out-var>...])

10.将指定的元素从列表中移除

CMake
list (REMOVE_ITEM <list> <value> [<value> ...])

11.将指定索引的元素从列表中移除

CMake
list (REMOVE_AT <list> <index> [<index> ...])

12.移除列表中的重复元素

CMake
list (REMOVE_DUPLICATES <list>)

13.列表翻转

CMake
list(REVERSE <list>)

14.列表排序

CMake
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])

COMPARE:指定排序方法。有如下几种值可选:

STRING:按照字母顺序进行排序,为默认的排序方法

FILE_BASENAME:如果是一系列路径名,会使用basename进行排序

NATURAL:使用自然数顺序排序

CASE:指明是否大小写敏感。有如下几种值可选:

SENSITIVE: 按照大小写敏感的方式进行排序,为默认值

INSENSITIVE:按照大小写不敏感方式进行排序

ORDER:指明排序的顺序。有如下几种值可选:

ASCENDING:按照升序排列,为默认值

DESCENDING:按照降序排列

9.宏定义

在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:

C++
#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("
我是一个程序猿, 我不会爬树...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

在程序的第七行对DEBUG进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。

为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在gcc/g++命令中去指定,如下:

CMake
$ gcc test.c -DDEBUG -o app

gcc/g++命令中通过参数 -D指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为DEBUG。

CMake中我们也可以做类似的事情,对应的命令叫做add_definitions:

CMake
add_definitions(-D宏名称)

针对于上面的源文件编写一个CMakeLists.txt,内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(TEST)
#
自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./test.c)

通过这种方式,上述代码中的第八行日志就能够被输出出来了。

预处理宏

一些CMake中常用的宏:

  • 宏        功能
  • 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目录进行的构建,那么得到的就是这个目录的路径

10.嵌套的CMake

如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。

10.1 前提条件

先来看一下下面的这个的目录结构:

CMake
$ tree
.
├── build
|—— CMakeLists.txt
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp #
插入排序
│   └── select.cpp #选择排序
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files

include 目录:头文件目录

calc 目录:目录中的四个源文件对应的加、减、乘、除算法

对应的头文件是include中的calc.h

sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法

对应的头文件是include中的sort.h

test1 目录:测试目录,对加、减、乘、除算法进行测试

test2 目录:测试目录,对排序算法进行测试

10.2 准备工作

众所周知,Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:

  • 根节点CMakeLists.txt中的变量全局有效
  • 父节点CMakeLists.txt中的变量可以在子节点中使用
  • 子节点CMakeLists.txt中的变量只能在当前节点中使用

接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:

CMake
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录

binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。

EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显示构建在子路径下的目标。

通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。

10.3 解决问题

在上面的目录中我们要做如下事情:

通过 test1 目录中的测试文件进行计算器相关的测试

通过 test2 目录中的测试文件进行排序相关的测试

现在相当于是要进行模块化测试,对于calcsort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。

10.3.1 根目录

根目录中的 CMakeLists.txt文件内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(test)
#
定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录

  • 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
  • 一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。

10.3.2 calc 目录

calc 目录中的 CMakeLists.txt文件内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})

  • 第3行aux_source_directory:搜索当前目录(calc目录)下的所有源文件
  • 第4行include_directories:包含头文件路径,HEAD_PATH是在根节点文件中定义的
  • 第5行set:设置库的生成的路径,LIB_PATH是在根节点文件中定义的
  • 第6行add_library:生成静态库,静态库名字CALC_LIB是在根节点文件中定义的

10.3.3 sort 目录

sort 目录中的 CMakeLists.txt文件内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})

  • 第6行add_library生成动态库,动态库名字SORT_LIB是在根节点文件中定义的

这个文件中的内容和calc节点文件中的内容类似,只不过这次生成的是动态库。

10.3.4 test1 目录

test1 目录中的 CMakeLists.txt文件内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC) #
搜索当前目录所有源代码
include_directories(${HEAD_PATH}) # 包括头文件的路径
link_directories(${LIB_PATH}) # 包括库的路径
link_libraries(${CALC_LIB}) # 链接静态库文件
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) # 设置输出路径
add_executable(${APP_NAME_1} ${SRC}) #名称(在根目录定义了)和代码

  • 第4行include_directories:指定头文件路径,HEAD_PATH变量是在根节点文件中定义的
  • 第6行link_libraries:指定可执行程序要链接的静态库,CALC_LIB变量是在根节点文件中定义的
  • 第7行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
  • 第8行add_executable:生成可执行程序,APP_NAME_1变量是在根节点文件中定义的

此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。

10.3.5 test2 目录

test2 目录中的 CMakeLists.txt文件内容如下:

CMake
cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
link_directories(${LIB_PATH})
add_executable(${APP_NAME_2} ${SRC})
target_link_libraries(${APP_NAME_2} ${SORT_LIB})

  • 第四行include_directories:包含头文件路径,HEAD_PATH变量是在根节点文件中定义的
  • 第五行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
  • 第六行link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH变量是在根节点文件中定义的
  • 第七行add_executable:生成可执行程序,APP_NAME_2变量是在根节点文件中定义的
  • 第八行target_link_libraries:指定可执行程序要链接的动态库的名字

10.3.6 构建项目

一切准备就绪之后,开始构建项目,进入到根节点目录的build 目录中,执行cmake 命令,如下:

CMake
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/cmake/calc/build

可以看到在build目录中生成了一些文件和目录,如下所示:

CMake
$ tree build -L 1    
build
├── calc                  #
目录
├── CMakeCache.txt        # 文件
├── CMakeFiles            # 目录
├── cmake_install.cmake   # 文件
├── Makefile              # 文件
├── sort                  # 目录
├── test1                 # 目录
└── test2                 # 目录

然后在build 目录下执行make 命令:

通过上图可以得到如下信息:

  • 在项目根目录的lib目录中生成了静态库libcalc.a
  • 在项目根目录的lib目录中生成了动态库libsort.so
  • 在项目根目录的bin目录中生成了可执行程序test1
  • 在项目根目录的bin目录中生成了可执行程序test2

CMake
$ tree bin/ lib/
bin/
├── test1
└── test2
lib/
├── libcalc.a
└── libsort.so

至此,项目构建完毕.

11.流程控制

https://www.subingwen.cn/cmake/CMake-advanced/

11.1 判断条件

11.2 循环

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
cmake开发手册详解.pdf》是一本介绍CMake开发工具的详细指南。CMake是一个开源的跨平台构建工具,用于自动生成各种编译环境和构建脚本,可以简化软件项目的编译过程。 该手册首先对CMake的基本概念和工作原理进行了详细介绍。它解释了CMake如何根据CMakeLists.txt文件中的指令生成相关的构建脚本,并且提供了丰富的示例代码帮助读者理解和使用CMake。通过学习这些内容,读者可以了解到如何编写CMakeLists.txt文件以及如何使用CMake来构建和管理项目。 手册还提供了对CMake的高级特性的深入讲解。例如,它介绍了CMake的模块化机制,可以通过导入外部模块来扩展CMake的功能。它还详细讲解了CMake中的变量、函数和宏的使用方法,以及如何通过条件判断和循环来控制构建过程。这些内容对于更复杂的项目来说非常有用,读者可以根据自己的需求来学习和应用。 除了基本的CMake功能外,手册还介绍了CMake在不同平台和构建系统下的应用。它对CMake与常用的编译器和IDE的集成进行了讲解,包括在Windows、Linux和Mac上的使用方法。此外,手册还涵盖了CMake在多个构建系统上的应用,如Make、Ninja等,读者可以根据自己的平台和构建系统选择适合自己的配置。 总的来说,《cmake开发手册详解.pdf》是一本全面介绍CMake的指南,适合初学者和有一定经验的开发者阅读。通过学习这本手册,读者可以更好地理解和使用CMake,提高软件项目的构建效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值