Cmake语法

2019-04-01  本文已影响0人  Tony__Ren

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] ])

inputoutput假如不指定绝对路径,则会被默认设置为CMAKE_CURRENT_SOURCE_DIRCMAKE_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循环

  1. 第一种形式
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。

  1. 第二种形式
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值,步进值是浮点数时会被转为整形

  1. 第四种形式
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。

上一篇下一篇

猜你喜欢

热点阅读