CMake应用:核心语法篇

本文是深入CMakeLists.txt之前的前导文章,介绍CMake语言的核心概念,以及常用的CMake脚本命令,以期对CMake的语法能有比较好的认知和实践基础。

在前一篇文章中介绍了CMake的核心概念,使用的一般流程,并通过一个实例讲解了CMake命令行工具之一的cmake命令的使用方法。
该系列往期文章:

  1. CMake应用:基础篇

在开始深入如何编写完备的CMakeLists.txt之前,先了解下CMake的语言和它的组织方式对后续内容的理解是很有帮助的。本文将会介绍以下内容:

  1. CMake语言的核心概念
  2. CMake常用脚本命令及示例

一 CMake语法核心概念

下面介绍的内容,可以只先有一些概念,不求甚解,在后续需要深入的时候查看文档即可。

CMake的命令有不同类型,包括脚本命令、项目配置命令和测试命令,细节可以查看官网cmake-commands

CMake语言在项目配置中组织为三种源文件类型:

  1. 目录:CMakeLists.txt,针对的是一个目录,描述如何针对目录(Source tree)生成构建系统,会用到项目配置命令;
  2. 脚本:<script>.cmake,就是一个CMake语言的脚本文件,可使用cmake -P直接执行,只能包含脚本命令;
  3. 模块:<module>.cmake,实现一些模块化的功能,可以被前面两者包含,比如include(CTest)启用测试功能。

1 注释

行注释使用"#";块注释使用"#[[Some comments can be multi lines or in side the command]]"。比如:

# Multi line comments follow
#[[
Author: FarmerLi, 公众号: 很酷的程序员/RealCoolEngineer
Date: 2021-04-27
]]

2 变量

CMake中使用setunset命令设置或者取消设置变量。CMake中有以下常用变量类型。

一般变量

设置的变量可以是字符串,数字或者列表(直接设置多个值,或者使用分号隔开的字符串格式为"v1;v2;v3"),比如:

# Set variable
set(AUTHOR_NAME Farmer)
set(AUTHOR "Farmer Li")
set(AUTHOR Farmer\ Li)

# Set list
set(SLOGAN_ARR To be)   # Saved as "To;be"
set(SLOGAN_ARR To;be)
set(SLOGAN_ARR "To;be")

set(NUM 30)   # Saved as string, but can compare with other number string
set(FLAG ON)  # Bool value

主要有以下要点:

  1. 如果要设置的变量值包含空格,则需要使用双引号或者使用"\"转义,否则可以省略双引号;
  2. 如果设置多个值或者字符串值的中间有";",则保存成list,同样是以";"分割的字符串;
  3. 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表;
  4. 引用变量:${<variable>},在if()条件判断中可以简化为只用变量名<variable>

Cache变量

Cache变量(缓存条目,cache entries)的作用主要是为了提供用户配置选项,如果用户没有指定,则使用默认值,设置方法如下:

# set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(CACHE_VAR "Default cache value" CACHE STRING "A sample for cache variable")

要点:

  1. 主要为了提供可配置变量,比如编译开关;
  2. 引用CACHE变量:$CACHE{<varialbe>}

Cache变量会被保存在构建目录下的CMakeCache.txt中,缓存起来之后是不变的,除非重新配置更新

环境变量

修改当前处理进程的环境变量,设置和引用格式为:

# set(ENV{<variable>} [<value>])
set(ENV{ENV_VAR} "$ENV{PATH}")
message("Value of ENV_VAR: $ENV{ENV_VAR}")

和CACHE变量类似,要引用环境变量,格式为:$ENV{<variable>}

3 条件语句

支持的语法有:

  1. 字符串比较,比如:STREQUAL、STRLESS、STRGREATER等;
  2. 数值比较,比如:EQUAL、LESS、GREATER等;
  3. 布尔运算,AND、OR、NOT
  4. 路径判断,比如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
  5. 版本号判断;等等;
  6. 使用小括号可以组合多个条件语句,比如:(cond1) AND (cond2 OR (cond3))

对于常量

  1. ON、YES、TRUE、Y和非0值均被视为True
  2. 0、OFF、NO、FALSE、N、IGNORE、空字符串、NOTFOUND、及以"-NOTFOUND"结尾的字符串均视为False

对于变量,只要其值不是常量中为False的情形,则均视为True

二 常用的脚本命令

有了前面的总体概念,下面掌握一些常用的CMake命令,对于CMake脚本编写就可以有不错的基础。

1 消息打印

前面已经有演示,即message命令,其实就是打印log,用来打印不同信息,常用命令格式为:

message([<mode>] "message text" ...)

其中mode就相当于打印的等级,常用的有这几个选项:

  1. 空或者NOTICE:比较重要的信息,如前面演示中的格式
  2. DEBUG:调试信息,主要针对开发者
  3. STATUS:项目使用者可能比较关心的信息,比如提示当前使用的编译器
  4. WARNING:CMake警告,不会打断进程
  5. SEND_ERROR:CMake错误,会继续执行,但是会跳过生成构建系统
  6. FATAL_ERROR:CMake致命错误,会终止进程

2 条件分支

这里以if()/elseif()/else()/endif()举个例子,for/while循环也是类似的:

set(EMPTY_STR "")
if (NOT EMPTY_STR AND FLAG AND NUM LESS 50 AND NOT NOT_DEFINE_VAR)
    message("The first if branch...")
elseif (EMPTY_STR)
    message("EMPTY_STR is not empty")
else ()
    message("All other case")
endif()

3 列表操作

list是CMake中进行列表操作的一个命令,有很多有用的子命令,比较常用的有:

  1. APPEND,往列表中添加元素;
  2. LENGTH,获取列表元素个数;
  3. JOIN,将列表元素用指定的分隔符连接起来;

示例如下:

set(SLOGAN_ARR To be)   # Saved as "To;be"
set(SLOGAN_ARR To;be)
set(SLOGAN_ARR "To;be")
set(WECHAT_ID_ARR Real Cool Eengineer)
list(APPEND SLOGAN_ARR a)                # APPEND sub command
list(APPEND SLOGAN_ARR ${WECHAT_ID_ARR}) # Can append another list
list(LENGTH SLOGAN_ARR SLOGAN_ARR_LEN)   # LENGTH sub command
# Convert list "To;be;a;Real;Cool;Engineer"
# To string "To be a Real Cool Engineer"
list(JOIN SLOGAN_ARR " " SLOGEN_STR)
message("Slogen list length: ${SLOGAN_ARR_LEN}")
message("Slogen list: ${SLOGAN_ARR}")
message("Slogen list to string: ${SLOGEN_STR}\n")

对于列表常用的操作,list命令都基本实现了,需要其他功能直接查阅官方文档即可。

4 文件操作

CMake的file命令支持的操作比较多,可以读写、创建或复制文件和目录、计算文件hash、下载文件、压缩文件等等。
使用的语法都比较类似,以笔者常用的递归遍历文件为例,下面是获取src目录下两个子目录内所有c文件的列表的示例:

file(GLOB_RECURSE ALL_SRC
        src/module1/*.c
        src/module2/*.c
        )

GLOB_RECURSE表示执行递归查找,查找目录下所有符合指定正则表达式的文件。

5 配置文件生成

使用configure_file命令可以将配置文件模板中的特定内容替换,生成目标文件。
输入文件中的内容@VAR@或者${VAR}在输出文件中将被对应的变量值替换。
使用方式为:

set(VERSION 1.0.0)
configure_file(version.h.in "${PROJECT_SOURCE_DIR}/version.h")

假设version.in.h的内容为:

#define VERSION "@VERSION@"

那么生成的version.h的内容为:

#define VERSION "1.0.0"

6 执行系统命令

使用execute_process命令可以执行一条或者顺序执行多条系统命令,对于需要使用系统命令获取一些变量值是有用的。比如获取当前仓库最新提交的commit的commit id:

execute_process(COMMAND bash "-c" "git rev-parse --short HEAD" OUTPUT_VARIABLE COMMIT_ID)

7 查找库文件

通过find_library在指定的路径和相关默认路径下查找指定名字的库,常用的格式如下:

find_library (<VAR> name1 [path1 path2 ...])

找到的库就可以被其他target使用,表明依赖关系。

8 include其他模块

include命令将cmake文件或者模块加载并执行。比如:

include(CPack) # 开启打包功能
include(CTest) # 开启测试相关功能

CMake自带有很多有用的模块,可以看看官网的链接:cmake-modules,对支持的功能稍微有所了解,后续有需要再细看文档。

当然,如果感兴趣,也可以直接看CMake安装路径下的目录CMake\share\cmake-3.20\Modules中的模块源文件。

文中的示例代码均共享在开源仓库:https://gitee.com/RealCoolEngineer/cmake-template,当前commit id:f8f3948
关于CMake脚本源文件的示例位于路径:cmake/script_demo.cmake,可以使用cmake -P cmake/script_demo.cmake执行查看结果。
关于配置文件生成的操作在项目根目录的CMakeLists.txt中也有示例。

Ok,对于CMake的核心语法概念,以及常用的脚本命令掌握这么多就可以开始下一步了。
下一篇文章将会详细介绍CMakeLists.txt的书写,将会涉及到编译、链接、测试和打包几个部分,值得期待(●'◡'●)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章