1. 概述
  2. 最小项目
  3. 变量操作
  4. 交互操作
  5. 构建控制
  6. 安装控制
  7. 静态库与动态库的构建
  8. 使用外部共享库和头文件

1. 概述

CMake 是一个用来自动生成 makefile 的工具(进一步简化了工程构建的步骤)
CLion 的构建系统就是 CMake,Qt 的构建系统也是类似的 qmake
CLion 可以自动生成 CMake,如果想要更细致的控制工程,还是有必要学一学 CMake 的
本文仅仅是简单总结了一下,更详细的内容还是要参考书

参考资料:\<\>

2. 最小项目

示例 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
# 声明当前 cmake 项目的名称及其语言(语言可以不声明)
# PROJECT(projectname [CXX] [C] [Java])
PROJECT (HELLO)

# 设置变量 SRC_LIST 为 main.c
SET(SRC_LIST main.c)

# 向控制台写一个信息
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})

# 为当前项目添加一个最终目标及其生成文件
ADD_EXECUTABLE(hello SRC_LIST)

执行下列命令使用这个 CMakeLists.txt 编译一个 main.c 文件

1
2
3
4
5
6
# 对当前文件夹运行 cmake
cmake .
# 运行完上述命令后,会生成
# CMakeFiles,CMakeCache.txt,cmake_install.cmake,Makefile
# 执行make命令即可完成编译
make

3. 变量操作

设置与使用变量的语法为

1
2
3
4
5
# 其中 VALUE 可以为多个值
SET(VAR [VALUE...] [CACHE TYPE DOCSTRING [FORCE]])

# 使用变量的方法和 shell 类似,使用 $ 和大括号表示变量值
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})

CMake 会自动初始化几个变量的值

1
2
3
4
5
6
7
8
# 源码和生成目标目录
# 设置这两个变量后会自动改变CMake寻找源码和构建目标的路径
PROJECT_BINARY_DIR 默认初始化为CMakeLists.txt所在目录
PROJECT_SOURCE_DIR 默认初始化为CMakeLists.txt所在目录

# 同时会初始化另外两个特殊变量与源码和生成目标目录一致
<projectname>_BINARY_DIR
<projectname>_SOURCE_DIR

4. 交互操作

可以使用 MESSAGE 命令向终端发出信息

1
2
3
4
# SEND_ERROR:产生错误,生成过程被跳过
# STATUS:生成过程信息
# FATAL_ERROR:严重错误,立即终止生成
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)

5. 构建控制

ADD_EXECUTABLE 命令指定当前项目要生成的可执行文件及其依赖

1
ADD_EXECUTABLE(hello [src...])

可以使用 ADD_SUBDIRECTORY 指令控制源码和目标文件的位置(外部构建)

1
2
3
4
# 添加源代码和目标文件的目录
# 注意这个 source_dir 是相对于项目路径,binary_dir 是相对于执行cmake命令的路径
# 在 <project_dir>/build 中执行 cmake 的话,binary_dir 就是 <project_dir>/build/binary_dir
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

可以通过 SET 指令设置特殊变量指定可执行文件和库文件的路径

1
2
3
4
# 这里主要是分离产生的可执行文件和库文件
# 这个命令应该和 ADD_EXECUTABLE 或 ADD_LIBRARY 写在一起
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

6. 安装控制

安装控制通过一个命令和一个特殊变量来实现
CMAKE_INSTALL_PREFIX 变量定义了安装部分的前缀部分,可以手动指定一个默认值,也可以在执行 cmake 命令的时候手动传入 CMAKE_INSTALL_PREFIX 参数,如:

1
2
# 具体方式可以参考 cmake 的 man 手册
cmake -DCMAKE_INSTALL_PREFIX=/usr

然后是 INSTALL 命令,可以指定二进制文件,静态库,动态库,文件,目录以及脚本的安装规则,完整格式如下:

  1. 安装目标文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # targets就是 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件
    # 可能是可执行二进制(RUNTIME),动态库(LIBRARY),静态库(ARCHIVE)
    # 如果 DESTINATION 是绝对路径,则 CMAKE_INSTALL_PREFIX 变量无效
    # 否则最终安装路径为 ${CMAKE_INSTALL_PREFIX}/<DESTINATION>
    # 当 targets 有多个的时候,可以写多个可选内容为每个 target 指定一套安装规则
    INSTALL(
    TARGETS targets...
    [
    [ARCHIVE|LIBRARY|RUNTIME]
    [DESTINATION <dir>]
    [PERMISSIONS permissions...]
    [
    CONFIGURATIONS
    [Debug|Release|...]
    ]
    [COMPONENT <component>]
    [OPTIONAL]
    ] [...]
    )
  2. 安装普通文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 如果不指定权限,则默认权限为644
    INSTALL(
    FILES files...
    DESTINATION <dir>
    [PERMISSIONS permissions...]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT <component>]
    [RENAME <name>]
    [OPTIONAL]
    )
  3. 安装非目标文件的可执行程序安装(比如脚本之类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 与普通文件类似,但默认权限为755
    INSTALL(
    PROGRAMS files...
    DESTINATION <dir>
    [PERMISSIONS permissions...]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT <component>]
    [RENAME <name>]
    [OPTIONAL]
    )
  4. 安装目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    INSTALL(
    DIRECTORY dirs...
    DESTINATION <dir>
    [FILE_PERMISSIONS permissions...]
    [DIRECTORY_PERMISSIONS permissions...]
    [USE_SOURCE_PERMISSIONS]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT <component>]
    [
    [PATTERN <pattern> | REGEX <regex>]
    [EXCLUDE]
    [PERMISSIONS permissions...]
    ] [...]
    )
  5. 安装命令

    1
    2
    3
    4
    5
    6
    7
    8
    # SCRIPT 指定 CMake 脚本文件(.cmake 文件)
    # CODE 指定 CMake 命令(双引号括起来)
    # 如 INSTALL(CODE "MESSAGE(\"Sample install message.\")")
    INSTALL(
    [
    [SCRIPT <file>] | [CODE <code>]
    ] [...]
    )

    7. 静态库与动态库的构建

使用 ADD_LIBRARY 控制构建库文件

1
2
3
4
5
6
7
8
# 可以编译动态库(SHARED),静态库(STATIC),MODULE选项可以暂时忽略
# EXCLUDE_FROM_ALL 表明如果没有其他组件依赖这个库,则不编译这个库
ADD_LIBRARY(
libname
[SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN
)

但是 ADD_LIBRARY 命令要求 libname 唯一,也就不能用两个 ADD_LIBRARY 生成同名静态库和动态库了
可以使用 SET_TARGET_PROPERTIES 命令在生成一个库的时候指定另一种同名库的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
# 为一个目标设置属性
# 可以为动态库设置版本
# VERSION 指代动态库版本
# SOVERSION 指代 API 版本
SET_TARGET_PROPERTIES(
target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...
)

# 同时还有一个对应的取属性命令
# 如果没有这个属性,则返回 NOTFOUND
GET_TARGET_PROPERTY(VAR target property)

为了同时生成动静两个库,可以组合使用如下指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 生成动态库
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

# 生成静态库
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

# 为静态库改名(与动态库同名)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

# 为两个库设置不清理掉其他使用这个名字的库
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT
1)

8. 使用外部共享库和头文件

INCLUDE_DIRECTORIES 命令让我们可以找到非标准位置的头文件

1
2
3
# 默认行为是加入的头文件最后搜索(即AFTER),加入BEFORE参数可以让我们的头文件最先被搜索
# 除此之外可以SET变量CMAKE_INCLUDE_DIRECTORIES_BEFORE为on来改变默认行为为优先搜索
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

只有头文件的话,在链接步骤会触发错误,有两个命令可以指定链接库位置

1
2
3
4
5
6
7
8
9
# 直接添加一个目录为链接库搜索路径
LINK_DIRECTORIES(directory1 directory2 ...)

# 为特定目标指定链接目标
TARGET_LINK_LIBRARIES(
target
[debug | optimized] library1
[debug | optimized] library2
...)

同时还可以使用两个特殊变量指定头文件和库的路径

1
2
3
4
5
6
7
8
# 这两个变量不是CMake变量,而是环境变量,也就是bash传进来的变量
# 分别代表头文件路径和库的路径
CMAKE_INCLUDE_PATH
CMAKE_LIBRARY_PATH

# 有了这两个环境变量,还需要用FIND_PATH指令显式地指出显式地找出路径并存到变量中
FIND_PATH(myHeader hello.h)
FIND_LIBRARY(myLib hello.so)