3. Makefile 基础:基本语法、变量、规则、函数
说到 Android 构建系统,Makefile 是绕不开的根基。虽然现在 Android 已经用 Soong(Blueprint + Ninja)替代了传统的 Android.mk,但理解 Makefile 依然是读懂构建流程的关键。我自己刚接触 Android 源码时,第一反应就是——这 Makefile 怎么这么长?后来才发现,核心语法其实就那么几条。
3.1 规则:Makefile 的灵魂
Makefile 最核心的东西就是规则。说白了,就是告诉 make 命令:你要生成什么文件,依赖哪些文件,怎么生成。
目标文件: 依赖文件列表
命令
举个例子,我经常用这个来演示:
hello.o: hello.c hello.h
gcc -c hello.c -o hello.o
这里 hello.o 是目标,hello.c 和 hello.h 是依赖,下面缩进的那行就是命令。注意,命令前面必须是一个 Tab 键,不能用空格。嗯,这个坑我踩过不止一次。
3.2 变量:别重复自己
写 Makefile 最怕什么?重复。一个项目里源文件几十上百个,总不能一个个写吧?变量就是用来解决这个问题的。
CC = gcc
CFLAGS = -Wall -O2
SRCS = main.c utils.c network.c
OBJS = $(SRCS:.c=.o)
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
这里我用了几个变量:CC 指定编译器,CFLAGS 是编译选项,SRCS 是源文件列表。最妙的是 $(SRCS:.c=.o) 这种替换写法——把 .c 后缀全换成 .o,省事吧?
变量引用用 $() 或 ${},我个人习惯用 $(),看着更清晰。还有几个自动变量特别常用:
| 变量 | 含义 | 例子 |
|---|---|---|
$@ |
目标文件名 | 上面例子中就是 hello.o |
$< |
第一个依赖文件 | 上面例子中就是 hello.c |
$^ |
所有依赖文件 | 用空格分隔的完整列表 |
$? |
比目标新的依赖 | 常用于增量编译 |
$(info ...) 打印变量值。比如 $(info OBJS = $(OBJS)),比猜变量值靠谱多了。
3.3 函数:让 Makefile 活起来
Makefile 里的函数,说白了就是一些内置的字符串处理工具。我刚开始觉得没啥用,后来发现没有它们还真不行。
最常用的几个:
- wildcard:匹配文件。比如
$(wildcard src/*.c)能拿到 src 目录下所有 .c 文件。 - patsubst:模式替换。比如
$(patsubst %.c,%.o,$(SRCS)),跟前面那个$(SRCS:.c=.o)效果一样。 - foreach:循环处理。比如遍历多个目录收集源文件。
- shell:执行 shell 命令。比如
$(shell find . -name "*.c")。
看个实际例子:
SRC_DIRS = src lib test
SRCS = $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c))
OBJS = $(patsubst %.c,%.o,$(SRCS))
all: app
app: $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
这段代码里,foreach 遍历三个目录,wildcard 在每个目录里找 .c 文件,patsubst 把 .c 换成 .o。三行代码搞定几十个文件的编译规则,是不是很爽?
3.4 伪目标:别跟文件重名了
你有没有遇到过这种情况:写了个 clean 目标,结果目录里恰好有个叫 clean 的文件,然后 make 就说「目标已是最新」?
这就是伪目标要解决的问题。用 .PHONY 声明一下,make 就知道这个目标不是文件:
.PHONY: clean all
all: app
clean:
rm -f *.o app
我曾经在一个项目里,因为没加 .PHONY,导致 make clean 怎么都删不掉文件。查了半天才发现目录里有个叫 clean 的空文件。从那以后,我所有非文件目标都会加上 .PHONY。
3.5 条件判断:让构建更智能
有时候需要根据条件决定编译哪些文件,或者用不同的编译选项。Makefile 支持条件判断:
ifeq ($(DEBUG), 1)
CFLAGS += -g -O0
else
CFLAGS += -O2
endif
ifeq ($(ARCH), arm)
CC = arm-linux-gcc
else
CC = gcc
endif
条件判断的语法有 ifeq、ifneq、ifdef、ifndef 四种。我个人最常用的是 ifeq 和 ifdef,简单直观。
3.6 知识体系总览
下面这张图总结了 Makefile 的核心知识结构,你可以对照着回顾一下:
这张图把 Makefile 的核心要素都串起来了。规则是骨架,变量是血肉,函数是工具,条件判断和伪目标是辅助。你在 Android 源码里看到的 Android.mk,本质上就是这些东西的组合。
好了,Makefile 基础就讲到这里。下一章我们会把这些知识用到 Android 构建系统中,看看 Android.mk 到底是怎么写的。