Skip to main content

makefile极简教程

··1849 words·10 mins· loading · loading · ·
GaleInk
Author
GaleInk
A Breezing Gale ~
Table of Contents
Operating Systems - This article is part of a series.
Part 2: This Article

Makefile
#

gnu官方手册:make.pdf

这里是我根据手册写的简略介绍:

什么是make
#

首先GNU提供了一个命令工具:make,其最大的作用就是能够自动决定一个项目需要编译/重新编译哪些源文件。每次修改某些源文件的时候,只需要在终端输入:

make

就能够自动执行所有必要的重新编译了。更具体的,make使用文件的元数据和最后一次更改的时间,来决定哪些文件需要更新和重新编译。

Makefile的规则
#

我们需要一个文件来告诉make到底要做什么,也就是这里介绍的Makefile文件,它会告诉make命令该如何编译和链接各个源代码。一个简单的书写makefile的规则如下:

target ... : prerequisites ...
	command
	...
	...

target就是一个目标文件,能够是Object File,也能够是运行文件。还可以是要执行的某种动作的名称(A target can also be the name of an action to carry out),比如clean

prerequisites就是要生成那个target所须要的文件或是目标,也就是作为输入的文件,往往是多个。也有可能不需要输入文件,比如一个删除规则clean就无需这些文件,而仅仅需要下面说的command中的东西。

command也就是make须要运行的命令(随意的Shell命令)。需要注意的是在每一天命令前要有一个tab

当一个target是文件的时候,如果它的prerequisites中有任何一个文件发生了改动,其都需要被重新编译或链接。

一个例子
#

下面给出一个例子,假设一个工程有3个头文件,和8个C文件,我们要写一个Makefile来告诉make命令怎样编译和链接这几个文件。我们的规则是:

  • 假设这个工程没有编译过,那么我们的全部C文件都要编译并被链接。
  • 假设这个工程的某几个C文件被改动,那么我们仅仅编译被改动的C文件,并链接目标程序。
  • 假设这个工程的头文件被改变了,那么我们须要编译引用了这几个头文件的C文件,并链接目标程序。 我们的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

在该文件夹下直接输入命令make就能够生成运行文件edit。假设要删除运行文件和全部的中间目标文件,那么,仅仅要简单地运行一下make clean就能够了。其中这个clean不依赖任何文件且仅仅是一个动作,叫做伪目标(phony targets)。

make是如何处理Makefile的
#

默认行为是:make完成makefile文件的第一个目标。比如对于上述例子,在执行make之后,会查找当前文件夹下的makefile文件并处理其第一个规则,也就是edit;但是在完全处理好这个规则之前,还需要处理这个目标所依赖的那些文件,在该例中就是那些object files。也就是说需要先把prerequisites处理好,再对目标进行编译或者链接。

设置变量来简化Makefile
#

在上例中edit目标需要罗列所有的目标文件两次,在未来如果要添加一个新的目标文件,就有可能忘记添两处。为了消除这个风险和简化内容可以使用变量。按照惯例,每个Makefile都应该定义一个名为objects / OBJECTS / objs / OBJS / obj / OBJ等等来作为所有目标文件名称的总和,格式为:

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

那么在其他地方想放这些目标文件就只需要写:$(objects)来代替即可。 在替换之后,例子就可以简化为:

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)

用一个等号来定义的变量,其引用会推迟到变量被使用的时候才展开,比如:

# 例1
A = foo
B = $(A) bar  # B 的值是字符串 "$(A) bar",此时并不展开 A
A = later

all:
    @echo $(B)  # 输出: later bar

而使用:=来定义的变量,会在定义时立即展开,比如:

# 例2
A := foo
B := $(A) bar  # B 的值在定义时立即展开为 "foo bar"
A := later     # 修改 A 对 B 已无影响

all:
    @echo $(B)  # 输出: foo bar

对于项目中的基础配置、工具路径等等优先用:=防止意外发生。

此外,命令中加上@的作用是禁止回显,在例1中输出只有B的值,假设没有@的话输出会第一行打印echo later bar然后再打印B的值。

使用隐式规则来简化Makefile
#

make可以自行推导出这样的命令:cc -c main.c -o main.o,也就是说如果需要目标文件main.o,那么make会自动把main.c添加到依赖项中,那么在书写依赖性的时候就可以把main.c忽略掉。下面是利用这个规则简化后的常见的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)

除此之外,还有另一种写法,上面这是对每一个目标罗列依赖项;还可以反过来,对每一个依赖性罗列目标:

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)

上述内容中多出的.PHONE : clean声明其是一个伪目标,意味着make不会期望真的有clean为名字的文件。假设目录里面真的有这个文件,且其已经是最新的,若是没有这个声明,就会拒绝执行clean规则。-rm告诉make即使rm命令执行出错(比如要删除的文件不存在),也要继续执行下去,从而可以友好多次执行make clean

Operating Systems - This article is part of a series.
Part 2: This Article