一、运用规则:
先来创建 simple 项目的两个源程序:
foo.c
#include <stdio.h>
void foo()
{
printf("This is foo ()!\n");
}
main.c
#include <stdio.h>
extern void foo();
int main()
{
foo();
return 0;
}
对于项目,在编写 Makefile 时,应该首先在脑子里勾勒出 Makefile 的依赖关系。
对于 simple 可执行程序来说,上图就是它的 “依赖树” 。有了它,Makefile 的编写就会变得轻松许多。
上图展示了依赖关系与规则间的映射。
all : main.c foo.o
gcc -o simple main.c foo.o
main.o : main.c
gcc -o main.c -c main.c
foo.o : foo.c
gcc -o foo.o -c foo.c
clean :
rm simple main.o foo.o
运行结果(终端):
$ make
gcc -c main.c -o main.o
gcc -c foo.c -o foo.o
gcc -o simple main.o foo.o
$ make clean
rm simple main.o foo.o
在不修改源程序文件的情况下再执行一次 make ,出现以下结果:
$ make
gcc -o simple main.o foo.o
第二次编译并没有构建目标文件的动作,但有构建 simple 的动作。
- make 在第二次执行时,首先查找是否存在 all 目标,若有就不执行其后面的命令,若没有,则执行。
- make 是通过文件的时间戳来判断哪些文件需要重新编译。 make 在分析一个规则以创建目标时,如果发现先决条件中文件的时间戳大于目标文件的时间戳,即目标文件比先决条件中的文件更新,就需要运行规则中命令重新构建目标。
假目标:
.PHONY : clean
simple : main.o foo.o
gcc -o simple main.o foo.o
main.o : main.c
gcc -o main.o -c main.c
foo.o : foo.c
gcc -o foo.o -c foo.c
clean:
rm -rf simple main.o foo.o
运行结果:
$ make clean
rm -rf simple main.o foo.o
采用 .PHONY 关键字声明一个目标后,make 并不会将其当作一个文件来处理。由于假目标不与一个文件关联,故无论 clean 文件是否存在,它都执行;
运用 “变量”
.PHONY : clean
CC = gcc
RM = rm
EXE = simple.exe
OBJS = main.o foo.o
$(EXE) : $(OBJS)
$(CC) -o $(EXE) $(OBJS)
main.o : main.c
$(CC) -o main.o -c main.c
foo.o : foo.c
$(CC) -o foo.o -c foo.c
clean :
$(RM) -rf $(EXE) $(OBJS)
引用变量需要采用 “$(变量名)” 或 “${变量名}” 的形式。
1.自动变量:
- $@ : 用于表示一个规则中的目标。当一个规则中有多个目标时,$@ 所指的是其中任何造成规则命令被运行的目标
- $^:表示规则中所有的先决条件
- $<:表示规则中第一个先决条件
.PHONY : all
all : first second third
@echo "\$$@ = $@"
@echo "$$^ = $^"
@echo "$$< = $<"
- first second third :
运行结果:
$make
$@ = all
$^ = first second third
$< = first
注意:
- 在 Makefile 中 “$” 具有特殊含义,如果采用 echo 输出 “$” ,则必须用两个连着的 “$”;
- “$@” 对于 Bash Shell 也有特殊含义,需要在 “$$@” 之前再加一个脱字符 “\” (引号不包括在内)
.PHONY : clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
&(EXE) : $(OBJS)
$(CC) -o $@ $^
main.o : main.c
$(CC) -o $@ -c $^
foo.o : foo.c
$(CC) -o $@ -c $^
clean :
$(RM) -rf $(EXE) $(OBJS)
2.特殊变量:
- MAKE
- MAKECMDGOALS
.PHONY : all
all :
@echo "MAKE = $(MAKE)"
运行结果:
$make
MAKE = make
$(MAKE) 的值就是 “make” ,当在一个 Makefile 中运行另一个 Makefile 时,需要用到这个变量。
.PHONY : all clean
all clean :
$echo "\$$@ = $@"
$echo "MAKECMDGOALS = $MAKECMDGOALS"
运行结果:
$make
$@ = all
MAKECMDGOALS =
$make all
$@ = all
MAKECMDGOALS = all
$make clean
$@ = clean
MAKECMDGOALS = clean
$make all clean
$@ = all
MAKECMDGOALS = all
$@ = cleanl
MAKECMDGOALS = clean
从运行结果:
- MAKECMDGOALS 变量指的是用户输入的目标,当 make 不带参数时,MAKECMDGOALS 为空,即使前面我们所提到 make 不带参数时 即生成默认目标。
- make 后可带多个参数,表示构建多个目标,其将从左到右逐个构建目标。
3.变量的类别与赋值:
“=”
.PHONY : all
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all :
@echo $(foo)
运行结果
$make
Huh?
只用 “=” 符号定义的变量称为递归扩展变量,它的引用时递归的,容易出现死循环。
“:=”
.PHONY : all
x = foo
y = $(x) b
x = later
xx := foo
yy := &(xx) b
xx := later
all :
@echo "x = $(y), xx = $(yy)"
运行结果
$make
x = later b, xx = foo b
用 “:=” 定义的变量称为简单扩展变量,make 只对其展开一次。
实例:
.PHONY : all
obj = main.o foo.o bar.o
obj := $(obj) another.o
all :
@echo $(obj)
运行结果
$make
main.o foo.o bar.o another.o
“?=”
.PHONY : all
foo = x
foo ?= y
bar ?= y
all :
@echo "foo = $(foo), bar = $(bar)"
运行结果
$make
foo = x, bar = y
当用到 “?= ” 若变量被定义了,不改变其原值,若变量没有被定义,则将右边的值赋值给它。
“+=”
.PHONY : all
obj = main.o foo.o bar.o
obj += another.o
all :
@echo $(obj)
运行结果
$make
main.o foo.o bar.o another.o
通过 “+=” 实现追加赋值,其效果与 “:=” 完全一样。
4.变量的来源:
除了在 Makefile 中直接定义变量外,还可以有其他几种方式是 make 获得变量值
- 对于自动变量,它的值是每一条规则根据上下文自动获取。
- 在运行 make 时, 可以使用 “make bar = x” 的形式对变量 bar 进行赋值。
- 变量也可以来自于 Shell 环境, 采用 export 命令定义变量,如(“?=”):
$make
foo = x, bar = y
$export bar = x
$make
foo = x, bar = x
5.高级变量的引用:
.PHONY : all
foo = a.c b.c c.c
bar := $(foo : .c = .o)
all :
@echo "bar = $(bar)"
运行结果
$make
bar = a.o b.o c.o
从运行结果:
- bar 变量中的文件名从 .c 后缀变成了 .o
- 这种功能可用后面介绍的 patsubst 函数
6.避免变量被覆盖:
.PHONY : all
override foo = x
all :
@echo "foo = $(foo)"
运行结果
$make foo = ha
foo = x
从运行结果:
根据上面的 “4.变量的来源” 所提, 变量的值可以有 make 来获取,若我们在设计 Makefile 时不希望它的值自定义后再被修改,可在变量前加上 “override“ 来进行保护。
二、采用精简规则
.PHONY : clean
CC = gcc
RM = rm
EXE = simple.exe
OBJS = main.o foo.o
$(EXE) : $(OBJS)
$(CC) -o $@ $^
%.o : %.c
$(CC) -o $@ -c $^
clean :
$(RM) -rf $(EXE) $(OBJS)
参考文献:《专业嵌入式软件开发》李云·著
2016年7月2日,星期六