4、构建系统入门:Makefile 基础、Android.bp 与 Soong 构建系统、lunch 与 make 命令
说实话,很多做Android应用开发的朋友,一听到「构建系统」四个字就头大。我当年刚接触AOSP时也一样,面对一堆Makefile和莫名其妙的命令,完全摸不着头脑。但后来我发现,搞懂构建系统,其实就是搞懂三件事:怎么描述编译规则、怎么组织这些规则、怎么一键执行。今天我们就来把这层窗户纸捅破。
4.1 从Makefile说起:构建的基石
Android早期用的是纯Makefile体系。虽然现在已经被Soong取代,但理解Makefile能帮你读懂很多历史代码。说白了,Makefile就是一个「菜谱」——告诉编译器先做什么、后做什么。
一个最简单的Makefile长这样:
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
这里有个坑,我刚开始学的时候踩过——命令前面必须是Tab键,不能是空格。你想想看,就这一个缩进问题,能让新手折腾半小时。
Makefile的核心逻辑是「目标-依赖-命令」三元组:
- 目标:要生成的文件(比如hello)
- 依赖:生成目标需要的原料(比如hello.c)
- 命令:怎么把原料变成目标(比如gcc编译)
在AOSP里,Makefile被玩出了花。一个典型的Android.mk文件是这样的:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_jni
LOCAL_SRC_FILES := hello_jni.c
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_SHARED_LIBRARY)
嗯,这里要注意,LOCAL_PATH必须放在最前面,否则会找不到文件。我在项目中遇到过有人把变量定义顺序搞反,结果编译报错说找不到源文件,排查了半天才发现是这个问题。
4.2 Soong与Android.bp:新时代的构建语言
Google在Android 7.0之后引入了Soong构建系统,用Android.bp文件替代了Android.mk。为什么要换?说白了,Makefile太灵活了,灵活到容易出错。Soong用了一种更结构化的方式——Blueprint配置语言。
一个Android.bp文件看起来是这样的:
cc_library_shared {
name: "hello_jni",
srcs: ["hello_jni.c"],
shared_libs: ["liblog"],
}
是不是清爽多了?没有那些LOCAL_前缀,没有include语句,就是一个纯粹的配置结构。我个人习惯把这种写法叫做「声明式构建」——你只管说你要什么,Soong帮你搞定怎么编译。
Soong的编译流程大致如下:
Soong里常用的模块类型,我整理了一张表:
| 模块类型 | 用途 | 典型输出 |
|---|---|---|
cc_library |
编译C/C++静态库 | .a 文件 |
cc_library_shared |
编译C/C++动态库 | .so 文件 |
cc_binary |
编译可执行文件 | 可执行程序 |
java_library |
编译Java库 | .jar 文件 |
android_app |
编译Android应用 | .apk 文件 |
cc_library_shared,能找到大量现成例子。
4.3 lunch与make:构建的入口
好了,规则写好了,怎么开始编译?这就轮到lunch和make登场了。
lunch命令的作用是选择编译目标。说白了,就是告诉构建系统:「我要给哪个设备编译?编译什么版本?」
执行lunch后,你会看到这样的菜单:
You're building on Linux
Lunch menu... pick a combo:
1. aosp_arm-eng
2. aosp_arm64-eng
3. aosp_x86_64-eng
4. aosp_car_arm64-userdebug
5. aosp_husky-userdebug
...
Which would you like? [aosp_arm-eng]
这里每个选项的格式是设备名-编译类型。编译类型有三种:
- eng:工程版,带调试工具,适合开发
- userdebug:用户调试版,有root权限,适合测试
- user:用户版,无root,适合发布
我个人习惯用lunch aosp_husky-userdebug,因为既有root权限方便调试,又接近正式版本的行为。
make命令就是真正开始编译了。最简单的用法:
make -j8
-j8表示用8个线程并行编译。这个数字怎么设?我建议设为CPU核心数的1.5到2倍。比如8核CPU就用-j12或-j16。设太小编译慢,设太大系统会卡死。
如果你只想编译某个模块,可以用make 模块名:
make hello_jni
这样只会编译hello_jni这个模块,及其依赖项。我第一次用这个功能时,感觉像发现了新大陆——再也不用每次全量编译了。
source build/envsetup.sh,结果lunch和make都找不到。记住,每次打开新终端,第一件事就是source这个脚本,否则所有命令都不认识。
4.4 构建系统的完整流程
把上面这些串起来,一次完整的构建流程是这样的:
- source build/envsetup.sh:加载构建环境
- lunch aosp_husky-userdebug:选择目标设备和版本
- make -j16:开始编译
编译过程中,你会看到大量输出。别慌,重点关注这几个地方:
- 错误信息:以
error:开头,后面跟着文件名和行号 - 警告信息:以
warning:开头,虽然不影响编译,但建议修复 - 编译进度:
[ 12% 1234/10240]这样的格式,前面是百分比,后面是已完成/总任务数
嗯,这里要提醒一下,第一次全量编译AOSP,根据机器配置不同,可能需要1到4个小时。我建议你找个周末,泡杯咖啡,让电脑慢慢跑。或者像我一样,只编译自己需要的模块,能省下大量时间。
核心要点回顾:
- Makefile是构建的基石,Android.bp是新时代的替代方案
- Soong将Android.bp解析为ninja文件,再由Ninja执行编译
- lunch选择编译目标,make执行编译
- 善用
make 模块名可以大幅提升效率
构建系统这东西,刚开始觉得复杂,用多了就会发现其实就那么几招。你只要记住:规则写在Android.bp里,入口是lunch和make,剩下的交给Soong和Ninja。下次我们深入看看,怎么在Android.bp里写更复杂的编译规则,比如条件编译、多架构支持这些实战技巧。
公众号:蓝海资料掘金营,微信deep3321