# CMake学习笔记

CMake是非常流行的构建系统（更准确地讲，是构建系统的“生成器”）。尤其是在C++项目中，现代CMake（3.10+版本）几乎已经成为事实上的标准（standard de facto）。      
在现代CMake中，使用目标（target）代替传统的目录层次作为构建的最小单位，避免将路径硬编码，构建命令再也不用与源码目录结构绑定；CMake只用一套语法，就可以在不同的平台上生成对应的构建命令（在linux上生成make、ninja, 在windows上生成vs),实现了跨平台编译；CMake在其顶层维护了一个全局变量空间，用于存储构建时各种编译选项与元数据，从而方便地切换编译器或编译同一份软件不同的版本。

## 如何组织构建目标
使用现代CMake的要点之一是以target为中心，而不是以目录为中心。然而，在网上随便搜索到的CMake样例却可能不符合这一原则。CMake的设计师花了很大的功夫，将旧版本兼容做的很好，所以到今天我们可以同时见到（甚至在同一个project中）两种组织方式，而它们都是可行的。但是围绕target的组织方式更稳健、更省心，应尽可能地优先采用。  
Target指的是构建过程中产生的阶段性文件，一般来说可以是可执行文件（executable）或库（library）。使用以下constructor初始化一个target，第一个变量为target名（必须有），之后为其源文件（可选）。

```cmake
add_executable(foo bar.cpp baz.cpp)
add_library(foo bar.cpp baz.cpp)
``` 
单独使用constructor时，虽然我们没有为此target指定编译器，依赖的头文件路径等，constructor还是有默认的参数、行为的（源于旧版本兼容）：通常来说，默认编译器是cc，不依赖任何头文件，最后生成一个名为foo的可执行文件……  
也可以通过以下命令更改constructor行为：   

|现代CMake命令|相当于旧版CMake中| 作用  |
|---|---|---|
|target_compile_definitions|add_definitions|增加一个源文件可见的#define宏|
|target_compile_features|||
| target_compile_options|C_FLAGS, CXX_FLAGS变量||
|target_include_directories| include_directories| |
|target_link_libraries|link_libraries| |
|target_sources|add_executable第二个变量开始|为|
|get_target_property||
|set_target_property||
以上命令使用时，可以使用PUBLIC，PRIVATE，INTERFACE三个关键字限定命令中变量的作用域。其中PRIVATE修饰仅仅用于当前target构建的文件，INTERFACE修饰依赖当前target的其它target构建时需要的文件，而PUBLIC是两者融合，其修饰的文件即用于当前target，也用于依赖当前target的其它target。如
```
add_executable(foo bar.cpp)
target_include_directories(foo PRIVATE header.h)
```
### 最小样例
考虑只有一层目录，一个源文件的构建情况。在根目录中创建一个名为CMakeLists.txt的文本文件，并在其中写入以下内容。
```
cmake_minimum_requirement(VERSION 3.10.0)
project(dummy_project)
add_executable(foo bar.cpp)
```
一次完整的构建称作一个project。project中需要指定cmake版本（想想CMake日新月异的语法）和project名。不论有多少个还可以给project加一些说明，方便后续读者。
```
project(dummy_project VERSION 0.0.1 DESCRIPTION "a dummy project")
```
### 多文件夹构建组织形式
使用add_subdirectories()告知CMake除当前目录外，另需构建的子目录，子目录中也必须存在CMakeLists.txt文件。在现代CMake中，并没有目录的概念，“添加一个子目录”实际上是去执行子目录中CMakeList.txt所指定的target的构建。
当工程中存在多个target且它们之间有依赖关系时，使用add_dependencies()规定依赖顺序，高层的target会等待底层的target构建完毕后再构建，这在使能多线程构建时格外重要。
### CMakeLists.txt与.cmake
有时会在源码目录下看到CMakeList.txt之外还有部分.cmake文件，如果熟悉make，可以类比为Makefile与.mk文件。当编写CMake文件时，总有一些变量、函数是可以共用的。这些公用部分就可以写入.cmake文件，之后被多个CMake文件引入。而.cmake文件本身不会被执行，避免影响构建过程。  
.cmake文件不一定总是要和CMakeList.txt放置在同一目录下, 可以通过include()命令引入别处的.cmake文件    
还可以将一个工程中所使用到的所有.cmake文件汇总到一个子文件夹下，并通过设置CMAKE_MODULE_PATH将它们一次性全部包含

```cmake
# .cmake files locate under src_root/cmake
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
```

### add_custom_target()与add_custom_command() 
这是在默认的add_executable()与add_library()之外另一种添加target的方式。不需要使用冗杂的target_add_...命令，而是像写makefile一样直接写出构建命令。这样做的好处是可以精确控制构建过程中的每一步（如自定义编译、链接，而非直接.c到.exe一步到位），还可以调用任何除编译器之外的工具（如反汇编等）。
```
add_custom_target(Name [ALL] [command1 [args1...]]
                  [COMMAND command2 [args2...] ...]
                  [DEPENDS depend depend depend ... ]
                  [COMMENT comment]
                  [SOURCES src1 [src2...]])
add_custom_command(OUTPUT output1 [output2 ...]
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [MAIN_DEPENDENCY depend]
                   [DEPENDS [depends...]])
```
add_custom_target- 与command的区别：
1. 前者是通常意义上的构建目标，可以类比到makefile中冒号左侧的文件名，最后可以生成一个文件
2. 后者不会构建一个文件，而更侧重于“操作”的概念（可以是环境变量设置、字符串操作、删除文件等操作）。当一个target依赖某个command时，会在生成该文件之前先将其依赖的command执行一遍
3. target与command可以互相依赖

## CMake变量（variable）
变量在旧版CMake中起到传递参数的作用，在现代CMake中（大约从2017年开始）更鼓励使用target property和对应的set-, get_properties，但是习惯没那么容易扭转，在许多项目中仍大量地使用变量。  
使用set()命令初始化变量，并使用${}操作符evaluate变量。    
CMake中常用的变量形式有字符串和列表。字符串即形如“abc”等字符字面量，字符串中可以evaluate其它的变量，形如set(hello "hello ${name}")。  
列表是多个以空格相隔的变量，形如 aa bb cc。列表可以隐式转化为字符串，直接当作字符串使用，这时它相当于各变量名以分号连接的字符串，形如“aa;bb;cc”。  
### 内置的变量
比如 ，在最小样例中，设置project的VERSION之后，CMake内部会初始化以下变量：
* PROJECT_VERSION, <PROJECT-NAME>_VERSION
* PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR  

哪怕在构建工程时从头到尾都没有使用，这些变量依旧存在于工程中，并且可以随时引用。它们称为CMake的内置变量 (internal variables)。  
阅读一份CMakeLists.txt文件时，常会见到一些突然冒出来的宏定义，让人感到非常迷惑，因为它们之前从未被手动定义过。如果这些变量是全大写、由下划线连接的，就要考虑一下它们是不是内置变量！  
“雪上加霜”的是，部分内置变量可能曾经一度作为普通变量，被定义在某个官方的.cmake文件中；却在之后新版本中改为内置变量。类似地、某些内置的宏定义可能之前就是个普通的宏定义。所以搞清楚某个变量、宏是否为内置之前，必须先确认使用的cmake版本，再去查阅该版本的官方文档。

举例
* CMAKE_SOURCE_DIR source tree根目录
* CMAKE_CURRENT_SOURCE_DIR  当前source目录
* CMAKE_BINARY_DIR 构建根目录
* CMAKE_CURRENT_BINARY_DIR 当前构建目录

* CMAKE_C_FLAGS 传递给C编译器的选项
* CMAKE_<LANG>_COMPILER 指定某种语言源文件的编译器
* CMAKE_C_COMPILE_OBJECT 指定到.o文件的编译命令
* CMAKE_C_LINK_EXECUTABLE 指定.o到elf的编译命令

### 字符串操作

#### 设置变量

临时变量（只在cmake此文件时有效）：set(<variable> <value>... [PARENT_SCOPE])

长期变量（存储在cache中）：set(<variable> <value>... CACHE <type> <docstring> [FORCE])，其中类型type可选收下几种情况

- `BOOL` Boolean `ON/OFF` value. [`cmake-gui(1)`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html#manual:cmake-gui(1)) offers a checkbox
- `FILEPATH`Path to a file on disk. [`cmake-gui(1)`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html#manual:cmake-gui(1)) offers a file dialog.
- `PATH` Path to a directory on disk. [`cmake-gui(1)`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html#manual:cmake-gui(1)) offers a file dialog.
- `STRING` A line of text. [`cmake-gui(1)`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html#manual:cmake-gui(1)) offers a text field or a drop-down selection if the [`STRINGS`](https://cmake.org/cmake/help/latest/prop_cache/STRINGS.html#prop_cache:STRINGS) cache entry property is set.
- `INTERNAL` A line of text. [`cmake-gui(1)`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html#manual:cmake-gui(1)) does not show internal entries. They may be used to store variables persistently across runs. Use of this type implies `FORCE`.

如果只是bool变量还可以使用option(<variable> "<help_text>" [value]), 默认为OFF

#### 获取文件名中的一部分字符串
get_filename_component(<var> <FileName> <mode> [CACHE])

- DIRECTORY = Directory without file name
- NAME      = File name without directory
- EXT       = File name longest extension (.b.c from d/a.b.c)
- NAME_WE (without extension)= File name with neither the directory nor the longest extension
- LAST_EXT  = File name last extension (.c from d/a.b.c)
- NAME_WLE  = File name with neither the directory nor the last extension

#### 将CMake变量传入其它文件
configure_file(<input> <output>
  [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
    FILE_PERMISSIONS <permissions>...]
  [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
  [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

将一个文件（往往以.in结尾）按当前CMAKE中的变量生成到另一个文件中， 所有形似`@VAR@` or `${VAR}`的变量都会被替换为对应的cmake变量值。

### 列表操作
```
list(APPEND demo_list aa bb cc)
```

#### 使用正则表达式列出当前文件夹下文件列表

file(GLOB <variable>

  [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]

  [<globbing-expressions>...])

- LIST_DIRECTORIES: 列表中包含文件夹
- RELAITIVE 列出相对路径
> 现代CMake中不鼓励使用file(GLOB)，这是因为该文件列表只在CMake中evalute一次，之后传递给底层构建系统（如make）。当源码目录中文件调整后，make依然只能获取cmake之前evaluate得到的文件目录，而对源码目录发生的变化一无所知。

## CMake流程控制
### 打印消息
写的CMake总报错，但找不到错怎么办？为什么不试试~~万能的~~打印呢？  
message()  
- `FATAL_ERROR` CMake Error, stop processing and generation. 立即停下
- `SEND_ERROR` CMake Error, continue processing, but skip generation. 停下但仍然继续生成正确的部分 
- `STATUS`The main interesting messages that project users might be interested in. Ideally these should be concise, no more than a single line, but still informative. 这一条是最常用的

### 循环
```
foreach(<loop_var> <items>)
  <commands>
endforeach()
```
### CMake函数(function)与宏(macro)
function与macro的目的都是为了复用代码

#### macro和function的区别
1. macro是文本替换，与调用函数在一个scope上。其中任何set()指令都会影响到全局
2. function是Subroutine，有自己的scope（内部定义的变量在外部看不到，必须显式地指定PARENT_SCOPE关键字）    

#### function或macro中ARGV，ARGN等常量是什么？

ARGC是参数数量，ARGV#是各个参数，而ARGN传入的多余的参数。下面的这个例子写得很清楚：

```text
cmake_minimum_required(VERSION 2.8)
function(use_llvm TARGET)
message("ARGC=\"${ARGC}\"")
message("ARGN=\"${ARGN}\"")
message("ARGV=\"${ARGV}\"")
message("ARGV0=\"${ARGV0}\"")
message("ARGV1=\"${ARGV1}\"")
endfunction()

add_custom_target(foo
COMMAND ls)

use_llvm(foo core bitwriter)
# Results:
# ARGC="3"
# ARGN="core;bitwriter"
# ARGV="foo;core;bitwriter"
# ARGV0="foo"
# ARGV1="core"
```  


## CMake GUI
mark_advanced([CLEAR] 各个变量...) 这个命令是用于cmake-gui的，设置各个变量是否默认出现在主界面（设置为advanced则不出现，clear后则默认出现，猜测其advanced的含义是类似于“高级设置”），使用命令行时，这个选项不起作用。

## Reference
* [C++Now 2017 Daniel Pfeifer "Effective CMake"](https://www.youtube.com/watch?v=bsXLMQ6WgIk&t=3030s)
* [It's Time To Do CMake Right](https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/)
* [How to properly add include directories with CMake](https://newbedev.com/how-to-properly-add-include-directories-with-cmake) 
