Makefile Howto

hide
Makefile Howto
hide
入门
hide
Why called Makefile
leaf
make 命令依次查找如下文件 `GNUmakefile', `makefile' and `Makefile'
leaf
GNUmakefile 可能不被非 gnu 的 make 识别
leaf
之所以用 Makefile,因为显示文件列表排在最前
hide
Makefile 规则介绍
leaf
target ... : prerequisites ...

command

...
leaf
命令前面是一个 Tab 制表符,而不是空格!
leaf
目标和依赖都可以是多个
leaf
依赖也可以为空。例如 clean 不需要依赖任何文件
leaf
依赖可以决定 target 是否 outofdate,命令告诉如何生成 target
hide
变量
hide
变量定义,如:
leaf
objects = main.o kbd.o command.o display.o \

insert.o search.o files.o utils.o

leaf
变量引用,如:$(objects)
leaf
如果要显示 $ 字符,则可以 $$
hide
示例
leaf
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)
hide
进阶
hide
Makefile 的五大要素
leaf
显示规则
leaf
隐含规则
leaf
变量定义
leaf
指令
leaf
注视
hide
5-1. 显示规则
leaf
指定目标以及该目标的依赖,以及生成目标文件的命令
hide
格式
hide
命令另起一行,首字符是 tab
leaf
target ... : prerequisites ...

command

...
hide
命令可以和 依赖处于同一行,分号隔开
leaf
targets : prerequisites ; command

command

...
hide
依赖
hide
normal prerequisites
leaf
作用1:指定编译顺序,先执行依赖本身的编译,之后再执行目标的编译
leaf
作用2:确定依赖关系,根据依赖文件于目标文件的时间戳对比,确认是否 outofdate
hide
order-only prerequisites
Arrow Link
leaf
格式:targets : normal-prerequisites | order-only-prerequisites

即用竖线分隔开普通依赖和顺序依赖
leaf
顺序依赖只起到前述的作用1,而不会影响 target 的 update 状态
hide
也不会影响自动变量 $^ 等
hide
例如 DocBook Makefile
leaf
autolayout.xml: layout.xml | docbook.test
hide
测试一下
leaf
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"
hide
5-2. 隐含规则
hide
built-in 隐含规则
hide
隐含的 C 规则
leaf
*.c 文件生成 *.o 文件,使用命令 $(CC) -c $(CPPFLAGS) $(CFLAGS)
hide
隐含的 C++ 规则
leaf
*.cc/*.C 文件生成 *.o 文件,使用命令 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
hide
隐含的 Pascal 规则
leaf
*.p 文件生成 *.o 文件,使用命令 $(PC) -c $(PFLAGS)
hide
链接目标文件规则
leaf
将目标文件 *.o 链接为可执行文件,命令: $(CC) $(LDFLAGS) *.o $(LOADLIBES) $(LDLIBS)
hide
自定义模式规则(Pattern Rules )
hide
例如
leaf
%.o : %.c

$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
leaf
% :: RCS/%,v

$(CO) $(COFLAGS) $<
leaf
%.tab.c %.tab.h: %.y

bison -d $<
leaf
自定义的模式规则,可以替代自带的隐含规则
hide
5-3. 变量定义
hide
变量
leaf
大小写敏感
hide
变量定义的

几种风格
hide
风格1: 递归扩展变量

(recursively expanded variable)
leaf
变量定义格式是,变量和值之间用等号,即 =
hide
例如:
hide


foo = $(bar)

bar = $(ugh)

ugh = Huh?

all:;echo $(foo)

leaf
将显示 Huh?
hide
再例如:
leaf
CFLAGS = $(include_dirs) -O

include_dirs = -Ifoo -Ibar
leaf
缺点是不能这么定义:CFLAGS = $(CFLAGS) -O ,将会死循环
hide
风格2: 简单扩展变量

(simply expanded variables)
leaf
变量定义格式是,变量和值之间用冒号等号,即 :=
hide
例如
leaf
x := foo

y := $(x) bar

x := later



等价于:



y := foo bar

x := later
hide
另外 ?= 含义为:没有定义则赋值
leaf
FOO ?= bar



等价于



ifeq ($(origin FOO), undefined)

FOO = bar

endif
leaf
+= 是为变量后面追加字符
hide
变量替换
Arrow Link
hide
$(var:a=b),是将 var 变量中每一个单词后面的 a 替换为 b
leaf
$(var:suffix=replacement)



等价于



$(patsubst %suffix,%replacement,$(var))
hide
$(foo:%.o=%.c) ,由于出现了 %, 其功能和 patsubst 等价
leaf
$(var:pattern=replacement)



等价于



$(patsubst pattern,replacement,$(var))
hide
变量计算
leaf
$($(var))
hide
leaf
x = $(y)

y = z

z = Hello

a := $($(x))
leaf
x = y

y = z

z = u

a := $($($(x)))
hide
通配符变量
leaf
如果在变量定义中使用通配符,objects = *.o ,并不能展开通配符,*.o 被当做3个字符的字符串
leaf
如下格式定义: objects := $(wildcard *.o)
leaf
使用函数,将 .c 文件转换为 .o 文件: $(patsubst %.c,%.o,$(wildcard *.c))
hide
自动变量
hide
$@
leaf
目标文件。当目标文件有多个,$@是触发规则的那个目标文件
leaf
当目标文件是 archive member,$@是 archive file,$% 是member name
hide
$%
leaf
当目标文件是 archive member,$@是 archive file,$% 是member name
leaf
例如 目标若是 foo.a(bar.o),则 $%是 bar.o,$@是 foo.a
hide
$<
leaf
第一个依赖文件
hide
$?
leaf
比目标文件新的所有依赖文件,文件之间用空格分开
leaf
当依赖文件是 archive members,$? 是 member name
hide
$^
leaf
所有依赖文件(包括比目标旧的依赖文件),文件之间用空格分开
leaf
当依赖文件是 archive members,$? 是 member name
leaf
当一个文件在依赖列表中被罗列多次, $^ 只包含一次
hide
$+
leaf
很 $^ 类似。
leaf
当一个文件在依赖列表中被罗列多次, $+ 不同于 $^,包含多个
hide
$*
leaf
Patterns Match 中和目标文件匹配的部分
leaf
如: 目标为 `dir/a.foo.b' 并且目标表达式为 `a.%.b,则 $* 返回匹配的部分: `dir/foo'
hide
$(@D), $(@F), $(*D), $(*F),

$(%D), $(%F), $(<D), $(<F),

$(^D), $(^F), $(+D), $(+F),

$(?D), $(?F)
leaf
分别标识上述变量中的目录部分(D),或者文件部分 (F)
leaf
目录部分最后的 /,被删除
leaf
如 `$(@F)' 等价于 `$(notdir $@)'.
hide
5-4. 指令
hide
include
leaf
包含其它文件
leaf
-include 含义为,如果被包含文件不存在,不报错
hide
条件判断
hide
ifeq(var1, var2) ... else ... endif
leaf
如果 var1, va2 相等
leaf
ifeq ($(CC),gcc)

$(CC) -o foo $(objects) $(libs_for_gcc)

else

$(CC) -o foo $(objects) $(normal_libs)

endif
hide
ifneq "var1" "var2" ... else ... endif
leaf
如果 var1, var2 不相等
hide
ifdef var ... else ... endif
leaf
如果 var 不为空
hide
例如
leaf
ifdef XML_CATALOG_FILES

ENSURE_XSL =

else

ENSURE_XSL = if ! test -e "$(TOOLS_DIR)/xsl"; \

then $(TOOLS_DIR)/bin/find-xsl.py; fi

endif
leaf
ifndef variable-name
hide
定义包含多行文本的变量
hide
例如下面的指令,定义了包含两条 echo 命令的变量 two-lines
leaf
define two-lines

echo foo

echo $(bar)

endef
hide
例如:
leaf
define run-yacc

yacc $(firstword $^)

mv y.tab.c $@

endef



foo.c : foo.y

$(run-yacc)



hide
5-5. 注释
leaf
# 注释一行,\# 代表真正的 井号
leaf
# 注释行最后的 \ 字符,将会使下一行也成为注释
hide
规则中的命令
hide
TAB 字符
leaf
除了第一行命令可以于 target-and-prerequisites 同一行,用分号分隔外,都要在行首用 Tab 缩进。
leaf
注释和空行被忽略。但要注意所谓空行,也要有一个 TAB 起始!
leaf
条件指令不需要 有 Tab 起始?
hide
@ 字符
leaf
执行命令,但不显示命令本身。@ 字符脱掉之后,传递给 Shell 执行
leaf
make -s/--silent 可以起到同样效果
hide
\ 续行符
leaf
位于行尾的 \ ,作为续行符
hide
cd 目录的作用范围
leaf
cd命令,改变目录,不会影响后续命令的路径
leaf
除非和 cd 命令处于同一行,用分号分开
hide
- 忽略错误
leaf
在 TAB 之后的减号 -, 将忽略该命令的错误
hide
如:
leaf
clean:

-rm -f *.o
hide
特殊目标:all
leaf
执行 make 如果不指定目标,将执行第一个目标
leaf
多目标 Makefile,则可以将第一个目标定为 all,将其它目标作为其依赖,这样就可以执行所有目标编译,并指定编译顺序。
hide
Phony Targets
hide
clean:

rm *.o temp
leaf
clean 这样的 target 本身没有任何依赖,

如果目录中存在名为 clean 的文件,则

不再执行,因为认为 clean 的状态是更新的。
hide
.PHONY : clean
leaf
将 clean 加入 .PHONY ,则 clean 的执行不会收到存在同名文件的影响
hide
.PHONY : all clean
leaf
像 all 这样拥有依赖目标的,也可以加入到 phony 中
hide
.PHONY 的替代方案 "FORCE"
hide
如:
leaf
clean: FORCE

rm $(objects)

FORCE:
leaf
有的 make 不支持 .PHONY,则可以定义一个不存在的目标,没有任何依赖,也没有任何命令,如 FORCE:
leaf
FORCE 因为不存在,且没有任何依赖,其本身如果被当做依赖,则相应的目标必然执行。起到了 .PHONY 的作用
hide
函数
hide
格式
leaf
$(function arguments) 或者 ${function arguments}
leaf
function 和 arguments 之间空格分开
leaf
各个 argument 之间用冒号分开
hide
字符串函数
hide
替换
hide
$(subst from,to,text)
leaf
子串替换。$(subst from,to,text) ,将 text 中出现的 from 用 to 替换
leaf
$(subst ee,EE,feet on the street)
hide
$(patsubst pattern,replacement,text)
leaf
$(patsubst %.c,%.o,x.c.c bar.c)
hide
去掉首尾空格
hide
$(strip string)
leaf
.PHONY: all

ifneq $(strip $(needs_made)) ""

all: $(needs_made)

else

all:;@echo 'Nothing to make!'

endif
hide
查找、过滤
hide
$(findstring find,in)
leaf
找到,则返回 find, 否则返回空串
hide
如:
leaf


$(findstring a,a b c)

$(findstring a,b c)
hide
$(filter pattern...,text)
leaf
在 text 中查找匹配 pattern(可为多个)的单词
hide
如:
leaf
sources := foo.c bar.c baz.s ugh.h

foo: $(sources)

cc $(filter %.c %.s,$(sources)) -o foo
hide
$(filter-out pattern...,text)
leaf
和 filter 函数相反,在 text 中查找除了 pattern(可为多个)之外的单词
hide
如:
leaf
objects=main1.o foo.o main2.o bar.o

mains=main1.o main2.o



$(filter-out $(mains),$(objects))
hide
排序、次序
hide
$(sort list)
leaf
如: $(sort foo bar lose)
hide
$(words text)
leaf
返回 text 中单词数量
hide
$(word n,text)
leaf
返回 text 中第几个单词,从1开始
leaf
$(word 2, foo bar baz)
hide
$(wordlist s,e,text)
leaf
返回 text 中第s个到第e个单词
leaf
$(wordlist 2, 3, foo bar baz)
hide
$(firstword names...)
leaf
返回第一个单词
hide
例如:测试 DocBook XSLT 引擎
leaf
# XSLT=java \

-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl \

...

XSLT=/usr/bin/xsltproc --nonet --timing



ifeq ($(notdir $(firstword $(XSLT))),xsltproc)

...

else

...

endif
hide
联合
leaf
$(join list1,list2)
hide
文件名函数
hide
$(dir names...)
hide
返回的目录名包括最后的斜杠
leaf
如: $(dir src/foo.c hacks)
hide
$(notdir names...)
Arrow Link
hide
返回文件名
leaf
如: $(notdir src/foo.c hacks)
leaf
例如
Arrow Link
hide
$(suffix names...)
hide
返回文件扩展名
leaf
如:$(suffix src/foo.c src-1.0/bar.c hacks) 返回 .c .c
hide
$(basename names...)
leaf
注意:此 basename 和 shell 的 basename 不同!返回去掉扩展名之后的文件名包含目录名。
leaf
$(basename src/foo.c src-1.0/bar hacks) 返回 src/foo src-1.0/bar hacks
hide
$(addsuffix suffix,names...)
leaf
为文件增加扩展名
leaf
$(addsuffix .c,foo bar)
hide
$(addprefix prefix,names...)
leaf
增加前缀
leaf
$(addprefix src/,foo bar)
leaf
$(join list1,list2)
hide
$(wildcard pattern)
leaf
展开通配符
hide
例如
leaf
ALL_SOURCE := $(wildcard $(XML_SRCDIR)/*.xml)

ALL_SOURCE := $(filter-out $(VERSION_SOURCE),$(ALL_SOURCE))



# 如果不用 wildcard, $(ALL_SOURCE) 依然是 *.xml ,仍然包括 version.xml,造成循环依赖

$(VERSION_SOURCE) : $(ALL_SOURCE)

... ...
hide
foreach
leaf
find_files = $(wildcard $(dir)/*)

dirs := a b c d

files := $(foreach dir,$(dirs),$(find_files))



等价于



files := $(wildcard a/* b/* c/* d/*)
hide
call
hide
如:
leaf
reverse = $(2) $(1)

foo = $(call reverse,a,b)
leaf
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))

LS := $(call pathsearch,ls)
leaf
map = $(foreach a,$(2),$(call $(1),$(a)))

o = $(call map,origin,o map MAKE)
hide
origin
leaf
$(origin variable)
leaf
查看变量 variable 的来源,variable 不要带 $。

返回值:undefined,default,environment,environment override,

command line,override,automatic
hide
如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
leaf
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
hide
SHELL 函数
hide
leaf
contents := $(shell cat foo)

files := $(shell echo *.c)
hide
出错处理函数
hide
$(error text...)
leaf
显示异常,并退出
hide
如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
leaf
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
hide
$(warning text...)
leaf
显示警告,不退出
hide
诊断、调试
hide
make -n
leaf
不执行命令,只是显示每条命令的执行
hide
@echo ...
leaf
打印消息
hide
$(error text...)
leaf
显示异常,并退出
hide
$(warning text...)
leaf
显示警告,不退出
hide
Makefile Samples
leaf
WHODO DocBook Makefile(s)
hide
关于本文
hide
版本
leaf
v0.1 at 2005/08
hide
作者
hide
J
leaf
Jiang Xin