Makefile Howto
Flash plugin or Javascript are turned off. Activate both and reload to view the mindmap.
Get Flash
Makefile Howto
入门
Why called Makefile
make 命令依次查找如下文件 `GNUmakefile', `makefile' and `Makefile'
GNUmakefile 可能不被非 gnu 的 make 识别
之所以用 Makefile,因为显示文件列表排在最前
Makefile 规则介绍
target ... : prerequisites ...
command
...
命令前面是一个 Tab 制表符,而不是空格!
目标和依赖都可以是多个
依赖也可以为空。例如 clean 不需要依赖任何文件
依赖可以决定 target 是否 outofdate,命令告诉如何生成 target
变量
变量定义,如:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
变量引用,如:$(objects)
如果要显示 $ 字符,则可以 $$
示例
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 的五大要素
显示规则
隐含规则
变量定义
指令
注视
5-1. 显示规则
指定目标以及该目标的依赖,以及生成目标文件的命令
格式
命令另起一行,首字符是 tab
target ... : prerequisites ...
command
...
命令可以和 依赖处于同一行,分号隔开
targets : prerequisites ; command
command
...
依赖
normal prerequisites
作用1:指定编译顺序,先执行依赖本身的编译,之后再执行目标的编译
作用2:确定依赖关系,根据依赖文件于目标文件的时间戳对比,确认是否 outofdate
order-only prerequisites
格式:targets : normal-prerequisites | order-only-prerequisites
即用竖线分隔开普通依赖和顺序依赖
顺序依赖只起到前述的作用1,而不会影响 target 的 update 状态
也不会影响自动变量 $^ 等
例如 DocBook Makefile
autolayout.xml: layout.xml | docbook.test
测试一下
test : 1.xxx 2.xxx | 3.xxx
@echo "test depends: $^"
%.xxx :
@echo "now make target: $@"
执行 make -n test 将显示
echo "now make target: 1.xxx"
echo "now make target: 2.xxx"
echo "now make target: 3.xxx"
echo "test depends: 1.xxx 2.xxx"
5-2. 隐含规则
built-in 隐含规则
隐含的 C 规则
*.c 文件生成 *.o 文件,使用命令 $(CC) -c $(CPPFLAGS) $(CFLAGS)
隐含的 C++ 规则
*.cc/*.C 文件生成 *.o 文件,使用命令 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
隐含的 Pascal 规则
*.p 文件生成 *.o 文件,使用命令 $(PC) -c $(PFLAGS)
链接目标文件规则
将目标文件 *.o 链接为可执行文件,命令: $(CC) $(LDFLAGS) *.o $(LOADLIBES) $(LDLIBS)
自定义模式规则(Pattern Rules )
例如
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
% :: RCS/%,v
$(CO) $(COFLAGS) $<
%.tab.c %.tab.h: %.y
bison -d $<
自定义的模式规则,可以替代自带的隐含规则
5-3. 变量定义
变量
大小写敏感
变量定义的
几种风格
风格1: 递归扩展变量
(recursively expanded variable)
变量定义格式是,变量和值之间用等号,即 =
例如:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)
将显示 Huh?
再例如:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
缺点是不能这么定义:CFLAGS = $(CFLAGS) -O ,将会死循环
风格2: 简单扩展变量
(simply expanded variables)
变量定义格式是,变量和值之间用冒号等号,即 :=
例如
x := foo
y := $(x) bar
x := later
等价于:
y := foo bar
x := later
另外 ?= 含义为:没有定义则赋值
FOO ?= bar
等价于
ifeq ($(origin FOO), undefined)
FOO = bar
endif
+= 是为变量后面追加字符
变量替换
$(var:a=b),是将 var 变量中每一个单词后面的 a 替换为 b
$(var:suffix=replacement)
等价于
$(patsubst %suffix,%replacement,$(var))
$(foo:%.o=%.c) ,由于出现了 %, 其功能和 patsubst 等价
$(var:pattern=replacement)
等价于
$(patsubst pattern,replacement,$(var))
变量计算
$($(var))
如
x = $(y)
y = z
z = Hello
a := $($(x))
x = y
y = z
z = u
a := $($($(x)))
通配符变量
如果在变量定义中使用通配符,objects = *.o ,并不能展开通配符,*.o 被当做3个字符的字符串
如下格式定义: objects := $(wildcard *.o)
使用函数,将 .c 文件转换为 .o 文件: $(patsubst %.c,%.o,$(wildcard *.c))
自动变量
$@
目标文件。当目标文件有多个,$@是触发规则的那个目标文件
当目标文件是 archive member,$@是 archive file,$% 是member name
$%
当目标文件是 archive member,$@是 archive file,$% 是member name
例如 目标若是 foo.a(bar.o),则 $%是 bar.o,$@是 foo.a
$<
第一个依赖文件
$?
比目标文件新的所有依赖文件,文件之间用空格分开
当依赖文件是 archive members,$? 是 member name
$^
所有依赖文件(包括比目标旧的依赖文件),文件之间用空格分开
当依赖文件是 archive members,$? 是 member name
当一个文件在依赖列表中被罗列多次, $^ 只包含一次
$+
很 $^ 类似。
当一个文件在依赖列表中被罗列多次, $+ 不同于 $^,包含多个
$*
Patterns Match 中和目标文件匹配的部分
如: 目标为 `dir/a.foo.b' 并且目标表达式为 `a.%.b,则 $* 返回匹配的部分: `dir/foo'
$(@D), $(@F), $(*D), $(*F),
$(%D), $(%F), $(<D), $(<F),
$(^D), $(^F), $(+D), $(+F),
$(?D), $(?F)
分别标识上述变量中的目录部分(D),或者文件部分 (F)
目录部分最后的 /,被删除
如 `$(@F)' 等价于 `$(notdir $@)'.
5-4. 指令
include
包含其它文件
-include 含义为,如果被包含文件不存在,不报错
条件判断
ifeq(var1, var2) ... else ... endif
如果 var1, va2 相等
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
ifneq "var1" "var2" ... else ... endif
如果 var1, var2 不相等
ifdef var ... else ... endif
如果 var 不为空
例如
ifdef XML_CATALOG_FILES
ENSURE_XSL =
else
ENSURE_XSL = if ! test -e "$(TOOLS_DIR)/xsl"; \
then $(TOOLS_DIR)/bin/find-xsl.py; fi
endif
ifndef variable-name
定义包含多行文本的变量
例如下面的指令,定义了包含两条 echo 命令的变量 two-lines
define two-lines
echo foo
echo $(bar)
endef
例如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
$(run-yacc)
5-5. 注释
# 注释一行,\# 代表真正的 井号
# 注释行最后的 \ 字符,将会使下一行也成为注释
规则中的命令
TAB 字符
除了第一行命令可以于 target-and-prerequisites 同一行,用分号分隔外,都要在行首用 Tab 缩进。
注释和空行被忽略。但要注意所谓空行,也要有一个 TAB 起始!
条件指令不需要 有 Tab 起始?
@ 字符
执行命令,但不显示命令本身。@ 字符脱掉之后,传递给 Shell 执行
make -s/--silent 可以起到同样效果
\ 续行符
位于行尾的 \ ,作为续行符
cd 目录的作用范围
cd命令,改变目录,不会影响后续命令的路径
除非和 cd 命令处于同一行,用分号分开
- 忽略错误
在 TAB 之后的减号 -, 将忽略该命令的错误
如:
clean:
-rm -f *.o
特殊目标:all
执行 make 如果不指定目标,将执行第一个目标
多目标 Makefile,则可以将第一个目标定为 all,将其它目标作为其依赖,这样就可以执行所有目标编译,并指定编译顺序。
Phony Targets
clean:
rm *.o temp
clean 这样的 target 本身没有任何依赖,
如果目录中存在名为 clean 的文件,则
不再执行,因为认为 clean 的状态是更新的。
.PHONY : clean
将 clean 加入 .PHONY ,则 clean 的执行不会收到存在同名文件的影响
.PHONY : all clean
像 all 这样拥有依赖目标的,也可以加入到 phony 中
.PHONY 的替代方案 "FORCE"
如:
clean: FORCE
rm $(objects)
FORCE:
有的 make 不支持 .PHONY,则可以定义一个不存在的目标,没有任何依赖,也没有任何命令,如 FORCE:
FORCE 因为不存在,且没有任何依赖,其本身如果被当做依赖,则相应的目标必然执行。起到了 .PHONY 的作用
函数
格式
$(function arguments) 或者 ${function arguments}
function 和 arguments 之间空格分开
各个 argument 之间用冒号分开
字符串函数
替换
$(subst from,to,text)
子串替换。$(subst from,to,text) ,将 text 中出现的 from 用 to 替换
$(subst ee,EE,feet on the street)
$(patsubst pattern,replacement,text)
$(patsubst %.c,%.o,x.c.c bar.c)
去掉首尾空格
$(strip string)
.PHONY: all
ifneq $(strip $(needs_made)) ""
all: $(needs_made)
else
all:;@echo 'Nothing to make!'
endif
查找、过滤
$(findstring find,in)
找到,则返回 find, 否则返回空串
如:
$(findstring a,a b c)
$(findstring a,b c)
$(filter pattern...,text)
在 text 中查找匹配 pattern(可为多个)的单词
如:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter-out pattern...,text)
和 filter 函数相反,在 text 中查找除了 pattern(可为多个)之外的单词
如:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
排序、次序
$(sort list)
如: $(sort foo bar lose)
$(words text)
返回 text 中单词数量
$(word n,text)
返回 text 中第几个单词,从1开始
$(word 2, foo bar baz)
$(wordlist s,e,text)
返回 text 中第s个到第e个单词
$(wordlist 2, 3, foo bar baz)
$(firstword names...)
返回第一个单词
例如:测试 DocBook XSLT 引擎
# XSLT=java \
-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl \
...
XSLT=/usr/bin/xsltproc --nonet --timing
ifeq ($(notdir $(firstword $(XSLT))),xsltproc)
...
else
...
endif
联合
$(join list1,list2)
文件名函数
$(dir names...)
返回的目录名包括最后的斜杠
如: $(dir src/foo.c hacks)
$(notdir names...)
返回文件名
如: $(notdir src/foo.c hacks)
例如
$(suffix names...)
返回文件扩展名
如:$(suffix src/foo.c src-1.0/bar.c hacks) 返回 .c .c
$(basename names...)
注意:此 basename 和 shell 的 basename 不同!返回去掉扩展名之后的文件名包含目录名。
$(basename src/foo.c src-1.0/bar hacks) 返回 src/foo src-1.0/bar hacks
$(addsuffix suffix,names...)
为文件增加扩展名
$(addsuffix .c,foo bar)
$(addprefix prefix,names...)
增加前缀
$(addprefix src/,foo bar)
$(join list1,list2)
$(wildcard pattern)
展开通配符
例如
ALL_SOURCE := $(wildcard $(XML_SRCDIR)/*.xml)
ALL_SOURCE := $(filter-out $(VERSION_SOURCE),$(ALL_SOURCE))
# 如果不用 wildcard, $(ALL_SOURCE) 依然是 *.xml ,仍然包括 version.xml,造成循环依赖
$(VERSION_SOURCE) : $(ALL_SOURCE)
... ...
foreach
find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))
等价于
files := $(wildcard a/* b/* c/* d/*)
call
如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
LS := $(call pathsearch,ls)
map = $(foreach a,$(2),$(call $(1),$(a)))
o = $(call map,origin,o map MAKE)
origin
$(origin variable)
查看变量 variable 的来源,variable 不要带 $。
返回值:undefined,default,environment,environment override,
command line,override,automatic
如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
docbook.test:
ifeq "$(XML_CATALOG_FILES)" ""
$(error XML_CATALOG_FILES is blank!)
endif
ifeq "$(origin XML_CATALOG_FILES)" "undefined"
$(error XML_CATALOG_FILES is $(origin XML_CATALOG_FILES) !)
endif
SHELL 函数
如
contents := $(shell cat foo)
files := $(shell echo *.c)
出错处理函数
$(error text...)
显示异常,并退出
如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
docbook.test:
ifeq "$(XML_CATALOG_FILES)" ""
$(error XML_CATALOG_FILES is blank!)
endif
ifeq "$(origin XML_CATALOG_FILES)" "undefined"
$(error XML_CATALOG_FILES is $(origin XML_CATALOG_FILES) !)
endif
$(warning text...)
显示警告,不退出
诊断、调试
make -n
不执行命令,只是显示每条命令的执行
@echo ...
打印消息
$(error text...)
显示异常,并退出
$(warning text...)
显示警告,不退出
Makefile Samples
WHODO DocBook Makefile(s)
关于本文
版本
v0.1 at 2005/08
作者
J
Jiang Xin