make(1/3):基本语法与规则

基本语法

Makefile采用top-down的结构,默认会更新最上层的工作目标(all),而下层的工作目标是为了让上层保持在最新状态(如用于删除无用的临时文件的clean工作目标应该放在最下层)。

每条规则的范例如下:

1
2
3
4
target1 target2 target3 : prerequisite1 prerequisite2
command1
command2
command3

说明:

  • 如果不存在prerequisite,只检查target是否存在
  • command前面要有一个tab,不能用空格
  • #表示注释

规则

通配符

makefile中允许使用shell中的~*?[][^]等通配符。

假想工作目标

假想工作目标(phony target)指的是不代表文件的工作目标,如all、clean。

如果当前工作目录下存在名为clean的文件,clean工作目标就被降级成普通工作目标了。为了解决这一问题,也为了自定义假想工作目标,我们可以将工作目标指定成PHONY,如

1
2
3
.PHONY: clean
clean:
rm -f *.o lexer.c

值得注意的是:假想工作目标总是尚未更新。因此,将它们作为target则永远不会自动执行。相反,如果将其作为prerequisite,则这个target总会被执行。此外,我们还可以将“假想工作目标”作为“假想工作目标”的必要条件,这样可以让某一个命令先于其它脚本执行。

标准的假想工作目标:

工作目标 功能
all 执行所有工作
install 安装
clean 删除产生自源代码的文件
distclean 删除编译过程中产生的任何文件(包括configure产生的Makefile)
TAGS 建立可供编辑器使用的标记表??
info 从Texinfo源代码来创建GNU info文件??
check 测试

空工作目标

有时候我们想要偶尔执行某段代码,又不想让其依存对象被更新,我们就可以建立一个空工作目标:它的工作目标是一个空文件,而且最后一个command是touch,创建出的新文件可作为这个空工作目标的时间戳。

例如,下面这个规则将会输出自从上次执行过make print后,变更过的所有文件:

1
2
3
print: *.[hc]
lpr $?
touch $@

变量

普通变量的语法位:$(variable-name)

下面是7个核心自动变量

变量名 含义
$@ 工作目标文件名
$% 档案文件成员结构中的文件名元素??
$< 第一个必要条件的文件名
$? 时间戳在工作目标之后的所有必要条件
$^ 所有必要条件的文件名(已删掉重复的)
$+ 同$^,不过包含重复的
$* 工作目标的主文件名

使用自动变量可以大大简化代码,如:

1
2
count_words: count_words.o counter.o lexer.o -lfl
gcc $^ -o $@

查找文件

对VPATH进行如下赋值:

1
VPATH = src include

这样做表示如果没有在当前目录找到所需要的文件,就从src和include文件夹里面找。

这样做没法处理重名文件的问题,make默认会选取找到的第一个文件,此时可以用vpath指令:

1
vpath pattern directory-list

这样,之前的VPATH可以这样改写:

1
2
vpath %.l %.c src
vpath %.h include

模式规则

make可以按照内置规则自动完成一些转换,不必手动指定。例如可以自动地把.c转换成.o或可执行文件,把.l文件自动转换成.c等等。

这项功能十分强大,例如,我们写了一个helloworld.c,要想生成可执行文件,Makefile可以只有一行!

1
helloworld:      # 对的!这就行了!连helloworld.c都可以省略!

在编写模式规则时,经常会用到%,它大体上等效于shell中的*,但是只能出现一次。

比如上述helloworld的模式规则如下:

1
2
%: %.c
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

此外,还有一种过时的后缀规则:如%: %.c可以略写为.c%.o: %.c可以略写为.c.o

模式规则和后缀规则统称为隐含规则。GNU make内置了90个隐含规则,涵盖了C, C++, Pascal, FORTRAN, ratfor, Modula, Texinfo, TEX等等语言。(但是Java没有。。)

很酷的一点是,隐含规则会自动递归地搜索,比如你可以直接从.l文件生成.o文件(省略了.c),而且由此产生的中间文件都会被删除!

下面我们以.c->.o为例来说明规则的结构:

1
2
%.o: %.c
$(COMPILE.c) $(OUTPUT_OPTION) $<

其中的变量定义如下:

1
2
3
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
CC = gcc
OUTPUT_OPTION = -o $@

这样只要变更CC的值就可以更换C编译器,此外还可以通过设置CFLAGS来设定编译选项。

help命令

这里记录一个打印出当前Makefile定义的目标列表的规则:

1
2
3
4
5
6
.PHONY: help
help:
@ $(MAKE) --print-data-base --question | \
awk '/^[^.%][-A-Za-z0-9_]*:/ {print substr($$1, 1, length($$1)-1) }' | \
sort | \
pr --omit-pagination --width=80 --columns=4

特殊工作目标

除了上面已经介绍的.PHONY还有下面这些常用 的工作目标:

  • .INTERMEDIATE: 这个特殊工作目标的必要条件会被视为中间文件,会在运行完成后被删除
  • .SECONDARY: 被视为中间文件,但不会被删除
  • .PRECIOUS: 指示make在出现中断的时候不删除未完成的文件(这个文件太precious了。。)
  • .DELETE_ON_ERROR: 与.PRECIOUS效果相反

系列导航