2.0 makefile介绍
make 的主要作用是用来编译和链接文件。但是 make 也可以用来管理和自动化很多杂七杂八的东西,加快我们使用命令行过程中的 workflow。比如我在 revel-md (opens new window) 中就使用 make 来一键生成或清除 slides。
- 如果这个工程没有编译过,那么我们的所有 c 文件都要编译并被链接。
- 如果这个工程的某几个 c 文件被修改,那么我们只编译被修改的 c 文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 c 文件,并链接目标程序。
但是 3 的规则存在一个问题: nemu/include/generated/autoconf.h 集合了所有由 kconfig 生成的宏定义,因为每个文件都可能会使用,干脆包含到 common.h 里面,而且每个文件都会直接 / 间接包含 common.h,所以导致但只要在 menuconfig 修改任意一个选项,都会导致所有文件重新编译
解决办法 fixdep:
将 autoconf.h 中的条目全部拆开,并生成一个对应的 include/config/ 目录条目,比如将对宏 #define CONFIG_ITRACE_COND "true" 的依赖分解为对 include/config/itrace/cond.h 的依赖,即在源文件的依赖关系中使用 fixdep 进行修改,这样如果使用 Kconfig 修改了某个配置,就直接更新 include/config 中某个空文件的 timestamp,并且只有将依赖了该空文件的源文件会被更新。
参考链接:
ysyx-nemu 解读 (opens new window)
fixdep 知乎 (opens new window)
# 2.1 makefile 的规则😁
target ... : prerequisites ...
recipe
...
...
2
3
4
每个 recipe 行首都要使用一个 tab 键作为 prefix。
通常来说 space 和 tab 都是不可见的,有没有什么方法可以区分它们呢?
- 可以在 vi 或者 vim 中使用
set list命令让 tab 以^I的形式显示,space 仍是不可见。 - 在 vim 中使用 ctrl+v+tab 来产生 tab
- 在 vscode 中打开 setting, editor.insertSpace 选项中将 Insert space when pressing Tab 取消☑️即可。
# 2.2 一个简单的 Makefile
接下来我们给出 2 中所提到的文本编辑器的 Makefile 如下图所示:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
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 main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 为了方便阅读,我们使用 backslash+new line 的方式将 makefile 中较长的行划分为多行
- 如果一个目标不是一个文件,那么就叫它 phony target。我们在之后还会详细讲解 phony target 的用法。
- 注意区别 cc 和 cc1,cc 是指 gcc 是编译,汇编和链接的 toolchain 的集合,但是 cc1 是编译器。
# 2.3 Makefile 是如何工作的
- make 会在当前目录下找名字叫 “Makefile” 或 “makefile” 的文件。
2. 如果找到,它会找文件中的第一个目标文件 (target),在上面的例子中,他会找到 “edit” 这个文件,并把这个文件作为最终的目标文件。
3. 如果 edit 文件不存在,或是 edit 所依赖的后面的.o 文件的文件修改时间要比 edit 这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件。
4. 如果 edit 所依赖的.o 文件也不存在,那么 make 会在当前文件中找目标为.o 文件的依赖性,如果找到则再根据那一个规则生成.o 文件。(这有点像一个堆栈的过程)
# 2.4 变量使得使用编写 Makefile 变得简单
(详细描述见 chapter6)
在我们 2.2 节的例子中,我们必须要列出所有的目标文件两次:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
2
3
4
人只要做这种重复的事情就容易犯错,而且难以维护:比如给默认目标添加一个新的源文件, 我们可能会记得要添加到 recipe 中,但是我们可能就会忘记在 prerequisite 中也要添加该目标文件。
- 我们可以通过使用变量消除这个风险:变量只需定义一次,然后可以在后面使用多次。
- Makefile 中的变量更类似于 C 语言中的 Macro,make 运行前期会展开所有的变量。
- 给每个 Makefile 都添加一个变量 objects (或者命名为 OBJECTS, OBJS, objs...) 已经成为了习惯。作为一个良好的书写习惯,每个 Makefile 都应该类似这样做:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
2
这样每个要用使用到目标文件的地方,我们就可以使用 $(objexts) 来进行替换。
使用这个诀窍后,得到的文本编辑器的 Makefile 就会如下所示 (看起来清爽多了):
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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.5 使用 make 的隐藏规则来减少 recipes (让 make 自动推导)
(详细描述见 chapter10)
如果你只是想编译一个源文件,那大可不必写 recipe。在 make 中有 implicit rule 可以自动 (通过使用 cc -c) 将.c 文件编译为.o 文件.
只要 make 看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果 make 找到一个 whatever.o ,那么 whatever.c 就会是 whatever.o 的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的 makefile 再也不用写得这么复杂
使用这种方法,改写后的文本编辑器的 Makefile 为 (这也是我们在实际应用中最常使用的方式):
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
clean :
rm edit $(objects)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.6 另一种风格的 Makefile
当一个 makefile 的目标文件 (.o) 都使用 implicit rule 时,就产生了另一种风格的 makefile。
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
2
3
4
5
6
7
这种方法比较难理解,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的 .o 文件,那就理不清楚了。
# 2.7 使用 make 清理构建后的项目
编译项目并不是 make 能做的唯一事情,有时候你还希望 make 能清理构建的文件夹目录,🆑所有生成的目标文件 (.o) 和可执行目标文件。
这时候我们可以使用下面这种类型的语句来进行清除构建的目标文件:
.PHONY : clean
clean :
-rm edit $(objects)
2
3
这样可以在 Makefile 所在目录有名为 clean 这个文件的时候,make 也能正常工作 (详细见 4.5 小节),还可以让 rm 产生错误的时候 make 也能正常工作 (详细见 5.5 小节)。