第1章 Makefile快速入门

基础概念

make :项目构建工具——用于管理文件的更新 Makefile:规则文件,告诉make如何进行编译与管理 Makefile(取名makefile也行)其实就是一个脚本文件。

快速入门

  1. 创建文本文档——文件名Makefile
  2. #代表注释
  3. 显示规则:一条显示规则由以下构成
目标文件:依赖文件
[Tab键]指令

示例: 1)首先我们新建一个文件夹,在该文件夹内写一个hello.cpp文件 2)我们新建一个Makefile文件。 3)在Makefile内第1个文件通常是我们的目标文件。 4)显然,在下面的文件中我们写上了4条显示规则。Makefile默认情况下会把第一条显示规则当作最终目标,也就是说,如果完成了第一条显示规则,那么Makefile是有可能不执行后面的显示规则的。可以通过ALL命令来指示最终目标:ALL hello,这样就无需将最终目标放在文件开头了。

hello:hello.o
	g++ hello.o -o hello

hello.o:hello.s
	g++ -c hello.s -o hello.o

hello.s:hello.i
	g++ -S hello.i -o hello.s

hello.i:hello.cpp
	g++ -E hello.cpp -o hello.i
  1. 伪目标 .PHONY:目标名(可以任意命名),使用伪目标指令就会执行目标后面的命令。
.PHONY: 
clean:
	rm  -rf hello.o hello.s hello.i

示例:执行clean命令

make clean 

变量的使用

变量=(替换) 变量+=(追加) 变量:=(常量) 使用变量——$(变量名)

TARGET=hello
OBJ=hello.o

$(TARGET):$(OBJ)
	g++ hello.o -o hello

hello.o:hello.s
	g++ -c hello.s -o hello.o

hello.s:hello.i
	g++ -S hello.i -o hello.s

hello.i:hello.cpp
	g++ -E hello.cpp -o hello.i 

.PHONY:
clean:
	-rm  -rf hello.o hello.s hello.i(rm前面加-表示执行过程中出错了也要继续往下执行)

隐含规则

%.c文件表示任意的.c文件,%.o表示任意的.o文件。

通配符(自动变量 )

$@ 表示所有的目标文件 
$^ 表示所有的依赖文件
$< 所有的依赖文件的第一个文件

第2章 Makefile详解

1个基本原则

  1. 若想生成目标,检查规则中的依赖条件是否存在,若不存在,则检查是否有规则用来生成该文件。
  2. 检查规则中的目标是否需要更新,必须先检查他的所有依赖,依赖中有任意一个被更新,则目标必须被更新(目标更新时间必须晚于依赖文件的更新时间,否则就会更新目标)。

2个函数

wildcard函数

src=$(wildcard *.c)
或者
src=$(wildcard ./*.c)

src是Makefile文件的一个变量,Makefile只有一种变量类型字符串类型。 wildcard是一个函数,函数名意思是通配符。 以上函数语句的意思就是把当前目录下所有.c文件提取出来作为一个列表赋值给src。

patsubst函数

patsubst是patten substitude的缩写,匹配替代的意思。

obj=$(patsubts %.c, %.o, $(src))

将参数3中,包含参数1的部分,替换为参数2。 比如src = add.c sub.c div.c 则obj =add.o sub.o div.o

3个自动变量

$@:在规则的命令中,表示规则中的目标 $^:在规则的命令行中,表示所有的依赖条件 $<:在规则的命令中,表示第一个依赖条件。如果将该变量应用在模式规则中,它可将依赖条件中的依赖依次取出,套用模式规则。

注意:在一条规则中,$@只能用于命令行,而不能用于目标文件和依赖文件.看不懂这句话就先忽略

假设我们有Makefile文件内容如下:

src=$(wildcard *.c) # add.c sub.c div.c main.c
obj=$(patsubts %.c, %.o, $(src)) # add.o sub.o div.o main.o

ALL:a.out

a.out:$(obj)
    gcc $(obj) -o a.out

add.o:add.c
    gcc -c add.c -o add.o
sub.o:sub.c
    gcc -c sub.c -o sub.o
div.o:div.c
    gcc -c div.c -o div.o
main.o:main.c
    gcc -c main.c -o main.o

clean:
    -rm -rf $(obj) a.out

通过自动变量我们可以做出如下替换:

src=$(wildcard *.c) # add.c sub.c div.c main.c
obj=$(patsubts %.c, %.o, $(src)) # add.o sub.o div.o main.o

ALL:a.out

#在下面这条规则中,
#目标文件是a.out,所以在命令行中$@的值就是a.out
#依赖文件是$(obj)——值为add.o sub.o div.o main.o,所以在命令行中$^的值就是以上的4个文件
a.out:$(obj)   
    gcc $^ -o $@

#在下面的这条规则中,目标文件的值是add.o,依赖文件的值是add.c
#所以$<的值就是add.c,$@的值就是add.o,这里由于只有一个依赖文件,所以$<与$^的取值是一样的,我们使用$<
add.o:add.c
    gcc -c $< -o $@
sub.o:sub.c
    gcc -c $< -o $@
div.o:div.c
    gcc -c $< -o $@
main.o:main.c
    gcc -c $< -o $@

clean:
    -rm -rf $(obj) a.out

现在,你知道$@为什么不能用于目标文件和依赖文件的值了吧。因为以上三个自动变量的值就取决于目标文件和依赖文件的值。

模式规则

现在,我们要求再添加进一个功能,我们要添加mul.c文件实现乘法功能。 那么,我们现在也就必须要修改我们的Makefile文件。 但是我们想要实现的最终目标是,添加进新的文件时,不用修改我们的Makefile文件,这就需要用到模式规则。 %.c文件表示任意的.c文件,%.o表示任意的.o文件。 所以,在Makefile文件中,下面的几条规则我们可以进行替换

add.o:add.c
    gcc -c $< -o $@
sub.o:sub.c
    gcc -c $< -o $@
div.o:div.c
    gcc -c $< -o $@
main.o:main.c
    gcc -c $< -o $@

我们只用一条规则就可以替换上面的规则了

%.o:%.c
    gcc -c $< -o $@

请你思考,在这里为什么不能使用*.c而是必须使用%.c?为什么不能使用*.o而是必须使用%.o?也就是说,通配符*与%的区别是什么? 这样,我们添加新的mul.c文件时,通过模式规则我们就不用修改Makefile文件了。

静态模式规则

$(obj):%.o:%.c 相当于在模式规则的前面加上了个前缀,指定了(obj).o(obj)应该要套用的模式规则。 假设有以下场景:下面有两条模式规则都能产生.o文件,(obj)应该套用哪天模式规则呢?这里就产生了歧义。需要用到静态模式规则。

src=$(wildcard *.c) # add.c sub.c div.c main.c
obj=$(patsubts %.c, %.o, $(src)) # add.o sub.o div.o main.o

ALL:a.out

a.out:$(obj)
    gcc $(obj) -o a.out
%.o:%.c
    gcc -c $< -o $@
%.o:%.s
    gcc -S $< -o $@
clean:
    -rm -rf $(obj) a.out

修改如下:

src=$(wildcard *.c) # add.c sub.c div.c main.c
obj=$(patsubts %.c, %.o, $(src)) # add.o sub.o div.o main.o

ALL:a.out

a.out:$(obj)
    gcc $(obj) -o a.out
$(obj):%.o:%.c
    gcc -c $< -o $@
%.o:%.s
    gcc -S $< -o $@
clean:
    -rm -rf $(obj) a.out