cmake 脚本编程简介

前言

学习,其实就是用已有的知识去理解未知的过程,如果能找到已有知识和未知之间的相似之处,那么学习将事半功倍。接下来,我将尝试用找寻已经学会的编程语言和cmake之间的相似点。
以前,我只能被动的去记忆cmake的一条条命令,把一cmake看作是一个工具,我要去用一条条命令去指挥cmake去工作,可我最后发现,根本记不住。后来我调整了想法,cmake应该被看做一个编译器,cmake那一条条命令其实是一种新语言的语法,我编写的一条条命令最终会被编译成另外一个脚本。那么,让我们开启愉快的编程之旅。
声明一下,我没有去了解过cmake内部的工作原理,好奇心得有个限度,先专注于目前需要解决的事情。这里不管它最终不是以编译的方式进行,在这里都认为它是一个编译器,cmake的命令就是语法。

预备知识

cmake是一个用于管理源码编译的工具,虽然常见的使用场景是用于生成Makefile便于使用make构建工程,但其实它也可以用于其他构建系统以及IDE,例如生成Visual Studio等的工程文件。
为了能方便例子的讲解,这里先给出一些编写例子需要的预备知识。

  1. cmake程序必须有的两行,声明最低的版本要求和确定工程名字,并且需要放在文件的开头:
cmake_minimum_required(VERSION 2.8)
project(Test)

其中VERSION后面的值可以换成已有的任何cmake版本值,形式是主版本号.次版本号,例如我的机子上的cmake版本是cmake version 3.5.1,那么我可以改成cmake_minimum_required(VERSION 3.5)Test也可以改成任何名字。
2. 注释使用井号#,可以独立一行或者和代码共用同一行。
3. 代码块类似于Python,使用缩进表示。
4. 假设读者有一定的shell编程知识,因为很多语法和shell语法类似。
5. 关键字都以英文输入状态的括号()结尾,表达式为于括号内。

语法语义

使用cmake,其实也就是在编写脚本。既然是脚本,万变不离其宗,编程上的一套东西其实是通用的,只要你了解它的语法,就可以开始编程。
篇(wo)幅(bi)有(jiao)限(lan),这里只介绍能跑起一个简单的程序的语法而不是cmake的所有语法,毕竟有些语法使用频率也很低。编程的目的是按照一定的算法对数据进行处理,最终得到我们想要的结果。对语法的介绍将从下面几个方面进行介绍:

  1. 变量定义;
  2. 数据操作;
  3. 程序结构;
  4. 代码复用;
  5. 输入输出。

变量定义

cmake中用于定语变量的语法有两种方式:set()option()

  1. set用于定义数值型变量,理论上它定义的变量都是字符串,但是有些特殊的变量如果你愿意也可把他看成数值类型,例如set(var 10),你可以把它看成数值10也可看成字符串"10"。其原型为set(<variable> <value>... [PARENT_SCOPE])。例如set(VAR helloword)就定义了一个值是helloworld的变量VAR。这里字符串加不加双引号都行,但是有细小的区别。一般情况下使用它对源文件进行归类,所以一般不加引号。
  2. option可以用于定语布尔变量,其作用是可以给用户提供编译的选项。其原型为option(<variable> "<help_text>" [value])。例如option(TRUE "boolean value true" ON)定义一个叫TRUE`的布尔变量,它的值为真。

数据操作

定义数据后,我们可以对数据进行操作,主要有两种:

  1. 数学运算;
  2. 字符串操作;
  3. 数组操作。

先说第一种,数学运算。其原型为math(EXPR <variable> "<expression>" [OUTPUT_FORMAT <format>]),需要注意的是,它的表达式需要的是带双引号的字符串表示,例如"10 + 2 * 3",支持的数学运算有+, -, *, /, %, |, &, ^, ~, <<, >>,这些符号的含义和他们在C++中的数学运算含义一样。例如math(EXPR mul "10 * 20")
接下来说说字符串操作。字符串操作也基本和主流编程语言相似,支持的操作有查找、替换、小写转大写、大写转小写、拼接以及去头去尾等,甚至还支持正则表达式。其原型有多个,这里只列举几个,详细的列表请看文末参考文档:

  string(TOLOWER <string> <out-var>)
  string(TOUPPER <string> <out-var>)
  string(LENGTH <string> <out-var>)

例如string(TOLOWER "helloworld" var),则var的值为HELLOWORLD
而数组操作,则是通过list()语句实现的。list()语句也包括对数组增删改查等基本操作。详细信息参阅参考文档。

基本结构

学计算机的都知道,任何算法,不论多么简单或者复杂,都可以由顺序结构、选择结构和循环结构这三种基本结构组合而成。因此,每一种语言都必须提供这三种操作的语法。

  1. 顺序结构:这个没什么好说的,cmake会从文件开始,顺序执行程序,以值到文件结束。如果在执行的过程中遇到include()包含的其他文件或者函数调用,那么就进入该文件或者函数内部去继续执行,执行完毕或者遇到return语句在返回到原来的地方继续执行;
  2. 选择结构:
if(<condition>)
  <commands>
elseif(<condition>) # 可以没有
  <commands>
else()              # 可以没有
  <commands>
endif()

与一般语言不同的是,else()也可以有条件判断语句,例如else(3 EQUAL 3)。可以用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。举个例子:

cmake_minimum_required(VERSION 3.5)
project(Test)

option(BOOLEAN_FALSE "boolean false" OFF)
if(TRUE AND BOOLEAN_FALSE)
    message(STATUS "Hello World!")
endif()
if(UNIX)
    message(STATUS "UNIX system")
else()
    message(STATUS "Other system")
endif()

则输出为:

-- UNIX system
  1. 循环结构:循环结构有两种方式:
while(<condition>)
  <commands>
endwhile()

foreach(<loop_var> <items>)
  <commands>
endforeach()

用于if()的条件语句中的操作符,也一样适用于while()语句。此外,break(), continue()使得你可以终止当前的控制流程。你一定好奇怎么定义个列表,这里举个栗子:

set(LIST one two three four five)

foreach(item ${LIST})
    message(STATUS ${item})
endforeach()

输出为:

-- one
-- two
-- three
-- four
-- five

代码复用

编程,除了Hello World这一类非常简单的栗子,肯定会有重复性的工作,因此,编程语言需要提供代码复用的方法。为了实现代码复用,cmake提供了三种代码复用的方法,分别是1)函数,2)宏,3)包含其他cmake文件,函数和文件包含都可以通过return()语句将控制权返回给调用者。

函数

在cmake中定义函数和调用的方法为,函数调用是大小写敏感的:

# 定义
function(<name> [<arg1> ...])
  <commands>
endfunction()
# 例子
function(foo)
  <commands>
endfunction()
foo()
Foo()
fOo()

宏的定义和函数定义看起来及其相似,使用上也是大小写不敏感。但是宏和C++里面的宏一样,其实做的是字符替换而不是传递真的值。

macro(<name> [<arg1> ...])
  <commands>
endmacro()
文件包含

使用include()去包含另外一个文件或者模块到当前文件中。

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>]
                      [NO_POLICY_SCOPE])

输入输出

这里说的输入输出,讲的是输出信息给用户,让用户知道程序做了什么或者处于什么状态,而不是说输出编译工程的文件脚本,注意区别。
有于cmake的目的是生成做在平台的编译脚本,所以这里只说如何输出信息。
输出信息使用message()语句:message([<mode>] "message text" ...)>。上面的例子中我们已经多次使用这个语句。

到此,我们所学的语法基本可以编写一个程序了,虽然这样的程序其实并没有什么用。为了让程序变得在实际中有用,我们需要学习另一部分——“系统调用”。

系统调用

这里所谓“系统调用”,并不是调用OS,而是使用一些语句,结合我们上节所介绍的语法所定义、处理的一些数据,真正能够生成对应平台的编译脚本。这里,也不会对所有的命令做一一的介绍,这里只简单介绍一些我认为常用的,想要获取全部的命令,参阅参考文档。

1. 编译可执行文件

想要将源代码编译成一个可执行文件,使用add_executable()命令,例如我有一个名字叫main.cpp的源文件,则我的编译命令为:

cmake_minimum_required(VERSION 3.5)
project(Test)

set(SRC main.cpp)
set(CMAKE_CXX_FLAGS -std=c++11)
add_executable(make_out ${SRC})

其中CMAKE_CXX_FLAGS相当于一个内部变量,对于内部变量,我们只需要设置它的值,不需要显试的使用它。

2. 编译库文件

编译库文件使用add_library()命令。原型如下:

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 ...])

3. 链接文件

编译库文件使用target_link_libraries()命令,相当于在gcc命令中通过-I指定链接的时候所依赖的库文件。原型如下:

target_link_libraries(<target>
                      <PRIVATE|PUBLIC|INTERFACE> <item>...
                     [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

类似的还有link_libraries()

4. 添加编译参数

内部变量

关于cmake还有一类值得一提的是内不变量,也可以说是于定义变量,例如上面提到的CMAKE_CXX_FLAGS。预定义变量有几百个,一一讲解是不可能的,也没有必要。对于编程,我一直主张的先对语言有个大概了解,一边使用一边深入,想学完在使用是不可能的,因为根本学不完。谁用谁知道,只需要有这么个概念,等到要用的时候查参考手册就行。

References

[1] cmake-commands
[2] cmake-variables

本文首发于个人微信公众号TensorBoy。如果你觉得内容还不错,欢迎分享并关注我的微信公众号TensorBoy,扫描下方二维码获取更多精彩原创内容!
公众号二维码

发布了45 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZM_Yang/article/details/104633597
今日推荐