7、编译命令详解:lunch、make、m/mm/mmm/mma、编译产物分析

编译命令,说白了就是你和构建系统对话的方式。我刚入行那会儿,面对一长串命令完全懵圈——lunch 是干嘛的?m 和 mm 有什么区别?编译完那一堆文件哪些是我要的?

今天咱们就把这些命令掰开揉碎,一个个讲清楚。

7.1 lunch:选目标,定乾坤

lunch 是编译的第一步。它的作用很简单——告诉系统:你要给哪个设备编译?什么版本?

执行 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_x86_64-userdebug
     5. aosp_husky-userdebug
     ...

选一个编号,或者直接传参数:

$ lunch aosp_husky-userdebug

这里 aosp_husky 是设备名,userdebug 是构建类型。构建类型有三种:

类型 说明 适用场景
eng 工程版,带 root 和调试工具 开发调试
userdebug 用户调试版,有 root 但更接近正式版 测试、性能分析
user 用户版,无 root,有签名保护 正式发布
我的习惯:日常开发用 eng,跑 CTS 测试用 userdebug,发版前切 user。千万别用 eng 版本去测性能——关掉了很多优化,数据不准。

7.2 make:全量编译,简单粗暴

make 是最基础的编译命令。不加参数就是编译整个系统:

$ make -j16

-j16 表示用 16 个线程并行编译。这个数字怎么定?我一般设成 CPU 核心数的两倍。比如 8 核机器就用 -j16

你也可以指定编译某个模块:

$ make framework
$ make libcamera_client

但 make 有个问题——它每次都会重新解析整个构建系统,光解析就要花几十秒。所以后来 Google 推出了更快的命令。

7.3 m / mm / mmm / mma:模块编译四兄弟

这四个命令是 Android 构建系统的精华。我一个个说。

m:从根目录编译

m 相当于 make,但更快。它不需要重新 source 环境变量:

$ m framework

m 会自动找到 Android 根目录,然后编译指定模块。我个人最常用这个。

mm:编译当前目录模块

mm 只编译你当前所在的模块目录。比如你在 frameworks/base/ 下:

$ mm

它只会编译 frameworks/base 这个模块,及其依赖。速度极快,适合改了一两个文件后快速验证。

注意:mm 不会编译依赖模块。如果你改了接口,记得先编译依赖。

mmm:编译指定目录

mmm 可以指定任意路径:

$ mmm frameworks/base/services/core

这个命令我经常用来编译某个子目录下的模块,不用 cd 过去。

mma:编译当前目录及依赖

mma 是 mm 的增强版。它会检查当前模块依赖的所有模块,一并编译:

$ mma

我曾经踩过一个坑:改了 AIDL 接口,用 mm 编译,结果运行时崩溃——因为依赖的模块没更新。后来我就改用 mma 了。

7.4 编译产物分析

编译完,东西都放在 out/ 目录下。我带你看看关键文件:

out/
├── target/
│   └── product/
│       └── husky/
│           ├── system.img        # 系统镜像
│           ├── vendor.img        # 厂商镜像
│           ├── boot.img          # 内核+ramdisk
│           ├── userdata.img      # 用户数据分区
│           ├── ramdisk.img       # 启动 ramdisk
│           ├── obj/              # 中间产物(.o .so .dex)
│           └── symbols/          # 带符号表的 so 文件
└── host/                         # 主机工具
    └── linux-x86/
        └── bin/                  # adb、fastboot 等

几个重点:

  • system.img:Android 系统核心,包含 framework、app、库文件
  • vendor.img:厂商定制部分,驱动、HAL 等
  • boot.img:Linux 内核 + 启动 ramdisk
  • obj/:编译中间产物,调试时很有用
  • symbols/:带符号表的 so 文件,分析 crash 日志必备
避坑指南:我曾经在分析 bug 时,直接拿 out/target/product/husky/system/lib64/libxxx.so 去 addr2line,结果符号全被 strip 了。后来才发现 symbols 目录下才是带符号的版本。记住:调试用 symbols/,刷机用 .img。

7.5 核心流程 SVG 图

下面这张图总结了编译命令的选择逻辑:

编译命令选择流程 开始编译 Step 1: lunch 选择设备 + 构建类型 Step 2: 选择编译方式 全量编译 vs 模块编译 全量编译 make / m 编译整个系统 模块编译 mm / mmm / mma 编译指定模块 单文件编译 make 模块名 快速验证修改 out/ 目录产物

7.6 实用技巧总结

最后分享几个我日常用的命令组合:

# 快速编译 framework 并生成 system.img
$ m framework
$ make systemimage

# 只编译某个模块并打包 vendor
$ mmm vendor/google/xxx
$ make vendorimage

# 编译并生成刷机包
$ m -j16
$ make otapackage    # 生成 update.zip
小技巧:如果你改了资源文件(比如 layout、drawable),用 mm 就够了。如果改了 Java 代码,建议用 mma 确保依赖更新。改了 C++ 代码?嗯,最好全量编译一次,链接问题很隐蔽。

编译命令就这些。记住一个原则:小改用 mm,中改用 mma,大改用 m。别一上来就全量编译,浪费时间。也别太自信只用 mm,漏了依赖编译——我吃过这个亏。


公众号:蓝海资料掘金营,微信deep3321