上一篇我们将到android build system, 讲了envsetup.sh和lunch, 在继续研究build过程之前有必要先讲讲Makefile的语法. android使用

1
GNUMake 3.81
(不支持其他版本!), 因此有许多GNU的特定语法, 和一般的Makefile长得很不一样. (为了叙述方便, 本文中凡是提到
1
Makefile
的地方, 均指
1
GNU Makefile
.) Makefile的基本语法是这样的:

1
2
TARGET: <DEPENDS>
<TAB>   instructions

运行

1
make TARGET
命令时, 根据TARGET对应的DEPENDS来确定依赖关系, 根据修改时间来判断某个TARGET是否需要rebuild.如果需要, 则执行
1
TARGET
对应的instructions. 这就是Makefile的基本原理.

.PHONY targets

在Makefile中, 有时会遇到标明为

1
.PHONY
的target, 它表示这个target只是一个名字, 不是一个文件. 例如:

clean:
		rm *.o temp

这个名为

1
clean
的target指示makefile去做一些删除临时文件的指令, 但如果你的目录下刚好有个名为
1
clean
的文件, 又因为clean没有任何的dependencies, make会认为
1
clean
这个文件是最新的, 不需要执行. 这时我们就要告诉make,
1
clean
这个target只是一个名称而已, 并不是一个文件:

.PHONY: clean

multiple targets

当一行写有个多个target时, 相当于把这些target分开写, 并使用一样的commands. 例如:

foo bar:
		echo $@    			

等价于:

foo:
		echo $@
bar:
		echo $@

其中$@会被替换为target名称.

1
GNUMake
中, 有一些特定的语法在android中广泛使用, 下面一一介绍一下:

define

1
Makefile
中, 定义一个变量可以用
1
=
或者
1
:=
. 其中
1
=
1
:=
的区别在于,
1
=
1
recusively expanded variable
, 它在每次被用到的时候都会被求值; 而
1
:=
1
simply expanded variable
, 它在定义的时候就被展开. 例如:

x = foo 
var1 = $(x)
var2 := $(x)
x = bar 
target:
        echo var1: $(var1)
        echo var2: $(var2)

执行

1
make
会显示:

1
2
3
4
echo var1: bar
var1: bar
echo var2: foo
var2: foo

除了

1
=
1
:=
, 还有一个
1
?=
, 它只在变量没有被定义时起作用, 常用于设置默认值.

define 也可以用来定义一个变量, 但define更强的一点在于可以定义多行变量. 例如:

define command
@echo foo
@echo bar
endef

target:
			$(command)

输出:

foo bar

其中

1
@
放在一个命令前, 表示不输出这一行执行的命令. 不过, 在Makefile中有一个
1
call
函数, 它可以”调用”一个变量, 因此变量可以用来实现类似于宏展开的功能. 例如:

define concat
$1,$2
endef

target:
			@echo $(call concat,foo,bar)

会输出:

1
foo,bar

这里有个坑:

1
$(call concat,foo,bar)
注意我没有输入空格, 如果我写成
1
$(call concat, foo, bar)
, 那么在concat展开的时候, 参数
1
$1
就会等于前面带一个空格的
1
_foo
,
1
$2
就会等于前面带一个空格的
1
_bar
(我这里用下划线来表示空格作为警示). 这个在用的时候要格外小心!

include

1
include
用于包含另一个Makefile, 这个在比较大规模的项目中比较有用, 有助于Makefile的重用和模块化. 例如你可以将一些常用的宏扩展和变量定义放在一个
1
definitions.mk
中, 然后从你的Makefile中include之.

include some_file.mk
-include other_file.mk

当include一个文件而这个文件不存在时, Make会报错, 而在

1
include
之前加上
1
-
表示文件不存在时不要报错. 这个常用来包含可选的文件.

if

在make语法中有一些常用的条件语句, 类似于C语言中的

1
#ifdef
1
#if
等. 例如:

ifeq($(var),foo)
	echo foo
else
	echo not foo
endif

1
ifeq
检测后面两个值是否相等. 类似的还有
1
ifneq
,
1
ifdef
,
1
ifndef
检测一个变量是否被定义, 等等.

函数

makefile中内置了一些函数, 方便实现一些常用的功能. 调用一个函数的语法是:

$(function arguments) 或者
${function arguments}

下面列举一些常见函数:

字符串操作

  • $(subst from, to, text) 字符串替换
  • $(strip string) 去掉开头和末尾的空白. (还记得前面说的那个坑吗?)
  • $(filter pattern…, text) 取出所有匹配的文本, 丢到所有不匹配的. 例如
    1
    
    $(filter %.c %.s,$(sources))
    
    取出
    1
    
    sources
    
    变量中所有以
    1
    
    .c
    
    1
    
    .s
    
    结尾的文本.
  • $(filter-out pattern…, _text) 和上面相反, 取出所有不匹配的.
  • $(word n, text) 取出
    1
    
    text
    
    中第
    1
    
    n
    
    项.

文件名操作

  • $(dir names…) 取出
    1
    
    names
    
    中所有文件的目录部分.
  • $(notdir names…) 和上面相反, 取出非目录部分(纯文件名部分).
  • $(add_prefix prefix, names…) 给文件加前缀
  • $(wildcard pattern) 获取匹配通配符的文件名. 例如
    1
    
    objects := $(wildcard *.o)
    
    返回所有
    1
    
    .o
    
    结尾的文件.

foreach

$(foreach var, list, text) 将

1
var
作为形式参数,
1
list
作为列表, 扩展
1
text
的内容. 相当于python中的:

	for var in list:
      text

call

在前面提到了, 用于”展开”一个宏(变量)

eval

1
eval
是个很重要的函数, 可以用来实现Makefile的模板. 比较关键的一点是需要知道传给
1
eval
的参数被展开了两次, 首先是传给eval之前, 然后再被当作makefile的语句include到当前的地方. 这个我们之后会继续讲到, 先放一个例子在这里.

PROGRAMS    = server client

server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol

client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol

# Everything after this is generic

.PHONY: all
all: $(PROGRAMS)

define PROGRAM_template =
 $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
 ALL_OBJS   += $$($(1)_OBJS)
endef

$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))

$(PROGRAMS):
        $(LINK.o) $^ $(LDLIBS) -o $@

clean:
        rm -f $(ALL_OBJS) $(PROGRAMS)

输出调试

这些语句被用于输出调试信息: (debug和研究时都非常有用!)

  • $(warning text…)
  • $(info text…)
  • $(origin variable) 可以告诉你一个变量是从哪里来的, 是环境中定义的, 还是makefile中定义的, 还是命令行中定义的. 这个在调试的时候也很有用. 参考这里

运行shell语句

  • $(shell command) 用于运行一个shell语句, 然后将输出返回. 这个也非常有用, 例如有了这个你就可以用你自己喜欢的工具例如perl之类来进行字符串操作了, 可以不用费力学习和记忆makefile中相应的函数的语法.