makefile概览

规则的基本形式

1
2
3
4
target ... : prerequisites ...
command
...
...
  • target:可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)
  • prerequisites:生成该target所依赖的文件
  • command:该target要执行的命令(任意的shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说:

prerequisites中如果有一个以上的文件比target文件要新 (比较修改日期) 的话,command所定义的命令就会被执行

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
  • clean 不是一个文件,它只不过是一个label,其冒号后什么也没有,make不会自动去找它的依赖性,也就不会自动执行其后所定义的命令;要执行其后的命令,就要在make命令后明显得指出这个label的名字

make的工作方式

只输入 make 命令:

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
  3. 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比 edit 这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件。
  4. 如果 edit 所依赖的 .o 文件也不存在,那么make会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件。

使用变量

1
2
3
4
5
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
cc -o edit $(objects)
...

makefile自动推导

只要make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果make找到一个 whatever.o ,那么 whatever.c 就会是 whatever.o 的依赖文件。并且 cc -c whatever.c 也会被推导出来,因此上述makefile可简化为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
  • .PHONY 表示 clean 是个伪目标文件
  • rm 前的 - 表示也许某些文件出现问题,但不要管,继续做后面的事

依赖关系也可以合并,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

makefile文件名

默认使用 makefileMakefile ,如果使用其他名称,如 make.linux ,那么需使用命令 make -f make.linux

引用其他makefile

你有这样几个Makefile: a.mkb.mkc.mk ,还有一个文件叫 foo.make ,以及一个变量 $(bar) ,其包含了 e.mkf.mk

1
2
3
include foo.make *.mk $(bar)
# 等价于
include foo.make a.mk b.mk c.mk e.mk f.mk

如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

  1. 如果make执行时,有 -I--include-dir 参数,那么make就会在这个参数所指定的目录下去寻找。
  2. 如果目录 <prefix>/include (一般是: /usr/local/bin/usr/include )存在的话,make也会去找。

make的执行步骤

  1. 读入所有的Makefile。
  2. 读入被include的其它Makefile。
  3. 初始化文件中的变量。
  4. 推导隐晦规则,并分析所有规则。
  5. 为所有的目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些目标要重新生成。
  7. 执行生成命令。

书写规则

通配符

make支持三个通配符: *?~

  • * : 匹配0个或多个字符
  • ? : 匹配一个字符
  • ~ : home目录?

在变量中使用通配符有所不同:

  • objects = *.o 表示 objects 的值就是 *.o ,而不会展开
  • objects := $(wildcard *.o) 表示所有以 .o 结尾的文件

文件搜索

特殊变量 VPATH

如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件;如果定义了这个变量,那么make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件

1
VPATH = src:../headers
  • 多个目录使用冒号隔开,依次搜索

关键字vpath

1
2
3
4
5
6
7
8
# 为符合模式<pattern>的文件指定搜索目录<directories>
vpath <pattern> <directories>

# 清除符合模式<pattern>的文件的搜索目录
vpath <pattern>

# 清除所有已被设置好了的文件搜索目录
vpath
  • <pattern>需要包含 % 字符,表示匹配零或若干字符,例如, %.h 表示所有以 .h 结尾的文件
1
2
vpath %.c foo:bar
vpath % blish
  • 表示搜索 .c 结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录

伪目标

为了避免和文件重名,使用特殊标记 .PHONY 来显式地指明一个目标是伪目标,不管是否有这个文件

1
2
3
.PHONY : clean
clean :
rm *.o temp

伪目标也可以指定所依赖的文件,可以用来在一个Makefile文件生成若干个可执行文件:

1
2
3
4
5
6
7
8
9
10
11
all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o

prog2 : prog2.o
cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o

伪目标的命名

  • all:所有目标的目标,其功能一般是编译所有的目标
  • clean:删除所有被make创建的文件
  • install:安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
  • print:列出改变过的源文件
  • tar:把源程序打包备份,也就是一个tar文件
  • dist:创建一个压缩文件,一般是把tar文件压成Z文件,或是gz文件。
  • TAGS:更新所有的目标,以备完整地重编译使用
  • check和test:一般用来测试makefile的流程

静态模式

1
2
3
4
5
6
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
  • 目标从objects中获取, %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o

  • 依赖模式 %.c 则取模式 %.o% ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c

  • 命令中的 $<$@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集(也就是“foo.o bar.o”)

  • 于是,上面的规则展开后等价于下面的规则:

    1
    2
    3
    4
    foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
    bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

自动生成依赖

把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个 name.c 的文件都生成一个 name.d 的Makefile文件, .d 文件中就存放对应 .c 文件的依赖关系,产生 .d 文件的模式规则如下:

1
2
3
4
5
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
  • 所有的 .d 文件依赖于 .c 文件
  • rm -f $@ 的意思是删除所有的目标,也就是 .d 文件
  • -M 参数表示自动找寻源文件中包含的头文件,并生成一个依赖关系
  • 第二行的意思是,为每个依赖文件 $< ,也就是 .c 文件生成依赖文件, $@ 表示模式 %.d 文件,如果有一个C文件是name.c,那么 % 就是 name$$$$ 意为一个随机编号,第二行生成的文件有可能是name.d.12345
  • 第三行使用sed命令做了一个替换
  • 第四行删除临时文件

书写命令

每条命令的开头必须以 Tab 键开头,除非,命令是紧跟在依赖规则后面的分号后的

显示命令

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @ 字符在命令行前,那么,这个命令将不被make显示出来,如:

1
2
3
4
5
6
@echo 正在编译XXX模块......
# 输出: 正在编译XXX模块……

echo 正在编译XXX模块......
# 输出: echo 正在编译XXX模块......
# 正在编译XXX模块......

带入make参数 -n--just-print ,那么其只是显示命令,但不会执行命令,这个功能有利于我们调试Makefile,看看命令执行的顺序等

命令执行

如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令,如:

1
2
3
4
5
6
exec1:
cd /home/hchen
pwd

exec2:
cd /home/hchen; pwd
  • 执行 make exec1,打印当前Makefile目录,cd 没有作用
  • 执行 make exec2,打印出“/home/hchen”

命令出错

有时候希望能忽略命令出错,继续执行:

  • 对于单个命令,最前面加-

    1
    2
    clean:
    -rm -f *.o
  • 全局方法:给make加上 -i 或是 --ignore-errors 参数,那么,Makefile中所有命令都会忽略错误; -k 或是 --keep-going 参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则

  • 对于单个规则,以 .IGNORE 作为目标,那么这个规则中的所有命令将会忽略错误

嵌套执行make

我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

1
2
subsystem:
cd subdir && $(MAKE)

总控Makefile的变量可以传递到下级的Makefile中:

  • 要传递变量到下级Makefile中:

    1
    export <variable ...>;
  • 不想让某些变量传递到下级Makefile中:

    1
    unexport <variable ...>;
  • 传递所有的变量,只要一个export就行了,后面什么也不用跟

注:

  • 两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管你是否export,总是要传递到下层Makefile中
  • -w 或是 --print-directory 参数会在make的过程中输出一些信息,让你看到目前的工作目录

定义命令包(函数)

1
2
3
4
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

调用:

1
2
foo.c : foo.y
$(run-yacc)
  • 命令包“run-yacc”中的 $^ 就是 foo.y$@ 就是 foo.c

使用变量

变量的基础

  • 变量在声明时需要给予初值
  • 在使用时,需要给在变量名前加上 $ 符号,使用小括号 () 或是大括号 {} 把变量给包括起来
  • 如果要使用真实的 $ 字符,那么需要用 $$ 来表示

变量中的变量

使用变量的值定义变量

使用 =

1
2
3
4
5
6
foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
echo $(foo)
  • = 右侧的变量的值可以定义在文件的任何一处

  • 这种方法的缺点在于可能造成递归定义,如

    1
    2
    A = $(B)
    B = $(A)

使用 :=

1
2
3
x := foo
y := $(x) bar
x := later
  • 最终y的值为"foo bar",x的值为"later"
  • 前面的变量不能使用后面的变量,只能使用前面已定义好了的变量

使用 ?=

1
FOO ?= bar
  • 如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做

定义一个空格

1
2
nullstring :=
space := $(nullstring) # end of the line
  • nullstring是一个Empty变量,其中什么也没有
  • space的值是一个空格:先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止

注:

如果以如下方式定义目录路径的变量:

1
dir := /foo/bar    # directory to put the frobs in

dir这个变量的值是“/foo/bar”,后面还跟了4个空格

变量高级用法

变量值替换

替换变量中的共有的部分,其格式是 $(var:a=b) 或是 ${var:a=b} ,其意思是,把变量var中所有以a字串结尾的a替换成b字串。这里的结尾意思是空格或是结束符,如:

1
2
3
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# bar的值为 a.c b.c c.c

也可以使用静态模式进行替换:

1
2
3
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
# bar的值为 "a.c b.c c.c"

变量值作为变量

1
2
3
4
x = y
y = z
a := $($(x))
# a的值为 "z"
1
2
3
4
5
first_second = Hello
a = first
b = second
all = $($a_$b)
# all的值为 "Hello"

追加变量值

1
2
3
4
5
6
7
objects = main.o foo.o bar.o utils.o
objects += another.o

# 相当于

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

override指示符

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:

1
2
3
override <variable>; = <value>;

override <variable>; := <value>;

多行变量

  • 使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令

  • define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束

1
2
3
4
define two-lines
echo foo
echo $(bar)
endef

环境变量

  • make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中
  • 但如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖
  • 如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量

目标变量

可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和全局变量同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值,其语法为:

1
2
3
<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

例子:

1
2
3
4
5
6
7
8
9
10
11
12
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
$(CC) $(CFLAGS) prog.c

foo.o : foo.c
$(CC) $(CFLAGS) foo.c

bar.o : bar.c
$(CC) $(CFLAGS) bar.c
  • 不管全局的 $(CFLAGS) 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g

模式变量

模式变量的好处是,可以给定一种“模式”,把变量定义在符合这种模式的所有目标上,如:

1
%.o : CFLAGS = -O
  • 给所有以 .o 结尾的目标定义目标变量 CFLAGS

条件判断

语法为:

1
2
3
4
5
6
7
8
9
10
11
<conditional-directive>
<text-if-true>
endif

# 或

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中 <conditional-directive> 表示条件关键字,在 <conditional-directive> 这一行上,多余的空格是被允许的,但是不能以 Tab 键作为开始(不然就被认为是命令)。而注释符 # 同样也是安全的。 elseendif 也一样,只要不是以 Tab 键开始就行了。

条件关键字有以下四种:

ifeq

1
2
3
4
5
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
  • 比较参数 arg1arg2 的值是否相同,相同则为真
  • 参数可以使用make的函数

ifneq

1
2
3
4
5
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
  • 比较参数 arg1arg2 的值是否相同,如果不同,则为真

ifdef

1
ifdef <variable-name>
  • 如果变量 <variable-name> 的值非空,那到表达式为真

  • <variable-name> 同样可以是一个函数的返回值

  • ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 示例1
    bar =
    foo = $(bar)
    ifdef foo
    frobozz = yes
    else
    frobozz = no
    endif
    # frobozz的结果为yes

    # 示例2
    foo =
    ifdef foo
    frobozz = yes
    else
    frobozz = no
    endif
    # frobozz的结果为no

ifndef

1
ifndef <variable-name>
  • ifdef 相反

使用函数

函数调用语法

$ 来标识:

1
$(<function> <arguments>)
  • <function> 就是函数名
  • <arguments> 为函数的参数,参数间以逗号 , 分隔
  • 函数名和参数之间以“空格”分隔

字符串处理函数

subst

1
$(subst <from>,<to>,<text>)
  • 名称:字符串替换函数

  • 功能:把字串 <text> 中的 <from> 字符串替换成 <to>

  • 返回:函数返回被替换过后的字符串

  • 示例:

    1
    $(subst ee,EE,feet on the street)
    • feet on the street 中的 ee 替换成 EE ,返回结果是 fEEt on the strEEt

patsubst

1
$(patsubst <pattern>,<replacement>,<text>)
  • 名称:模式字符串替换函数

  • 功能:查找 <text> 中的单词(单词以空格、Tab或回车换行分隔)是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换

    • <pattern> 可以包括通配符 %
    • 如果 <replacement> 中也包含 % ,那么, <replacement> 中的这个 % 将是 <pattern> 中的那个 % 所代表的字串
    • 可以用 \ 来转义,以 \% 来表示真实含义的 % 字符
  • 返回:函数返回被替换过后的字符串

  • 示例:

    1
    $(patsubst %.c,%.o,x.c.c bar.c)
    • 把字串 x.c.c bar.c 符合模式 %.c 的单词替换成 %.o ,返回结果是 x.c.o bar.o

注:

  • $(var:<pattern>=<replacement>;) 相当于 $(patsubst <pattern>,<replacement>,$(var))

  • $(var: <suffix>=<replacement>) 相当于$(patsubst %<suffix>,%<replacement>,$(var))

  • 如:

    1
    objects = foo.o bar.o baz.o,

    那么, $(objects:.o=.c)$(patsubst %.o,%.c,$(objects)) 是一样的

strip

1
$(strip <string>)
  • 名称:去空格函数

  • 功能:去掉 <string> 字串中开头和结尾的空字符

  • 返回:返回被去掉空格的字符串值

  • 示例:

    1
    $(strip a b c )
    • 把字串 "a b c “去掉开头和结尾的空字符,结果为"a b c”

findstring

1
$(findstring <find>,<in>)
  • 名称:查找字符串函数

  • 功能:在字串 <in> 中查找 <find> 字串

  • 返回:如果找到,那么返回 <find> ,否则返回空字符串

  • 示例:

    1
    2
    $(findstring a,a b c)
    $(findstring a,b c)
    • 第一个函数返回 a 字符串,第二个返回空字符串

filter

1
$(filter <pattern...>,<text>)
  • 名称:过滤函数

  • 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <pattern> 的单词。可以有多个模式

  • 返回:返回符合模式 <pattern> 的字串

  • 示例:

    1
    2
    3
    sources := foo.c bar.c baz.s ugh.h
    foo: $(sources)
    cc $(filter %.c %.s,$(sources)) -o foo
    • $(filter %.c %.s,$(sources)) 返回的值为 foo.c bar.c baz.s

filter-out

1
$(filter-out <pattern...>,<text>)
  • 名称:反过滤函数

  • 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。可以有多个模式

  • 返回:返回不符合模式 <pattern> 的字串

  • 示例:

    1
    2
    objects=main1.o foo.o main2.o bar.o
    mains=main1.o main2.o
    • $(filter-out $(mains),$(objects)) 返回值为 foo.o bar.o

sort

1
$(sort <list>)
  • 名称:排序函数
  • 功能:给字符串 <list> 中的单词排序(升序)
  • 返回:返回排序后的字符串
  • 示例: $(sort foo bar lose) 返回 bar foo lose
  • 备注: sort 函数会去掉 <list> 中相同的单词

word

1
$(word <n>,<text>)
  • 名称:取单词函数
  • 功能:取字符串 <text> 中第 <n> 个单词。(从1开始数)
  • 返回:返回字符串 <text> 中第 <n> 个单词。如果 <n><text> 中的单词数要大,那么返回空字符串
  • 示例: $(word 2, foo bar baz) 返回值是 bar

wordlist

1
$(wordlist <ss>,<e>,<text>)
  • 名称:取单词串函数
  • 功能:从字符串 <text> 中取从 <ss> 开始到 <e> 的单词串。 <ss><e> 是一个数字
  • 返回:返回字符串 <text> 中从 <ss><e> 的单词字串。如果 <ss><text> 中的单词数要大,那么返回空字符串。如果 <e> 大于 <text> 的单词数,那么返回从 <ss> 开始,到 <text> 结束的单词串
  • 示例: $(wordlist 2, 3, foo bar baz) 返回值是 bar baz

words

1
$(words <text>)
  • 名称:单词个数统计函数
  • 功能:统计 <text> 中字符串中的单词个数
  • 返回:返回 <text> 中的单词数
  • 示例: $(words, foo bar baz) 返回值是 3
  • 备注:如果我们要取 <text> 中最后的一个单词,我们可以这样: $(word $(words <text>),<text>)

firstword

1
$(firstword <text>)
  • 名称:首单词函数
  • 功能:取字符串 <text> 中的第一个单词
  • 返回:返回字符串 <text> 的第一个单词
  • 示例: $(firstword foo bar) 返回值是 foo
  • 备注:这个函数可以用 word 函数来实现: $(word 1,<text>)

函数应用实例

make使用 VPATH 变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 CFLAGS ,如:

1
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我们的 $(VPATH) 值是 src:../headers ,那么 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 将返回 -Isrc -I../headers ,这正是cc或gcc搜索头文件路径的参数

文件名操作函数

dir

1
$(dir <names...>)
  • 名称:取目录函数
  • 功能:从文件名序列 <names> 中取出目录部分。目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./
  • 返回:返回文件名序列 <names> 的目录部分
  • 示例: $(dir src/foo.c hacks) 返回值是 src/ ./

notdir

1
$(notdir <names...>)
  • 名称:取文件函数
  • 功能:从文件名序列 <names> 中取出非目录部分。非目录部分是指最後一个反斜杠( / )之后的部分
  • 返回:返回文件名序列 <names> 的非目录部分
  • 示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks

basename

1
$(basename <names...>)
  • 名称:取前缀函数
  • 功能:从文件名序列 <names> 中取出各个文件名的前缀部分
  • 返回:返回文件名序列 <names> 的前缀序列,如果文件没有前缀,则返回空字串
  • 示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks

addsuffix

1
$(addsuffix <suffix>,<names...>)
  • 名称:加后缀函数
  • 功能:把后缀 <suffix> 加到 <names> 中的每个单词后面
  • 返回:返回加过后缀的文件名序列
  • 示例: $(addsuffix .c,foo bar) 返回值是 foo.c bar.c

addprefix

1
$(addprefix <prefix>,<names...>)
  • 名称:加前缀函数
  • 功能:把前缀 <prefix> 加到 <names> 中的每个单词前面
  • 返回:返回加过前缀的文件名序列
  • 示例: $(addprefix src/,foo bar) 返回值是 src/foo src/bar

join

1
$(join <list1>,<list2>)
  • 名称:连接函数
  • 功能:把 <list2> 中的单词对应地加到 <list1> 的单词后面。如果 <list1> 的单词个数要比 <list2> 的多,那么, <list1> 中的多出来的单词将保持原样。如果 <list2> 的单词个数要比 <list1> 多,那么, <list2> 多出来的单词将被复制到 <list1>
  • 返回:返回连接过后的字符串
  • 示例: $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333

foreach函数

1
$(foreach <var>,<list>,<text>)
  • 把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中
  • 然后再执行 <text> 所包含的表达式
  • 每一次 <text> 会返回一个字符串,循环过程中,<text> 的所返回的每个字符串会以空格分隔
  • 最后当整个循环结束时, <text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值
  • <var> 一般是变量名,<list> 可以是表达式,<text> 一般使用 <var> 这个参数来枚举 <list> 中的单词
  • <var> 参数是一个临时的局部变量,foreach函数执行完后,参数 <var> 的变量将不在作用

示例:

1
2
3
4
5
names := a b c d

files := $(foreach n,$(names),$(n).o)

# files的值为 "a.o b.o c.o d.o"

if函数

1
2
3
4
5
$(if <condition>,<then-part>)

# 或

$(if <condition>,<then-part>,<else-part>)

if函数的返回值:如果 <condition> 为真(非空字符串), <then-part> 会是整个函数的返回值;如果 <condition> 为假(空字符串),那么 <else-part> 会是整个函数的返回值,此时如果 <else-part> 没有被定义,那么,整个函数返回空字串

call函数

可以用来创建新的参数化的函数:

1
$(call <expression>,<parm1>,<parm2>,...,<parmn>)

<expression> 参数中的变量,如 $(1)$(2) 等,会被参数 <parm1><parm2><parm3> 依次取代。而 <expression> 的返回值就是 call 函数的返回值,如:

1
2
3
4
reverse =  $(2) $(1)
foo = $(call reverse,a,b)

# foo的值为 "b a"

需要注意:在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向call函数提供参数时,最安全的做法是去除所有多余的空格。

origin函数

1
$(origin <variable>)

注意, <variable> 是变量的名字,不应该是引用。所以你最好不要在 <variable> 中使用 $ 字符。

origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值:

  • undefined<variable> 从来没有定义过,
  • default<variable> 是一个默认的定义,比如“CC”这个变量
  • environment<variable> 是一个环境变量,并且当Makefile被执行时, -e 参数没有被打开
  • file<variable> 被定义在Makefile中
  • command line<variable> 是被命令行定义的
  • override<variable> 是被override指示符重新定义的
  • automatic<variable> 是一个命令运行中的自动化变量

假设我们有一个Makefile其包了一个定义文件 Make.def,在 Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写:

1
2
3
4
5
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif

shell函数

shell函数的参数是操作系统shell的命令,shell函数把执行操作系统命令后的输出作为函数返回,于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:

1
2
contents := $(shell cat foo)
files := $(shell echo *.c)

这个函数会新生成一个Shell程序来执行命令,所以要注意其运行性能

控制make的函数

检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止:

error函数

1
$(error <text ...>)
  • 产生一个致命的错误, <text ...> 是错误信息

  • 示例1:

    1
    2
    3
    4
    ifdef ERROR_001
    $(error error is $(ERROR_001))
    endif
    # 在变量ERROR_001定义了后执行时产生error调用
  • 示例2:

    1
    2
    3
    4
    5
    6
    7
    ERR = $(error found an error!)

    .PHONY: err

    err: $(ERR)

    # 在目录err被执行时发生error调用

warning函数

1
$(warning <text ...>)

error 函数很像,但是不会让make退出,只是输出一段警告信息,而make继续执行

make的运行

make的退出码

make命令执行后有三个退出码:

  • 0:表示成功执行
  • 1:如果make运行时出现任何错误,返回1
  • 2:如果使用make的“-q”选项,并且make使得一些目标不需要更新,那么返回2

指定makefile

使用 -f 或 --file 或 --makefile 参数:

1
make –f hchen.mk

指定目标

  • 一般来说,make的最终目标是makefile中的第一个目标

  • 可以指示make,让其完成你所指定的目标:需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)

  • 有一个make的环境变量叫 MAKECMDGOALS ,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值,使用方法如:

    1
    2
    3
    4
    sources = foo.c bar.c
    ifneq ( $(MAKECMDGOALS),clean)
    include $(sources:.c=.d)
    endif
    • 只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d”和“bar.d”这两个makefile

检查规则

有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列,可以使用以下参数:

  • -n, --just-print, --dry-run, --recon :不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行

  • -t, --touch :把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态

  • -q, --question :找目标,如果目标存在,那么什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息

  • -W <file>, --what-if=<file>, --assume-new=<file>, --new-file=<file> :指定一个文件。一般是是源文件(或依赖文件),make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令

make的参数

  • -b, -m :忽略和其它版本make的兼容性
  • -B, --always-make :认为所有的目标都需要更新(重编译)
  • -C <dir>, --directory=<dir> :指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于“make -C ~hchen/test/prog”
  • -debug[=<options>] :输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
    • a: 也就是all,输出所有的调试信息(会非常的多)
    • b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标
    • v: 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等
    • i: 也就是implicit,输出所以的隐含规则
    • j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等
    • m: 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息
  • -d :相当于“–debug=a”
  • -e, --environment-overrides :指明环境变量的值覆盖makefile中定义的变量的值
  • -f=<file>, --file=<file>, --makefile=<file> :指定需要执行的makefile
  • -h, --help :显示帮助信息
  • -i, --ignore-errors :在执行时忽略所有的错误
  • -I<dir>, --include-dir=<dir> :指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录
  • -j [<jobsnum>], --jobs[=<jobsnum>] :指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的
  • -k, --keep-going :出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了
  • -l <load>, --load-average[=<load>], --max-load[=<load>] :指定make运行命令的负载
  • -n, --just-print, --dry-run, --recon :仅输出执行过程中的命令序列,但并不执行
  • -o <file>, --old-file=<file>, --assume-old=<file> :不重新生成的指定的<file>,即使这个目标的依赖文件新于它
  • -p, --print-data-base :输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用 “make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号
  • -q, --question :不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生
  • -r, --no-builtin-rules :禁止make使用任何隐含规则
  • -R, --no-builtin-variables :禁止make使用任何作用于变量上的隐含规则
  • -s, --silent, --quiet :在命令运行时不输出命令的输出
  • -S, --no-keeping-going, --stop :取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效
  • -t, --touch :相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行
  • -v, --version :输出make程序的版本、版权等关于make的信息
  • -w, --print-directory :输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用
  • --no-print-directory :禁止“-w”选项
  • -W <file>, --what-if=<file>, --assume-new=<file>, --new-file=<file> :假定目标<file>;需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作;如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>;的修改时间为当前时间
  • --warn-undefined-variables :只要make发现有未定义的变量,那么就输出警告信息

隐含规则

使用隐含规则

1
2
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

这个Makefile中并没有写下如何生成 foo.obar.o 这两目标的规则和命令。因为make调用的隐含规则,把 .o 的目标的依赖文件置成 .c ,并使用C的编译命令 cc –c $(CFLAGS) foo.c 来生成 foo.o 的目标

一些语言的隐含规则

  1. 编译C程序的隐含规则。

    <n>.o 的目标的依赖目标会自动推导为 <n>.c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)

  2. 编译C++程序的隐含规则。

    <n>.o 的目标的依赖目标会自动推导为 <n>.cc 或是 <n>.C ,并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CFLAGS) 。(建议使用 .cc 作为C++源文件的后缀,而不是 .C

  3. 编译Pascal程序的隐含规则。

    <n>.o 的目标的依赖目标会自动推导为 <n>.p ,并且其生成命令是 $(PC) –c $(PFLAGS)

  4. 编译Fortran/Ratfor程序的隐含规则。

    <n>.o 的目标的依赖目标会自动推导为 <n>.r<n>.F<n>.f ,并且其生成命令是:

    • .f $(FC) –c $(FFLAGS)
    • .F $(FC) –c $(FFLAGS) $(CPPFLAGS)
  5. 预处理Fortran/Ratfor程序的隐含规则。

    <n>.f 的目标的依赖目标会自动推导为 <n>.r<n>.F 。这个规则只是转换Ratfor 或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是:

    • .F $(FC) –F $(CPPFLAGS) $(FFLAGS)
    • .r $(FC) –F $(FFLAGS) $(RFLAGS)
  6. 编译Modula-2程序的隐含规则。

    <n>.sym 的目标的依赖目标会自动推导为 <n>.def ,并且其生成命令是: $(M2C) $(M2FLAGS) $(DEFFLAGS)<n>.o 的目标的依赖目标会自动推导为 <n>.mod ,并且其生成命令是: $(M2C) $(M2FLAGS) $(MODFLAGS)

  7. 汇编和汇编预处理的隐含规则。

    <n>.o 的目标的依赖目标会自动推导为 <n>.s ,默认使用编译器 as ,并且其生成命令是: $ (AS) $(ASFLAGS)<n>.s 的目标的依赖目标会自动推导为 <n>.S ,默认使用C预编译器 cpp ,并且其生成命令是: $(AS) $(ASFLAGS)

  8. 链接Object文件的隐含规则。

    <n> 目标依赖于 <n>.o ,通过运行C的编译器来运行链接程序生成(一般是 ld ),其生成命令是: $(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS) 。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:

    1
    x : y.o z.o

    并且 x.cy.cz.c 都存在时,隐含规则将执行如下命令:

    1
    2
    3
    4
    5
    6
    7
    cc -c x.c -o x.o
    cc -c y.c -o y.o
    cc -c z.c -o z.o
    cc x.o y.o z.o -o x
    rm -f x.o
    rm -f y.o
    rm -f z.o

    如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。

  9. Yacc C程序时的隐含规则。

    <n>.c 的依赖文件被自动推导为 n.y (Yacc生成的文件),其生成命令是: $(YACC) $(YFALGS) 。(“Yacc”是一个语法分析器,关于其细节请查看相关资料)

  10. Lex C程序时的隐含规则。

    <n>.c 的依赖文件被自动推导为 n.l (Lex生成的文件),其生成命令是: $(LEX) $(LFALGS) 。(关于“Lex”的细节请查看相关资料)

  11. Lex Ratfor程序时的隐含规则。

    <n>.r 的依赖文件被自动推导为 n.l (Lex生成的文件),其生成命令是: $(LEX) $(LFALGS)

  12. 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。

    <n>.ln (lint生成的文件)的依赖文件被自动推导为 n.c ,其生成命令是: $(LINT) $(LINTFALGS) $(CPPFLAGS) -i 。对于 <n>.y<n>.l 也是同样的规则。

隐含规则使用的变量

关于命令的变量

  • AR : 函数库打包程序。默认命令是 ar
  • AS : 汇编语言编译程序。默认命令是 as
  • CC : C语言编译程序。默认命令是 cc
  • CXX : C++语言编译程序。默认命令是 g++
  • CO : 从 RCS文件中扩展文件程序。默认命令是 co
  • CPP : C程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
  • FC : Fortran 和 Ratfor 的编译器和预处理程序。默认命令是 f77
  • GET : 从SCCS文件中扩展文件的程序。默认命令是 get
  • LEX : Lex方法分析器程序(针对于C或Ratfor)。默认命令是 lex
  • PC : Pascal语言编译程序。默认命令是 pc
  • YACC : Yacc文法分析器(针对于C程序)。默认命令是 yacc
  • YACCR : Yacc文法分析器(针对于Ratfor程序)。默认命令是 yacc –r
  • MAKEINFO : 转换Texinfo源文件(.texi)到Info文件程序。默认命令是 makeinfo
  • TEX : 从TeX源文件创建TeX DVI文件的程序。默认命令是 tex
  • TEXI2DVI : 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是 texi2dvi
  • WEAVE : 转换Web到TeX的程序。默认命令是 weave
  • CWEAVE : 转换C Web 到 TeX的程序。默认命令是 cweave
  • TANGLE : 转换Web到Pascal语言的程序。默认命令是 tangle
  • CTANGLE : 转换C Web 到 C。默认命令是 ctangle
  • RM : 删除文件命令。默认命令是 rm –f

关于命令参数的变量

如果没有指明默认值,则为空

  • ARFLAGS : 函数库打包程序AR命令的参数。默认值是 rv
  • ASFLAGS : 汇编语言编译器参数。(当明显地调用 .s.S 文件时)
  • CFLAGS : C语言编译器参数
  • CXXFLAGS : C++语言编译器参数
  • COFLAGS : RCS命令参数
  • CPPFLAGS : C预处理器参数。( C 和 Fortran 编译器也会用到)
  • FFLAGS : Fortran语言编译器参数
  • GFLAGS : SCCS “get”程序参数
  • LDFLAGS : 链接器参数。(如: ld
  • LFLAGS : Lex文法分析器参数。
  • PFLAGS : Pascal语言编译器参数
  • RFLAGS : Ratfor 程序的Fortran 编译器参数
  • YFLAGS : Yacc文法分析器参数

隐含规则链

有些时候,一个目标可能被一系列的隐含规则所作用。

例如,一个 .o 的文件生成,可能会是先被 Yacc的[.y]文件生成 .c ,然后再被C的编译器生成:

  • 如果文件 .c 存在,那么就直接调用C的编译器的隐含规则
  • 如果没有 .c 文件,但有一个 .y 文件,那么Yacc的隐含规则会被调用,生成 .c 文件,然后,再调用C编译的隐含规则最终由 .c 生成 .o 文件,达到目标

我们把这种 .c 的文件(或是目标),叫做中间目标

  • 除非中间的目标不存在,才会引发中间规则
  • 只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以 rm -f 删除

中间目标

  • 显式说明一个文件或是目标是中间目标:.INTERMEDIATE : mid
  • 阻止make自动删除中间目标:.SECONDARY : sec
  • 在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况

模式规则

使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有 % 字符;在依赖目标中同样可以使用 % ,只是依赖目标中的 % 的取值,取决于其目标。

注意:

  • % 的展开发生在变量和函数的展开之后
  • 变量和函数的展开发生在make载入Makefile时
  • 模式规则中的 % 展开发生在运行时

模式规则示例

1
2
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
  • 把所有的 .c 文件都编译成 .o 文件

1
2
%.tab.c %.tab.h: %.y
bison -d $<
  • 这条规则告诉make把所有的 .y 文件都以 bison -d <n>.y 执行,然后生成 <n>.tab.c<n>.tab.h 文件(其中, <n> 表示一个任意字符串)
  • 如果我们的执行程序 foo 依赖于文件 parse.tab.oscan.o ,并且文件 scan.o 依赖于文件 parse.tab.h ,如果 parse.y 文件被更新了,那么根据上述的规则, bison -d parse.y 就会被执行一次,于是, parse.tab.oscan.o 的依赖文件就齐了。(假设, parse.tab.oparse.tab.c 生成,和 scan.oscan.c 生成,而 fooparse.tab.oscan.o 链接生成,而且 foo 和其 .o 文件的依赖关系也写好,那么,所有的目标都会得到满足)

自动化变量

把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完

  • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合
  • $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o$@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的
  • $? : 所有比目标新的依赖目标的集合。以空格分隔
  • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份
  • $+ : 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标
  • $* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值

当你希望只对更新过的依赖文件进行操作时, $? 在显式规则中很有用,例如,假设有一个函数库文件叫 lib ,其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是:

1
2
lib : foo.o bar.o lose.o win.o
ar r lib $?

在上述所列出来的自动量变量中。四个变量( $@$<$%$* )在扩展时只会有一个文件,而另三个的值是一个文件列表。

这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上 DF 字样:

  • $(@D):表示 $@ 的目录部分(不以斜杠作为结尾),如果 $@ 值是 dir/foo.o ,那么 $(@D) 就是 dir ,而如果 $@ 中没有包含斜杠的话,其值就是 . (当前目录)
  • $(@F):表示 $@ 的文件部分,如果 $@ 值是 dir/foo.o ,那么 $(@F) 就是 foo.o$(@F) 相当于函数 $(notdir $@)
  • $(*D), $(*F):和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, $(*D) 返回 dir ,而 $(*F) 返回 `foo``
  • ``$(%D), $(%F):分别表示了函数包文件成员的目录部分和文件部分。这对于形同 archive(member)形式的目标中的member` 中包含了不同的目录很有用。
  • $(<D), $(<F):分别表示依赖文件的目录部分和文件部分。
  • $(^D), $(^F):分别表示所有依赖文件的目录部分和文件部分。(无相同的)
  • $(+D), $(+F):分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
  • $(?D), $(?F):分别表示被更新的依赖文件的目录部分和文件部分

重载内建隐含规则

重新构造和内建隐含规则不同的命令,如:

1
2
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

取消内建的隐含规则,只要不在后面写命令就行。如:

1
%.o : %.s

隐含规则搜索算法

比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。如果目标是 archive(member) 的函数库文件模式,那么这个算法会被运行两次,第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把 member 当作T来搜索。

  1. 把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是 src/foo.o ,那么,D就是 src/ ,N就是 foo.o
  2. 创建所有匹配于T或是N的模式规则列表。
  3. 如果在模式规则列表中有匹配所有文件的模式,如 % ,那么从列表中移除其它的模式。
  4. 移除列表中没有命令的规则。
  5. 对于第一个在列表中的模式规则:
    1. 推导其“茎”S,S应该是T或是N匹配于模式中 % 非空的部分。
    2. 计算依赖文件。把依赖文件中的 % 都替换成“茎”S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
    3. 测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫“理当存在”)
    4. 如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
  6. 如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
    1. 如果规则是终止规则,那就忽略它,继续下一条模式规则。
    2. 计算依赖文件。(同第5步)
    3. 测试所有的依赖文件是否存在或是理当存在。
    4. 对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
    5. 如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
    6. 如果没有隐含规则可以使用,查看 .DEFAULT 规则,如果有,采用,把 .DEFAULT 的命令给T使用。

一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。

使用make更新函数库文件

  • 函数库文件也就是对Object文件(程序编译的中间文件)的打包文件
  • 在Unix下,一般是由命令 ar 来完成打包工作

函数库文件的成员

一个函数库文件由多个文件组成,指定函数库文件及其组成:

1
archive(member)

从而在 ar 命令中,如此使用:

1
2
foolib(hack.o) : hack.o kludge.o
ar cr foolib(hack.o kludge.o)

函数库成员的隐含规则

当make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 a(m) 形式的,其会把目标变成 (m) 。于是,如果我们的成员是 %.o 的模式定义,并且如果我们使用 make foo.a(bar.o) 的形式调用Makefile时,隐含规则会去找 bar.o 的规则,如果没有定义 bar.o 的规则,那么内建隐含规则生效,make会去找 bar.c 文件来生成 bar.o ,如果找得到的话,make执行的命令大致如下:

1
2
3
cc -c bar.c -o bar.o
ar r foo.a bar.o
rm -f bar.o