CMake学习笔记

概述

CMake是一个跨平台的项目构建工具。CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。

具体的项目构建流程如下图所示:

CMake中的预定义宏

含义
PROJECT_SOURCE_DIR 一般是工程根目录
PROJECT_BINARY_DIR 执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeList.txt所在的目录
CMAKE_CURRENT_BINARY_DIR target编译目录
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接文件的存放位置
PROJECT_NAME 返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR 项目实际构建路径,比如在build目录进行的构建,那么就是build目录的路径

CMake的使用

  1. 注释
shell 复制代码
# 这是一个 CMakeLists.txt 文件,
# CMake使用#来注释,可以放在任意位置
cmake_minimum_required(VERSION 3.10.0)
  1. 注释块
lua 复制代码
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
# 使用#[[]]进行块注释
cmake_minimum_required(VERSION 3.10.0)

假设有以下文件a.cpp、b.cpp、main.cpp,可以再上述源文件所在目录添加一个新的CMakeList.txt文件。在文件中写:

scss 复制代码
cmake_minimum_required(VERSION 3.10)    #指定最低的CMake版本,可选,不写可能会报错
project(test_project_name)    #定义工程的名称
add_executable(TEST a.cpp b.cpp main.cpp)    #定义工程会生成一个可执行程序名字

通过project来设置工程属性:

ini 复制代码
# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
       [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]    #工程版本
       [DESCRIPTION <project-description-string>]    #工程描述
       [HOMEPAGE_URL <url-string>]    #web主页地址
       [LANGUAGES <language-name>...])    #支持语言(默认支持所有语言)
#工程名称是必须的,其他属性是非必须的

通过add_executable来生成可执行程序:

css 复制代码
add_executable(可执行程序名 源文件名称) #可执行程序名和工程名并没有直接关系,多个源文件可以使用空格或者;隔开,比如
add_executable(TEST a.cpp b.cpp main.cpp)
add_executable(TEST a.cpp;b.cpp;main.cpp)

执行cmake指令之后,会生成makefile文件,再执行make指令,就会生成可执行程序TEST。

一般项目中有很多源文件,在编写CMakeLists时需要使用到这些文件的名称,这样一个个添加文件名称是一件很麻烦的事情。因此,可以定义一个变量,将文件名对应的字符串存储到变量中,比如:

bash 复制代码
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写,var为变量名,value为变量值
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

# 方式1: 各个源文件之间使用空格或者;间隔
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})

#通过set指定C++标准
#CMake中CMAKE_CXX_STANDARD是对应的C++标准的宏
set(CMAKE_CXX_STANDARD 11)#指定C++11
set(CMAKE_CXX_STANDARD 14)#。。。
set(CMAKE_CXX_STANDARD 17)#。。。

#或者,在执行cmake指令编译时也可以添加宏
cmake -DCMAKE_CXX_STANDARD=11

#通过set来设置输出路径
#CMake中输出路径对应的宏为EXECUTABLE_OUTPUT_PATH
set(HOME /home/work/test)    #定义变量HOME,代表路径/home/work/test
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)    #指定输出路径为/home/work/test/bin,如果指定路径不存在,会自动创建并输出到此处
  1. 搜索文件

如果一个项目中有很多源文件,这样将文件一个个罗列出来是一件很麻烦的事,可以通过一些命令来自动查找指定文件。

通过aux_source_directory关键字搜索文件:

bash 复制代码
aux_source_directory(<dir> <var>) #在dir路径下搜索源文件,并存储到变量var中

#比如
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
#CMAKE_CURRENT_SOURCE_DIR是CMake中的宏,表示当前CMakeList.txt文件所在的绝对路径
#表示在当前CMakeList所在路径的src文件夹中查找所有的源文件,并保存到SRC_LIST变量中,后续可以通过SRC_LIST来访问所有的源文件,而不需要一个个罗列出来

通过file关键字搜索文件:

bash 复制代码
file(GLOB/GLOB_RECURSE 变量名 路径和文件类型)
#GLOB表示将搜索到的文件生成列表保存到变量中,GLOB_RECURSE表示递归搜索并保存

file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) #查找当前CMakeList所在目录的src下的所有cpp文件,保存到MAIN_SRC中
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) #查找当前CMakeList所在目录的include下的所有h文件,保存到MAIN_HEAD中
#对于要搜索的文件路径和类型,可加双引号也可不加,比如
file(GLOB MAIN_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
  1. 包含头文件

通过include_directories关键字设置头文件路径:

scss 复制代码
include_directories(文件路径)

include_directories(${PROJECT_SOURCE_DIR}/include)    #设置头文件的路径是当前工程根目录的include文件夹下
#PROJECT_SOURCE_DIR是如果首次使用,代表工程的根目录,如果在其他路径下的CMakeList中使用project(),会更新为对应的路径
  1. 制作动态库或者静态库
scss 复制代码
#制作库的时候通过STATIC、SHARED来区别静、动态库
add_library(库名称 STATIC 源文件1、2、3)    #制作静态库
add_library(库名称 SHARED 源文件1、2、3)    #制作动态库

#对于动态库而言,Linux下生成的动态库是默认有执行权限的,所以可以通过EXECUTABLE_OUTPUT_PATH宏指定动态库的输出路径,比如
set(EXECUTABLE_OUTPUT_PATH output_path)

#对于静态库而言,默认是没有可执行权限的,所以不能使用EXECUTABLE_OUTPUT_PATH宏,而应该使用LIBRARY_OUTPUT_PATH
set( LIBRARY_OUTPUT_PATH output_path)

add_library(lib_name STATIC 源文件1、2、3)
add_library(lib_name SHARED 源文件1、2、3)#动态库同样适用这种方式
  1. 链接库

库制作好之后,其他程序通过链接库,来使用库提供的方法:

scss 复制代码
link_libraries(静态库1、2、3)    #可以使用全名libxxx.a或者xxx

#如果链接的库不是系统提供的,是自定义的或者第三方提供的,可能找不到库,可以指定静态库的路径
link_directories(库文件路径)
vbnet 复制代码
target_link_libraries(链接目标 动态库1、2、3)
#链接目标是可执行文件、库或者源文件
#链接动态库可以单独设置访问权限PUBLIC\PRIVATE\INTERFACE,默认为PUBLIC,
#PUBLIC表示其后的库会被链接到target中,并且里面的符号也会被导出,提供给第三方使用,依赖项不仅用于当前目标的实现,也会传递给以来当前目标的其他目标
#PRIVATE表示其后的库会被链接到target中,但是第三方不能感知到调用了什么库,依赖项仅用于当前目标,若是其他目标引用了当前目标中某些函数,会导致错误
target_link_library(test PRIVATE lib_a)
  • 静态库会在生成可执程序的链接阶段被打包到可执行程序中,所在可执行程序启动,静态库就被加载到内存中。

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

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

scss 复制代码
add_executable(app ${SRC_LIST})    #生成可执行程序
target_link_libraries(app pthread)    #链接动态库

暂时无法在飞书文档外展示此内容

  1. 宏定义

在代码中会经常添加一些宏定义,通过这些宏来控制对应的代码是否生效,比如以下代码:

c 复制代码
int main()
{
#ifdef TEST
    cout<<"1111111"<<endl;
#endif
    cout<<"2222222"<<endl; 
}

//如果宏TEST被定义了,就会输出111111 2222222,否则,111111哪行代码不会被执行
//可以通过命令行来定义这个宏,比如编译时 g++ test.cpp -DTEST -o test
//-D参数指定要定义的红,即TEST
bash 复制代码
#通过CMakeLists.txt来定义宏
add_definitions(-DTEST)   #定义宏TEST
  1. 嵌套的CMake

在日常学习、工作中,很多大型项目为了模块化,都是分模块来进行开发。一个大型项目中,会定义各种功能模块,每个功能模块之间是相对独立的,可以独立进行编译,这样项目的健壮性、可维护性都会更好。在这种情况下,可以通过嵌套的CMake来进行管理。

Linux的目录是树状结构,嵌套的CMake也是一个树状结构,最顶层的CMakeLists.txt是根节点,其次都是子节点。 根节点中的变量全局有效,父节点的变量可以在子节点中使用,子节点中的变量只能在当前节点使用。

举例说明,假如有以下目录结构,lib1和lib2是分别提供一些方法,导出为库,test1和test2链接库

objectivec 复制代码
├── build
├── CMakeLists.txt
├── include
│   └── test.h
├── lib1
│   ├── lib1.cpp
│   ├── lib1.h
│   └── CMakeLists.txt
├── lib2
│   ├── lib2.cpp
│   ├── lib2.h
│   └── CMakeLists.txt
├── test1
│   ├── test1.cpp
│   └── CMakeLists.txt
└── test2
    ├── test2.cpp
    └── CMakeLists.txt
  1. 添加子目录
less 复制代码
add_subdirectory(source_dir binary_dir EXCLUDE_FROM_ALL)
#source_dir指定了CMakeList源文件,也就是指定了子目录
#binary_dir指定了输出文件的路径,一般不需要指定
#EXCLUDE_FROM_ALL在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标

顶层CMakelists.txt编写:

scss 复制代码
cmake_minimum_required(VERSION 3.10)
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(LIB_1 lib1)    #设置静态库名字
set(LIB_2 lib2)

set(APP_1 test1)    #设置可执行程序名字
set(APP_2 test2)

add_subdirectory(lib1)    #设置子目录
add_subdirectory(lib2)
add_subdirectory(test1)
add_subdirectory(test2)

将lib1制作为静态库:

scss 复制代码
cmake_minimum_required(VERSION 3.10)
project(LIB_1)
aux_source_directory(./ SRC)    #搜索当前目录下的所有文件,存到变量SRC
include_directories(${HEAD_PATH})    #设置头文件目录,在根目录下的CMakeList中定义了改变量
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${LIB_1} STATIC ${SRC})    #生成静态库,名字也是在根目录的CMakeList中定义的
target_include_directories(${LIB_1} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})    #指定头文件目录,同时设置为PUBLIC,其他连接到lib1的程序可以在这个路径下找到lib1的头文件
#lib2的CMakeLists写法类似,不过制作为动态库

test1目录下的CMakelists.txt编写:

scss 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST_1)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${LIB_1})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_1} ${SRC})
target_link_libraries(${APP_1} ${LIB_2})
#test2的CMakeLists写法类似

在test1.cpp文件中,需要引入lib1.h这个头文件,即可使用lib1中提供的方法与变量,使用cmake、make命令编译项目,会生成lib、bin目录,lib目录下有lib1.a、lib2.so文件,在bin目录下会有TEST_1、TEST_2可执行程序。

流程控制

scss 复制代码
#条件判断
if......else......elseif......endif语句

#逻辑判断
NOT......AND......OR

#基于数值比较
LESS(小于)......GREATER(大于)......EQUAL(等于)......LESS_EQUAL(小于等于)......GREATER_EQUAL(大于等于)

#基于字符串比较
STRLESS(小于)......STRGREATER(大于)......STREQUAL(等于)......STRLESS_EQUAL(小于等于)......STRGREATER_EQUAL(大于等于)

#文件操作
if(EXISTS file_path)    #文件存在返回True,不存在返回False
if(IS_DIRECTORY dir_path)    #判断是不是目录,必须是绝对路径
if(IS_SYMLINK file_name)    #判断是否是软连接,必须是绝对路径
if(IS_ABSOLUTE path)    #判断是否是绝对路径

#其他
if(item IN_LIST list_name)    #判断元素是否在列表中
if(path1 PATH_EQUAL path2)    #判断两个路径是否相等,本质是两个字符串的比较,所以=
if(path1 STREQUAL path2)
#不过,等路径中不小心写多个分隔符,比如a//b//c与a/b/c,如果是通过STAEQUAL去比较,这两个路径肯定不相等,但是通过PATH_EQUAL来判断,这两个路径是相等的,PATH_EQUAL会自动剔除多余的分割线


#循环
foreach( loop_item items)    #遍历items中的元素,通过loop_item访问
endforeach()

#有如下例子:
foreach(loop_item RANGE stop_val) #表示从0开始遍历,递增到stop_val停止
foreach(loop_item RANGE start_val stop_val step)#表示从start_val开始遍历到stop_val,步长为step,步长默认为1
相关推荐
虾球xz40 分钟前
游戏引擎学习第276天:调整身体动画
c++·学习·游戏引擎
虾球xz40 分钟前
游戏引擎学习第275天:将旋转和剪切传递给渲染器
c++·学习·游戏引擎
虾球xz6 小时前
游戏引擎学习第268天:合并调试链表与分组
c++·学习·链表·游戏引擎
fpcc6 小时前
跟我学c++高级篇——模板元编程之十三处理逻辑
c++
格林威7 小时前
Baumer工业相机堡盟工业相机的工业视觉中为什么偏爱“黑白相机”
开发语言·c++·人工智能·数码相机·计算机视觉
Dream it possible!8 小时前
LeetCode 热题 100_只出现一次的数字(96_136_简单_C++)(哈希表;哈希集合;排序+遍历;位运算)
c++·leetcode·位运算·哈希表·哈希集合
Dddle19 小时前
C++:this指针
java·c语言·开发语言·c++
不見星空10 小时前
2025年第十六届蓝桥杯软件赛省赛C/C++大学A组个人解题
c语言·c++·蓝桥杯
jiunian_cn10 小时前
【c++】异常详解
java·开发语言·数据结构·c++·算法·visual studio
梁下轻语的秋缘10 小时前
每日c/c++题 备战蓝桥杯(洛谷P1387 最大正方形)
c语言·c++·蓝桥杯