Makefile里有什么
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了,如何生成一个或多个目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写Makefile,这是由make所支持的。
- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,(1)在一个Makefile中引用另一个Makefile,(2)根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样,(3)定义一个多行的命令。
- 注释。注释是用“#”字符,如果你要在你的Makefile中使用“#”字符,可以用反斜杠进行转义,如:“#”。
规则
关于目标与依赖新旧比较
main:
cmd
如果main不存在,则永远执行。
如果main存在,则不执行。
.PHONY: clean
clean:
cmd
开始目录下还没有clean文件,每次clean都能执行。
突然目录下生成了一个clean文件,导致clean不执行了。
我的本意就没打算把clean作为一个文件,它就是纯目标,那就加到.PHONY后面,表示它是一个伪目标,这样clean每次都会执行。
main: main.c
cmd
如果main不存在,则默认比依赖旧,会执行。
如果main存在,则与依赖比较新旧。
自动推导
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么 whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile 再也不用写得这么复杂。
简写Makefile:
main: foo.o
执行make:
$ make
cc -c -o foo.o foo.c
cc main.c foo.o -o main
引用其他的Makefile
include mks/a.mk b.mk
命令
命令前缀
@rm -f *.o #不会输出命令
rm -f *.o #会输出命令
-rm -f *.o #忽略出错命令
嵌套执行make
export VERSION = 1.0
subsystem:
make -C subdir
先进入“subdir”目录,然后执行make命令。并传递变量到下级Makefile中。
定义命令包
定义:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
使用:
foo.c : foo.y
$(run-yacc)
就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的“ ” 就是“ f o o . y ”,“ ^”就是“foo.y”, “ ”就是“foo.y”,“@”就是“foo.c”。
变量
定义/使用变量
定义变量:
objects = program.o foo.o utils.o
使用变量:
a = $(objects)
b = ${objects}
变量赋值
"="表示延迟赋值
x = foo
y = $(x) # y=bar
x = bar
":="表示直接赋值
x := foo
y := $(x) # y=foo
x := bar
"?="表示如果该变量没有被赋值,则赋予等号后面的值
x ?= foo # x=foo
x ?= bar # x=foo
"+="表示将符号后面的值添加到前面的变量上
x = foo # x=foo
x += bar # x=foobar
"override"表示覆盖make命令行参数设置的变量
override <variable> = <value>
变量值的替换
foo := a.o b.o c.o
bar := $(foo:.o=.c)
把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
把变量的值再当成变量
x = y
y = z
a := $($(x))#a=z
局部变量
t1: x=foo
t1: tt1 tt2
@echo $(x)
tt1:
@echo $(x)
tt2:
@echo $(x)
变量x会作用到由目标t1所引发的所有的规则中去。
可以以如下方式给所有以[.o]结尾的目标定义目标变量:
%.o: CFLAGS = -O
foo.o: foo.c
gcc $(CFLAGS) ...
bar.o: bar.c
gcc $(CFLAGS) ...
自动化变量
$@
表示目标文件
$^
表示所有的依赖文件
$<
表示第一个依赖文件
$?
表示所有比目标新的依赖文件的集合
函数
调用函数格式:
$(function arg1,arg2)
# 或者
${function arg1,arg2}
字符串处理函数
# 匹配目录下所有.c 文件
$(wildcard ./*.c)
# 把字串<text>中的<from>字符串替换成<to>
$(subst <form>,<to>,<text>)
# 模式字符串替换函数
$(patsubst %.c,.o,<text>)
# 去掉开头个结尾的空字符
$(strip <string>)
# 查找字符串,找到则返回<find>,找不到则返回空字符串
$(findstring <find>,<in>)
# 以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
$(filter <pattern...>,<text>)
# 以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
$(filter-out <pattern...>,<text>)
# 给字符串<list>中的单词排序(升序)。sort函数会去掉<list>中相同的单词。
$(sort <list>)
# 取字符串<text>中第<n>个单词。(从一开始)找不到则返回空字符串。
$(word <n>,<text>)
# 从字符串<text>中取从<ss>开始到<e>的单词串。<ss>和<e>是一个数字。
$(wordlist <ss>,<e>,<text>)
# 单词个数统计函数
$(words <text>)
文件名操作函数
# 从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
$(dir <names...>)
# 从文件名序列<names>中取出非目录部分。非目录部分是指最後一个反斜杠(“/”)之后的部分。
$(notdir <names...>)
# 从文件名序列<names>中取出各个文件名的后缀。
$(suffix <names...>)
# 从文件名序列<names>中取出各个文件名的前缀部分。
$(basename <names...>)
# 把后缀<suffix>加到<names>中的每个单词后面。
$(addsuffix <suffix>,<names...>)
# 把前缀<prefix>加到<names>中的每个单词后面。
$(addprefix <prefix>,<names...>)
# 连接函数,如$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。
$(join <list1>,<list2>)
foreach 函数
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中, ( n a m e ) 中的单词会被挨个取出,并存到变量“ n ”中,“ (name)中的单词会被挨个取出,并存到变量“n”中,“ (name)中的单词会被挨个取出,并存到变量“n”中,“(n).o”每次根据“ ( n ) ”计算出一个值,这些值以空格分隔,最后作为 f o r e a c h 函数的返回,所以, (n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, (n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,(files)的值是“a.o b.o c.o d.o”。
if 函数
$(if <condition>,<then-part>)
# 或者
$(if <condition>,<then-part>,<else-part>)
if函数的返回值是,如果<condition>
为真(非空字符串),那个<then-part>
会是整个函数的返回值,如果<condition>
为假(空字符串),那么<else-part>
会是整个函数的返回值,此时如果<else-part>
没有被定义,那么,整个函数返回空字串。
call 函数
call函数是唯一一个可以用来创建新的参数化的函数。
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
当make执行这个函数时,<expression>
参数中的变量,如
(
1
)
,
(1),
(1),(2),$(3)等,会被参数<parm1>
,<parm2>
,<parm3>
依次取代。而<expression>
的返回值就是 call 函数的返回值。例如:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么,foo的值就是“a b”。
origin 函数
告诉你你的这个变量是哪里来的?
$(origin <variable>)
origin函数的返回值有这几种:
undefined:未定义
default:是一个默认的定义,比如“CC”这个变量
environment:是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开
file:变量被定义在Makefile中
command line:变量是被命令行定义的
override:是被override指示符重新定义的
automatic:是一个命令运行中的自动化变量
shell 函数
contents := $(shell cat foo)
files := $(shell echo *.c)
控制make的函数
$(error <text ...>;)
$(warning <text ...>;)
产生一个致命的错误,<text …>;是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。
示例一:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
示例二:
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)
示例一会在变量ERROR_001定义了后执行时产生error调用,而示例二则在目录err被执行时才发生error调用。
条件判断
ifeq
name=foo
show:
ifeq ($(name),foo)
@echo true
else
@echo false
endif
ifneq
name=foo
show:
ifneq ($(name),foo)
@echo true
else
@echo false
endif
ifdef
name=foo
show:
ifdef name
@echo true
else
@echo false
endif
ifndef
name=foo
show:
ifndef name
@echo true
else
@echo false
endif
运行make
指定Makefile
所有指定的makefile将会被连在一起传递给make执行。
make -f zc.mk -f zc2.mk
检查规则
有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:
(1)“-n” “–just-print” “–dry-run” “–recon” 不执行参数,这些参数只是打印命令:
name=foo
show:
ifeq ($(name),foo)
@echo true
else
@echo false
endif
运行:
$ make show -n
echo true
(2)“-t” “–touch” 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
$ make mks -t
make: “mks”已是最新。
(3)“-q” “–question” 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
$ make dsasd -q
make: *** 没有规则可制作目标“dsasd”。 停止。
(4)“-W <file>
” “--what-if=<file>
” “--assume-new=<file>
” “--new-file=<file>
” 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n
”参数一同使用,来查看这个依赖文件所发生的规则命令。
t1: t2
@echo t1
t2: mks
@echo t2
运行:
$ make -W mks -n t1
echo t2
echo t1