Makefile简单介绍
本文摘自GNU官方教程,更详细的介绍请参考GNU Makefile官方教程
Makefile简介
Makefile中的规则
简单makefile通常由一些规则组成,类似如下:
target … : prerequisites …
recipe
…
…
target通常是要生成的文件的文件名,可以是可执行文件或者obj文件;也可以是一个动作的名字,比如’clean’。
prerequisites是生成target所依赖的文件。
recipe是make所执行的指令。recipe必须以tab键开始。
这个规则表示,执行recipe指定的指令,把prerequisites生成target。
一个简单的makefile
下面是个简单的makefile的例子,该makefile目的是生成可执行文件edit, 它依赖8个C文件和3个头文件:
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
反斜杠(\)用来将比较长的句子分成两行。
如何生成可执行文件edit:
把该makefile文件名取为Makefile, 然后在命令行执行:
make
如何删除当前存在的可执行文件edit和.o文件:
make clean
这个makefile由一些列的规则组成:
-
edit
target是edit,也就是要生成一个名为edit的文件,prerequisites(依赖)是8个.o文件,recipe是(生成edit所执行的指令是:
cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
-
.o文件:
target是.o文件,prerequisites是对应的.c和.h文件,recipe是对应的cc -c指令。
-
clean
target是clean,这不是要生成的文件名,它只是一个动作的名字,recipe是删除当前文件夹下面的edit和.o文件。它没有依赖,也不是其他任何target的依赖,这个规则的唯一目的就是执行recipe中的指令。这类target我们称之为“伪目标(phony targets)”,这个将在后面的章节中详细介绍。
make如何执行
一般情况下,make会从makefile文件中的第一个target(非"."开头的target)开始执行。
make执行一条规则的时候会对比target和其依赖的文件的时间戳,如果target文件不存在,或者依赖文件的时间戳比target新,那么就会执行这条规则。
Makefile中的变量
在上面的makefile中,那些.o文件我们重复写了几遍, 我们可以使用变量来使得该makefile变得更加简单一些:
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 : 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 $(objects)
make自动推导
针对以’.o’结尾的的target,make可以自动推导出其依赖的.c文件和执行的recipe,比如
main.o : main.c defs.h
cc -c main.c
我们可以简单的写为:
main.o : defs.h
因为make会自动为其加上依赖main.c和recipe: cc -c main.c -o main.o。所以上面的makefile可以简化为:
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
clean :
rm edit $(objects)
target - clean
makefile除了能够编译出文件之外,还可以做一些其他事情,比如上面例子中的clean就是用来删除当前目录下的edit文件和上面列举的.o文件:
clean:
rm edit $(objects)
比较完美的写法应该是:
.PHONY : clean
clean :
-rm edit $(objects)
.PHONY关键字表示这是一个伪目标(后面会细讲)而不是某个文件的名字, -rm前面的“-”表示即使rm执行出错,也继续执行。
当我们执行make的时候,clean不会自动执行,因为它不是任何target的依赖。当我们想执行clean的时候,我们可以在执行的时候加上参数:
make clean
编写Makefile
Makefile包含哪些内容
- 显示规则: 定义target, target依赖的文件及其要执行的recipe.
- 隐式规则: makefile根据target名字的自动推导功能. 比如main.o可以自动推导出依赖文件main.c和recipe: cc -c main.c -o main.o
- 变量定义: 把字符串赋值给一个变量
- 指令: 包含3个部分:1. include其他makefile; 2. 使用条件判断来决定执行makefile的哪部分代码; 3. 定义一个包含多行字符串的变量.
- 注释: 用#开头
Makefile如何命名
默认情况下make指令会按照下面的顺序来寻找
- GNUmakefile
- makefile
- Makefile
一般推荐使用Makefile作为名字.
也可以用"-f"或者"–file"来指定任意文件作为makefile:
make -f customize_makefile
编写规则
规则示例
foo.o : foo.c defs.h # module for twiddling the frobs
cc -c -g foo.c
该规则告诉我们两件事:
- 何时该更新foo.o:当foo.o不存在或者它的依赖文件(foo.c defs.h)比它新时。
- 如何更新foo.o:cc -c -g foo.c
规则语法
一般情况下,规则语法如下:
targets : prerequisites
recipe
…
或者:
targets : prerequisites ; recipe
recipe
…
- targets可以有多个,以空格分开
- recipe必须以tab开始
- 为 特 殊 符 号 , 一 般 用 在 变 量 开 始 , 如 果 需 要 一 个 为特殊符号,一般用在变量开始,如果需要一个 为特殊符号,一般用在变量开始,如果需要一个符号,那么需要这样写$$
- 如果一行太长,可以在行尾加 \,然后新起一行,这两行会被当做一行
依赖种类
依赖可以分为普通依赖和纯指令依赖。纯指令依赖在执行后,并不会强制target更新,比如创建一个文件夹等。两种依赖以“|”分隔,左边为普通依赖,右边为纯指令依赖。
在文件名中适用通配符
示例
在recipe中使用通配符:
clean:
rm -f *.o
在依赖中使用通配符:
print: *.c
lpr -p $?
touch print
错误的用法:
objects = *.o
正确的用法应该为:
objects := $(wildcard *.o)
函数wildcard
格式:
$(wildcard pattern…)
示例
$(wildcard *.c)
$(patsubst %.c,%.o,$(wildcard *.c))
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
为依赖文件搜索文件夹
VPATH
- make会在当前文件夹和VPATH指定的文件夹搜索依赖文件或者目标文件;
- VPATH指定的路径用冒号或者空格分开;
- make会按照指定的VPATH中指定的顺序来搜索。
示例:
VPATH = src:../headers
make会从src和…/headers中去搜索文件。
vpath指令
vpath和VPATH的区别在于,vpath可以指定搜索指定类型的文件,比如只搜索以.c结尾的文件。
vpath有三种使用方法:
-
vpath pattern directories
在directories文件夹下,搜索符合pattern格式的文件。
-
vpath pattern
清除符合pattern格式的搜索。 -
vpath
清除前面所有通过vpath指定的搜索。
示例:
vpath %.h ../headers
在…/headers文件夹中搜寻以.h结尾的文件。
vpath %.c src1:src2
先在src1文件夹然后在src2文件夹中搜寻以.c结尾的文件。
不管是VPATH还是vpath,都是针对target或者prerequisites,和gcc编译时候的-I路径无关。
文件夹搜索场景下编写recipes
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
假设foo.c在另外一个目录src1里面,那么它的路径真实路径为src1/foo.c, 在写recipe的时候,我们无法知道foo.c的真实路径,如果这个时候我们继续用foo.c的话,会出现找不到文件的错误。那么这个时候我们就需要引入" " 这 个 关 键 字 。 比 如 " "这个关键字。比如" "这个关键字。比如"“表示所有prerequisites的列表,”$@“表示target,这样即使foo.c在src1里面,”$"也能够保证make能找到该文件。
伪目标 - Phony Targets
伪目标不同于普通的target,它并不是一个文件名字,它可能只是一个指令,比如删除某些文件,或者创建文件夹,等等。使用伪目标有两个好处:
- 避免和真实的文件冲突;
- 提高效率
假设有这样一个用来删除某些文件的指令:
clean:
rm *.o temp
我们的预期是每次执行 make clean的时候,就调用 rm *.o tmp 指令。但是这个规则有一个风险:如果存在clean这样一个文件,该规则又没有任何依赖,那么make会认为clean是最新的,所以就不需要执行,这样就与我们的预期不符了。所以这就需要用伪目标来解决这类问题。
看一个伪目标的例子:
.PHONY: clean
clean:
rm *.o temp
.PHONY是make预留的关键字,作为target时,系统会知道这是一个伪目标。
静态模式规则
静态模式规则指定多个target,并且可以根据名字为target的推导出所需的依赖。
静态模式规则语法
targets …: target-pattern: prereq-patterns …
recipe
…
示例:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
" < " 表 示 p r e r e q u i s i t e , " <"表示prerequisite, " <"表示prerequisite,"@"表示target。$(objects)中的每一个target,都应该满足target-pattern, 也就是%.o这种格式,如果不能满足,则需要用filter过滤下,比如下面的例子:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
变量
变量基础
- 变量一般用 ( f o o ) 或 者 (foo)或者 (foo)或者{foo}来引用
- 如果要写一个 符 号 , 则 需 要 这 样 写 : 符号,则需要这样写: 符号,则需要这样写:$
- 也可以用$foo这样来写,但是不推荐
示例:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量的两种风格
- 递归展开(recursively expanded)
递归展开用“=”赋值,在整个make file展开之后才能决定变量的值,比如有下面的make file:
x = foo
y = $(x) bar
x = later
.PHONY: test
echo ${y}
该make file是为了输出y的值,x最开始赋值为foo,但是在第三行又赋值为later,所以输出结果为:
later bar
- 简单展开(simply expanded)
简单展开用“:=”赋值,在变量定义的时候,就决定变量的值,比如修改上面的make file为如下格式:
x := foo
y := $(x) bar
x := later
.PHONY: test
echo ${y}
输出结果为:
foo bar
字符串函数
基本用法
推荐用法:
$(function arguments)
可用但不推荐:
${function arguments}
function和arguments之间用空格分开,参数之间用逗号分隔。示例:
$(subst a,b,$(x))
字符串替换函数
$(subst from,to,text)
该调用表示,在text中,把from替换为to。示例:
$(subst ee,EE,feet on the street)
输出结果为"fEEt on the strEEt"。
$(patsubst pattern,replacement,text)
使用正则表达式,把text中的pattern替换成replacement。示例:
$(patsubst %.c,%.o,x.c.c bar.c)
该代码表示在"x.c.c bar.c"中的搜寻*.c这种格式,并且把后缀.c替换为.o。所以该代码输出结果为"x.c.o bar.o"。
有两种简便的写法。
第一种:
$(var:pattern=replacement)
这种写法等同于:
$(patsubst pattern,replacement,$(var))
第二种:
$(var:suffix=replacement)
等同于:
$(patsubst %suffix,%replacement,$(var))
示例:
objects = foo.o bar.o baz.o
$(objects:.o=.c)
等同于:
objects = foo.o bar.o baz.o
$(patsubst %.o,%.c,$(objects))
$(strip string)
去掉string开头和结尾的空格,对于string中间的空格,如果有多个连在一起,则替换为一个空格。
比如:"$(strip a b c )" 结果为 “a b c”
$(findstring find,in)
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一句输出"a",第二句输出""。
$(filter pattern…,text)
返回text中符合pattern的部分。pattern以空格分隔, text也以空格分隔。
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
上面示例中的 ( f i l t e r (filter %.c %.s, (filter(sources))的结果为"foo.c barc baz.s"。
$(filter-out pattern…,text)
返回text中不符合pattern的部分。pattern以空格分隔, text也以空格分隔。
$(sort list)
排序并且去掉重复的元素。
示例:
$(sort foo bar lose)
结果为"bar foo lose"。
$(word n,text)
返回text中第n个元素
$(word 2, foo bar baz)
返回"bar"。
$(wordlist s,e,text)
从text中截取第s(包含)个和第e个(包含)元素。
$(wordlist 2, 3, foo bar baz)
返回结果为"bar baz"。
$(words text)
返回text中元素个数。示例:
$(word $(words text),text)
结果为text中最后一个元素。
$(lastword names…)
选取names中最后一个元素。
$(lastword foo bar)
结果为bar。等同于:
$(word $(words foo bar),foo bar)
文件名相关函数
$(dir names…)
获取names中的文件夹部分,比如:
$(dir src/foo.c hacks)
结果为:“src/ ./”。
$(notdir names…)
去掉names中的文件夹部分。比如:
$(notdir src/foo.c hacks)
结果为"foo.c hacks"。
$(suffix names…)
获取names中各元素的后缀。
示例:
$(suffix src/foo.c src-1.0/bar.c hacks)
结果为:".c .c"。
$(basename names…)
去掉names中各元素的后缀
$(basename src/foo.c src-1.0/bar hacks)
结果为:“src/foo src-1.0/bar hacks”。
$(addsuffix suffix,names…)
为names中各元素加上后缀suffix。
示例:
$(addsuffix .c,foo bar)
结果为:“foo.c bar.c”。
$(addprefix prefix,names…)
为names中各元素加上前缀prefix。
示例:
$(addprefix src/,foo bar)
结果为:“src/foo src/bar”。
$(join list1,list2)
把list1和list2中序号相同的元素拼接起来。
示例:
$(join a b,.c .o)
结果为:“a.c b.o”。
$(wildcard pattern)
寻找符合pattern格式的文件。
比如
$(wildcard *.c)
foreach
用法:
$(foreach var,list,text)
var为变量,用来遍历list中的元素,text为每遍历一个元素后执行的操作,通常text中会有队var的引用。
示例:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
该代码是用来遍历文件夹a,b,c,d下的所有文件。该代码也等同于下面的代码:
files := $(wildcard a/* b/* c/* d/*)
或者
find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))
shell
执行shell指令,比如:
contents := $(shell cat foo)
files := $(shell echo *.c)
隐式规则
make会根据文件名进行一些自动的推导,而不用我们显示的指定。比如make会知道a.o是通过a.c生成的。
示例:
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
这里我们用到了foo.o,但是却没指定foo.o如何生成,此时系统就会自己去推导foo.o如何生成。
内置的一些隐式规则:
-
编译C代码
$(CC) $(CPPFLAGS) $(CFLAGS) -c
-
编译C++代码
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c
-
编译成单个的.o文件
x: y.o z.o
当x.c, y.c和z.c都存在时,会执行以下操作:
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