CMake语法详解

最近研究Monado runtime和ORB_SLAM3源码

这两个工程源码都是在Linux环境下使用CMake进行工程构建,所以各级目录下的 CMakeLists.txt 是一个很好了解项目代码结构的途径,但是发现对 CMake 的语法较为欠缺,下面是收集整理的一些关于 CMake 的语法细节,以便以后查阅。

1.CMake基础:

    1.1 CMake 是什么:
     (1).CMake是一个支持生成跨平台建构文件的工具
     (2).CMake并不直接建构最终的软件,而是描述项目文件被编译的过程,生成标准的建构档(如 Unix 的 Makefile 或 VS 的 projects/workspaces),然后再以对应平台的建构方式使用。

     1.2 CMake源文件:
      (1).CMake编写的源⽂件以CMakeLists.txt 命名或以.cmake为扩展名
      (2).CMake的源⽂件包括 命令和注释
      (3).CMake源文件中所有有效的语句都是命令
            可以是内置命令或者自定义的函数(function) 或 宏命令(macro)
      (4).可以通过add_subdirectory()命令把子录的CMake源文件添加进来


    1.3 CMake编译C/C++原理:
      (1).CMake比Unix的make更为高级,使用起来要方便得多。
      (2).终端cmake命令将CMakeLists.txt文件建构为make所需要的makefile文件
            最后用make命令编译源码生成可执行程序或共享库(so(shared object))
            因此CMake在Linux终端执行步骤总的来说就两个:
            1.cmake
            2.make
      (3).终端执行cmake后会生成很多编译中间文件以及makefile文件,
          一般会新建一个build目录专门用来编译:
           1.mkdir build
           2.cd build
           3.cmake ..
           4.make

          build的创建也可以在CMakeLists.txt中使用命令创建。
          cmake指向CMakeLists.txt所在的目录,
          cmake .. 表示当前CMakeLists.txt目录的上一级目录

   对于一个庞大的工程,编写Makefile相当复杂,
   有了CMake工具之后就可以读入所有源文件,自动生成Makefile等构建文件。

2.CMake注释:

   (1).单行注释:#注释内容
   (2).多行注释:可以使用括号来实现多行注释:
                          #[[多行注释
                              多行注释
                              多行注释]]

3.CMake变量:

   (1).CMake中所有的变量都是string类型。
   (2).set()/unset()声明/移除一个变量
   (3).声明变量:set(变量名 变量值)
                           set(var 123)
   (4).引用变量:${变量名}
                          ${var}
   (5).打印变量:message("变量名 = ${变量名}")
                          message("var = ${var}")

4.CMake列表(LISTS)

   (1).列表也是字符串,可以把列表看做是一个特殊的变量,这个变量有多个值
   (2).语法格式:set(列表名 值1 值2 ... 值n)  或  set(列表名 “值1;值2;...值n”)
   (3).声明列表:set(列表名 值1 值2 ... 值n)  或  set(列表名 “值1;值2;...值n”)
                           set(list_var 1 2 3 4 5)          或  set(list_var "1;2;3;4;5")
   (4).引用列表:${列表名}
   (5).打印列表:message("列表名 = ${列表名}")
                          message("list_var = ${list_var}")

5.CMake中变量的作用域

   (1).全局层:cache变量,在整个项目范围可见,
                      一般在set定义变量式,指定CACHE参数就能定义cache变量
   (2).目录层:在当前⽬录CMakeLists.txt中定义,
                       以及在该文件包含的其他CMake源文件中定义的变量
   (3).函数层:在命令函数中定义的变量,属于函数作用域内的变量

6.CMake流程控制

   (1).操作符:
         优先级: () > 一元 > 二元 > 逻辑
        CMake语法详解_第1张图片

   (2).布尔常量值:
        CMake语法详解_第2张图片

   (3).条件命令 if():
        语法格式
            if (表达式)
                COMMAND(ARGS...)
            elseif(表达式)
                COMMAND(ARGS...)
            else(表达式)
                COMMAND(ARGS...)
            endif(表达式)

        示例
            set(if_tap OFF)
            set(elseif_tap ON)

            if(${if_tap})
                 message("if")
            elseif(${elseif_tap})
                 message("elseif")
            else(${if_tap})
                 message("else")
            endif(${if_tap})

        elseif和else部分是可选的, 也可以使⽤多个elseif部分
        缩进和空格对语句的解析没有影响

   (4).循环命令 while():
        语法格式
            while(表达式)
                COMMAND(ARGS...)
            endwhile(表达式)

        示例
            set(a "")

            while(NOT a STREQUAL "xxx")
                set(a "${a}x")
                message("a = ${a}")
            endwhile()
            
        break() 可以跳出整个循环
        continue() 可以跳出当前循环

   (5).循环遍历 foreach():
        语法格式
            foreach(循环变量 参数1 参数2... 参数N)
                 COMMAND(ARGS...)
            endforeach(循环变量)
            
            遍历RANGE:
            #循环范围从start到stop,循环增量为step
            foreach(循环变量 RANGE start stop step)
                 COMMAND(ARGS...)
            endforeach(循环变量)
            
            遍历LISTS
            foreach(循环遍历 IN LISTS 列表)
                 COMMAND(ARGS...)
            endforeach(循环变量)

        示例
            foreach(item 1 2 3)
                message("item = ${item}")
            endforeach(item)
            
            #RANGE:RANGE 4 表示从0到4
            foreach(item RANGE 4)
                message("item = ${item}")
            endforeach(item)
        
            #RANGE:打印 1 3 5
            foreach(item RANGE 1 5 2)
                message("item = ${item}")
            endforeach(item)

            #LISTS
            set(list_var 1 2 3)
            foreach(item IN LISTS list_var)
                message("item = ${item}")
            endforeach(item)

          foreach也支持 break() 和 continue() 命令跳出循环

7.CMake自定义函数命令

    语法格式
    function([arg1 [arg3 [arg3...]]])
         COMMAND(ARGS...)
    endfunction()

    调用格式
    name(参数列表)
    
    示例
    function(func x y z)
        message("call function func")
        message("x = ${x}")
        message("y = ${y}")
        message("z = ${z}")
        # ARGC 内置变量   参数个数
        message("ARGC = ${ARGC}")
        # ARGVn 内置变量   第 n 个参数,从0开始
        message("arg1 = ${ARGV0}")
        message("arg2 = ${ARGV1}")
        message("arg3 = ${ARGV2}")
         # ARGV 内置变量   参数列表
        message("all args = ${ARGV}")
    endfunction(func)

    调用:fun(1 2 3)

8.CMake自定义宏命令

    语法格式
    macro([arg1 [arg3 [arg3...]]])
         COMMAND(ARGS...)
    endmacro()

    调用格式
    name(实参列表)
    
    示例
    marco(ma x y z)
        message("call macro ma")
        message("x = ${x}")
        message("y = ${y}")
        message("z = ${z}")
    endmacro(ma)    

    调用:ma(1 2 3)

    函数命令有自己的作用域
    宏的作用域和调用者的作用域是一样的

9.CMake常用变量

  CMake预设了一些常用变量,这些变量通常会在编写CMakeLists.txt文件时使用到:

CMAKE_MAJOR_VERSION:cmake 主版本号
CMAKE_MINOR_VERSION:cmake 次版本号
CMAKE_C_FLAGS:设置 C 编译选项
CMAKE_CXX_FLAGS:设置 C++ 编译选项
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录
CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt 所在路径
CMAKE_CURRENT_BINARY_DIR:目标文件编译目录
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置
UNIX:如果为真,表示为UNIX-like的系统,包括AppleOSX和CygWin
WIN32:如果为真,表示为 Windows 系统,包括 CygWin
APPLE:如果为真,表示为 Apple 系统
CMAKE_SIZEOF_VOID_P:表示void*的大小(例如为4或者8),可以使用其来判断当前构建为32位还是64位
CMAKE_CURRENT_LIST_DIR:表示正在处理的CMakeLists.txt文件所在目录的绝对路径
CMAKE_ARCHIVE_OUTPUT_DIRECTORY:用于设置ARCHIVE目标的输出路径
CMAKE_LIBRARY_OUTPUT_DIRECTORY:用于设置LIBRARY目标的输出路径
CMAKE_RUNTIME_OUTPUT_DIRECTORY:用于设置RUNTIME目标的输出路径

10.CMake常用命令

(1) project命令:

命令语法:project( [languageName1 languageName2 ...])
命令简述:用于指定项目的名称
使用范例:project(Main)

(2) cmake_minimum_required命令:

命令语法:cmake_minimum_requried(VERSION major[.minor[.patch)
命令简述:用于指定需要的CMake的最低版本
使用范例:cmake_minimum_requried(VERSION 2.8.3)

(3) aux_source_directory命令:

命令语法:aux_source_directory( )
命令简述:用于包含源文件目录,dir目录下的所有源文件的名字保存在变量variable中
使用范例:aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src  DIR_SRCS)

(4) add_executable命令:

命令语法:add_executable( [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL] source1 source2 … sourceN)
命令简述:用于指定从一组源文件source1 source2 ... sourceN 编译出一个可执行文件且命名为name
使用范例:add_executable(Main $(DIR_SRCS))

(5) add_library命令:

命令语法:add_library([STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1source2 … sourceN)
命令简述:用于指定从一组源文件 source1 source2 ... sourceN编译出一个库文件且命名为name
使用范例:add_library(Lib $(DIR_SRCS))

(6) add_dependencies命令:

命令语法:add_dependencies(target-name depend-target1 depend-target2 …)
命令简述:用于指定某个目标(可执行文件或者库文件)依赖于其他的目标。
        这里的目标必须是add_executable、add_library、add_custom_target命令创建的目标

(7) add_subdirectory命令:

命令语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
命令简述:用于添加一个需要进行构建的子目录
使用范例:add_subdirectory(Lib)

(8) target_link_libraries命令:

命令语法:target_link_libraries( [item1 [item2 […]]][[debug|optimized|general] ] …)
命令简述:用于指定target需要链接item1 item2 ...。这里target必须已经被创建,链接的item可以是已经存在的target(依赖关系会自动添加)
使用范例:target_link_libraries(Main Lib)

(9) set命令:

命令简述:用于设定变量 variable 的值为 value。如果指定了 CACHE 变量将被放入 Cache(缓存)中。
命令语法:set(  [[CACHE  [FORCE]] | PARENT_SCOPE])
使用范例:set(ProjectName Main)

(10) unset命令:

命令语法:unset( [CACHE])
命令简述:用于移除变量 variable。如果指定了 CACHE 变量将被从 Cache 中移除。
使用范例:unset(VAR CACHE)

(11) message命令:

命令语法:message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] “message todisplay”…)
命令简述:用于输出信息
使用范例:message(“Hello World”)

(12) include_directories命令:

命令语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
命令简述:用于设定目录,这些设定的目录将被编译器用来查找 include 文件
使用范例:include_directories(${PROJECT_SOURCE_DIR}/lib)

(13) find_path命令:

命令语法:find_path( name1 [path1 path2 …])
命令简述:用于查找包含文件name1的路径,如果找到则将路径保存在VAR中(此路径为一个绝对路径),如果没有找到则结果为-NOTFOUND.默认情况下,VAR会被保存在Cache中,这时候我们需要清除VAR才可以进行下一次查询(使用unset命令)
find_path(LUA_INCLUDE_PATH lua.h ${LUA_INCLUDE_FIND_PATH})
if(NOT LUA_INCLUDE_PATH)
     message(SEND_ERROR "Header file lua.h not found")
endif()

(14) find_library命令:

命令语法:find_library( name1 [path1 path2 …])
命令简述:用于查找库文件 name1 的路径,如果找到则将路径保存在 VAR 中(此路径为一个绝对路径),
        如果没有找到则结果为 -NOTFOUND。
        一个类似的命令 link_directories 已经不太建议使用了

(15) add_definitions命令:

命令语法:add_definitions(-DFOO -DBAR …)
命令简述:用于添加编译器命令行标志(选项),通常的情况下我们使用其来添加预处理器定义
使用范例:add_definitions(-D_UNICODE -DUNICODE)

(16) file命令:

命令简述:此命令提供了丰富的文件和目录的相关操作(这里仅说一下比较常用的)
使用范例:
# 目录的遍历
# GLOB 用于产生一个文件(目录)路径列表并保存在variable 中
# 文件路径列表中的每个文件的文件名都能匹配globbing expressions(非正则表达式,但是类似)
# 如果指定了 RELATIVE 路径,那么返回的文件路径列表中的路径为相对于 RELATIVE 的路径
file(GLOB variable [RELATIVE path][globbing expressions]...)
 
# 获取当前目录下的所有的文件(目录)的路径并保存到 ALL_FILE_PATH 变量中
file(GLOB ALL_FILE_PATH ./*)
# 获取当前目录下的 .h 文件的文件名并保存到ALL_H_FILE 变量中
# 这里的变量CMAKE_CURRENT_LIST_DIR 表示正在处理的 CMakeLists.txt 文件的所在的目录的绝对路径(2.8.3 以及以后版本才支持)

11.CMakeLists.txt 编写、编译、示例

11.1 基本流程

  (1).需要编译的源文件
  (2).编写CMakeLists.txt
  (3).终端运行cmake命令(1.3中有讲到),由CMake根据CMakeLists.txt生成Makefile
  (4).终端运行make命令(1.3中有讲到),由Make根据Makefile,调用gcc生成可执行文件

CMake语法详解_第3张图片

 11.2 基础命令:

 一个CMakeLists.txt的编写,如下几个基础命令是十分常用的,具体释义在上一节中已有讲到

  (1).cmake_minimum_required(VERSION x.x.x):用于指定cmake所需最低版本
  (2).project(Project) :用于指定项目名称
  (3).include_directories() :用于包含头文件目录
  (4).aux_source_directory(src dir_srcs):用于包含源文件目录
  (5).set(TEST_MATH) :用于设置环境变量,编译用到的源文件全部都要放到这里
  (6).add_executable(${PROJECT_NAME} ${TEST_MATH}):用于添加要编译的可执行文件
  (7).target_link_libraries(${PROJECT_NAME} m):用于添加可执行文件所需要的库

  11.3 Hello_CMake
  (1).目录结构:

├── CMakeLists.txt     #父目录的CMakeList.txt
├── main.cpp           #源文件,包含main函数
├── sub                #子目录
 └── CMakeLists.txt    #子目录的CMakeLists.txt
 └── test.h            #子目录头文件
 └── test.cpp          #子目录源文件

  (2).源文件代码:

/hello_cmake/sub/test.h

#include 

void test(std::string str);
/hello_cmake/sub/test.cpp 

#include "test.h"
#include 

void test(std::string str)
{
    std::cout << str << std::endl;
}
/hello_cmake/main.cpp

#include "test.h"
#include 

int main(int argc, char** argv)
{
    std::cout << "In main..." << std::endl;
    test("hello, world!");
    return 0;
}

 (3).CMakeLists.txt代码:

#/hello_cmake/sub/CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)     #编译所需cmake最低版本号
project(sub)
add_library(sub test.cpp)                  #test.cpp被编译成名为sub的库,参看之前的函数释义
                                            test.cpp会被编译成[STATIC|SHARED|MODULE]其中 
                                            一种类型的库,如果没有设置,则默认编程STATIC也就 
                                            是.a库
#/hello_cmake/CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)
project(test)

include_directories(sub)                  #包含目录sub
add_subdirectory(sub output)              #指定需要进行构建的子目录sub ,并且子目录中编译输出在 
                                           output,如果不设置,默认会建一个build目录作为输出
                                           当执行到add_subdirectory(xx)命令的时候会进入(xx)子
                                           目录并执行其中的CMakeLists.txt文件
add_executable(test main.cpp)             #main.cpp编译为执行文件test
target_link_libraries(test sub)           #test可执行文件需要链接Lib库

(4).编译前后对比

编译前目录结构:

CMake语法详解_第4张图片   CMake语法详解_第5张图片

编译后目录结构:

CMake语法详解_第6张图片    CMake语法详解_第7张图片

 CMake语法详解_第8张图片

11.4 target_link的三种属性

target_link_libraries( ... ... ...)
target必须先由add_executable()或add_library()之类的命令创建,并且不能是别名(ALIAS)

target_link_libraries()有三种链接属性
PUBLIC:target源文件和头文件中都包含target文件头
INTERFACE:只有target头文件中包含了item文件头
PRIVATE: 只有target源文件(例如cpp)中包含了item头文件

如果没有设置,默认是INTERFACE


CMake基本语法释义就先写到这里。
后续在实际开发过程中再遇到新的需要摸清的知识点,再进行添加。

参考文档:

https://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pcCmake语法详解 - 百度文库
https://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pchttps://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pc

(六) CMake基本语法_li三河的博客-CSDN博客

Cmake命令之add_subdirectory介绍 - 简书

你可能感兴趣的:(c++,开发语言,xr,android,算法)