目录
一、介绍
CMake是一个跨平台的源码构建工具,它使用统一的语法编写CMakeLists.txt文件,可以生成不同平台下的构建系统,然后再使用原生的开发工具(如VS/make等)进行编译安装。CMake可以看作是一门脚本语言,很多大型开源项目都在使用它,广泛用于C/C++项目中。
二、构建系统
构建系统(Buildsystem)就是指原始的项目工程文件,比如VS的.sln .vcxproj,各种Makefile等等
#查看CMake可以生成哪些构建系统
cmake -G
三、构建步骤
- 通过CMakeLists.txt文件生成构建系统,这里会检查环境配置,比如编译器等,如果出错则直接退出。
可以使用cmake命令或cmake-gui完成这一步,比如cmake命令: cmake [选项] <CMakeLists.txt路径>
- 生成.vcxproj或Makefile之后就可以使用原生工具VS/nmake/make编译安装
常用选项:
-G:表示生成哪种构建系统
#一般性的构建步骤:
mkdir build #在项目目录下创建build目录
cd build
cmake -G "Visual Studio 14 2015" .. #生成VS2015工程文件,..表示CMakeLists.txt在上一级目录中
cmake -G "NMake Makefiles" .. #生成Windows平台的Makefile
cmake -G "Unix Makefiles" .. #生成Linux/Unix平台下标准的Makefile
#根据不同平台选择相应的构建档,在CMake中称之为generator
#接下来使用VS/nmake/make等工具编译安装
-D:定义预处理变量,比如用CMAKE_BUILD_TYPE指定编译类型
cmake -DCMAKE_BUILD_TYPE=Debug|Release .. #生成Debug版或者Release版
-P: 执行CMake语言编写的脚本文件,通常以.cmake为后缀
cmake [{-D <var>=<value>}...] -P <script.cmake>
四、CMake源文件
1、目录(CMakeLists.txt)
当CMake处理工程源码树的时候,入口点是顶层目录的CMakeLists.txt文件,文件中还可能使用add_subdirectory()命令添加子目录,每个子目录也必须有一个CMakeLists.txt
2、脚本(script.cmake)
通常<script>.cmake会使用macro()/function()命令定义一些公共的复用的宏和函数(类似于c语言宏和函数的功能),脚本主要有下面两种执行方式:
- 通过cmake -P <script>.cmake单独解释执行,而不会生成构建系统,这种方式可以用来测试脚本文件中是否有语法错误
- 在CMakeLists.txt中使用include()命令加载并执行脚本文件中的代码,这样CMakeLists.txt后续的代码就可以直接调用这些宏和函数,通过CMAKE_MODULE_PATH内置变量指定脚本文件的查找路径,如果没找到则会搜索CMake安装目录
3、模块(module.cmake)
find_package命令用来查找项目中的依赖库,有可能是系统库(如OpenGL),也可能是第三方库(如OpenCV)
命令格式:
find_package(<PackageName> [version] [module] [REQUIRED] [[COMPONENTS] [components...]])
该命令首先会在CMAKE_MODULE_PATH指定的路径之中查找,然后在CMake的安装路径中查找并执行Find<PackageName>.cmake文件,在该文件中会搜索PackageName的相关信息,比如.h、.lib、.so的位置等
如果找到PackageName的这些信息,Find<PackageName>.cmake文件就会设置PackageName_FOUND、PackageName_INCLUDE_DIR、PackageName_LIBRARIES等变量,那么在CMakeLists.txt中就可以使用这些变量,比如用target_include_directories、target_link_libraries命令设置依赖库的头文件、库文件。
五、变量
CMake语言的变量都是字符串类型
1、变量范围
- 函数范围
function()/endfunction()命令会创建一个新的变量范围,在函数中set/unset设定的变量都属于该范围,return之后这些变量就不可见了
- 目录范围
function()/endfunction()命令之外set/unset的变量属于目录范围,在当前目录及其子目录的CMakeLists.txt都可见
注:add_subdirectory()添加的子目录的CMakeLists.txt会继承父目录CMakeLists.txt的变量
- 持久缓存
CMake可以持久保存一些变量的值,这些变量每次构建都存在,使用set/unset的CACHE选项设置
2、变量引用
形式:${<variable>}
引用顺序:函数—>目录—>CACHE,如果没找到设置为空字符串
3、自定义变量
通过set/unset命令在函数范围或目录范围定义/取消
4、内置变量
以CMAKE_开头的都属于内置变量,但也不限于CMAKE_开头,这里介绍最常用的几个
变量名 | 含义 |
PROJECT_NAME | 由project指令设置的工程名 |
${ProName}_SOURCE_DIR | 工程(顶层)CMakeLists.txt所在的目录 |
PROJECT_SOURCE_DIR | 工程(顶层)CMakeLists.txt所在的目录,等同于${ProName}_SOURCE_DIR,推荐使用这个 |
${ProName}_BINARY_DIR | 工程(顶层)CMakeLists.txt的构建目录,比如上面的build |
PROJECT_BINARY_DIR | 工程(顶层)CMakeLists.txt的构建目录,等同于${ProName}_BINARY_DIR,推荐使用这个 |
CMAKE_CURRENT_SOURCE_DIR | 当前CMakeLists.txt所在的目录 |
CMAKE_CURRENT_BINARY_DIR | 当前CMakeLists.txt的构建目录 |
CMAKE_RUNTIME_OUTPUT_DIRECTORY | 指定exe的生成目录 |
CMAKE_LIBRARY_OUTPUT_DIRECTORY | 指定dll/lib的生成目录 |
注意:PROJECT_SOURCE_DIR是固定不变的,就是指最顶层的CMakeLists.txt,包含project指令的那个
CMAKE_CURRENT_SOURCE_DIR是变化的,指当前CMakeLists.txt,通过add_subdirectory()添加的每个子目录都有一个CMakeLists.txt
5、环境变量
环境变量的范围是全局的,每个CMakeLists.txt都可以引用,它不会被缓存。CMake进程启动时进行初始化,可以通过set/unset改变
引用:$ENV{variable}
修改:set(ENV{<variable>} [<value>])
unset(ENV{<variable>})
六、平台相关变量
WIN32 如果是windows平台则为True
UNIX 如果是unix-like平台则为True
APPLE 如果是apple平台则为True
七、条件表达式
格式:
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else()
<commands>
endif()
条件判断关键字:
关键字 | 意义 |
AND | 与 |
OR | 或 |
NOT | 非 |
EXISTS | 文件或目录是否存在 |
COMMAND | 是否为命令 |
DEFINED | 变量是否定义 |
TARGET | 是否为目标,通过add_executable或add_library添加 |
MATCHES | 匹配正则表达式 |
IS_DIRECTORY | 是否为目录 |
IS_NEWER_THAN | 文件1是否比文件2新 |
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 | 版本比较 |
八、循环
-
while(<condition>) #condition跟if的语法一样 <commands> endwhile()
-
foreach(<loop_var> <items>) #items是用分号或空格分隔的列表,loop_var每次迭代一个列表项 <commands> endforeach()
-
foreach(<loop_var> RANGE <start> <stop> [<step>]) #数字范围循环,start和step可以省略
-
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]]) #列表(项)循环
注:break()/continue()相当于C语言的break和continue关键字
九、宏/函数
-
macro(<name> [<arg1> ...]) <commands> endmacro()
-
function(<name> [<arg1> ...]) <commands> endfunction()
注:宏和函数一旦定义之后,就可以想普通命令一样去调用
十、生成二进制目标
1、生成可执行文件
命令格式:
add_executable(<name> [WIN32] [source1] [source2 ...])
选项:
WIN32:表示生成win32应用程序,即程序入口点为WinMain
source1 source2 ...指定要编译的源文件(.h,.c,.cpp,.ui,资源文件等)
2、生成链接库
命令格式:
add_library(<name> [STATIC | SHARED] [<source>...])
选项:
STATIC:生成静态库
SHARED:生成动态库
3、源文件指定方式
- 通过set命令把要编译的文件放在变量中
- 通过file(GLOB ...)命令生成源文件列表
- add_executable/add_library命令中不指定源文件,通过target_source命令指定
命令格式:
file(GLOB <variable>
[LIST_DIRECTORIES true|false] [RELATIVE <path>] [<globbing-expr>...])
file(GLOB_RECURSE <variable>
[LIST_DIRECTORIES true|false] [RELATIVE <path>] [<globbing-expr>...])
作用:
根据globbing-expr表达式匹配目录下的文件,globbing-expr类似于正则表达式,常用的通配符有:
* 任意个任意字符
? 一个任意字符
[] 必有一个中括号的字符
示例:*.cpp,*.vt?,f[3-5].txt
选项:
LIST_DIRECTORIES :是否列出目录,默认为true
RELATIVE:相对路径
GLOB_RECURSE模式会递归查找子目录
#头文件
set(HDR_LIST common.h)
#源文件
set(SRC_LIST main.cpp utils.cpp log.cpp)
#UI文件
set(UI_LIST frame.ui)
#资源文件
set(RC_LIST dolphin.rc)
add_executable(test ${HDR_LIST} ${SRC_LIST} ${UI_LIST} ${RC_LIST})
#通过下面两步设置与上面等价
#add_executable(test)
#target_source(test PRIVATE ${HDR_LIST} ${SRC_LIST} ${UI_LIST} ${RC_LIST})
#================================================================
file (GLOB SRCS LIST_DIRECTORIES false
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/*.ui"
"${CMAKE_CURRENT_SOURCE_DIR}/*.rc"
)
add_executable(test ${SRCS})
十一、指定头文件目录
target_include_directories(<target> [SYSTEM] <PUBLIC|PRIVATE> [items1...])
选项: PUBLIC:为当前target及其依赖目标(通过target_link_libraries命令指定)指定包含目录
PRIVATE:只为当前target指定包含目录
注:老式的指定包含目录的命令为:include_directories([SYSTEM] dir1 [dir2 ...]),它会为当前CMakeLists.txt中的所有target都指定这个包含目录
十二、指定链接库
target_link_libraries(<target> <PRIVATE|PUBLIC> <item>...)
十三、定义预处理宏
ADD_DEFINITIONS、ADD_COMPILE_DEFINITIONS预处理宏
十四、常用命令
1、字符串string
字符串查找:
string(FIND <string> <substring> <output_variable> [REVERSE])
说明:
查找子字符串首次出现的位置,并把所有保存在output_variable中,若没找到则为-1
REVERSE表示反向查找,即最后一次出现的位置
字符串替换:
string(REPLACE <match_str> <replace_str> <result_str> <src_str1> [<src_str2>...])
说明:
在源字符串src_str中查找匹配的子字符串match_str,并替换为replace_str,替换后的字符串保存在result_str中,源字符串src_str保持不变
字符串拼接:
string(APPEND <string_variable> [<input>...])
string(PREPEND <string_variable> [<input>...])
string(CONCAT <output_variable> [<input>...])
string(JOIN <glue> <output_variable> [<input>...])
说明:
APPEND在变量最后添加
PREPEND在变量前面添加
CONCAT把参数字符串直接连接在一起
JOIN把参数字符串用glue连接在一起
string(APPEND str1 "hello " "cmake") #str1: hello cmake
string(PREPEND str2 "Lists.txt" "CMake") #str2: CMakeLists.txt
string(CONCAT str3 "Build" "System") #str3: BuildSystem
string(JOIN "+" str4 "1" "2" "3" "4") #str4: 1+2+3+4
大小写转换:
string(TOLOWER <string> <output_variable>)
string(TOUPPER <string> <output_variable>)
字符串长度:
string(LENGTH <string> <output_variable>)
子字符串提取:
string(SUBSTRING <string> <begin> <length> <output_variable>)
删除首尾空白:
string(STRIP <string> <output_variable>)
字符串比较:
string(COMPARE LESS <string1> <string2> <output_variable>)
string(COMPARE GREATER <string1> <string2> <output_variable>)
string(COMPARE EQUAL <string1> <string2> <output_variable>)
string(COMPARE NOTEQUAL <string1> <string2> <output_variable>)
string(COMPARE LESS_EQUAL <string1> <string2> <output_variable>)
string(COMPARE GREATER_EQUAL <string1> <string2> <output_variable>)
说明:
比较结果保存在变量output_variable,其值为true或false
2、列表list
3、信息打印message
message([<mode>] "message text" ...)
常用的mode选项有:
- FATAL_ERROR:致命错误,终止构建
- SEND_ERROR:一般错误,继续构建
- WARNING:警告信息
- NOTICE:需要注意的信息
- STATUS:一般输出信息
- DEBUG:调试信息
4、文件file
5、源文件分组source_group
source_group(<name> [FILES <src>...] [REGULAR_EXPRESSION <regex>])
把源文件、头文件、ui文件等进行分组归类,在IDE中放在一起显示
FILES:要添加的文件
REGULAR_EXPRESSION:正则表达式匹配