一、显式规则(Explicit Rules)
通常在写makefile时使用的都是显式规则,这需要指明target和prerequisite文件。一条规则可以包含多个target,这意味着其中每个target的prerequisite都是相同的。当其中的一个target被修改后,整个规则中的其他target文件都会被重新编译或执行。通配符(Wildcards)
make支持的通配符与Bourne shell基本相同,包括~, *, ?, [...], [^...]。*.* 表示了所有文件;
? 表示任意单个字符;
[...] 表示一个字符类;
[^...] 表示相反的字符类。
~ 表示当前用户的/home路径,~加用户名可以表示该用户的/home路径。
注意: make会在读取makefile的时候就自动补充好通配符替代的内容,而shell则是在执行命令的时候才会进行通配符替代,在某些复杂情况,这两种方式会有极大的区别。
伪目标(Phony Targets)
多数情况下makefile中的target目标文件是像前面提到的那样带有指定的prerequisite文件,但也有一些target仅仅是作为一个标签,代表了一条命令,这种不代表任何文件的目标就被称为伪目标。常见的伪目标例如在makefile开头部分的第一个目标 all, 以及前面例子中见到的 clean:clean:
rm -f *.o lexer.c
但是,make本身是无法区别出目标文件和伪目标的,如果碰巧在编译路径下有一个与伪目标同名的文件存在,那么make会在依赖关系图中把这个文件与伪目标名相关联。而再运行make clean 命令则会因为clean文件存在且如果没有被更新过,则makefile中的clean对应的命令将不会被执行。
为了避免这种情况,GNU make提供了一种特殊目标: “.PHONY”,用来表示目标文件不是真正的文件,即伪目标。clean命令可以被写作:
.PHONY: clean
clean:
rm -f *.o lexer.c
这样,即使再有名为clean的文件存在,make也会执行clean后面的命令。
通常不会将一个伪目标的prerequisite设置为真是存在的文件,因为.PHONY会让他后面的文件在每次make时都进行重新编译。伪目标可以被认为是内嵌在makefile中的shell脚本。
优势: 通过使用伪目标在编译过程中进行屏幕输出,可以使make的可读性增加。例如:
$(Program): build_msg $(OBJECTS) $(BUILTINS_DEP) $(LIBDEP)
$(RM) $@
$(CC) $(LDFLAGS) -o $(Program) $(OBJECTS) $(LIBS)
ls -l $(Program)
size $(Program)
.PHONY: build_msg
build_msg:
@printf "#\n# Building $(Program)\n#\n"
这里将printf命令作为伪目标,可以使make在更新任何prerequisite之前就将指定的编译信息输出。
同时,伪目标也可也作为makefile的默认目标,放在文件的最前端,由于伪目标的特性,他指出的所有prerequisite都会被重新编译。这样可以用来同时生成多个目标。另外,与普通目标文件一样,伪目标也可也使用依赖关系,例如:
.PHONY: clean cleano cleanc
clean: cleano cleanc
-rm $(program)
cleano:
-rm *.o
cleanc:
-rm lexer.c
这样就可以对不同类型的文件进行单独删除。
空目标(Empty Targets)
空目标是伪目标的一种变形形式,通常情况下通过创建一个空文件来实现。例如:size: count_words.o
size $^
touch size
这样,空文件size就被make当作时间戳,只有当count_words.o被更新时,size里面的命令才会再次被执行。另外,所有加载了size作为prerequisite的目标,都不会因为size被编译而强制编译,他们的其他prerequisite目标被更新。
二、变量
变量最简单的形式就是:$(variable_name)变量可以包含几乎所有的字符包括标点符号。一般情况下,变量名需要被$( )所包裹,但是当变量名只有一个字符时,括号可以省略。makefile可以定义很多变量,但同时make本身也定义了一些自动变量。
自动变量
自动变量是make自动根据规则生成的,不需要用户显式的指出相应的文件或目标名称。以下就是七个最核心的自动变量:
$@ 目标文件的文件名;
$% 仅当目标文件为归档成员文件(.lib 或者 .a)时,显示文件名,否则为空;
$< 依赖(prerequisite)列表里面的第一个文件名;
$? 所有在prerequisite列表里面比当前目标新的文件名,用空格隔开;
$^ 所有在prerequisite列表中的文件,用空格隔开; 如果有重复的文件名(包含扩展名),会自动去除重复;
$+ 与$^相似,也是prerequisite列表中的文件名用空格隔开,不同的是这里包含了所有重复的文件名;
$* 显示目标文件的主干文件名,不包含后缀部分。
此外,上面的每个变量都带有两个不同的变种,用于适应不同种类的make。分别是在后面附加一个“D”或者“F”。例如,$(^D)就是代表所有依赖文件的路径,$(<F)表示依赖文件第一个的文件部分的值。使用上述内容前面的makefile可以重写为:
CC = gcc
object = lexer.o count_words.o
program = count_words
$(program): size $(object) -lfl
$(CC) $(object) -lfl -o $@
count_words.o: count_words.c
$(CC) -c $^
lexer.o: lexer.c
$(CC) -c $^
lexer.c: lexer.l
flex -t $^ > $@
size: count_words.o
size $^
touch size
.PHONY: clean cleano cleanc
clean: cleano cleanc
-rm $(program)
cleano:
-rm *.o
cleanc:
-rm lexer.c