https://liu-yaohua.github.io

0%

cmake

CMake的应用与实践

1.认识CMake及应用

CMake是什么?

全称Cross Platform Make,起初为了跨平台需要,而后不断完善并广泛使用。

一款优秀的工程构建工具

特点及优势:

开放源码

跨平台,支持Linux,Mac和Windows等

编译语言简单,易用,简化编译构建过程和编译过程

编程高效,可扩展

CMake与其他编译工具的对比

GCC

由GNU开发的编程语言译器,C/C++,Java等语言开发

当项目简单,可以gcc/g++编译目标和项目

但比较复杂时,只用gcc组织编译架构变得极其困难

Makefile

Makefile是有条理的gcc编译命令的文件,利用make工具来执行Makefile文件的编译指令

当程序简单,可以手写Makefile

当程序复杂时,一般利用CMake和autotools来自动生成Makefile

Autotools

autotools是一个工具集,具有灵活性较大,对用户角度使用较为友好

开发步骤太,配置繁琐

通常编译的./configure文件,大多通过由autotools构建的,最终生成Makefile和config.h文件

CMake

Cmake类似Make工具功能,用来读取并执行CMakeList.txt文件的语句,最终生成Makefile文件

Cmake语言开发相对简单,易于理解

目前很多项目正在抛弃Autotools,qmake等,转而采用cmake

2.CMake实战

1.同一个目录,单个源文件

工程结构如下:

1
2
3
./Demo1
|
+---main.cc

对应Cmake文件如下:

1
2
3
4
5
6
#CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
#项目信息
project(Demo1)
#指定生成目标
add_executable(Demo1 main.cc)

CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。

对于上面的 CMakeLists.txt 文件,依次出现了几个命令:

  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
  2. project:参数值是 Demo1,该命令表示项目的名称是 Demo1
  3. add_executable: 将名为 main.cc 的源文件编译成一个名称为 Demo 的可执行文件。

编译项目

之后,在当前目录执行 cmake . ,得到 Makefile 后再使用 make 命令编译得到 Demo1 可执行文件。

其实说白了,cmake是为了生成makefile文件,再用make指令来执行makefile文件,即编译

用cmake就可以简化写makefile的过程。

2.同一个目录,多个源文件

工程结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
./Demo2

|

+--- main.cc

|

+--- MathFunctions.cc

|

+--- MathFunctions.h

对应Cmake如下:

1
2
3
4
5
6
7
8
9
10
11
# CMake 最低版本号要求

cmake_minimum_required (VERSION 2.8)

# 项目信息

project (Demo2)

# 指定生成目标

add_executable(Demo2 main.cc MathFunctions.cc)

唯一的改动只是在 add_executable 命令中增加了一个 MathFunctions.cc 源文件。这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。

1
aux_source_directory(<dir> <variable>)

因此,可以修改 CMakeLists.txt 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# CMake 最低版本号要求

cmake_minimum_required (VERSION 2.8)

# 项目信息

project (Demo2)

# 查找当前目录下的所有源文件

# 并将名称保存到 DIR_SRCS 变量

aux_source_directory(. DIR_SRCS)

# 指定生成目标

add_executable(Demo2 ${DIR_SRCS})

3.多个目录,多个源文件

工程结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
./Demo3

|

+--- main.cc

|

+--- math/

|

+--- MathFunctions.cc

|

+--- MathFunctions.h

对于这种情况,需要分别在项目根目录 Demo3 和 math 目录里各编写一个 CMakeLists.txt 文件。为了方便,我们可以先将 math 目录里的文件编译成静态库再由 main 函数调用。

根目录中的 CMakeLists.txt :

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
# CMake 最低版本号要求

cmake_minimum_required (VERSION 2.8)

# 项目信息

project (Demo3)

# 查找当前目录下的所有源文件

# 并将名称保存到 DIR_SRCS 变量

aux_source_directory(. DIR_SRCS)

# 添加 math 子目录

add_subdirectory(math)

# 指定生成目标

add_executable(Demo main.cc)

# 添加链接库

target_link_libraries(Demo MathFunctions)

该文件添加了下面的内容: 第3行,使用命令 add_subdirectory 指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理 。第6行,使用命令 target_link_libraries 指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。

子目录中的 CMakeLists.txt:

1
2
3
4
5
6
7
8
9
# 查找当前目录下的所有源文件

# 并将名称保存到 DIR_LIB_SRCS 变量

aux_source_directory(. DIR_LIB_SRCS)

# 生成链接库

add_library (MathFunctions ${DIR_LIB_SRCS})

在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。

3.自定义编译选项

CMake 允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。

例如,可以将 MathFunctions 库设为一个可选的库,如果该选项为 ON ,就使用该库定义的数学函数来进行运算。否则就调用标准库中的数学函数库。

修改 CMakeLists 文件

我们要做的第一步是在顶层的 CMakeLists.txt 文件中添加该选项:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# CMake 最低版本号要求

cmake_minimum_required (VERSION 2.8)

# 项目信息

project (Demo4)

# 加入一个配置头文件,用于处理 CMake 对源码的设置

configure_file (

"${PROJECT_SOURCE_DIR}/config.h.in"

"${PROJECT_BINARY_DIR}/config.h"

)

# 是否使用自己的 MathFunctions 库

option (USE_MYMATH

"Use provided math implementation" ON)

# 是否加入 MathFunctions 库

if (USE_MYMATH)

include_directories ("${PROJECT_SOURCE_DIR}/math")

add_subdirectory (math)

set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)

endif (USE_MYMATH)

# 查找当前目录下的所有源文件

# 并将名称保存到 DIR_SRCS 变量

aux_source_directory(. DIR_SRCS)

# 指定生成目标

add_executable(Demo ${DIR_SRCS})

target_link_libraries (Demo ${EXTRA_LIBS})

configure_file 命令用于加入一个配置头文件 config.h ,这个文件由 CMake 从 config.h.in 生成,通过这样的机制,将可以通过预定义一些参数和变量来控制代码的生成。option 命令添加了一个 USE_MYMATH 选项,并且默认值为 ON 。根据 USE_MYMATH 变量的值来决定是否使用我们自己编写的 MathFunctions 库。

修改 main.cc 文件

之后修改 main.cc 文件,让其根据 USE_MYMATH 的预定义值来决定是否调用标准库还是 MathFunctions 库:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include

#include

#include "config.h"

#ifdef USE_MYMATH

#include "math/MathFunctions.h"

#else

#include

#endif

int main(int argc, char *argv[])

{

if (argc < 3){

printf("Usage: %s base exponent \n", argv[0]);

return 1;

}

double base = atof(argv[1]);

int exponent = atoi(argv[2]);

#ifdef USE_MYMATH

printf("Now we use our own Math library. \n");

double result = power(base, exponent);

#else

printf("Now we use the standard library. \n");

double result = pow(base, exponent);

#endif

printf("%g ^ %d is %g\n", base, exponent, result);

return 0;

}

编写 config.h.in 文件

这个文件预定义了 USE_MYMATH 的值。但我们并不直接编写这个文件,为了方便从 CMakeLists.txt 中导入配置,我们编写一个 config.h.in 文件,内容如下:

1
#cmakedefine USE_MYMATH

这样 CMake 会自动根据 CMakeLists 配置文件中的设置自动生成 config.h 文件。

4.库

在 Linux 中,库文件分成静态库和共享库两种x。静态库以.a 作为后缀名,共享库以.so 结尾。所有库都是一些函数打包后的集合,差别在于静态库每次被调用都会生成一个副本, 而共享库则只有一个副本,更省空间。

生成静态库:

1
add_library( hello libHelloSLAM.cpp )

生成动态库:

1
add_library( hello_shared SHARED libHelloSLAM.cpp )

此时得到的文件是 libhello_shared.so 了。

库文件是一个压缩包,里头带有编译好的二进制函数。不过,仅有.a 或.so 库文件的 话,我们并不知道它里头的函数到底是什么,调用的形式又是什么样的。为了让别人(或 者自己)使用这个库,我们需要提供一个头文件,说明这些库里都有些什么。因此,对于库 的使用者,只要拿到了头文件和库文件,就可以调用这个库了。下面我们来写 libhello 的
头文件。