从GNU Make到cmake

想了想还是开始学这个,反正时间充裕。
我的目标包括:

  • 学会makefile的基本概念和操作;
  • 学会cmake的基本概念和操作。

GNU make:#

参见这篇教程所讲的内容,这里只做一点记录。

介绍makefile:#

makefile可以指定编译的规则,并且自动化编译。

GNU make 是linux环境下的make命令。

重温程序编译链接:#

编译检测语法是否正确,勾勒程序结构,得到中间目标文件;链接实现函数和全局变量的落实,得到可执行文件。

初识makefile:#

makefile的本质就是,描述项目结构、编译规则,然后根据这个进行项目代码的构建。

1
2
3
4
target ...: prerequisites ...
recipe
...
...
  • target: 可以是中间目标文件,也可以是一个可执行文件,还可以是一个标签(label)
  • prerequisites: 生成该target所依赖的文件和/或target。
  • repice: 该target要执行的命令(任意的shell命令)。

如果pre中有任意文件比target文件更新(比较更新日期),也就是做出了一些变动,就执行recipe。

一个例子:#

如果一个工程有三个头文件和八个源文件(C),我们的makefile应该是下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
edit : main.o kbd.o command.o display.o \ # 反斜杠表示换行符 用来提高代码的可读性
insert.o search.o files.o utils.o # 到这里列完了所有的pre
cc -o edit main.o kbd.o command.o display.o \ # 这里就是执行的shell命令了。原教程使用的是linux os,因此命令不太一样。
insert.o search.o files.o utils.o # 这个命令就是使用cc将若干文件生成一个可执行文件edit。

main.o : main.c defs.h
cc -c main.c # main.c中引入了头文件defs.h,所以我们前面写出了依赖关系,后面给出执行的操作,这里生成的是中间目标文件。
kbd.o : kbd.c defs.h command.h
cc -c kbd.c # 类似的内容
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o # 这里指创建完可执行文件后,删除中间文件,只保留我们需要的文件。

创建好了这个makefile后,我们就可以在这个makefile的目录下使用make命令进行项目构造了。

当我们只输入make命令后,默认执行下面操作:

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。

  3. 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比 edit 这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件。

  4. 如果 edit 所依赖的 .o 文件也不存在,那么make会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件。(这有点像一个堆栈的过程)

  5. 当然,你的C文件和头文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生成make的终极任务,也就是可执行文件 edit 了。

总之就是一层一层的往下搜索,深度优先,如果到最后找不到文件,就会直接退出,报错。如果命令定义错误,或是编译不成功,make不会管。

使用变量:#

makefile支持使用变量将需要重复使用的值进行保存,类似于引用。

上面的例子中,虽然文件不多,但是容易写错,我们可以声明一个变量

1
2
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

然后这么用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)

提高了代码的复用性。这可以视为变量的作用的一个理解。

make自动推导:#

.o文件必然要使用相关的.c文件,因此我们可以省略写出这个文件,只添加相关的头文件,从而我们的代码修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean # .PHONY表示clean是一个伪目标文件。它只是一个行为。
clean :
rm edit $(objects)

还可以按照头文件到目标文件的映射来写

1
2
3
4
5
6
7
8
9
10
11
12
13
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
rm edit $(objects)

这在头文件重复率较高的时候非常好用,不过依赖性就没那么明显了。

#

直接看吧,比较简单。

以上就是makefile的概貌和基础。剩下的内容将在我的makefile单独学习的博客中给出答案。

Cmake:#

1. 啥是Cmake:#

cmake是用于 自动生成 用于 构建软件 的 构建脚本,可以简化构建过程和管理依赖关系来帮助管理复杂的软件项目。

2. cmake能干啥:#

  • 简化构建过程:只需要描述项目的结构和依赖关系,无需直接操作复杂的构建脚本的语言。
  • 自动化配置:自动检测系统环境和可用的编译器,自动调整生成的 构建系统以适应不同的配置需求。
  • 模块化设计:将项目分成多个子模块,独立配置和编译。
  • 多配置支持:通过调整配置参数,控制编译器/链接器的行为。
  • 自定义构建规则:可以定义各种构建规则、编译器选项和预处理宏。

3. cmake的基本结构:#

CMake 的基本结构由一个或多个 CMakeLists.txt 文件组成,通常位于项目的各个子目录中。每个 CMakeLists.txt 文件描述了相应目录的构建规则、依赖项和编译选项。

4. 构建:#

我们想要构建一个程序,需要创建一个CMakeLists.txt,并在其中指定以下几个内容:

  • 使用的cmake版本:
  • 我们的项目名称、项目的语言。
  • 需要生成的可执行文件的名称,和这个文件的依赖项。
  • 其他配置(可选)。

并将这个文件放置在和想要构建的项目的源文件相同的目录中。

之后创建build文件夹,然后在build中配置项目:只需要输入cmake ..表示对当前文件夹的父文件夹进行cmake操作。也即我们刚才已经写好了的CMakeLists.txt和一些源文件夹。

如果一切顺利,项目的配置就会在build文件夹中生成。于是可以根据这个文件夹下的配置进行编译,只需要使用cmake --build .即可。