CMake入门

CMake Start

1. CMake介绍

CMake,是Cross Platform Make的缩写,是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。

CMake 自己本身并不是构建工具(build tool),它不直接建构出最终的软件。它的职责是从抽象配置代码生成原生构建 工具(native build tool)文件。

原生构建工具比如:

  • Xcode
  • Visual Studio
  • Ninja
  • Make

CMake使用指定名为CMakeLists.txt的配置文件可以控制软件的构建、测试和打包等流程。同时,通过编写平台无关的 CMakeLists.txt文件和需要简单的配置,CMake就能生成对应目标平台的构建文件,例如:类Unix系统的makefile文件、 Windows的Visual Studio工程或者Mac的Xcode工程,大大简化了跨平台和交叉编译方面的工作。

2. 为什么要使用CMake

答案是跨平台开发,假设你有C++的跨平台项目,其代码在不同的平台/IDE共享。比如,Windows的Visual Studio、 OSX的XCode和 Linux 的Makefile
在这里插入图片描述
如果要添加一个bar.cpp源文件,你会怎么做?你不得不将该文件添加到你所使用的每个平台对应的编译环境中:这种操作非常繁琐,而且和容易因为项目配置不一致导致编译错误:
在这里插入图片描述
 CMake 通过在开发过程中增加额外的步骤来解决这一设计缺陷。你可以在CMakeList.txt文件中描述项目,并使用 CMake 通过跨平台的 CMake 代码来生成你感兴趣的构建工具。
 在这里插入图片描述
同样地,当要添加bar.cpp源文件的时候,就只需要一个步骤了:
在这里插入图片描述
上述图表的底部并没有发生变化,各个平台开发可以使用对应的工具,比如Visual Studio/msbuild, Xcode/xcodebuildMakefile/make

3. CMake 最小构建例子

3.1 源文件列表

foo.cpp

// foo.cpp
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
   std::cout << "Hello World" << std::endl;
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cpp)

3.2 构建、编译和运行

现在就可以构建和运行我们的项目了,就是先运行 cmake 命令来构建项目,然后使用你选择的编译工具进行编译。

先从命令行进入到 src目录,并创建一个构建目录 build,接下来,进入 build 目录并运行 CMake 来配置项目,并生成构建系统:

mkdir build
cd build
#构建系统是需要指定 CMakeLists.txt 所在路径,此时在 build 目录下,所以用 .. 表示 CMakeLists.txt 在上一级目录。
#Windows 下,CMake 默认使用微软的 MSVC 作为编译器,我想使用 MinGW 编译器,可以通过 -G 参数来进行指定
#下面命令执行完后,会在build目录下会生成平台对应项目文件:
cmake -G"MinGW Makefiles" .. 
#--build 指定编译生成的文件存放目录,其中就包括可执行文件,. 表示存放到当前目录,
cmake --build .
#在build 目录下生成了一个 Tutorial.exe 可执行文件,试着执行它
Tutorial.exe

4. 基本基本命令

4.1 指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.4.1)

4.2 打印信息

message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error")

4.3 包含其它 cmake 文件

include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径

4.4 文件操作

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

#GLOB_RECURSE表示执行递归查找,查找目录下所有符合指定正则表达式的文件。
file(GLOB_RECURSE ALL_SRC
        src/module1/*.c
        src/module2/*.c
        )

4.5 执行系统命令

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

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

5. CMake 基本语法

CMake定义了一套领域编程语言或者说脚本,称为CMake语言,支持变量定义、流程控制、函数、预制函数。

提示:CMake脚本如果要单独执行,需要cmake -P xxx.cmake。stripts脚本不会生成构建系统,
因为在stripts脚本中,不允许指定构建目标。

5. 1 command

CMake代码由一系列command的调用组成。包括if else 都属于command。
类似于下面这个命令

\# 添加可执行目标hello, 参数为world.c
add_executable(hello world.c)

command调用语法为

identifier(以空格隔开的参数表)
//参数可以用()括起来,表示这个单个参数。if(TRUE OR (TRUE AND FALSE))
//注意:command名大小写不敏感

参数类型有:
方括号形式

[={len}[
内部随便写点文本,cmake不会内部的变量引用或者换行进行处理。可以保持文本原始样子。${variable}

\-escape
]=]

方括号不允许嵌套
={len}的意思:len表示结束符的=个数。当[=2]时, ]==] 才是结束符。

例子:

message([=3[
This is the first line in a bracket argument with bracket length 1.
No \-escape sequences or ${variable} references are evaluated.
This is always one argument even though it contains a ; character.
The text does not end on a closing bracket of length 0 like ]].
It does end in a closing bracket of length 1.
]===])

引号形式
引号形式的就是放在"“内部的参数,”“会被当成一个参数传进函数。”"内部的变量引用或者转义会
被解析。可以用\表示字符串还没有结束。

message("This is a quoted argument containing multiple lines.
This is always one argument even though it contains a ; character.
Both \\-escape sequences and ${variable} references are evaluated.
The text does not end on an escaped double-quote like \".
It does end in an unescaped double quote.
")

无引号形式
CMake支持参数不带任何引号,因为所有值都会转换成String。所有的参数会被封装成List。
List的分隔符为;,所以参数列表内如果一个字符串用;分割,;两边会被当成两个参数。

\#这里有四个参数
commandName(arg arg2 arg3;arg4)
foreach(arg
    NoSpace
    Escaped\ Space
    This;Divides;Into;Five;Arguments
    Escaped\;Semicolon
    )
  message("${arg}")
endforeach()

输出

NoSpace
Escaped Space
This
Divides
Into
Five
Arguments
Escaped;Semicolon

注释

注释分为行注释和块注释

行注释

#行注释,只能写一行内容

块注释
块注释有结尾有开头,可以写多行注释。用[[]]括起来,注意[要紧跟#。

#[[这是多
行注释]]

变量定义和引用

  • CMake中,变量的值要么是String要么是String组成的List。
  • 变量名字大小写敏感,并且可以包含任意字符。
  • 如果要设置的变量值包含空格,则需要使用双引号或者使用""转义,否则可以省略双引号。
  • 如果设置多个值或者字符串值的中间有";“,则保存成list,同样是以”;"分割的字符串;
  • 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表;
  • 引用变量:${<variable>},在if()条件判断中可以简化为只用变量名<variable>
  • CMake没有用=赋值的操作,只有通过set,option来定义变量。
  • option只能定义OFF,ON的变量。
  • 采用set()/unset()定义和取消定义,变量作用域存在于set的当前作用域

变量作用域:

  • Function Scope
    在函数内部set的变量,作用域作用于当前函数及其调用的函数内。return 后就没了。

  • Directory Scope
    再CMakeLists.txt定义的变量(非function内部),作用域在当前Directory及其子Directory中。

  • Persistent Cache
    持久缓存。变量值会缓存到CMakeCache.txt中,下次运行,会使用CMakeCache中的值。

变量定义

set
set分为两种

  1. set普通变量
set(<variable> <value>... [PARENT_SCOPE])

例如

//VA=a;b, VA是一个字符串list
set(VA a b)
//VA=a,VA是一个字符串
set(VB a)
  1. set CACHE变量
    Cache变量(缓存条目,cache entries)的作用主要是为了提供用户配置选项,如果用户没有指定,则使用默认值。CACHE变量会自动保存到CMakeCache.txt中,上次的结果下次继续用。
    设置方法如下:
# set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(CACHE_VAR "Default cache value" CACHE STRING "A sample for cache variable")

要点:

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

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

set(<variable> <value>... CACHE <type> <docstring> [FORCE])

示例

set(ICD_LIBRARY "${PROJECT_BINARY_DIR}/lib" CACHE INTERNAL "ICD Library location" )
option
option(<option_variable> "help string describing option"
       [initial value])

变量使用

可以使用${variable_name} 。如果变量没有定义,返回空. 变量引用可以嵌套,变量引用的值从内往外计算。
如:

${outer_${inner_variable}_variable}.

CMake系统内置了一堆的变量,可以查阅

https://cmake.org/cmake/help/v3.7/manual/cmake-variables.7.html

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

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

Lists 变量
在CMake中,所有的值都会被当成string来存储,但是在某些情况下, 多个string可以组成list。
例如在无""参数,多个字符串中间加了一个;。可以使用循环来遍历List

set(srcs a.c b.c c.c) #sets "srcs" to "a.c;b.c;c.c"

CMake专门提供了一个内置command来处理list

list(LENGTH <list> <output variable>) //获得list长度
list(GET <list> <element index> [<element index> ...]
     <output variable>) //获得list的某个位置元素
list(APPEND <list> [<element> ...])//add
list(FILTER <list> <INCLUDE|EXCLUDE> REGEX <regular_expression>)//清理
list(FIND <list> <value> <output variable>) //查找
list(INSERT <list> <element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value> [<value> ...])
list(REMOVE_AT <list> <index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)

示例如下:

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")

流程控制

首先,在cmake语言中,不管是条件语句,循环语句,函数或者宏,都是命令。

操作符

用于条件/循环的表达式的操作符,这些操作符是大小写敏感的。操作符的处理优先级:

带()的表达式 > 一元 > 二元 > 逻辑

操作符类型操作符名称
一元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

布尔常量值

类型
true1, ON, YES, TRUE, Y, 非0的值
false0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, 空字符串””, 以-NOTFOUND 结尾的字符串

条件控制

支持的语法有:

  • 字符串比较,比如:STREQUAL、STRLESS、STRGREATER等;
  • 数值比较,比如:EQUAL、LESS、GREATER等;
  • 布尔运算,AND、OR、NOT;
  • 路径判断,比如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
  • 版本号判断;等等;
  • 使用小括号可以组合多个条件语句,比如:(cond1) AND (cond2 OR (cond3))。
if(condition)
elseif(condition)
else()
endif()

if(VAR1 MATCHES "Hello")
    message("this is hello")
    message("this is hello2")
elseif(VAR1 MATCHES "world")
    message("this is world")
    message("this is world2")
endif()

#逻辑判断和比较:
if (expression)#expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真
if (not exp)#与上面相反
if (var1 AND var2)
if (var1 OR var2)
if (COMMAND cmd)#如果 cmd 确实是命令并可调用为真
if (EXISTS dir) if (EXISTS file)#如果目录或文件存在为真
if (file1 IS_NEWER_THAN file2)#当 file1 比 file2 新,或 file1/file2 中有一个不存在时为真,#文件名需使用全路径
if (IS_DIRECTORY dir)#当 dir 是目录时为真
if (DEFINED var)#如果变量被定义为真
if (var MATCHES regex)#给定的变量或者字符串能够匹配正则表达式 regex 时为真,此处 var 可以用 var 名,也可以用 ${var}
if (string MATCHES regex)

#数字比较:
if (variable LESS number):LESS #小于
if (string LESS number)
if (variable GREATER number):GREATER #大于
if (string GREATER number)
if (variable EQUAL number):EQUAL #等于
if (string EQUAL number)

#字母表顺序比较:
if (variable STRLESS string)
if (string STRLESS string)
if (variable STRGREATER string)
if (string STRGREATER string)
if (variable STREQUAL string)
if (string STREQUAL string)

循环

for循环

语法为

foreach(loop_var arg1 arg2 ...)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)

示例:

set(mylist "a" "b" c "d")
foreach(_var ${mylist})
     message("当前变量是:${_var}")
endforeach()

上面是最简单的用法,还有一个foreach(loop_var RANGE start stop [step]) 的用法, 在foreach循环中,支持break()和continue()。

set(result 0)
foreach(_var RANGE 0 100)
     math(EXPR result "${result}+${_var}")
endforeach()
message("from 0 plus to 100 is:${result}")

foreach(i RANGE 1 9 2)
    message(${i})
endforeach(i)
# 输出:13579
while循环
while(condition)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endwhile(condition)

例子:

set(a "")
while(NOT a STREQUAL "xxxxx")
  set(a "${a}x")
  message("  a = ${a}")
endwhile()

6 CMake构建项目

0. 预定义变量

PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

1. 设置项目名称

project(demo)

上面代码会引入两个变量 demo_BINARY_DIRdemo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR

2. 添加全局宏定义

add_definitions(-DDEBUG -DREAL_COOL_ENGINEER)

3. 指定编程语言版本

set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

4. 配置编译选项
通过设置变量CMAKE_BUILD_TYPE来配置编译类型,可设置为:DebugReleaseRelWithDebInfoMinSizeRel等,比如:

set(CMAKE_BUILD_TYPE Debug)

更好的方式应该是在执行cmake命令的时候通过参数-D指定:

cmake -B build -DCMAKE_BUILD_TYPE=Debug

5. 添加include目录

include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

5. 设置链接库搜索目录

link_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/libs
)

5. 设置链接库

# 指定链接动态库或静态库
target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so

# 指定全路径
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)

# 指定链接多个库
target_link_libraries(demo
    ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
    boost_system.a
    boost_thread
    pthread)

6. 添加指定输出类型和源文件列表

add_executable(demo demo.cpp) # 生成可执行文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库
以上命令将生成:
在 Linux 下是:
        demo
        libcommon.a
        libcommon.so
在 Windows 下是:
        demo.exe
        common.lib
        common.dll

如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名

唯一的改动只是在 add_executable 命令中增加了一个 MathFunctions.cc 源文件。这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:

搜索当前目录下的所有.cpp文件,并命名为SRC_LIST,它会查找目录下的.c,.cpp ,.mm,.cc 等等C/C++语言后缀的文件名
aux_source_directory(. SRC_LIST) 
add_library(demo ${SRC_LIST})

#自定义搜索规则:
file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST})
# 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
file(GLOB_RECURSE SRC_LIST "*.cpp") #递归搜索
FILE(GLOB SRC_PROTOCOL RELATIVE "protocol" "*.cpp") # 相对protocol目录下搜索
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值