前言
学习,其实就是用已有的知识去理解未知的过程,如果能找到已有知识和未知之间的相似之处,那么学习将事半功倍。接下来,我将尝试用找寻已经学会的编程语言和cmake之间的相似点。
以前,我只能被动的去记忆cmake的一条条命令,把一cmake看作是一个工具,我要去用一条条命令去指挥cmake去工作,可我最后发现,根本记不住。后来我调整了想法,cmake应该被看做一个编译器,cmake那一条条命令其实是一种新语言的语法,我编写的一条条命令最终会被编译成另外一个脚本。那么,让我们开启愉快的编程之旅。
声明一下,我没有去了解过cmake内部的工作原理,好奇心得有个限度,先专注于目前需要解决的事情。这里不管它最终不是以编译的方式进行,在这里都认为它是一个编译器,cmake的命令就是语法。
预备知识
cmake是一个用于管理源码编译的工具,虽然常见的使用场景是用于生成Makefile便于使用make构建工程,但其实它也可以用于其他构建系统以及IDE,例如生成Visual Studio等的工程文件。
为了能方便例子的讲解,这里先给出一些编写例子需要的预备知识。
- cmake程序必须有的两行,声明最低的版本要求和确定工程名字,并且需要放在文件的开头:
cmake_minimum_required(VERSION 2.8)
project(Test)
其中VERSION
后面的值可以换成已有的任何cmake版本值,形式是主版本号.次版本号
,例如我的机子上的cmake版本是cmake version 3.5.1
,那么我可以改成cmake_minimum_required(VERSION 3.5)
;Test
也可以改成任何名字。
- 注释使用井号
#
,可以独立一行或者和代码共用同一行。 - 代码块类似于Python,使用缩进表示。
- 假设读者有一定的shell编程知识,因为很多语法和shell语法类似。
- 关键字都以英文输入状态的括号
()
结尾,表达式为于括号内。
语法语义
使用cmake,其实也就是在编写脚本。既然是脚本,万变不离其宗,编程上的一套东西其实是通用的,只要你了解它的语法,就可以开始编程。
篇(wo)幅(bi)有(jiao)限(lan),这里只介绍能跑起一个简单的程序的语法而不是cmake的所有语法,毕竟有些语法使用频率也很低。编程的目的是按照一定的算法对数据进行处理,最终得到我们想要的结果。对语法的介绍将从下面几个方面进行介绍:
- 变量定义;
- 数据操作;
- 程序结构;
- 代码复用;
- 输入输出。
变量定义
cmake中用于定语变量的语法有两种方式:set()
和option()
。
-
set
用于定义数值型变量,理论上它定义的变量都是字符串,但是有些特殊的变量如果你愿意也可把他看成数值类型,例如set(var 10)
,你可以把它看成数值10
也可看成字符串"10"
。其原型为set(
。例如... [PARENT_SCOPE]) set(VAR helloword)
就定义了一个值是helloworld
的变量VAR
。这里字符串加不加双引号都行,但是有细小的区别。一般情况下使用它对源文件进行归类,所以一般不加引号。 - option可以用于定语布尔变量,其作用是可以给用户提供编译的选项。其原型为
option(
。例如" " [value]) option(TRUE "boolean value true" ON)定义一个叫
TRUE`的布尔变量,它的值为真。
数据操作
定义数据后,我们可以对数据进行操作,主要有两种:
- 数学运算;
- 字符串操作;
- 数组操作。
先说第一种,数学运算。其原型为math(EXPR
,需要注意的是,它的表达式需要的是带双引号的字符串表示,例如" " [OUTPUT_FORMAT ]) "10 + 2 * 3"
,支持的数学运算有+, -, *, /, %, |, &, ^, ~, <<, >>
,这些符号的含义和他们在C++中的数学运算含义一样。例如math(EXPR mul "10 * 20")
。
接下来说说字符串操作。字符串操作也基本和主流编程语言相似,支持的操作有查找、替换、小写转大写、大写转小写、拼接以及去头去尾等,甚至还支持正则表达式。其原型有多个,这里只列举几个,详细的列表请看文末参考文档:
string(TOLOWER )
string(TOUPPER )
string(LENGTH )
例如string(TOLOWER "helloworld" var)
,则var
的值为HELLOWORLD
。
而数组操作,则是通过list()
语句实现的。list()
语句也包括对数组增删改查等基本操作。详细信息参阅参考文档。
基本结构
学计算机的都知道,任何算法,不论多么简单或者复杂,都可以由顺序结构、选择结构和循环结构这三种基本结构组合而成。因此,每一种语言都必须提供这三种操作的语法。
- 顺序结构:这个没什么好说的,cmake会从文件开始,顺序执行程序,以值到文件结束。如果在执行的过程中遇到
include()
包含的其他文件或者函数调用,那么就进入该文件或者函数内部去继续执行,执行完毕或者遇到return
语句在返回到原来的地方继续执行; - 选择结构:
if()
elseif() # 可以没有
else() # 可以没有
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
- 循环结构:循环结构有两种方式:
while()
endwhile()
foreach( )
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( [ ...])
endfunction()
# 例子
function(foo)
endfunction()
foo()
Foo()
fOo()
宏
宏的定义和函数定义看起来及其相似,使用上也是大小写不敏感。但是宏和C++里面的宏一样,其实做的是字符替换而不是传递真的值。
macro( [ ...])
endmacro()
文件包含
使用include()
去包含另外一个文件或者模块到当前文件中。
include( [OPTIONAL] [RESULT_VARIABLE ]
[NO_POLICY_SCOPE])
输入输出
这里说的输入输出,讲的是输出信息给用户,让用户知道程序做了什么或者处于什么状态,而不是说输出编译工程的文件脚本,注意区别。
有于cmake的目的是生成做在平台的编译脚本,所以这里只说如何输出信息。
输出信息使用message()
语句:message([
>。上面的例子中我们已经多次使用这个语句。
到此,我们所学的语法基本可以编写一个程序了,虽然这样的程序其实并没有什么用。为了让程序变得在实际中有用,我们需要学习另一部分——“系统调用”。
系统调用
这里所谓“系统调用”,并不是调用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( [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
3. 链接文件
编译库文件使用target_link_libraries()
命令,相当于在gcc命令中通过-I
指定链接的时候所依赖的库文件。原型如下:
target_link_libraries(
- ...
[
- ...]...)
类似的还有link_libraries()
。
4. 添加编译参数
- 使用
add_compile_definitions()
添加与处理参数; - 使用
include_directories()
添加头文件目录; - 使用
add_compile_options()
添加别的选项。
内部变量
关于cmake还有一类值得一提的是内不变量,也可以说是于定义变量,例如上面提到的CMAKE_CXX_FLAGS
。预定义变量有几百个,一一讲解是不可能的,也没有必要。对于编程,我一直主张的先对语言有个大概了解,一边使用一边深入,想学完在使用是不可能的,因为根本学不完。谁用谁知道,只需要有这么个概念,等到要用的时候查参考手册就行。
References
[1] cmake-commands
[2] cmake-variables