1. Directory
当CMake处理一个项目时,入口点是一个名为CMakeLists.txt
的源文件,这个一定是根目录下的CMakeLists.txt。这个文件包含整个工程的构建规范,当我们有多个子文件夹需要编译时,使用add_subdirectory(<dir_name>)
命令来为构建添加子目录。添加的每个子目录也必须包含一个CMakeLists.txt文件作为该子目录的入口点。每个子目录的CMakeLists.txt
文件被处理时,CMake在构建树中生成相应的目录作为默认的工作和输出目录。记住这一点非常关键,这样我们就可以使用外部构建了,而不必每次都使用蛋疼的内部构建,然后删除一堆文件才能从新构建。
2. Script
一个单独的<script>.cmake
源文件可以使用cmake命令行工具
cmake -P <script>.cmake
选项来执行脚本。脚本模式只是在给定的文件中运行命令,并且不生成构建系统。它不允许CMake命令定义或执行构建目标。
3. Module
在Directory或Script中,CMake代码可以使用include()
命令来加载.cmake。cmake内置了许多模块用来帮助我们构建工程,前边文章中提到的CheckFunctionExists。也可以提供自己的模块,并在CMAKE_MODULE_PATH
变量中指定它们的位置。
mkdir build # 创建build目录
cd build # 进入build目录
cmake .. # 因为程序入口构建文件在项目根目录下,采用相对路径上级目录来使用根目录下的构建文件
message([<mode>] "message to display" ...)
//mode
(none)
重要的信息
STATUS
附带的信息
WARNING
警告,继续处理
AUTHOR_WARNING
CMake警告(dev),继续处理
SEND_ERROR
CMake错误,继续处理,但跳过生成
FATAL_ERROR
CMake错误,停止处理和生成
Deprecation
如果变量CMAKE_ERROR_DEPRECATED或CMAKE_WARN_DEPRECATED分别启用,则CMake Deprecation错误或警告,否则没有消息。
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] [FATAL_ERROR])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[LANGUAGES <language-name>...])
//设置项目名称并将该名称存储在PROJECT_NAME变量中。同时也指定了四个变量:
PROJECT_SOURCE_DIR, <PROJECT-NAME>_SOURCE_DIR
PROJECT_BINARY_DIR, <PROJECT-NAME>_BINARY_DIR
project (Tutorial
VERSION 1.2.3
DESCRIPTION "this is description"
LANGUAGES CXX)
message(STATUS ${PROJECT_VERSION})
message(STATUS ${PROJECT_VERSION_MAJOR})
message(STATUS ${PROJECT_VERSION_MINOR})
message(STATUS ${PROJECT_VERSION_PATCH})
message(STATUS ${PROJECT_VERSION_TWEAK})
message(STATUS ${PROJECT_DESCRIPTION})
//输出日志如下
-- 1.2.3
-- 1
-- 2
-- 3
--
-- this is description
在这设置版本号和用set设置版本号效果一样,取最后一次设置的值。由于我们没有指定tweak版本,所以为空,同时看到description被存储到PROJECT_DESCRIPTION这个变量中了。
可以通过设置LANGUAGES来指定编程语言是C、CXX(即c++)或者Fortran等,如果没有设置此项,默认启用C和CXX。设置为NONE,或者只写LANGUAGES关键字而不写具体源语言,可以跳过启用任何语言。一般都是用cmake来编译c或者c++程序,所以用默认的就可以了。
4.configure_file
该命令的作用是复制文件到另一个地方并修改文件内容。语法如下:
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
input
和output
假如不指定绝对路径,则会被默认设置为CMAKE_CURRENT_SOURCE_DIR
和CMAKE_CURRENT_BINARY_DIR
,也就是项目根目录和构建的目录;
COPYONLY
则只是复制文件,不替换任何东西,不能和NEWLINE_STYLE <style>
一起使用。
ESCAPE_QUOTES
禁止为"转义。这个很蛋疼,不加这个命令的话假如变量中有a"b,则在生成的文件中会直接使用转义后的字符a"b,加上指令后则按原来的文字显示a"b;
@ONLY
只允许替换@VAR@
包裹的变量${VAR}
则不会被替换;
NEWLINE_STYLE <style>
设置换行符格式
5.include_directories
这一行
这句话的意思将当前的二进制目录添加到编译器搜索include目录中,这样就可以直接使用上一步生成的头文件了。
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
复制代码将给定的目录添加到编译器用来搜索包含文件的目录。相对路径为相对于当前根目录。
括号中的目录被添加到当前CMakeLists文件的INCLUDE_DIRECTORIES目录属性中。它们也被添加到当前CMakeLists文件中的每个目标的INCLUDE_DIRECTORIES
目标属性中。。
默认情况下,指定的目录被追加到当前的include目录列表中。通过将CMAKE_INCLUDE_DIRECTORIES_BEFORE
设置为ON,可以更改此默认行为。通过明确使用AFTER或BEFORE,您可以选择添加和预先设置。
6.流程
if(expression)
# then section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
elseif(expression2)
# elseif section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
else(expression)
# else section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
#...
endif(expression)
if表达式可以用长表达式,优先级顺序如下:
> EXISTS, COMMAND, DEFINED
> EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, STREQUAL, STRLESS, STRLESS_EQUAL, STRGREATER, STRGREATER_EQUAL, VERSION_EQUAL, VERSION_LESS, VERSION_LESS_EQUAL, VERSION_GREATER, VERSION_GREATER_EQUAL, MATCHES
> NOT,AND,OR
表达式 | true | false | 说明 |
---|---|---|---|
<constant> | 1, ON, YES, TRUE, Y,或者是非0数字0, | OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串,或者带-NOTFOUND后缀 | 布尔判断值大小写不敏感 |
<variable|string> | 已经定义且不是false的变量 | 未定义或者是false的变量 | 变量就是字符串 |
<NOT expression> | expression为false | expression为true | |
AND | 两个条件全部成立 | 至少有一个为假 | |
COMAND command-name | 已经定义的command,macro或者function | 未定义 | |
POLICY policy-id | policy存在 | policy不存在 | 形式为CMP |
TARGET target-name | 已经用add_executable(), add_library(), or add_custom_target()定义过的target | 未定义 | |
TEST test-name | add_test()创建过的测试名称 | 未创建 | |
EXISTS path-to-file-or-directory | 文件或者路径存在 | 文件或者路径不存在 | 此处是全路径 |
file1 IS_NEWER_THAN file2 | file1的时间戳大于file2的时间戳其中一个文件不存在两个文件时间戳相同 | 其他情况 | 文件路径必须是全路径 |
IS_DIRECTORY path-to-directory | 给定的变量是文件夹 | 不是文件夹 | 全路径 |
IS_SYMLINK file-name | 变量是链接 | 不是 | 全路径 |
IS_ABSOLUTE path | 是绝对路径 | 不是 | |
<variable|string> MATCHES regex | 正则表达式匹配成功 | 匹配失败 | |
<variable|string> LESS <variable|string> | 给定的变量是数字并且左边小于右边 | 左边大于右边 | 用于比较数字的大小LESS:小于GREATER:大于EQUAL:等于GREATER_EQUAL:大于等于LESS_EQUAL:小于等于 |
<variable|string> STRLESS <variable|string> | 按字典顺序左边小于右边 | 左边大于右边 | 用于比较字符串LESS:小于STRGREATER:大于STREQUAL:等于STRLESS_EQUAL:小于等于STRGREATER_EQUAL:大于等于 |
<variable|string> VERSION_LESS <variable|string> | 左边的版本号小于右边的版本号 | 大于 | 用于版本号的比较LESS:小于VERSION_GREATER:大于VERSION_EQUAL:等于VERSION_LESS_EQUAL:小于等于VERSION_GREATER_EQUAL:大于等于 |
<variable|string> IN_LIST | 右边的item中有左边 | 没有 | |
DEFINED | 已定义变量 | 未定义变量 | |
(expr1) AND (expr2 OR (expr3)) | 1为真且2或者3至少有一个为真 | 其他情况 |
在if条件表达式中,是不必用${var}来取变量的值的,系统会自动转换。例如设置两个变量,然后比较各种取值的情况:
set(var1 OFF)
set(var2 "var1")
复制代码if(var2)实际是判断var1是否为false;
if(${var2})相当于if(var1),实际是判断OFF;
7.foreach循环
- 第一种形式
foreach(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endforeach(loop_var)
复制代码此处注意endforeach(loop_var)的变量最好不要省略,因为foreach循环是依靠变量来跳出循环的。
在foreach和匹配endforeach之间的所有命令都会被系统记录而不被调用。 一旦找到了了endforeach,则会执行原来记录的命令。在循环的每次迭代之前,${loop_var}将被设置为具有列表中当前值的变量。
foreach(i 0 1 2 3)
message(STATUS "current is ${i}")
endforeach(i)
message(STATUS "end")
endforeach(i)
复制代码一个简单的循环,但是多了一个endforeach。看一下结果
StepTest git:(master) ✗ cmake -P foreach.cmake
-- current is 0
-- current is 1
-- current is 2
-- current is 3
-- end
CMake Error at foreach.cmake:5 (endforeach):
endforeach An ENDFOREACH command was found outside of a proper FOREACH
ENDFOREACH structure. Or its arguments did not match the opening FOREACH
command.
复制代码报错了。没有匹配的foreach。
- 第二种形式
foreach(loop_var RANGE total)
复制代码从0开始直到total结束(包含total)
foreach(i RANGE 3)
message(STATUS "current is ${i}")
endforeach(i)
复制代码范围将会是0-3,查看一下结果:
StepTest git:(master) ✗ cmake -P foreach.cmake
-- current is 0
-- current is 1
-- current is 2
-- current is 3
复制代码3. 第三种形式
foreach(loop_var RANGE start stop [step])
复制代码从start开始直到stop结束之间的值,可以设置步进值step。
foreach(i RANGE 0 3 1)
message(STATUS "current is ${i}")
endforeach(i)
复制代码输出结果和上面的一样.
注意一点:最后的结果不会大于stop值,步进值是浮点数时会被转为整形
- 第四种形式
foreach(loop_var IN [LISTS [list1 [...]]]
[ITEMS [item1 [...]]])
复制代码也比较简单,多了LIST关键字来循环list。不多讲。
while循环
while(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endwhile(condition)
复制代码注意endwhile中的条件最好不要省略。这个条件和if中的表达式是一样的规则。
循环形式和foreach循环类似,直到碰到endwhile才开始执行每一条指令。
在while和foreach循环中,取变量的值请用${var}。break和continue的用法基本与c一样,放心使用。
在实际项目中,经常使用option来和if搭配。
option使用比较简单:
option(<option_variable> "help string describing option"
[initial value])
复制代码initial value只能使用ON或者OFF,假如未设定,默认为false。
cmake_dependent_option是cmake内置的一个module,用来生成依赖其他option的option,这个相当蛋疼。
看一个简单的例子:
include(${CMAKE_ROOT}/Modules/CMakeDependentOption.cmake)
option(USE_CURL "use libcurl" ON)
option(USE_MATH "use libm" ON)
cmake_dependent_option(DEPENT_USE_CURL "this is dependent on USE_CURL" ON "USE_CURL;NOT USE_MATH" OFF)
if(DEPENT_USE_CURL)
message(STATUS "using lib curl")
else()
message(STATUS "not using lib curl")
endif()
复制代码第一行包含了我们需要的依赖模块。
第二行第三行定义了两个option,USE_CURL,USE_MATH全为ON。
第四行定义了一个option,DEPENT_USE_CURL,后边紧跟的是它的说明
this is dependent on USE_CURL,再后边相当于一个三元判断式,假如USE_CURL;NOT USE_MATH为真时,取前边的值,否则取后边的值。
5-9行是一个if语句,用来输出我们想要的结果。
输出结果:
StepTest git:(master) ✗ cmake -P optionc.cmake
-- not using lib curl
cmake中有两个相似的关键字,macro和function。这两个都是创建一段有名字的代码稍后可以调用,还可以传参数。
macro宏定义与function函数的相同点
macro形式如下:
macro(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro(<name>)
复制代码function形式如下:
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
function(<name>)
复制代码定义一个名称为name的宏(函数),arg1...是传入的参数。我们除了可以用${arg1}来引用变量以外,系统为我们提供了一些特殊的变量:
变量 | 说明 |
---|---|
ARGV# | #是一个下标,0指向第一个参数,累加 |
ARGV | 所有的定义时要求传入的参数 |
ARGN | 定义时要求传入的参数以外的参数,比如定义宏(函数)时,要求输入1个,书记输入了3个,则剩下的两个会以数组形式存储在ARGN中 |
ARGC | 传入的实际参数的个数,也就是调用函数是传入的参数个数 |
macro宏定义与function函数的不同点 | 宏的ARGN、ARGV等参数不是通常CMake意义上的变量。 它们是字符串替换,很像C预处理器对宏的处理。 因此,如下命令是错误的: |
if(ARGV1) # ARGV1 is not a variable
if(DEFINED ARGV2) # ARGV2 is not a variable
if(ARGC GREATER 2) # ARGC is not a variable
foreach(loop_var IN LISTS ARGN) # ARGN is not a variable
复制代码正确写法如下:
if(${ARGV1})
if(DEFINED ${ARGV2})
if(${ARGC} GREATER 2)
foreach(loop_var IN LISTS ${ARGN})
or
set(list_var "${ARGN}")
foreach(loop_var IN LISTS list_var)
复制代码一个简单的例子
macro(FOO arg1 arg2 arg3)
message(STATUS "this is arg1:${arg1},ARGV0=${ARGV0}")
message(STATUS "this is arg2:${arg2},ARGV1=${ARGV1}")
message(STATUS "this is arg3:${arg3},ARGV2=${ARGV2}")
message(STATUS "this is argc:${ARGC}")
message(STATUS "this is args:${ARGV},ARGN=${ARGN}")
if(arg1 STREQUAL one)
message(STATUS "this is arg1")
endif()
if(ARGV2 STREQUAL "two")
message(STATUS "this is arg2")
endif()
set(${arg1} nine)
message(STATUS "after set arg1=${${arg1}}")
endmacro(FOO)
function(BAR arg1)
message(STATUS "this is arg1:${arg1},ARGV0=${ARGV0}")
message(STATUS "this is argn:${ARGN}")
if(arg1 STREQUAL first)
message(STATUS "this is first")
endif()
set(arg1 ten)
message(STATUS "after set arg1=${arg1}")
endfunction(BAR arg1)
set(p1 one)
set(p2 two)
set(p3 three)
set(p4 four)
set(p5 five)
set(p6 first)
set(p7 second)
FOO(${p1} ${p2} ${p3} ${p4} ${p5})
BAR(${p6} ${p7})
message(STATUS "after bar p6=${p6}")
复制代码输出结果如下:
-- this is arg1:one,ARGV0=one
-- this is arg2:two,ARGV1=two
-- this is arg3:three,ARGV2=three
-- this is argc:5
-- this is args:one;two;three;four;five,ARGN=four;five
-- after set arg1=nine
-- this is arg1:first,ARGV0=first
-- this is argn:second
-- this is first
-- after set arg1=ten
-- after bar p6=first
复制代码接下来看一个让我们蛋都能疼碎了的例子,简直不想用cmake:
macro(_bar)
foreach(arg IN LISTS ARGN)
message(STATUS "this is in macro ${arg}")
endforeach()
endmacro()
function(_foo)
foreach(arg IN LISTS ARGN)
message(STATUS "this in function is ${arg}")
endforeach()
_bar(x y z)
endfunction()
_foo(a b c)
复制代码看一下输出:
-- this in function is a
-- this in function is b
-- this in function is c
-- this is in macro a
-- this is in macro b
-- this is in macro c
复制代码就是这么蛋疼,我们传给了_bar(x y z),结果打印出来的是a b c,那我们把第二行的foreach改成foreach(arg IN LISTS ${ARGN}),
看一下结果:
-- this in function is a
-- this in function is b
-- this in function is c
复制代码没有输出_bar中的信息。为啥?因为这个ARGN的作用域是在function中的,也就是_foo函数中的那个ARGN。有兴趣的话可以试试在macro中调用function。
网友评论