1 前言

CMake指令放入一个名为 CMakeLists.txt 的文件中,CMake命令是不区分大小写的,所以常用的做法是使用小写,参数使用大写。

2 cmake_minimum_required cmake最低版本指定

每个CMakeLists.txt都必须包含cmake_minimum_required作为第一行,设置项目工程依赖CMake的最低版本。如下代码:

cmake_minimum_required(VERSION 3.1)

指定CMake版本为3.1

每个CMakeLists.txt都必须包含cmake_minimum_required作为第一行,设置项目工程依赖CMake的最低版本。

cmake_minimum_required(VERSION 3.7...3.21)

指定CMake版本为3.7到3.21,这意味着这个工程最低可以支持3.7版本,但是也最高在 3.21版本上测试成功过。实际只会设置为3.7版本的特性,因为这些版本处理这个工程没有什么差异。注意:数字3.7...3.21不能有空格。

cmake_minimum_required(VERSION 3.7 FATAL_ERROR)

设置CMake所需的最低版本为3.7,如果使用的CMake版本低于该版本,则会发出致命错误。

3 cmake_policy

CMake版本特性,cmake_minimum_required和cmake_policy搭配使用。

cmake_minimum_required(VERSION 3.7)
if(${CMAKE_VERSION} VERSION_LESS 3.21)
    cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
    cmake_policy(VERSION 3.21)
endif()

如果CMake的版本低于3.21,if 块条件为真,CMake 将会被设置为当前版本。如果CMake版本是3.21或者更高,if 块条件为假,CMake会设置3.21版本。

4 project

设置项目名称。

project(MyProject VERSION 1.0
    DESCRIPTION "Very nice project"
    LANGUAGES CXX)

MyProject是.sln文件的名称,如下图所示。

这里的字符串是带引号的,因此内容中可以带有空格。项目名称是这里的第一个参数。所有的关键字参数都可选的。VERSION 设置了一系列变量,例如 MyProject_VERSION和PROJECT_VERSION。

语言可以是 C,CXX,Fortran,ASM,CUDA(CMake3.8+),CSharp(3.8+),SWIFT(CMake3.15+ experimental),默认是CXX。在 CMake 3.9,可以通过DESCRIPTION 关键词来添加项目的描述。

5 add_executable

add_executable指令是生成可执行文件。

add_executable(one two.cpp three.h)

one 既是生成的可执行文件的名称,也是创建的 CMake 目标(target)的名称。紧接着的是源文件的列表,你想列多少个都可以。CMake很聪明,它根据拓展名只编译源文件。在大多数情况下,头文件将会被忽略;列出他们的唯一原因是为了让他们在 IDE 中被展示出来,目标文件在许多 IDE 中被显示为文件夹。

cmake_minimum_required(VERSION 3.7...3.21)
 
if(${CMAKE_VERSION} VERSION_LESS 3.21)
    cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
    cmake_policy(VERSION 3.21)
endif()
                    
project(Test01 VERSION 1.0
    DESCRIPTION "Very nice project"
    LANGUAGES CXX)
                    
add_executable(${PROJECT_NAME} main.cpp test01.h test01.cpp)

6 add_library

生成一个库。

add_library(one STATIC two.cpp three.h)

你可以选择库的类型,可以是 STATIC、SHARED或者MODULE。如果你不选择它,CMake 将会通过 BUILD_SHARED_LIBS 的值来选择构建 STATIC 还是 SHARED 类型的库。

在下面的章节中你将会看到,你经常需要生成一个虚构的目标,也就是说,一个不需要编译的目标。例如,只有一个头文件的库。这被叫做 INTERFACE 库,这是另一种选择,和上面唯一的区别是后面不能有文件名。

你也可以用一个现有的库做一个 ALIAS 库,这只是给已有的目标起一个别名。这么做的一个好处是,你可以制作名称中带有::的库。

如果add_library不添加STATIC、SHARED和MODULE任何参数,默认只生成lib文件,如果添加STATIC也只生成lib文件,如果添加SHARED,则只生成dll文件。

如果我们想同时生成lib和dll文件时,就需要给add_library设置SHARED参数,并设置导出符号__declspec(dllimport) 。

7 add_definitions

添加宏定义

add_definitions(-D宏名称)

-D关键词不能少,且后面紧接着时宏名称,没有空格。

8 target_include_directories

给项目工程加入引用代码目录,也就是引用头文件所在目录。

target_include_directories(one PUBLIC include)

target_include_directories为目标添加了一个目录。PUBLIC 对于一个二进制目标没有什么含义;

但对于库来说,它让CMake知道,任何链接到这个目标的目标也必须包含这个目录。

其他选项还有 PRIVATE(只影响当前目标,不影响依赖),以及 INTERFACE(只影响依赖)。

注意:命令target_include_directories必须放在add_library或者add_executable之后,否则CMake构建工程时报类似“Cannot specify link libraries for target “test“ which is not built by this project.”的错误。

9 include_directories

给项目工程加入引用代码目录,也就是引用头文件所在目录,但是include_directories命令包含其子目录。

10 link_directories

给项目工程添加库目录。

注意:link_directories必须放在add_executable指令前。

11 target_link_directories

给项目工程添加库目录。

注意:target_link_directories必须放在add_executable指令后。

使用语法如下:

target_link_directories(${PROJECT_NAME} PUBLIC "../tecio/lib")

12 target_link_libraries

连接引用库,也就是链接库。

add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
target_link_libraries可能是CMake中最有用也最令人迷惑的命令。它指定一个目标,并且在给出目标的情况下添加一个依赖关系。如果不存在名称为one的目标,那他会添加一个链接到你路径中one库(这也是命令叫 target_link_libraries 的原因)。或者你可以给定一个库的完整路径,或者是链接器标志。最后再说一个有些迷惑性的知识:),经典的CMake允许你省略 PUBLIC 关键字,但是你在目标链中省略与不省略混用,那么CMake会报出错误。

只要记得在任何使用目标的地方都指定关键字,那么就不会有问题。

13 target_compile_features

编译特性

target_compile_features(calclib PUBLIC cxx_std_11)

目标calclib使用c++11特性。

14 set

本地变量、缓存变量和环境变量。

声明一个本地变量MY_VARIABLE

set(MY_VARIABLE "value")

取消常规变量MY_VARIABLE

unset(MY_VARIABLE)

变量名通常全部用大写,变量值跟在其后。你可以通过 ${} 来解析一个变量,例如 ${MY_VARIABLE}。CMake 有作用域的概念,在声明一个变量后,你只可以它的作用域内访问这个变量。如果你将一个函数或一个文件放到一个子目录中,这个变量将不再被定义。你可以通过在变量声明末尾添加 PARENT_SCOPE 来将它的作用域置定为当前的上一级作用域。

set(MY_VARIABLE "value" PARENT_SCOPE)

MY_VARIABLE本地变量在父作用域中。

set(MY_LIST "one" "two")
set(MY_LIST "one;two")

列表就是简单地包含一系列变量,可以通过;分隔变量,这和空格的作用是一样的。

有一些和list(进行协同的命令,separate_arguments可以把一个以空格分隔的字符串分割成一个列表。需要注意的是,在CMake中如果一个值没有空格,那么加和不加引号的效果是一样的。这使你可以在处理知道不可能含有空格的值时不加引号。

当一个变量用${}括起来的时候,空格的解析规则和上述相同。对于路径来说要特别小心,路径很有可能会包含空格,因此你应该总是将解析变量得到的值用引号括起来,也就是应该这样"${MY_PATH}" 。

15 设置可执行文件输出目录

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin)

上面命令,必须在add_subdirectory的前面。

16 add_subdirectory和include

CMake中项目(project指令)和目标(add_library和add_executable指令),与Visual Studio中是不一样的,CMake 项目相当于Visual Studio解决方案,而CMake目标相当于Visual Studio一个一个的不同类型的项目。Visual Studio一个解决方案,可以包含多个项目。

我们非常关心解决方案的属性和项目的属性,包括 名称、版本、语言、路径、描述和网站。

如果用add_subdirectory命令,在子目录的CMakeLists.txt文件中,也有project命令,那么构建后,都会生成项目

我们用include()命令也可以加载 子目录中的CMakeLists.txt文件,例如:

include(sub/CMakeLists.txt)

虽然include()命令与 add_subdirectory()命令,都会加载CMakeLists.txt文件,但是include()命令是将子目录中CMakeLists.txt文件内容导入到父目录的源文件中,而 add_subdirectory() 命令是将子目录中 CMakeLists.txt 添加到构建中。

所以,有一个比较明显的区别,都执行project命令,如果使用include()那么项目会被覆盖,如果使用add_subdirectory()那么会创建子项目。

17 打包项目

在编译完lib库后,需要手动拷贝lib文件、头文件给别人,做二次开发,这样相当麻烦。可以使用install指令,将生成的lib文件等拷贝到指定的位置。

配置库文件、头文件和执行文件到install的目录下,cmake中的install根目录为CMAKE_INSTALL_PREFIX变量的路径,如果我们要设置配置路径可以使用set命令设置CMAKE_INSTALL_PREFIX变量的值来改变路径。一般默认情况CMAKE_INSTALL_PREFIX变量的值为,在UNIX系统中为:/usr/local,在windows系统中为:c:/Program Files/${PROJECT_NAME}

打包项目指令如下:

# 设置打包目录
set(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR})
# 配置可执行文件到安装路径 CMAKE_INSTALL_PREFIX的bin中
install(TARGETS glew DESTINATION glew-sdk/lib)
# 配置程序的头文件到安装路径 CMAKE_INSTALL_PREFIX的include文件中
install(FILES "include/GL/glew.h"
DESTINATION glew-sdk/include)

18 release和debug调用不同的lib库

使用方法1

target_link_libraries ( app
    debug ${Boost_FILESYSTEM_LIBRARY_DEBUG}
    optimized ${Boost_FILESYSTEM_LIBRARY_RELEASE} )

使用方法2

set( MyFavLib_LIBRARIES 
    debug debug/module1 optimized release/module1
    optimized debug/module2 optimized release/module2 )
target_link_libraries( app ${MyFavLib_LIBRARIES} )

注意,方法2中,module1和module2的lib后缀名要省略,vs会自动补全lib后缀名。

19 release和debug生成不同的lib名称库

使用方法

if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
    set(CMAKE_DEBUG_POSTFIX "_debug")
  endif()
  if(NOT DEFINED CMAKE_RELEASE_POSTFIX)
    set(CMAKE_RELEASE_POSTFIX "_release")
  endif()

注意,该指令必须放在add_library之前,如下图所示。

20 多个CMakeLists.txt共同引用一个.cmake文件

include(${CMAKE_CURRENT_DIR}/../share.cmake)

21 设置输出目录

(1) 设置exe输出目录

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

在Win + VS环境下,会自动在你所设置的目录后面扩展一层 目录,所以最终生成的Debug版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Debug 目录下,Release版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Release 目录下.

(2) 设置lib输出目录

SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../lib)

在Win + VS环境下,会自动在你所设置的目录后面扩展一层 目录,所以最终生成的Debug版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Debug 目录下,Release版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Release 目录下. 在Linux + GCC环境下,无论是Debug还是Release,生成的库文件会直接放在你所设置的目录下,不会有差异.

(3) 设置debug和release输出目录

用法1

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../bin)

上面两条语句分别设置了Debug版本和Release版本可执行文件的输出目录, 一旦设置上面的属性,在任何环境下生成的可执行文件都将直接放在你所设置的目录.

用法2

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../lib) 
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../lib)

上面两条语句分别设置了Debug版本和Release版本库文件的输出目录, 一旦设置上面的属性,在任何环境下生成的库文件都将直接放在你所设置的目录.

22 设置后缀名

用法1

set(CMAKE_DEBUG_POSTFIX "_d") 
set(CMAKE_RELEASE_POSTFIX "_r") 

上面两条语句分别设置了Debug版本和Release版本下库文件的后缀名

用法2

set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX "_d") 
set_target_properties(${TARGET_NAME} PROPERTIES RELEASE_POSTFIX "_r")

上面两条语句分别设置了Debug版本和Release版本下可执行文件的后缀名

23 message

语法格式

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

mode 的值包括 FATAL_ERROR、WARNING、AUTHOR_WARNING、STATUS、VERBOSE等。主要使用其中的 2 个——FATAL_ERROR、STATUS。

FATAL_ERROR:产生 CMake Error,会停止编译系统的构建过程;

STATUS:最常用的命令,常用于查看变量值,类似于编程语言中的 DEBUG 级别信息。

"message text"为显示在终端的内容。

24 环境变量ENV

使用set函数 + ENV指令

set(ENV{变量名} 值)

注意:

(1)读环境变量时,需要加上$;写环境变量时,不需要加$;

(2)cmake文件内定义的环境变量仅用于cmake编译过程,不能用于目标程序。

例子如下:

message("myenvvar: $ENV{myenvvar}")
# 定义环境变量myenvvar
set(ENV{myenvvar} "123")
message("myenvvar: $ENV{myenvvar}")

25 option

CMake中的option用于控制编译流程,相当于C语言中的宏条件编译。

options基本格式如下:

option(<variable> "<help_text>" [value])

variable:定义选项名称

help_text:说明选项的含义

value:定义选项默认状态,一般是OFF或者ON,除去ON之外,其他所有值都为认为是OFF。

26 条件控制if

if(expression)
    #...
elseif(expression2)
    #...
else()
    #...
endif()

对于 if(string) 来说:

(1)如果 string 为(不区分大小写)1、ON、YES、TRUE、Y、非 0 的数则表示真

(2)如果 string 为(不区分大小写)0、OFF、NO、FALSE、N、IGNORE、空字符串、以 -NOTFOUND 结尾的字符串则表示假

(3)如果 string 不符合上面两种情况,则 string 被认为是一个变量的名字。变量的值为第二条所述的各值则表示假,否则表示真。

27 操作符优先级

表达式中可以包含操作符,操作符包括:

(1) 一元操作符,例如:EXISTS、COMMAND、DEFINED 等

(2) 二元操作符,例如:EQUAL、LESS、GREATER、STRLESS、STREQUAL、STRGREATER 等

(3) NOT(非操作符)

(4) AND(与操作符)、OR(或操作符)

(5) 操作符优先级:一元操作符 > 二元操作符 > NOT > AND > OR

使用例子1

if (variable)
  # 当 variable 不为 空值,1,TRUE、ON时为真

if (NOT variable)
  # 当 variable 为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (variable1 AND variable2)
  # 当 variable1, variable1 同时不为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (variable1 OR variable2)
  # 当 variable1, variable1 有一个不为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (COMMAND command-name)
  # 当 command-name 是可调用的命令时为真

if (DEFINED variable)
  # 当 variable 已经被设置了值时为真

if (EXISTS file-name)
if (EXISTS directory-name)
  # 当指定的文件或者目录时为真

if (IS_DIRECTORY name)
if (IS_ABSOLUTE name)
  # 当 name 是目录或者是绝对路径是为真

if (name1 IS_NEWER_THAN name2)
  # 当 name1 文件的修改时间比 name2 文件的修改时间要新时为真

if (variable MATCHES regex)
if (string MATCHES regex)
  # 当给定的变量或者字符串与给定的正则表达式相匹配时为真

使用例子2

if(NOT expression)
//为真的前提是 expression 为假

if(expr1 AND expr2)
//为真的前提是 expr1 和 expr2 都为真

if(expr1 OR expr2)
//为真的前提是 expr1 或者 expr2 为真

if(COMMAND command-name)
//为真的前提是存在 command-name 命令、宏或函数且能够被调用

if(EXISTS name)
//为真的前提是存在 name 的文件或者目录(应该使用绝对路径)

if(file1 IS_NEWER_THAN file2)
//为真的前提是 file1 比 file2 新或者 file1、file2 中有一个文件不存在(应该使用绝对路径)

if(IS_DIRECTORY directory-name)
//为真的前提是 directory-name 表示的是一个目录(应该使用绝对路径)

if(variable|string MATCHES regex)
//为真的前提是变量值或者字符串匹配 regex 正则表达式

if(variable|string LESS variable|string)
if(variable|string GREATER variable|string)
if(variable|string EQUAL variable|string)
//为真的前提是变量值或者字符串为有效的数字且满足小于(大于、等于)的条件

if(variable|string STRLESS variable|string)
if(variable|string STRGREATER variable|string)
if(variable|string STREQUAL variable|string)
//为真的前提是变量值或者字符串以字典序满足小于(大于、等于)的条件

if(DEFINED variable)
//为真的前提是 variable 表示的变量被定义了。

28 foreach 循环范例

set(VAR a b c)
foreach(f ${VAR})
    message(${f})
endforeach()

29 while 循环范例

set(VAR 5)
while(${VAR} GREATER 0)
   message(${VAR})
   math(EXPR VAR "${VAR} - 1")
endwhile()

30 list指令

30.1 list指令格式

list (subcommand <list> [args...])

subcommand为具体的列表操作子命令,例如读取、查找、修改、排序等;

<list>为待操作的列表变量;

[args...]为对列表变量操作需要使用的参数表,不同的子命令对应的参数也不一致。

list命令即对列表的一系列操作,cmake中的列表变量是用分号;分隔的一组字符串,创建列表可以使用set命令(参考set命令),例如:set (var a b c d)创建了一个列表 "a;b;c;d",而set (var "a b c d")则是只创建了一个变量"a c c d"。list命令的具体格式根据子命令不同会有所区别。

对列表的操作分为读取、查找、修改、排序等4个大类,下面按照这四个大类逐一对列表的子命令进行介绍。

30.2 列表的读取

LENGTH:子命令LENGTH用于读取列表长度

list (LENGTH <list> <output variable>)

<output variable>为新创建的变量,用于存储列表的长度。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (LENGTH list_test length)
message (">>> LENGTH: ${length}")

输出:>>> LENGTH: 4

GET:子命令GET用于读取列表中指定索引的的元素,可以指定多个索引。

list (GET <list> <element index> [<element index> ...] <output variable>)

<element index>为列表元素的索引,从0开始编号,索引0的元素为列表中的第一个元素;索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推。注意:当索引(不管是正还是负)超过列表的长度,运行会报错(list index: XX out of range)。

 <output variable>为新创建的变量,存储指定索引元素的返回结果,也是一个列表。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (GET list_test 0 1 -1 -2 list_new)
message (">>> GET: ${list_new}")

输出:>>> GET: a;b;d;c

JOIN:子命令JOIN用于将列表中的元素用连接字符串连接起来组成一个字符串,注意,此时返回的结果已经不是一个列表。

list (JOIN <list> <glue> <output variable>)

将列表中的元素用<glue>链接起来,组成一个字符串后,返回给<output variable>变量。对于不属于列表的多个字符串的连接操作,可以使用string()命令的连接操作。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (JOIN list_test -G- list_new)
message ("<<< JOIN: ${list_new}")

输出:<<< JOIN: a-G-b-G-c-G-d

SUBLIST:子命令SUBLIST用于获取列表中的一部分(子列表)。

list (SUBLIST <list> <begin> <length> <output variable>)

返回列表<list>中,从索引<begin>开始,长度为<length>的子列表。如果长度<length>为0,返回的时空列表。如果长度<length>为-1或列表的长度小于<begin>+<length>,那么将列表中从<begin>索引开始的剩余元素返回。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (SUBLIST list_test 1 2 list_new)
message (">>> SUBLIST: ${list_new}")
list (SUBLIST list_test 1 0 list_new)
message (">>> SUBLIST: ${list_new}")
list (SUBLIST list_test 1 -1 list_new)
message (">>> SUBLIST: ${list_new}")
list (SUBLIST list_test 1 8 list_new)
message (">>> SUBLIST: ${list_new}")

输出:

>>> SUBLIST: b;c

>>> SUBLIST:

>>> SUBLIST: b;c;d

>>> SUBLIST: b;c;d

30.3 列表的查找

FIND:子命令FIND用于查找列表是否存在指定的元素。

list (FIND <list> <value> <output variable>)

如果列表<list>中存在<value>,那么返回<value>在列表中的索引,如果未找到则返回-1。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (FIND list_test d list_index)
message (">>> FIND 'd': ${list_index}")
list (FIND list_test e list_index)
message (">>> FIND 'e': ${list_index}")

输出:

>>> FIND 'd': 3

>>> FIND 'e': -1

30.4 列表的修改

列表的修改子命令可能会改变原列表的值。

APPEND:子命令APPEND用于将元素追加到列表。

list (APPEND <list> [<element> ...])

此命令会改变原列表的值。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (APPEND list_test 1 2 3 4)
message (">>> APPEND: ${list_test}")

输出:>>> APPEND: a;b;c;d;1;2;3;4

FILTER:子命令FILTER用于根据正则表达式包含或排除列表中的元素。

list (FILTER <list> <INCLUDE|EXCLUDE> REGEX <regular_expression>)

根据模式的匹配结果,将元素添加(INCLUDE选项)到列表或者从列表中排除(EXCLUDE选项)。此命令会改变原来列表的值。模式REGEX表明会对列表进行正则表达式匹配。(从官方文档目前未找到其他模式)

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d 1 2 3 4) # 创建列表变量"a;b;c;d;1;2;3;4"
message (">>> the LIST is: ${list_test}")
list (FILTER list_test INCLUDE REGEX [a-z])
message (">>> FILTER: ${list_test}")
list (FILTER list_test EXCLUDE REGEX [a-z])
message (">>> FILTER: ${list_test}")

输出:

>>>the LIST is: a;b;c;d;1;2;3;4

>>>FILTER: a;b;c;d

>>>FILTER:

INSERT:子命令INSERT用于在指定位置将元素(一个或多个)插入到列表中。

list (INSERT <list> <element_index> <element> [<element> ...])

<element_index>为列表指定的位置,如果元素的位置超出列表的范围,会报错。此命令会改变原来列表的值

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (INSERT list_test 0 8 8 8 8)
message (">>> INSERT: ${list_test}")
list (INSERT list_test -1 9 9 9 9)
message (">>> INSERT: ${list_test}")
list (LENGTH list_test lenght)
list (INSERT list_test ${length} 0)
message (">>> INSERT: ${list_test}")

输出:

>>> INSERT: 8;8;8;8;a;b;c;d

>>> INSERT: 8;8;8;8;a;b;c;9;9;9;9;d

>>> INSERT: 8;8;8;8;a;b;c;9;9;9;9;d;0

POP_BACK:子命令POP_BACK用于将列表中最后元素移除。

list (POP_BACK <list> [<out-var>...])

<out-var>如果未指定输出变量,则仅仅是将原列表的最后一个元素移除。如果指定了输出变量,则会将最后一个元素移入到该变量,并将元素从原列表中移除。如果指定了多个输出变量,则依次将原列的最后一个元素移入到输出变量中,如果输出变量个数大于列表的长度,那么超出部分的输出变量未定义。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (POP_BACK list_test)
message (">>> POP_BACK: ${list_test}")
list (POP_BACK list_test outvar1 outvar2 outvar3 outvar4)
message (">>> POP_BACK: ${outvar1}、${outvar2}、${outvar3}、${outvar4}")

输出:

>>> POP_BACK: a;b;c

>>> POP_BACK: c、b、a、

POP_FRONT:子命令POP_FRONT用于将列表中第一个元素移除。

list (POP_FRONT <list> [<out-var>...])

<out-var>如果未指定输出变量,则仅仅是将原列表的第一个元素移除。如果指定了输出变量,则会将第一个元素移入到该变量,并将元素从原列表中移除。如果指定了多个输出变量,则依次将原列的第一个元素移入到输出变量中,如果输出变量个数大于列表的长度,那么超出部分的输出变量未定义。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (POP_FRONT list_test)
message (">>> POP_FRONT: ${list_test}")
list (POP_FRONT list_test outvar1 outvar2 outvar3 outvar4)
message (">>> POP_FRONT: ${outvar1}、${outvar2}、${outvar3}、${outvar4}")

输出:

>>> POP_FRONT: b;c;d

>>> POP_FRONT: b、c、d、

PREPEND:子命令PREPEND用于将元素插入到列表的0索引位置。

list (PREPEND <list> [<element> ...])

如果待插入的元素是多个,则相当于把多个元素整体“平移”到原列表0索引的位置。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d) # 创建列表变量"a;b;c;d"
list (PREPEND list_test z)
message (">>> PREPEND: ${list_test}")
list (PREPEND list_test p q r s t)
message (">>> POP_FRONT: ${list_test}")

输出:

>>> PREPEND: z;a;b;c;d

>>> PREPEND: p;q;r;s;t;z;a;b;c;d

REMOVE_ITEM:子命令REMOVE_ITEM用于将指定的元素从列表中移除。

list (REMOVE_ITEM <list> <value> [<value> ...])

注意:指定的是元素的值,当指定的值在列表中存在重复的时候,会删除所有重复的值。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a a b c c d) # 创建列表变量"a;a;b;c;c;d"
list (REMOVE_ITEM list_test a)
message (">>> REMOVE_ITEM: ${list_test}")
list (REMOVE_ITEM list_test b e)
message (">>> REMOVE_ITEM: ${list_test}")

输出:

>>> REMOVE_ITEM: b;c;c;d

>>> REMOVE_ITEM: c;c;d

REMOVE_AT:子命令REMOVE_AT用于将指定索引的元素从列表中移除。

list (REMOVE_AT <list> <index> [<index> ...])

注意:指定的是元素的索引,当指定的索引不存在的时候,会提示错误;如果指定的索引存在重复,则只会执行一次删除动作。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d e f) # 创建列表变量"a;b;c;d;e;f"
list (REMOVE_AT list_test 0 -1)
message (">>> REMOVE_AT: ${list_test}")
list (REMOVE_AT list_test 1 1 1 1)
message (">>> REMOVE_AT: ${list_test}")

输出:

>>> REMOVE_AT: b;c;d;e

>>> REMOVE_AT: b;d;e

REMOVE_DUPLICATES:子命令REMOVE_DUPLICATES用于移除列表中的重复元素。

list (REMOVE_DUPLICATES <list>)

如果没有重复元素,原列表不会做更改。如果原列表所有元素都是一样的,则会保留一个;如果有多个重复元素,则保留第一个。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a a a b b b c c c d d d) # 创建列表变量"a;a;a;b;b;b;c;c;c;d;d;d;"
list (REMOVE_DUPLICATES list_test)
message (">>> REMOVE_DUPLICATES: ${list_test}")
set (list_test a a a a) 
list (REMOVE_DUPLICATES list_test)
message (">>> REMOVE_DUPLICATES: ${list_test}")
set (list_test a b c a d a e a f)  # 多个元素重复,只保留第一个 
list (REMOVE_DUPLICATES list_test)
message (">>> REMOVE_DUPLICATES: ${list_test}")

输出:

>>> REMOVE_DUPLICATES: a;b;c;d

>>> REMOVE_DUPLICATES: a

>>> REMOVE_DUPLICATES: a;b;c;d;e;f

TRANSFORM:子命令TRANSFORM用于将指定的动作运用到所有或者部分指定的元素,结果可以存到原列表中,或存到指定输出新的变量中。

list (TRANSFORM <list> <ACTION> [<SELECTOR>]
[OUTPUT_VARIABLE <output variable>])

选项ACTION用于指定应用到列表元素的动作,动作必须在如下中选择一个:APPEND/PREPEND/TOUPPER/TOUPPER/STRIP/GENEX_STRIP/REPLACE;选项SELECTOR用于指定哪些列表元素会被选择,并执行动作,SELECTOR只能从如下中选择一个:AT/FOR/REGEX;选项OUTPUT_VARIABLE用于指定新的输出变量。 TRANSFORM命令不会改变列表中元素的个数。

ACTION选项的解析如下:

APPEND,PREPEND:在列表的每个元素后/前插入指定的值。

list (TRANSFORM <list> <APPEND|PREPEND> <value> [OUTPUT_VARIABLE <output variable>]) 
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d)
list (TRANSFORM list_test APPEND B OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test APPEND B)
message (">>> TRANSFORM-APPEND: ${list_test} --- ${list_test_out}")
set (list_test a b c d)
list (TRANSFORM list_test PREPEND F OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test PREPEND F)
message (">>> TRANSFORM-PREPEND: ${list_test} --- ${list_test_out}")

输出:

>>> TRANSFORM-APPEND: aB;bB;cB;dB --- aB;bB;cB;dB

>>> TRANSFORM-PREPEND: Fa;Fb;Fc;Fd --- Fa;Fb;Fc;Fd

TOUPPER, TOLOWER:将列表的每一个元素转换成大写/小写。

list(TRANSFORM <list> <TOLOWER|TOUPPER> [OUTPUT_VARIABLE <output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a bb c d)
list (TRANSFORM list_test TOUPPER OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test TOUPPER)
message (">>> TRANSFORM-TOUPPER: ${list_test} --- ${list_test_out}")
set (list_test A B C DD)
list (TRANSFORM list_test TOLOWER OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test TOLOWER)
message (">>> TRANSFORM-TOLOWER: ${list_test} --- ${list_test_out}")

输出:

>>> TRANSFORM-TOUPPER: A;BB;C;D --- A;BB;C;D

>>> TRANSFORM-TOLOWER: a;b;c;dd --- a;b;c;dd

STRIP:去除列表中每一个元素的头尾空格。

list(TRANSFORM <list> STRIP [OUTPUT_VARIABLE <output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test " a b " "   bb   " "c e" d)
message (">>> TRANSFORM list: ${list_test}")
list (TRANSFORM list_test STRIP OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test STRIP)
message (">>> TRANSFORM-STRIP: ${list_test} --- ${list_test_out}")

输出:

>>> TRANSFORM list: a b ; bb ;c e;d

>>> TRANSFORM-STRIP: a b;bb;c e;d --- a b;bb;c e;d

GENEX_STRIP:裁剪列表中任何为产生表达式的元素(产生表达式请参考此处)从下面的例子可以看出,虽然列表中是产生表达式的元素被清除了,但是实际列表的长度没有变化,产生表达式的位置被空元素占据了(TRANSFROM命令不会改变列表元素的个数)。

list(TRANSFORM <list> GENEX_STRIP [OUTPUT_VARIABLE <output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d $ $)
list (LENGTH list_test len)
message (">>> TRANSFORM list: ${list_test}, len=${len}")
list (TRANSFORM list_test GENEX_STRIP OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test GENEX_STRIP) 
list (LENGTH list_test len_new)
message (">>> TRANSFORM-STRIP: ${list_test} --- ${list_test_out}, new len = ${len_new}")

输出:

>>> TRANSFORM list: a;b;c;d;$;$, len = 6

>>> TRANSFORM-STRIP: a;b;c;d;; --- a;b;c;d;;, new len = 6

REPLACE:尽可能多的查找(用正则表达式<regular_expression>匹配)并替换(用<replace_expression>替换)列表中符合条件的元素。

list(TRANSFORM <list> REPLACE <regular_expression> <replace_expression> [OUTPUT_VARIABLE<output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test aa bb cc dd 1a b2 33 44)
message (">>> TRANSFORM-list: ${list_test}")
list (TRANSFORM list_test REPLACE "[a-c]" 9)    
message (">>> TRANSFORM-REPLACE: ${list_test}")

输出:

>>> TRANSFORM-list: aa;bb;cc;dd;1a;b2;33;44

>>> TRANSFORM-REPLACE: 99;99;99;dd;19;92;33;44

SELECTOR选项的解析如下:

AT:指定元素的索引(可以是多个)。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d)
message (">>> TRANSFORM-list: ${list_test}")
list (TRANSFORM list_test APPEND Q AT 1 2 OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test APPEND Q AT 1 2)
message (">>> TRANSFORM-AT: ${list_test} --- ${list_test_out}")

输出:

>>> TRANSFORM-list: a;b;c;d

>>> TRANSFORM-REPLACE: a;bQ;cQ;d --- a;bQ;cQ;d

FOR:指定元素的范围和迭代的步长。

list(TRANSFORM <list> <ACTION> FOR <start> <stop> [<step>] [OUTPUT_VARIABLE <output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d e f g h i j k)
message (">>> TRANSFORM-list: ${list_test}")
list (TRANSFORM list_test APPEND Q FOR 0 -1 2 OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test APPEND Q FOR 0 -1 2)
message (">>> TRANSFORM-FOR: ${list_test} --- ${list_test_out}")

输出:

>>> TRANSFORM-list: a;b;c;d;e;f;g;h;i;j;k

>>> TRANSFORM-REPLACE: aQ;b;cQ;d;eQ;f;gQ;h;iQ;j;kQ --- aQ;b;cQ;d;eQ;f;gQ;h;iQ;j;kQ

REGEX:指定正则表达式,符合正则表达式的元素才会被处理。/p>

list(TRANSFORM <list> <ACTION> REGEX <regular_expression>[OUTPUT_VARIABLE <output variable>])
# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test a b c d e f g h i j k)
message (">>> TRANSFORM-list: ${list_test}")
list (TRANSFORM list_test APPEND Q REGEX "[a-f]" OUTPUT_VARIABLE list_test_out)
list (TRANSFORM list_test APPEND Q REGEX "[a-f]")
message (">>> TRANSFORM-REGEX: ${list_test} --- ${list_test_out}") 

输出:

>>> TRANSFORM-list: a;b;c;d;e;f;g;h;i;j;k

>>> TRANSFORM-REGEX: aQ;bQ;cQ;dQ;eQ;fQ;g;h;i;j;k --- aQ;bQ;cQ;dQ;eQ;fQ;g;h;i;j;k

30.5 列表的排序

REVERSE:子命令REVERSE用于将整个列表反转。

list (REVERSE <list>)

反转列表。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test aa bb cc dd) 
message (">>> list: ${list_test}")
list (REVERSE list_test)
message (">>> REVERSE: ${list_test}")

输出:

>>> list: aa;bb;cc;dd

>>> REVERSE: dd;cc;bb;aa

SORT:子命令SORT用于对列表进行排序。

list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])

对列表进行排序。

COMPARE:指定排序方法。有如下几种值可选:1)STRING:按照字母顺序进行排序,为默认的排序方法;2)FILE_BASENAME:如果是一系列路径名,会使用basename进行排序;3)NATURAL:使用自然数顺序排序。

CASE:指明是否大小写敏感。有如下几种值可选:1)SENSITIVE:按照大小写敏感的方式进行排序,为默认值;2)INSENSITIVE:按照大小写不敏感方式进行排序。

ORDER:指明排序的顺序。有如下几种值可选:1)ASCENDING:按照升序排列,为默认值;2)DESCENDING:按照降序排列。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.12.2)
project (list_cmd_test)
set (list_test 3 1 1.1 10.1 3.4 9)
list (SORT list_test) # 以字母顺序,按照大小写敏感方式,升序排列
message (">>> SORT STRING-SENSITIVE-ASCENDING: ${list_test}")
list (SORT list_test COMPARE NATURAL ORDER DESCENDING) # 以自然顺序,降序排列
message (">>> SORT STRING-SENSITIVE-DESCENDING: ${list_test}")

输出:

>>> SORT STRING-SENSITIVE-ASCENDING: 1;1.1;10.1;3;3.4;9

>>> SORT STRING-SENSITIVE-DESCENDING: 10.1;9;3.4;3;1.1;1

31 mark_as_advanced

mark_as_advanced 将CMake 的缓存变量标记为高级。

mark_as_advanced([CLEAR|FORCE] VAR VAR2 VAR...)

将缓存的变量标记为高级变量。其中,高级变量指的是那些在CMake GUI中,只有当“显示高级选项”被打开时才会被显示的变量。如果CLEAR是第一个选项,参数中的高级变量将变回非高级变量。如果FORCE是第一个选项,参数中的变量会被提升为高级变量。如果两者都未出现,新的变量会被标记为高级变量;如果这个变量已经是高级/非高级状态的话,它将会维持原状。

该命令在脚本中无效。

32 add_compile_options

add_compile_options是设置编译选项指令

(1) 设置utf-8编码

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

(2) 设置使用c++11标准

add_compile_options(-std=c++11)

33 文件分组

有时候,一个项目中,文件很多,我们想让文件看起来整洁一下,就需要将文件进行分组

file(GLOB LOG "log/*.h" "log/*.cpp")
file(GLOB LOGWIN "log/logan-win/*.h" "log/logan-win/*.cpp")
file(GLOB CLOGAN "log/logan-win/clogan/*.h" "log/logan-win/clogan/*.cpp")
file(GLOB MBEDTLS_INCLUDE "log/logan-win/mbedtls/include/mbedtls/*.h" )
file(GLOB MBEDTLS_LIB "log/logan-win/mbedtls/library/*.h" "log/logan-win/mbedtls/library/*.cpp")

set(group group1.h group1.cpp main.cpp)
source_group(group FILES ${group})

source_group(group/log FILES ${LOG})
source_group(group/log/logan-win FILES ${LOGWIN})
source_group(group/log/logan-win/clogan FILES ${CLOGAN})
source_group(group/log/logan-win/mbedtls/include FILES ${MBEDTLS_INCLUDE})
source_group(group/log/logan-win/mbedtls/library FILES ${MBEDTLS_LIB})

add_executable(${PROJECT_NAME}  
${group} ${LOG} ${LOGWIN} ${CLOGAN} ${MBEDTLS_INCLUDE} ${MBEDTLS_LIB})

34 按照文件目录对文件分组

在使用CMake后,手工维护filter是不现实的。CMake也提供了可以生成filter的机制,就是source_group()命令。结合source_group(), file(), string()等命令,我们可以让实现CMake自动按目录结构生成filter。

(1) 在比较顶层的CMakeLists.txt中定义该宏

定义宏代码:

macro(source_group_by_dir source_files)
    if(MSVC)
        set(sgbd_cur_dir ${CMAKE_CURRENT_SOURCE_DIR})
        foreach(sgbd_file ${${source_files}})
            string(REGEX REPLACE ${sgbd_cur_dir}/\(.*\) \\1 sgbd_fpath ${sgbd_file})
            string(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
            string(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
            string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
            if(sgbd_nogroup)
                set(sgbd_group_name "\\")
            endif(sgbd_nogroup)
            source_group(${sgbd_group_name} FILES ${sgbd_file})
        endforeach(sgbd_file)
    endif(MSVC)
endmacro(source_group_by_dir)

(2) 使用宏

在添加工程(add_library或者add_executable)的CMakeLists.txt文件中调用该宏,调用如下:

source_group_by_dir(all_files)

其中all_files是保存了所有文件名的变量。注意,这里用的是变量名,而没有引用其值。

一般这个文件列表可以用file()或者aux_source_directory()来得到。例如对于C++工程,通常是这样的:

file(GLOB_RECURSE project_headers *.h)
file(GLOB_RECURSE project_cpps *.cpp)
set(all_files ${project_headers} ${project_cpps})
source_group_by_dir(all_files)

35 项目分组

有时候,一个项目中,可能有很多工程,工程多了后,就会显得杂乱无章,此时我们需要对工程进行分组

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "4")    # 分组名称为4

常用变量

变量 作用
${CMAKE_CURRENT_SOURCE_DIR} 当前CMakeLists.txt文件所在目录
${CMAKE_CURRENT_BINARY_DIR} 当前CMakeLists.txt构建输出所在的目录
${CMAKE_SOURCE_DIR} 顶级CMakeLists.txt文件所在路径
${CMAKE_BINARY_DIR} 顶级CMakeLists.txt构建输出所在的目录
${PROJECT_SOURCE_DIR} 项目源码顶层目录
${PROJECT_BINARY_DIR} 项目编译输出目录
${PROJECT_NAME} 项目名称
${CMAKE_VERSION} CMake的版本,由 MAJOR、MINOR、PATCH、TWEAK组成
${CMAKE_MAJOR_VERSION} CMake的主要版本,假设CMake的版本是3.21,那么3就表示主要版本,21表示次要版本
${CMAKE_MINOR_VERSION} CMake的次要版本
${CMAKE_PATCH_VERSION} CMake的补丁版本
${CMAKE_TWEAK_VERSION} CMake的微调版本