# Makefile 介绍
# Makefile 的规则
<target> : <prerequisites>
[tab]  <commands>
    ...
 2
3
target目标,可以是执行文件,也可以是一个标签。prerequisites生成target所依赖的文件或target。command该target要执行的命令。
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
# Makefile 里有什么?
Makefile 里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由 Makefile 的书写者明显指出要 生成的文件、文件的依赖文件和生成的命令。
 - 隐晦规则。由于我们的 make 有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile,这是由 make 所支持的。
 - 变量的定义。在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像你 C 语 言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
 - 文件指示。其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像 C 语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像 C 语言中的预编译 #if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
 - 注释。Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用 # 字符,这个就像 C/C++
中的 // 一样。如果你要在你的 Makefile 中使用 # 字符,可以用反斜杠进行转义,如:
\#。 最后,还值得一提的是,在 Makefile 中的命令,必须要以 Tab 键开始。 
# 引用其他 Makefile
include <filename>
filename 可以是当前操作系统 Shell 的文件模式(可以包含路径和通配符)。
make 命令开始时,会找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。
-I 选项指定查找目录。
# 环境变量
如果你的当前环境中定义了环境变量 MAKEFILES ,那么,make 会把这个变量中的值做一个类似于 include 的动作。
建议不要使用。
# Make 的工作方式
GNU 的 make 工作时的执行步骤如下:
- 读入所有的 Makefile。
 - 读入被 include 的其它 Makefile。
 - 初始化文件中的变量。
 - 推导隐晦规则,并分析所有规则。
 - 为所有的目标文件创建依赖关系链。
 - 根据依赖关系,决定哪些目标要重新生成。
 - 执行生成命令。
 
# 书写规则
规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
一般来说,定义在 Makefile中的第一条规则中的目标将被确立为最终的目标。
# 规则语法
<target> : <prerequisites>
[tab]  <commands>
    ...
 2
3
target目标,可以是执行文件,也可以是一个标签。prerequisites生成target所依赖的文件或target。command该target要执行的命令。
如果命令太长,你可以使用反斜杠(\ )作为换行符。
# 通配符
make 支持三个通配符:* ,? 和 ~ 。
~ 表示用户的 $HOME 目录。
如果要将通配符用于变量中,需要这样写:
objects := $(wildcast *.o)
列出一确定文件夹中的所有 .c 文件:
objects := $(wildcard *.c)
列出文件对应的 .o 文件:
$(patsubst %.c %.o, $(wildcard, *.c))
objects := $(patsubst %.c,%.o, $(wildcard *.c))
foo: $(objects)
    cc -o foo $(objects)
 2
3
# 文件搜寻
当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉 make,让 make 在自动去找。
Makefile 文件中的特殊变量 VPATH 就是完成这个功能的,如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。
VPATH = src:../headers
上面的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜索。目录由“冒号”分隔。
vpath 关键字
vpath <pattern> <directories>
为符合模式<pattern> 的文件指定搜索目录 <directories>。
vpath <pattern>
清除符合模式<pattern> 的文件的搜索目录。
vpath 清除所有设置好的文件搜索目录。
vpath 使用方法中的 <pattern> 需要包含 % 字符。% 的意思是匹配零或若干字符
vpath %.h ../headers
该语句表示,要求 make 在“../headers”目录下搜索所有以 .h 结尾的文件。
# 伪目标
“伪目标”并不是一个文件,只是一个标签,需要显式指明才能让其生效。为了避免和文件重名,需要使用特殊标记:
.PHONY: clean
clean:
    rm  -f *.o temp
 2
3
可以利用伪目标一次性生成多个目标:
.PHONY : all
all : prog1 prog2 prog3
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
 2
3
4
5
6
7
8
9
10
11
伪目标也可以是依赖:
.PHONY: cleanall cleanobj cleandiff
cleanall: cleanobj cleandiff
    rm program
cleanobj:
    rm *.o
cleandiff:
    rm *.diff
 2
3
4
5
6
7
8
9
10
# 多目标
Makefile 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。
bigoutput littleoutput: text.g
    generate text.g -$(subst output,,$@) > $@
 2
等价于:
bigoutoutput: text.g
    generate text.g -big > bigoutput
littleoutput: text.g
    generate text.g -little > littleoutput
 2
3
4
5
# 静态模式
静态目标可以更容易地定义多目标的规则。
<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>
    ...
 2
3
targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-pattern 是指明了 targets 的模式,也就是的目标集模式。
prereq-patterns 是目标的依赖模式,它对 target-pattern 形成的模式再进行一次依赖目标的定义。
例子:
CC = gcc
CFLAGS = -Wall -Werror -g -O0
OBJS := foo.o bar.o
all: $(OBJS)
$(OBJS): %.o : %.c
    $(CC) $(CFLAGS) -o $@ -c $<
 2
3
4
5
6
7
8
9
%.o 指明我们的目标从 OBJS 中获取所有以 .o 结尾的目标,%.c 则取 % 匹配的内容,加上 .c 的后缀。
依赖目标就变为 foo.c bar.c
上面例子等价于
CC = gcc
CFLAGS = -Wall -Werror -g -O0
foo.o : foo.c
    $(CC) -o $@ $(FLAGS) -c $<
bar.o : bar.c
    $(CC) -o $@ $(FLAGS) -c $<
 2
3
4
5
6
7
8
例子二:
files = foo.elc bar.o lose.o
$(filter %.o, $(files)) : %.o : %.c
    $(CC) -c $(CFLAGS) $< -o $@
$(filter %.o, $(files)) : %.o : %.c
    emacs -f batch-byte-compile $<
 2
3
4
5
6
7
$(filter %.o $(files)) 表明调用 makefile 的 filter 函数,过滤 $(files) 集。只要其中模式为 %.o 的内容。
# 自动生成依赖性
太复杂,跳过,以后用到再学。
%.d: %.c
    @set -e; rm -f $@; \
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$
 2
3
4
5
这个规则的意思是,所有的 .d 文件依赖于 .c 文件,rm -f $@ 的意思是删除所有的目标,也就是
.d 文件,第二行的意思是,为每个依赖文件 $< ,也就是 .c 文件生成依赖文件,$@ 表示模式 %.d 文件,
如果有一个 C 文件是 name.c,那么 % 就是 name ,$$$$ 意为一个随机编号,第二行生成的文件有可能
是“name.d.12345”,第三行使用 sed 命令做了一个替换
这个模式要做的事就是在编译器生成的依赖关系中加入 .d 文件的依赖,即把依赖关系
main.o : main.c defs.h
转成
main.o main.d : main.c defs.h
# 书写命令
@ 屏蔽回显。
-n 参数,只打印,不执行。
-s silent 只执行不打印。
# 命令执行
如果需要上一条的命令应用在下一条命令时,写在同一行,并用 ; 分隔。
exec:
    cd /home/hchen; pwd
 2
# 命令出错
命令执行完后,make 会检测每个命令的返回码。
为了忽略命令的出错,可在命令行前加上一个 - 号。
clean:
    -rm -f *.o
 2
# 嵌套执行 Make
举例:
subsystem:
    $(MAKE) -C subdir
 2
表示进入 subdir 目录,执行 make 命令。
传递变量给下级的 Makefile。
export <variable ...>
variable = value
export variable
 2
传递所有变量,只要一个 export 就行。
有两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管你是否 export,其总是要传递到下层 Makefile 中
如果我们在执行 make 时,有参数,那么 MAKEFLAGS 变量会将这些参数传递到下层 Makefile,这是系统级的环境变量。
但是 make 命令中的有几个参数并不往下传递,它们是 -C , -f , -h, -o 和 -W
如果不想往下传参数,可以这样写:
subsystem:
    cd substr && $(MAKE) MAKEFLAGS=
 2
# 定义命令包
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
    $(run-yacc)
 2
3
4
5
6
7
# 使用变量
类似于 C 中的宏。
自定义变量建议不要全大写,首字母大写,避免和系统变量冲突。
# 变量基础
使用变量需要用 $() 包裹。
Objects = program.o foo.o utils.o
program : $(Objests)
    cc -o program $(Objects)
$(Objects) : defs.h
 2
3
4
5
6
例如:
foo = c
prog.o : prog.$(foo)
    $(foo)$(foo) -$(foo) prog.$(foo)
 2
3
4
# 变量中的变量
延时变量表示:使用该变量的时候,才展开该变量,并确定该变量的值
立即变量表示:定义的时候就已经确定了该变量的值
= 递归赋值。
:= 赋值。
碰到变量需要展开,如果是=,则先往后面查找,如果是:=,则先往前面查找
?= 如果没有被赋值才赋值
# 变量的高级用法
变量值的替换
$(var:a=b) 把变量 "var" 中所有以 'a' 字符串结尾的 'a' 替换为 'b' 。
foo := a.o b.o c.o
bar := $(foo:.o=.c)
 2
把变量的值当作变量:
$($(x))
静态模式替换:
foo := a.o b.o c.o
bar := $(foo: %.o=%.c)
 2
# 追加变量值
+= 以:= 作为赋值符。
# 函数
函数调用:
$(<function> <arguments>) 参与用逗号分隔。
# 字符串处理函数
subst
$(sbust <from>,<to>,<text>)
$(subst ee,EE,feet on the street)
patsubst
$(patsubst <pattern>,<replacement>,<text>)
$(patsubst %.c,%.o,x.c bar.c )
# 文件名操作函数
dir
$(dir <names...>)
$(dir src/foo.c hacks) 返回值是 src/ ./。
notdir
$(not dir src/foo.c hacks) 返回值是 foo.c hacks。
wildcard
$(patsubst %.c,%.o,$(wildcard *.c)),首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。
# Make 的运行
-f 指定 make。
# 常见伪目标:
- all:所有目标。
 - clean:清理。
 - install;安装编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
 - print:列出所有改变过的源文件。
 - tar:把源代码打包备份成一个 tar 文件。
 - dist:创建一个压缩文件,一般是
xz文件。 - TAGS:更新所有的目标,以备完整编译使用。
 - check 和 test,测试用。
 
# 选项
-n打印不执行。-B重编译所有目标。-C指定读取 makefile 的目录。如果有多个“-C”参数,make 的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于“make -C hchen/test/prog”。
# 隐含规则
例如,把 .c 文件编译成 .o 文件这一规则,你根本就不用写出来,make 会自动推导出这种规则,并生成我们需要的 .o 文件。
make 调用的隐含规则是,把 .o 的目标的依赖文件置成 .c ,并使用 C 的编译命令 cc –c $(CFLAGS) foo.c 来生成 foo.o 的目标。
隐含规则一览:
- 编译 C 程序的隐含规则。
<n>.o的目标的依赖目标会自动推导为<n>.c,并且其生成命令是$(CC) –c $(CPPFLAGS) $(CFLAGS) - 编译 C++ 程序的隐含规则。
<n>.o的目标的依赖目标会自动推导为<n>.cc或是<n>.C,并且其生成命令是$(CXX) –c $(CPPFLAGS) $(CXXFLAGS)。(建议使用 .cc 作为 C++ 源文件的后缀,而不是 .C ) 
# 命令变量
- 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 文法分析器参数。
 
# 自动化变量
$@: 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@ 就是匹配于目标中模式定义的集合。$%: 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o)- 那么,$%就是 bar.o ,$@ 就是 foo.a 。如果目标不是函数库文件(Unix 下是 .a ,Windows下是 .lib ),那么,其值为空。$<: 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?: 所有比目标新的依赖目标的集合。以空格分隔。$^: 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。$+: 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*: 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是a.%.b ,那么,$* 的值就是 dir/foo 。这个变量对于构造有关联的文件名是比较有效。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是 make 所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是 make 所能识别的后缀名,所以,$* 的值就是 foo 。这个特性是 GNU make 的,很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么 $* 就是空值。
打包:
lib : foo.o bar.o lose.o win.o
    ar r lib $?
 2
四个变量($@ 、$< 、$% 、$* )在扩展时只会有一个文件,另外三个是文件列表。
$(@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)分别表示被更新的依赖文件的目录部分和文件部分。
最后想提醒一下的是,对于 $< ,为了避免产生不必要的麻烦,我们最好给 $ 后面的那个特定字符
都加上圆括号,比如,$(<) 就要比 $< 要好一些。
# 参考
陈皓-跟我一起写 Makefile (opens new window)
自动生成依赖关系
foreach 函数。
filter 函数
filter-out 函数。
ifneq
通过命令自动生成对头文件的依赖,将生成的依赖自动包含进 Makefile 中, 头文件改动后,自动确认需要重新编译的文件。
gcc -M des 获取 des 的完整依赖关系
gcc -MM des 获取目标des 的部分依赖关系(-E 仅对依赖关系做初步解析)
gcc -MM 命令可以自动生成源文件对头文件的依赖关系,且剔除掉库里面的头文件
makefile 中的 include 关键字
类似 C 中的 include,将其他文件的内容原封不动的搬入当前文件
include 是按次序来载入文件,最先载入的 .d 文件中的目标会成为默认目标。
GNU Make手册建议使用正则表达式显式创建依赖文件
GNU make 官方手册建议写法:
all: main
main: main.o stack.o maze.o
	gcc $^ -o $@
clean:
	-rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
-include $(sources:.c=.d)
%.d: %.c
	set -e; rm -f $@; \
	$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$
clean:
    rm -rf $(TARGET) *.o *.d *.d.*
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
自动处理头文件的依赖关系 (opens new window) Makefile-自动生成依赖-示例分析 (opens new window)
objs := Hello.o Fun.o
Hello : $(objs)
gcc -o $@ $^
#判断是否存在依赖文件
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
#包含依赖文件
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o:%.c
#编译.c文件,并且把依赖的文件写入.$@.d
    gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
    rm -rf *.o Hello
#增加伪目标
.PHONY : clean
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 自动以最大线程编译
sudo make -j $(nproc)