CMake学习笔记
我参考的教程视频:【现代C++: CMake简明教程】 https://www.bilibili.com/video/BV1xa4y1R7vT/?p=17&share_source=copy_web&vd_source=8ad424dfb8b17f7477da296c84de01be
CMake构建项目的简单示例
该部分通过一个简单的CMakeLists.txt演示如何构建项目.其中用到的大多数指令可以在下面的CMake常用操作中找到.
#要构建一个项目需要以下几步:指明可执行文件名/库名,指明可执行文件/库所需要的头文件和源文件,链接外部库文件.而链接外部库文件需要指明库的头文件路径,库的库文件路径,库的名字.下面的代码就依次完成了这些步骤
cmake_minimum_required(VERSION 3.20)
#指明cmake所需的最小版本
project(hello)
#指明项目名
file(GLOB src ${PROJECT_SOURCE_DIR}/src/*.cpp)
#将项目源文件路径保存在变量src中(见CMake常用命令-file())
add_executable(nptest ${src})
#指明目标(该目标为可执行文件,省略后缀)和所用的源文件(由上文file()命令取得)
target_include_directories(nptest PRIVATE
D:/tools/npcap-sdk-1.15/Include
${PROJECT_SOURCE_DIR}/include
)
#指明头文件所在路径(若和CMakeLists.txt或源文件位于同一路径则该路径可以省略)
target_link_directories(nptest PRIVATE D:\\tools\\npcap-sdk-1.15\\Lib\\x64)
#指明外部库文件所在路径
target_link_libraries(nptest PRIVATE wpcap Packet)
#将外部库文件链接到目标nptest
CMake 一些重要概念
- target: 构建系统中的一个目标,指可执行文件或库.目标通过
add_executive()
指令或者add_library()
指令指明,前者生成一个可执行文件,后者生成库文件.很多命令包含target字眼,指的是该指令对指定目标生效.比如target_include_directories()
指令,在构建指定目标时在该目录寻找头文件,构建其他目标时不寻找该目录.对应的去掉target的include_directories()
命令指明所有目标都使用的路径.(建议使用带target的指令) - PUBLIC|PRIVATE|INTERFACE 访问控制参数.在一些指令中需要.
PUBLIC
指该指令对指定目标和依赖于该目标的其他目标都生效.PRIVATE
指该指令只对给定目标生效,INTERFACE
指该指令对给定目标不生效,对依赖于该目标的其他目标生效 - 依赖: 依赖关系是指一个目标(如可执行文件或库)对其他目标或库的依赖.例如通过
target_link_libraries()
将一个库链接到一个目标,则该目标依赖于库
cmake变量操作
- 创建变量: `set(
...) - set命令可以给一个变量赋予多个值.内部存储时会将各个值中间用分号隔开.但是取值的时候会拼接在一起.
- 取消变量可以使用
unset(<varname>)
使用后变量被撤销,不能再取值 - 变量取值方法:
${<varname>}
- 变量列表操作:
list(<type> <varname> <other>...)
- 给一个变量赋多个值后这个变量就是一个列表.可以进行很多列表上的操作例如查找/排序/倒序/插入/删除等
- 变量查找:
list(FIND <varname> <findContent> <index>)
列表操作类型为FIND.对名为varname的列表进行查找,查找值为findContent的元素,返回元素的索引(从0开始) 将该索引保存在index中. - 列表添加元素:
list(APPEND <varname> <val1> <val2> ...)
列表操作类型为APPEND,将val1,val2等等元素按序添加到varname变量的末尾.该方法也可以用来创建列表.当varname变量不存在时会创建一个 - 列表删除元素:
list(REMOVE_ITEM <varname> <value1> <value2>..)
列表操作类型为REMOVE_ITEM,从列表varname中删除value1,value2等元素 - 列表插入元素:
list(INSERT <varname> <index> <value1> <value2>..)
列表操作类型为INSERT.在列表varname中下标index位置插入value1 value2等元素 - 获取列表元素个数:
list(LENGTH <varname> <output>)
列表操作类型为LENGTH.获取列表varname中元素个数,保存在变量output中 - 列表排序:
list(SORT <varname>)
列表操作类型为SORT.对列表中的元素进行排序 - 列表反转:
list(REVERSE <varname>)
反转列表元素
cmake流程控制
cmake_minimum_required(VERSION 3.20)
#if的用法 if(<condition>)其中condition值为true或false.可以通过NOT取反,通过AND和OR进行多条件判断,通过LESS和EQUAL进行比较(字符串比较)
if(<condition>)
#指令
endif()
#for的几种常用用法
#foreach循环支持break()和continue()流程控制
#1.foreach(<var> RANGE <range>)
# endforeach() var将会进行从0到<range>跨步为1的循环
foreach(index RANGE 3)
message(${index})
endforeach()
#输出0 1 2 3(中间是回车)
message(-------------------)
set(myList a1 a2 a3)
#2.foreach(<var> IN LISTS <list> [ITEMS <temp1> <temp2>...])
#该语法每次循环遍历list中与个元素保存在var中.遍历完再遍历ITEMS后面给的各个元素
foreach(var IN LISTS myList ITEMS a4 a5 a6)
message(${var})
endforeach()
#将会输出a1 a2 a3 a4 a5 a6
message(--------------------)
set(alab 1 2 3)
set(equa = = =)
set(eng one two three)
#3.foreach(<var> IN ZIP_LISTS <list1> <list2>...)
#该语法每次同时遍历list1,list2... 各个list取一个元素保存在var中.通过var_0 var_1..来访问
foreach(pair IN ZIP_LISTS alab equa eng)
message(${pair_0}${pair_1}${pair_2})
endforeach()
#将会输出1=one 2=two 3=three
CMake函数
函数定义:
function(<funcName> <arg1> <arg2>...)
#funcName:函数名称.后面跟的是可选的多个参数名
#函数体写在function()和endfunction()中间
#函数内提供的多个变量及其含义:
#CMAKE_CURRENT_FUNCTION:当前函数的函数名.
#ARGVx:第x个参数(下标从0开始)
endfunction()
函数调用:
funcName(<arg1> <arg2>...)
#函数调用时传参如果要传变量,需要使用${var}.如果直接传入var则会被当做字符串"var"
#由于传参只是传入了值,因此传入的变量值不会被改变,在函数中改变值只是改变了"形参"的值,对函数外并无影响
函数作用域: 在函数外定义的变量,可以在函数内使用.但是函数内修改了变量值只在函数内生效.函数退出后就会失效.
function(OutFunc)
message(->OutFunc:${var})#2.执行这句.var继承自global,var=3
set(var 2)
InFunc()
message(<-OutFunc:${var})#5.执行这句,var不受InFunc影响,var=2
endfunction()
function(InFunc)
message(->InFunc:${var})#3.执行这句.var继承自OutFunc,var=2
set(var 1)
message(<-InFunc:${var})#4.执行这句,var由InFunc修改,var=1
endfunction()
set(var 3)
message(->global:${var})#1.执行这句 var=3
OutFunc()
message(<-global:${var})#6.执行这句,var不受OutFunc影响,var=3
CMake宏(尽量不要用,增加复杂性)
#宏实际上是进行代码替换.类似于将宏的代码直接替换到调用宏的位置.在宏内对变量进行操作会直接改变变量的值.调用完宏之后在访问就会得到修改后的值.
#宏的定义:
macro(<macroName> <arg1> <arg2>...)
#宏体写在macro和endmacro中间.
endmacro()
#宏的使用:
<macroName>(<arg1> <arg2>...)
宏的作用域: 宏实际上类似inline函数,将代码直接替换到调用位置.因此在宏内进行的set等操作会对宏外定义的变量直接产生影响.但是如果宏传入的参数名和全局变量产生冲突,例如形参名叫var而全局变量也有一个var,则使用set修改var会修改全局变量var的值,而调用${var}会得到形参var的值.可以理解为宏myMacro调用时就将宏代码中所有#{var}都直接替换为传入的参数var的值
macro(myMacro var)
set(var 2)#set global::var=2
set(var1 2)#set global::var1=2
message(var:${var})#current::var=1 输出var=1
message(var1:${var1})#current::var1=global::var1=2 输出var1=2
endmacro()
set(var 1)#global::var=1
set(var1 1)#global::var1=1
myMacro(${var})
message(var:${var})#global::var=2 输出var=2
message(var1:${var1})#global::var1=2 输出var1=2
CMake常用操作
file()命令:
file(GlOB <var> <filePath>)
#匹配<filePath>的所有文件路径都会被保存在变量var中.该命令可以用于获取源文件.
#实例:
file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)
#该命令将项目目录/src下所有cpp后缀文件路径保存在SRC变量中.可以在add_library或add_executive等命令中使用它
指定头文件包含路径:
#可以通过两种命令指定头文件包含路径:target_include_directories()
#和include_directories().二者的区别在于target_命令可以指定由add_executive()命令或者add_library()命令生成的目标,并只对该目标生效.而include_directories命令是全局生效的.只需调用一次就能为所有目标指定头文件搜索路径.
#命令格式:
target_include_directories(<target> [PRIVATE|PUBLIC|INTERFACE] <directories>)
#target为指定的路径,PUBLIC表示该目标和依赖于该目标的其他目标都可见,PRIVATE表示仅当前目标可见,INTERFACE表示当前目标不使用这些包含目录,但依赖于它的其他目标可以使用
include_directories(<directories>)
#为所有目标使用directories路径.
生成库文件/导入外部库文件:
#生成库文件需要使用指令add_library.这个指令同时也可以导入已经存在的外部库文件.
#生成库文件的指令格式如下:
add_library(<name> [STATIC|SHARED|MODULE] [EXCLUDE_FROM_ALL] [sources])
#name为库文件名称,STATIC表示要生成静态库,SHARED表示要生成动态库,MODULE生成一个模块,在库不提供任何可供外部调用的函数或符号时需要使用MODULE.
#生成库文件所在路径可以通过指定LIBRARY_OUTPUT_PATH来改变
set(LIBRARY_OUTPUT_PATH <path>)
#通过该指令就可以把生成的库文件保存在路径path下
#导入外部库文件的指令格式如下:
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> <dir>
)
#通过该指令指明<target>目标使用的库文件的include目录,从<dir>中找到头文件位置.
target_link_directories(<target> [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> <dir>...)
#通过该指令指明<target>目标使用的外部库文件所在路径.从<dir>中找到库文件
target_link_libraries(
<target> [<PRIVATE|PUBLIC|INTERFACE>] <lib>...
)
#通过该文件指明<target>要使用哪些库文件.<lib>...可写多个库文件(不需要写后缀名)
#另一种方法导入外部库文件的指令格式如下:
add_library(<name> <SHARED|STATIC|MODULE|OBJECT|UNKNOWN> IMPORTED [GLOBAL])
set_target_properties(<name> PROPERTIES
IMPORTED_LOCATION <path>
INTERFACE_INCLUDE_DIRECTORIES <path>
)
#add_library创建一个外部库目标,再使用set_target_properties()命令指定库文件路径和库的头文件路径.之后使用target_link_libraries()命令链接库文件就不需要添加包含目录了
CMake和源文件的交互:
#命令config_file(<source> <target>)会将source文件复制到build目录下target中,同时将source文件中打标签的内容替换成实际内容.一般情况下source下标会设置成h.in target会写成config.h
#打标签:使用@<var>@标记一个cmake变量,config_file会将@<var>@替换成变量<var>的实际内容.
#例如,CMakeLists.txt中存在如下指令:
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
file_config(config.h.in config.h)
#config.h.in内容如下
#define CMAKE_CXX_STANDARD @CMAKE_CXX_STANDARD@
#调用config_file(config.h.in config.h)后,build目录下生成文件config.h.内容如下:
#define CMAKE_CXX_STANDARD 11
#也即将@CMAKE_CXX_STANDARD@内容替换成了其真实值11.而后源文件只需要include "config.h"就可以访问到CMakeLists.txt中定义的变量
CMake 条件编译:
#使用到的指令:option(),target_compile_definitions()和流程控制语句
option(<var> "help string" [initial value])
#option指令类似set指令,会定义一个var选项."help string"为编程者设定的选项的帮助信息,用于描述选项的作用.这个字符串可以在CMake GUI或命令行中显示,帮助用户理解该选项的用途.initial value是选项的默认值,可也是ON或者OFF.可以使用if()对选项var进行判断,如果是ON则执行if内语句,如果是OFF则执行else
target_compile_definitions(<target> [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> <definition>...)
#该命令用于为特定目标添加宏定义,目标执行过程中给可以访问定义并据此进行流程控制.
#条件编译的整体流程:
#首先使用option()设置开关变量,根据该变量的开关决定编译文件的不同.而后编写流程控制语句if(),根据开关变量的值设置源文件路径变量,并使用target_compile_definitions()添加宏定义.最后根据流程控制语句中设置的源文件路径变量,利用add_library或者add_executive添加目标.同时可能还要修改源文件根据宏定义是否存在决定调用函数的不同.
#举例如下:假设存在两个cpp文件cat.cpp和cattwo.cpp.两者都实现了同一函数meow().则可以通过条件编译改变程序中使用哪个函数.
cmake_minimum_required(VERSION 3.20)
option(cattwo "cattwo used" ON)
#注意这个cattwo选项不是变量,进行判断时不需要用${}取内部值
if(cattwo)
set(src cattwo.cpp)
else()
set(src cat.cpp)
endif()
add_library(catLib ${src})
if(cattwo)
target_compile_definitions(catLib PRIVATE "USE_CATTWO")
#该命令的用途就是在catLib中生成一个宏定义USE_CATTWO,源文件中可以通过#ifdef来判断是否编译了cattwo.cpp
endif()