美文网首页
Cmake语法

Cmake语法

作者: Tony__Ren | 来源:发表于2019-04-01 15:21 被阅读0次

    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。

    相关文章

      网友评论

          本文标题:Cmake语法

          本文链接:https://www.haomeiwen.com/subject/pyqvbqtx.html