target ... : prerequisites ...command......
makefile 的核心:
- prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
一个示例
edit : main.o kbd.o command.o display.o \insert.o search.o files.o utils.occ -o edit main.o kbd.o command.o display.o \insert.o search.o files.o utils.omain.o : main.c defs.hcc -c main.ckbd.o : kbd.c defs.h command.hcc -c kbd.ccommand.o : command.c defs.h command.hcc -c command.cdisplay.o : display.c defs.h buffer.hcc -c display.cinsert.o : insert.c defs.h buffer.hcc -c insert.csearch.o : search.c defs.h buffer.hcc -c search.cfiles.o : files.c defs.h buffer.h command.hcc -c files.cutils.o : utils.c defs.hcc -c utils.cclean :rm edit main.o kbd.o command.o display.o \insert.o search.o files.o utils.o
\换行- 编译
make - 清除
make clean - 命令以
tab开头 - make 不管 command 是否执行成功,只管依赖关系是否完整,否则报错
变量
使用:
objects = main.o kbd.o command.o display.o \insert.o search.o files.o utils.oedit : $(objects)cc -o edit $(objects)main.o : main.c defs.hcc -c main.ckbd.o : kbd.c defs.h command.hcc -c kbd.ccommand.o : command.c defs.h command.hcc -c command.cdisplay.o : display.c defs.h buffer.hcc -c display.cinsert.o : insert.c defs.h buffer.hcc -c insert.csearch.o : search.c defs.h buffer.hcc -c search.cfiles.o : files.c defs.h buffer.h command.hcc -c files.cutils.o : utils.c defs.hcc -c utils.cclean :rm edit $(objects)
自动推导
不必写相同命令:
objects = main.o kbd.o command.o display.o \insert.o search.o files.o utils.oedit : $(objects)cc -o edit $(objects)# 省略 cc -c xxxmain.o : defs.hkbd.o : defs.h command.hcommand.o : defs.h command.hdisplay.o : defs.h buffer.hinsert.o : defs.h buffer.hsearch.o : defs.h buffer.hfiles.o : defs.h buffer.h command.hutils.o : defs.h.PHONY : cleanclean :rm edit $(objects)
清空目标文件的规则
.PHONY : cleanclean :-rm edit $(objects)
-许某些文件出现问题,但不要管,继续做后面的事-f可以指定 makefile 文件
引用其它的Makefile
被包含的文件会原模原样的放在当前文件的包含位置:
include <filename># 例子include foo.make *.mk $(bar)-include <filename>
书写规则
make 默认第一个规则的第一个目标是最终目标.
targets : prerequisitescommand...
- make会以UNIX的标准Shell,也就是 /bin/sh 来执行命令
在规则中使用通配符
make 支持三个通配符:
- *
- ?
~用户的 home 目录
例子:
clean:rm -f *.oprint: *.clpr -p $?touch printobjects = *.o# 展开# objects := $(wildcard *.o)objects := $(patsubst %.c,%.o,$(wildcard *.c))foo : $(objects)cc -o foo $(objects)
$?自动化变量objects的值就是*.o, 并没有展开- 使用
$(wildcard *.o)展开
- 使用
文件搜寻
把一个路径告诉make,让make在自动去找依赖文件.
- VPATH 环境变量, 当 make 在当前目录下找不到依赖文件时, 到 VPATH 中寻找
VPATH = src:../headers
- vpath 关键字, 很灵活
vpath <pattern> <directories> # 符合 pattern 的文件的搜索目录vpath %.h ../headersvpath <pattern> # 清除符合 pattern 的文件的搜索目录vpath # 清除已设置的搜索目录vpath %.c foo:bar # 在 foo, bar 中找 %.cvpath % blish
伪目标
.PHONY : cleanclean :rm *.o temp
.PHONY避免伪目标与文件重名
一次性生成所有目标的推荐写法:
- prog1, 2, 3 之间没有依赖关系, 且最终要生成3个文件, 所以应该告诉 make 最终产物是
伪的
all : prog1 prog2 prog3.PHONY : allprog1 : prog1.o utils.occ -o prog1 prog1.o utils.oprog2 : prog2.occ -o prog2 prog2.oprog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.o
伪目标也可以成为依赖:
.PHONY : cleanall cleanobj cleandiffcleanall : cleanobj cleandiffrm programcleanobj :rm *.ocleandiff :rm *.diff
多目标
bigoutput littleoutput : text.ggenerate text.g -$(subst output,,$@) > $@# 等价于bigoutput : text.ggenerate text.g -big > bigoutputlittleoutput : text.ggenerate text.g -little > littleoutput
$@类似 for 中的 a[i], 指目标中的某一个, 按顺序取出
静态模式
<targets ...> : <target-pattern> : <prereq-patterns ...><commands>...
有点像是根据 targets 名称倒推 prereq 名称.
例子:
objects = foo.o bar.oall: $(objects)$(objects): %.o: %.c$(CC) -c $(CFLAGS) $< -o $@# 等价于foo.o : foo.c$(CC) -c $(CFLAGS) foo.c -o foo.obar.o : bar.c$(CC) -c $(CFLAGS) bar.c -o bar.o
$<第一个依赖文件
自动生成依赖性
该功能依赖编译器, 编译器将自动分析查找依赖, 输出 makefile 的依赖关系:
cc -M main.c# makefilemain.o : main.c defs.h
书写命令
- 默认使用 /bin/sh
显示命令
@不显示该命令-n或--just-print只显示命令而不执行-s--silent--quiet不显示所有命令
命令执行
exec:cd /home/hchen; pwd # 这样才能 cd 到指定目录
命令出错
- 当命令的状态码不为0时, 说明执行出错, make 会中断执行
- 使用
-来忽略命令的错误并继续执行
clean:-rm -f *.o
其它忽略错误的方法:
-i--ignore-errors- 规则以
.IGNORE为目标
命令出错时终止执行:
-k--keep-going如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则
嵌套执行 make
# 执行子目录的 Makefilesubsystem:cd subdir && $(MAKE)# 等价写法subsystem:$(MAKE) -C subdir
- 传递变量到下级Makefile中
export <variable ...>;
- 不想让某些变量传递到下级Makefile中
unexport <variable ...>;
- 只写一个 export 将传递所有变量
export
默认总是传递的系统环境变量:
- SHELL
- MAKEFLAGS
不往下传递的参数:
-C-f-h-o-W
如果你不想往下层传递参数:
subsystem:cd subdir && $(MAKE) MAKEFLAGS=
在嵌套执行中有用的参数:
w--print-directory打印目前的工作目录
# 进入目录make: Entering directory `/home/hchen/gnu/make'.# 离开目录make: Leaving directory `/home/hchen/gnu/make'
当指定 -C 时, -w 会自动打开. 如果参数中有 -s --no-print-directory, 那么 -w 总是失效.
定义命令包
重用相同命令序列:
define run-yaccyacc $(firstword $^)mv y.tab.c $@endef# 使用命令包foo.c : foo.y$(run-yacc)
使用变量
Makefile 中的变量与 C/C++ 中的宏类似.
变量名规则:
- 不能含有
:#=空白字符 - 大小写敏感
- 建议使用
MakeFlags形式
变量的基础
- 声明时要赋初值
- 使用变量
$()${} - 使用真实的
$用$$
变量可以使用在很多地方:
objects = program.o foo.o utils.oprogram : $(objects)cc -o program $(objects)$(objects) : defs.h# 就像是字符串替换foo = cprog.o : prog.$(foo)$(foo)$(foo) -$(foo) prog.$(foo)
变量中的变量
使用变量为变量赋值:
- 等号右侧的变量的定义可以在文件的任何一处, 即右侧变量不一定要已定义好
foo = $(bar)bar = $(ugh)ugh = Huh?all:echo $(foo)
- 使用
=方式的缺点- 递归问题
- 导致 make 运行慢
- 使得 wildcard 和 shell 发生不可预知的错误
使用 := 操作符:
- 这种方式只能使用前面已定义的变量
x := fooy := $(x) barx := later
复杂的使用:
- MAKELEVEL: 记录嵌套使用 make 的层数
ifeq (0,${MAKELEVEL})cur-dir := $(shell pwd)whoami := $(shell whoami)host-type := $(shell arch)MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}endif
定义值为空格的变量的方法:
nullstring :=space := $(nullstring) # end of the line# 原理是)到#之间存在一个空格, 会将该空格赋给 space# dir 的值的末尾有四个空格dir := /foo/bar # directory to put the frobs in
?= 的用法:
FOO ?= bar
如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)FOO = barendif
变量高级用法
变量值替换:
- 将以 a 字串结尾替换成以 b 字串结尾
$(var:a=b)# 或${var:a=b}# 例foo := a.o b.o c.obar := $(foo:.o=.c)# $(bar) 的值为 a.c b.c c.c# 使用静态模式foo := a.o b.o c.obar := $(foo:%.o=%.c)
把变量的值当成变量:
x = yy = za := $($(x))# $(a) 的值为 z
结合 “在变量定义中使用变量”:
x = $(y)y = zz = Helloa := $($(x))# $(a) 的值为 Hello
加上函数:
- subst 用
2替换1,$(subst 1,2,variable1)的结果是 variable2
x = variable1variable2 := Helloy = $(subst 1,2,$(x))z = ya := $($($(z)))# $(a) 的值为 Hello
把变量的值当成变量, 可用于组成变量名:
first_second = Helloa = firstb = secondall = $($a_$b)
结合变量值替换:
a_objects := a.o b.o c.o1_objects := 1.o 2.o 3.o# $(a1) 的值假设取 a 或 1sources := $($(a1)_objects:.o=.c)
结合函数, 条件:
ifdef do_sortfunc := sortelsefunc := stripendifbar := a d b g q cfoo := $($(func) $(bar))
“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:
dir = foo$(dir)_sources := $(wildcard $(dir)/*.c)define $(dir)_printlpr $($(dir)_sources)endef# 三个变量: dir, foo_sources, foo_print
追加变量值
+=:
objects = main.o foo.o bar.o utils.oobjects += another.o# 模拟 +=objects = main.o foo.o bar.o utils.oobjects := $(objects) another.o
- 如果变量之前没有定义,
+=会变成= - 如果变量之前有定义,
+=会继承前一次的操作符:
# := 情况variable := valuevariable += more# 等价于variable := valuevariable := $(variable) more# = 情况variable = valuevariable += more# 等价于variable = valuevariable = $(variable) more
override 指示符
如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override <variable>; = <value>;override <variable>; := <value>;override <variable>; += <more text>;override define foobarendef
多行变量
可以有换行符.
define two-linesecho fooecho $(bar)endef
- define 的工作方式于
=相同
如果你用define定义的命令变量中没有以 Tab 键开头,那么make 就不会把其认为是命令。
环境变量
- Makefile 可以覆盖环境变量 (同名)
目标变量
<target ...> : <variable-assignment>;<target ...> : overide <variable-assignment>
或是?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。
prog : CFLAGS = -gprog : prog.o foo.o bar.o$(CC) $(CFLAGS) prog.o foo.o bar.oprog.o : prog.c$(CC) $(CFLAGS) prog.cfoo.o : foo.c$(CC) $(CFLAGS) foo.cbar.o : bar.c$(CC) $(CFLAGS) bar.c
不管全局的 $(CFLAGS) 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g
依赖链上的都是局部.
模式变量
把变量定义在符合这种模式的所有目标上。
给所有以 .o 结尾的目标定义目标变量:
%.o : CFLAGS = -O
模式变量的语法:
<pattern ...>; : <variable-assignment>;<pattern ...>; : override <variable-assignment>;
使用条件判断
libs_for_gcc = -lgnunormal_libs =foo: $(objects)ifeq ($(CC),gcc)$(CC) -o foo $(objects) $(libs_for_gcc)else$(CC) -o foo $(objects) $(normal_libs)endif
语法
<conditional-directive><text-if-true>endif<conditional-directive><text-if-true>else<text-if-false>endif
条件关键字:
ifeq (<arg1>, <arg2>)ifeq '<arg1>' '<arg2>'ifeq "<arg1>" "<arg2>"ifeq "<arg1>" '<arg2>'ifeq '<arg1>' "<arg2>"ifeq ($(strip $(foo)),)<text-if-empty>endififneq (<arg1>, <arg2>)ifneq '<arg1>' '<arg2>'ifneq "<arg1>" "<arg2>"ifneq "<arg1>" '<arg2>'ifneq '<arg1>' "<arg2>"ifdef <variable-name>
- ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置
bar =foo = $(bar)ifdef foofrobozz = yes # 这个分支生效elsefrobozz = noendiffoo =ifdef foofrobozz = yeselsefrobozz = no # 这个分支生效endif
ifndef <variable-name>
使用函数
函数的调用语法
$(<function> <arguments>)${<function> <arguments>}
comma:= ,empty:=space:= $(empty) $(empty)foo:= a b cbar:= $(subst $(space),$(comma),$(foo))# $(bar) 的值为 a,b,c
函数列表:
make 的运行
make的退出码
- 0: 成功
- 1: 出错
- 2: 使用了
-q选项, 且 make 使得一些目标不需要更新
指定Makefile
-f--file--makefile
make –f hchen.mk
可以多次指定 -f, 所有指定的文件都传给 make 执行.
指定目标
all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。clean:这个伪目标功能是删除所有被make创建的文件。
install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
print:这个伪目标的功能是例出改变过的源文件。
tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件。
dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。
check和test:这两个伪目标一般用来测试makefile的流程。
