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的编译流程大致如下:

Android.bp 文件 Blueprint 解析器 生成 ninja 文件 Ninja 执行引擎 并行编译 .so Soong 编译流程:从配置到产物 💡 关键点 • Android.bp → Blueprint 解析 → .ninja 文件 → Ninja 执行 • 所有模块在 out/soong/build.ninja 中汇总 • 支持增量编译,只重新编译修改过的文件

Soong里常用的模块类型,我整理了一张表:

模块类型 用途 典型输出
cc_library 编译C/C++静态库 .a 文件
cc_library_shared 编译C/C++动态库 .so 文件
cc_binary 编译可执行文件 可执行程序
java_library 编译Java库 .jar 文件
android_app 编译Android应用 .apk 文件
💡 我的小技巧: 如果你不确定某个模块怎么写,直接去AOSP源码里搜类似模块的Android.bp文件。比如想看动态库怎么写,就搜 cc_library_shared,能找到大量现成例子。

4.3 lunch与make:构建的入口

好了,规则写好了,怎么开始编译?这就轮到lunchmake登场了。

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,结果lunchmake都找不到。记住,每次打开新终端,第一件事就是source这个脚本,否则所有命令都不认识。

4.4 构建系统的完整流程

把上面这些串起来,一次完整的构建流程是这样的:

  1. source build/envsetup.sh:加载构建环境
  2. lunch aosp_husky-userdebug:选择目标设备和版本
  3. 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