CMake学习

CMake概述

可以把CMake看成一款自动生成Makefile的工具,其编译流程如下图:
在这里插入图片描述

  • 蓝色虚线表示使用Makefile构建项目的过程
  • 红色实线表示使用cmake构建项目的过程

CMake使用

注释

  • CMake使用#进行行注释,可以放在任何位置。
  • CMake使用#[[ ]]形式进行块注释。

创建CMakeLists.txt文件

  1. 注意文件名为CMakeLists.txt,大小写不能错
  2. 内容如下:
cmake_minimum_required(VERSION 3.0)
project(CALC)
add_executable(app add.c div.c main.c mult.c sub.c)
  • cmake_minimum_required:指定使用的cmake的最低版本
  • project:定义工程名称,可以有多个对项目的描述
  • add_executable:定义工程会生成一个可执行程序,这里app就是可执行程序的名字,与项目名字无关,注意这里都是.c文件,没有.h文件

执行cmake命令

$ cmake CMakeLists.txt文件所在路径
$ make # 生成可执行程序
  • 注意CMakeLists.txt文件所在的路径,如果和当前路径一致,则命令为cmake .,如果文件在当前路径的上一级目录,则命令为cmake ..
  • 可以创建一个build文件,在这个文件路径下执行cmake命令,这样可以将一些没用的文件放到这个文件夹中

set命令

  1. 定义变量
set(SRC_LIST add.c  div.c   main.c  mult.c  sub.c)
add_executable(app  ${SRC_LIST})
  • set()里面的数据为string类型
  • SET(VAR VALUE)VAR为变量名,VALUE为变量值
  1. 指定使用的C++标准
# 增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
  • CMAKE_CXX_STANDARD这个是宏,用于指定C++标准
  1. 指定输出的路径
set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH
  • 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建
  • 由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径./xxx/xxx,那么这个路径中的 ./ 对应的就是makefile文件所在的那个目录,建议使用 相对路径

搜索文件

  1. 方式1
aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录
  • variable:将从dir目录下搜索到的源文件列表存储到该变量中
  • 例:
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app  ${SRC_LIST})

其中CMAKE_CURRENT_SOURCE_DIR宏表示当前访问的CMakeLists.txt文件所在的路径,PROJECT_SOURCE_DIR宏表示执行cmake ..命令时cmake后面跟随的路径..

  1. 方式2
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
  • GLOB_RECURSE递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
  • 要搜索的文件路径和文件类型可加双引号,也可不加
  • 例:
# 搜索当前目录的src目录下所有的.cpp源文件,并存储到变量MAIN_SRC中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
  • 注意使用file()时要指定需要搜索的文件格式

包含头文件

include_directories(${PROJECT_SOURCE_DIR}/include)
  • 注意是指定头文件所在的目录,不是某个具体的.h文件

制作静态库或动态库

C++中的库分为静态库和动态库的区别

静态库在链接时被打包到可执行文件中,而动态库则是在运行时加载到内存中。它们有以下几个区别:

  1. 静态库将库代码静态地编译到可执行文件中,因此可执行文件的大小会增大;而动态库则是在运行时从共享库中加载所需的代码,因此可执行文件的大小较小。
  2. 静态库一旦被链接,其中所有的代码都不再发生更改;而动态库可以在程序运行时被升级或替换。
  3. 多个可执行文件可以使用同一个动态库,以减少内存占用和磁盘空间的使用。但每个可执行文件都需要包含其自己的静态库副本,因此可能会浪费大量的磁盘空间。
  4. 在Windows操作系统中静态库是以.lib为后缀的文件,动态库是以.dll为后缀的文件;在Linux操作系统中静态库是以.a为后缀的文件,动态库是以.so为后缀的文件;

制作静态库

  1. 制作库文件的时候不需要main.c文件,这个文件是用来测试的
  2. 在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。在Windows中虽然库名和Linux格式不同,但也只需指定出名字即可。
add_library(库名称 STATIC 源文件1 [源文件2] ...) 
  • STATIC为静态库,SHATED为动态库

制作动态库

  1. 在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
  2. 在发布动态库或者静态库的时候,还应该发布需要调用的头文件

指定库的输出路径

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
  • LIBRARY_OUTPUT_PATH能够指定生成的库的路径
  • 注:动态库可以使用之前指定可执行程序的生成路径的宏EXECUTABLE_OUTPUT_PATH,因为由于在Linux下生成的动态库默认是有执行权限的,但静态库没有
  • 不指定生成路径时,库文件会生成到构建目录里面(也就是和生成的Makefile文件同一个目录)

链接静态库或动态库

链接静态库

# 链接静态库(可以链接多个静态库)
link_libraries(<static lib> [<static lib>...])
# 指定静态库或者动态库的路径(系统提供的库文件可以不用指定)
link_directories(<lib path>)
  • <static lib>可以是全名libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字xxx
  • 注意最后生成可执行程序的函数add_executable()应该在链接静态库的函数下面(在动态库前)
  • 链接库就是将.c或者.cpp等原文件和库文件链接到一起,最后生成可执行程序

链接动态库

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target:指定要加载动态库的文件的名字:该文件可能是一个源文件、动态库文件、可执行文件
  • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
    • 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的PUBLIC即可。
    • 动态库的链接具有传递性,如果动态库A链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
    target_link_libraries(A B C)
    target_link_libraries(D A)
    
    • PUBLIC:在PUBLIC后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用(默认,可以不用写出来)
    • PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库(在B、C前分别加上PRIVATE,A就没有传递性了,D不能调用B和C,B或者C有一个不加就是PUBLIC)
    • INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号(在B、C前分别加上INTERFACE,在A中可以使用B和C的函数,但A不知道这个函数是属于B还是C)
  • 链接动态库的函数target_link_libraries应该写在CMakeLists文件的最后(写在add_executable()函数后面)
  • 使用target_link_libraries也可以链接静态库文件。

日志

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的信息是在cmake ..命令执行时打印出来的
  • message中打印变量用${}

变量操作

拼接

使用set拼接
set(变量名1 ${变量名1} ${变量名2} ...)
  • 上述命令为将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。
使用list拼接
list(APPEND <list> [<element> ...])
# 例
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
  • list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了
  • 在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var abc be cdd)命令将会创建一个list:abc;be;cdd

移除

list(REMOVE_ITEM <list> <value> [<value> ...])
  • REMOVE_ITEM代表删除
  • 例:
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)

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

  • list还有很多其他功能(有需要再查)

宏定义

定义宏

add_definitions(-D宏名称)
  • 可以通过该命令将宏定义出来,用于下面这段代码,DEBUG在C代码中没有被定义,如果cmake中也没有定义,则不打印信息,如果使用add_definitions(-DDEBUG)将宏定义出来,则会打印出信息,在cmake中定义该类宏更加灵活
#ifdef DEBUG
    printf("我是一个程序猿, 我不会爬树...\n");
#endif

常用宏定义

功能
PROJECT_SOURCE_DIR使用cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIRtarget 编译目录
EXECUTABLE_OUTPUT_PATH重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH重新定义目标链接库文件的存放位置
PROJECT_NAME返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

嵌套的CMake

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

$ tree
.
├── build
├── 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
  • test1test2生成可执行程序,calcsort生成库文件,

节点关系

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

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

建立父子关系

在父文件中定义:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
  • binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可
  • EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标

流程控制

条件判断

if(<condition>)
  <commands>
elseif(<condition>) # 可选快, 可以重复
  <commands>
else()              # 可选快
  <commands>
endif()

基本表达式(有需要再查手册)

if(<expression>)

如果是基本表达式,expression有以下三种情况:常量变量字符串

  • 如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
  • 如果是0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, 空字符串时,条件判断返回False

逻辑判断

if(NOT <condition>)
if(<cond1> AND <cond2>)
if(<cond1> OR <cond2>)

循环

foreach

foreach(<loop_var> <items>)
    <commands>
endforeach()
  • 通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出

while

while(<condition>)
    <commands>
endwhile()

参考链接

爱编程的大丙:https://subingwen.cn/cmake/CMake-primer/

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值