banner
NEWS LETTER

CMake教程

Scroll down

自己在使用CLion集成环境编写C++程序时发现其自动生成了CMakeLists.txt文件,通过查阅相关资料发现其跟CMake有关,于是便写下这篇文章进行相关知识记录方便自己复习。

一、前言

CMake是一个支持跨平台的项目构建工具。另外还有一个更为人熟知的项目构建工具——Makefile(通过make命令进行项目的构建),然而大多数IDE软件都集成了Make,比如:Visual CodenmakeLinux下的GNU make以及QTQMake等。Makefile通常依赖于当前的编译平台,有时程序员编写Makefile工作量会比较大且解决依赖关系时也容易出错。而CMake恰好能解决上述问题,其允许开发者指定整个工程的编译流程,再根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需要make编译即可。其编译流程如下图:Cmake执行流程图

  • 蓝色虚线表示使用Makefile构建项目的过程
  • 红色实现表示使用CMake构建项目的过程

二、CMake的使用

CMake支持大写、小写以及混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,无需太过在意。

2.1 注释

  • 注释行:CMake中使用#实现行注释,可以放在任何位置
  • 注释块:CMake中使用#[[]]实现块注释
1
2
3
4
5
6
7
# 这是一个CMakeLists.txt文件
cmake_minimum_required(VERSION 3.0.0)

# [[这是一个CMakeLists.txt文件
这是一个CMakeLists.txt文件
这是一个CMakeLists.txt文件]]
cmake_minimum_required(VERSION 3.0.0)

2.2 CMakeLists.txt文件编写

假设源文件夹中文件结构如下:

1
2
3
4
5
6
7
8
$ tree
.
├── add.c
├── div.c
├── head.h
├── main.c
├── multi.c
└── sub.c

2.2.1 生成的配置文件不单独放入源文件夹中的子文件中

在上述源文件夹中添加CMakeLists.txt。文件内容如下:

1
2
3
cmake_minimum_required(VERSION 3.0)
project(CALC)
add_executable(app add.c div.c main.c multi.c sub.c)

上述文件中三个命令含义如下:

  • cmake_minimum_required: 指定使用的CMake最低版本。(改命令可选,若不加可能会有警告!)
  • project: 定义工程的名称并可指定工程的版本、工程描述、web主页地址、支持的语言(默认支持所有的语言)。若不需要则可省略,只需指定出工程名即可。
  • add_executable: 定义工程生成的可执行程序名字。(这里的可执行程序名字与项目名字无关)

上述命令官方语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
add_executable(可执行程序名 源文件名称)
# 样式1
add_executable(app add.c div.c main.c multi.c sub.c)
# 样式2
add_executable(app add.c;div.c;main.c;multi.c;sub.c)

Ps: 源文件名称可以有多个,若有多个可用空格;隔开
在控制台执行cmake CMakeLists.txt文件所在路径后,CMakeLists.txt中的命令就会被执行。执行之后,源文件夹中的文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ tree -L 1
.
├── add.c
├── CMakeCache.txt # new add file
├── CMakeFiles # new add dir
├── cmake_install.cmake # new add file
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── Makefile # new add file
├── multi.c
└── sub.c

可以看见在对应目录下生成了一个Makefile文件,这时再在控制台中执行make命令就可以对项目进行构建得到所需要的可执行程序了。结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ make
Scanning dependencies of target app
[ 16%] Building C object CMakeFiles/app.dir/add.c.o
[ 33%] Building C object CMakeFiles/app.dir/div.c.o
[ 50%] Building C object CMakeFiles/app.dir/main.c.o
[ 66%] Building C object CMakeFiles/app.dir/multi.c.o
[ 83%] Building C object CMakeFiles/app.dir/sub.c.o
[100%] Linking C executable app
[100%] Built target app

# 查看可执行程序是否已经生成
$ tree -L 1
.
├── add.c
├── app # 生成的可执行程序
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── Makefile
├── multi.c
└── sub.c

最终可执行程序app就被编译出来了,其名字由CMakeLists.txt中指定。

2.2.2 生成的配置文件单独放入源文件夹中的子文件中

在上面的例子中,若CMakeLists.txt文件所在目录执行了cmake命令之后就会生成一些目录和文件(包括makefile)文件。若再基于makefile文件执行make命令,程序在编译过程中还会产生一些中间文件和可执行文件,这样会导致整个项目目录看起来很混乱,不太容易管理和维护,此时我们就可以把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,通常将整个目录命名为build。执行代码以及执行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ mkdir build
$ cd build
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/Linux/build

在上面的代码中,首先创建了一个目录build并进入至该目录中,然后再再build目录中执行cmake命令,由于CMakeLists.txt文件中build的上级目录中,因此执行Shell指令为cmake ..。(..代表当前目录的上级目录)
当上述命令执行完毕后,在build目录中会生成一个makefile文件。该目录下的文件结构如下:

1
2
3
4
5
6
7
8
9
$ tree build -L 1
build
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
└── Makefile

1 directory, 3 files

这样就可以在build目录中执行make命令编译项目,生成的相关文件自然也就被存储到build目录中了。

2.3 进阶语法

在上面的例子中一共提供了5个源文件,假设这五个源文件需要被反复使用,每次都直接将它们的名字写出来比较麻烦,这时我们需要定义一个变量,该变量可将这些文件名存储起来,在CMake中定义变量需要使用关键字set

1
2
3
# SET指令语法:
# []中的参数为可选项
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
  • VAR: 变量名
  • VALUE: 变量值
1
2
3
4
5
6
# 方式一: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c div.c main.c multi.c sub.c)

# 方式二: 各个源文件之间使用分号;间隔
set(SRC_LIST add.c;div.c;main.c;multi.c;sub.c)
add_executable(app ${SRC_LIST})

2.3.1 指定使用C++标准

在编写C++程序时可能会用到C++11、C++14、C++17、C++20等新特性,那么就需要在编译的时候指定出要使用哪个标准:g++ *.cpp -std=c++11 -o app。此命令通过-std=c++11指定出要使用C++11编译程序,C++标准对应有一宏叫做DCMAKE_CXX_STANDARD。在CMake中想要指定C++有两种方式:

  • 方式一:在CMakeLists.txt中通过set命令指定
    1
    2
    3
    4
    5
    6
    # 增加-std=c++11
    set(CMAKE_CXX_STANDARD 11)
    # 增加-std=c++14
    set(CMAKE_CXX_STANDARD 14)
    3 增加-std=c++17
    set(CMAKE_CXX_STANDARD 17)
  • 方式二:在执行cmake命令时指定出这个宏的值
    1
    2
    3
    4
    5
    6
    #增加-std=c++11
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
    #增加-std=c++14
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
    #增加-std=c++17
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

2.3.2 指定输出的路径

CMake中指定可执行程序输出的路径,也对应一个宏叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置。

1
2
set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 第一行:定义一个变量用于存储一个绝对路径。
  • 第二行:将拼接好的路径值设置给EXECUTABLE_OUTPUT_PATH`宏。(若这个路径的子目录不存在则会自动生成)
    由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录。

2.3.3 搜索文件

若一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来。在CMake中提供了文件搜索命令,可以使用aux_source_directory命令或者file命令。

  • 方式一:在CMake中使用aux_source_directory命令可以查找某个路径下的的所有源文件。
    1
    aux_source_directory(<dir> <variable>)
  • dir: 要搜索的目录
  • variable: 将从dir目录下搜索到的源文件列表存储到该变量中
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app ${SRC_LIST})

  • 方式二:采用file命令
    1
    file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到满足条件的所有文件名生成一个列表并将其存储到变量中。
  • GLOB_RECURSE: 递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表并将其存储到变量中。
1
2
3
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
# file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")

搜索当前目录的src目录下所有源文件并存储到变量中。

三、最后的话

以上是常用基础的CMake命令,还有很多需要完善的地方,后续会慢慢补充…

其他文章
请输入关键词进行搜索