Skip to content

m-mm-mmm编译命令

解释 m、mm、mmm 的独立脚本、Soong build-mode、目录目标转换、兼容别名和常见错误。

基于android-17.0.0_r1
AndroidAOSPSoong编译命令

m-mm-mmm编译命令 ​

完成 lunch 后,最常见的 AOSP 构建入口是 m、mm 和 mmm。旧资料通常把它们描述为 envsetup.sh 中的 shell functions,并把 mma/mmma 解释成“额外构建依赖”的版本。 Android 17 当前实现已经变化:

  • 五个命令位于 build/soong/bin/,是独立脚本;
  • mm 与 mma 内容完全相同;
  • mmm 与 mmma 内容完全相同;
  • 实际目录目标由 Soong UI 的 --build-mode 解析;
  • 依赖由构建图决定,不再通过命令末尾的字母 a 区分。

lunch目标与编译变体 已说明 Product、Release 和 Variant。 本文假设三元组已经有效,只讨论命令如何把模块名和源码目录转换成构建目标。

本文面向已经能完成 lunch、但经常把目录名、模块名和最终 Ninja 目标混为一谈的读者。边界止于 Soong UI 接收 build mode 和目录参数,不展开 Ninja 调度或设备部署;源码主线是独立脚本、 getConfigArgs、getTargetsFromDirs 到后续构建 action。读完后应能从命令入口判断 owner、解释 mm/mmm 的目录转换和失败时机,并选择最小命令验证目标是否正确。

1. 命令来源 ​

1.1 脚本PATH ​

加载 build/envsetup.sh 后,build/soong/bin 被加入 PATH。envsetup.sh 中还保留提示: 这些命令过去定义在该文件中,现在已经迁移为独立脚本。

text
build/soong/bin/
├── m
├── mm
├── mmm
├── mma
├── mmma
├── allmod
├── dirmods
├── pathmod
└── outmod

这意味着分析命令行为时,应先读对应脚本,再进入 Soong UI,而不是在 envsetup.sh 中搜索旧函数。

1.2 脚本路由 ​

源码文件:build/soong/bin/m

bash
_wrap_build "$TOP/build/soong/soong_ui.bash" \
  --build-mode \
  --all-modules \
  --dir="$(pwd)" \
  "$@"

源码文件:build/soong/bin/mm

bash
_wrap_build "$TOP/build/soong/soong_ui.bash" \
  --build-mode \
  --modules-in-a-dir \
  --dir="$(pwd)" \
  "$@"

源码文件:build/soong/bin/mmm

bash
_wrap_build "$TOP/build/soong/soong_ui.bash" \
  --build-mode \
  --modules-in-dirs \
  --dir="$(pwd)" \
  "$@"

三个命令共享 soong_ui.bash --build-mode,区别只在 build action:

命令Build action目录来源
m--all-modules当前目录仅用于记录调用位置
mm--modules-in-a-dirpwd
mmm--modules-in-dirspwd 加命令参数中的目录

require_top 会确认命令位于 AOSP 工作区中,soong_ui.bash 随后回到源码根目录启动 Soong UI。

2. m 命令 ​

2.1 默认目标 ​

bash
m

m 选择 BUILD_MODULES。不带模块参数时,构建系统按当前 lunch 配置执行默认目标。

2.2 Android.bp ​

bash
m framework-minus-apex
m SystemUI
m services
m droid

模块名来自 Android.bp 或 Make 兼容图,不一定等于目录名、Java package 或最终文件名。

2.3 常用参数 ​

bash
m -j8 SystemUI
m -k SystemUI services
m showcommands SystemUI
m dist

这些参数继续交给 Soong build config 和后续 Ninja/Kati 阶段解释。m 本身只负责选择 build action 并转发参数。

2.4 make 与 m ​

envsetup.sh 仍包装了 make。在 AOSP 根目录且没有 -C 时,它通常转到 soong_ui.bash --make-mode;m 则显式使用新的 --build-mode --all-modules。

新文章和脚本应优先使用 m、mm、mmm 表达构建意图,不依赖 shell 中的 make wrapper 细节。

3. mm 命令 ​

3.1 目标路径 ​

bash
cd frameworks/base/services/core
mm

mm 将当前目录传给 Soong UI。getConfigArgs 会确认目录位于源码树内,寻找当前目录或适用父目录 中的 Android.bp/Android.mk,再转换为 MODULES-IN-* 目标。

例如:

text
frameworks/base/services/core
        ↓
MODULES-IN-frameworks-base-services-core

目录目标依赖该目录下定义的模块及其构建依赖。它不是简单地对目录内每个源文件执行编译器。

3.2 MODULES-IN-* ​

bash
mm services
mm -j8 services

测试覆盖了“当前目录目标 + 显式模块参数”的组合。最终参数同时包含传入模块和 MODULES-IN-* 目录目标。

3.3 模块构建 ​

如果当前目录没有构建文件,Soong 会向父目录查找适用边界。最终仍找不到时报告:

text
Build file not found for <directory>

此时不要随意移动 Android.bp。先确认当前目录是否只是某个模块的深层源码目录,再用 pathmod <module> 或 gomod <module> 找模块定义位置。

4. mmm 命令 ​

4.1 相对路径 ​

bash
mmm frameworks/base/services/core \
    frameworks/base/packages/SystemUI

mmm 使用 --modules-in-dirs。命令参数中的普通值被当作目录;以 - 开头的参数和 key=value 仍作为构建参数保留。

4.2 hmm ​

hmm 给出的语法是:

bash
mmm dir/:target1,target2

例如:

bash
mmm frameworks/base/packages/SystemUI/:SystemUI,SystemUI-tests

Soong 使用冒号拆分目录和模块列表,模块之间使用逗号。冒号超过一个、目标列表包含空项或目录不存在 都会失败。

源码文件:build/soong/ui/build/config.go

相关函数/类型:getTargetsFromDirs

go
for _, dir := range dirs {
    // 说明:冒号只允许出现一次,用来分隔目录和模块列表。
    parts := strings.Split(dir, ":")
    if len(parts) > 2 {
        ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
    }

    dir = filepath.Join(relDir, parts[0])
    if _, err := os.Stat(dir); err != nil {
        ctx.Fatalf("couldn't find directory %s", dir)
    }

    var newTargets []string
    if len(parts) == 2 && parts[1] != "" {
        newTargets = strings.Split(parts[1], ",")
        if inList("", newTargets) {
            ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
        }
    }

    // 说明:未指定模块时,目录会转换为MODULES-IN-*目标。
    if len(newTargets) == 0 {
        buildFile := findBuildFile(ctx, dir)
        if buildFile == "" {
            ctx.Fatalf("Build file not found for %s directory", dir)
        }
        newTargets = []string{
            convertToTarget(filepath.Dir(buildFile), targetNamePrefix),
        }
    }

    targets = append(targets, newTargets...)
}

4.3 子目录路径 ​

mmm 把调用时的 pwd 作为 --dir。目录参数会与该相对目录组合。为了减少歧义,跨较远目录构建 时建议先 croot,再传源码根相对路径。

5. mma/mmma语义 ​

5.1 Android17别名 ​

固定源码中:

text
build/soong/bin/mm  == build/soong/bin/mma
build/soong/bin/mmm == build/soong/bin/mmma

逐字节比较结果相同。hmm 也明确写着:

  • mma:Same as mm;
  • mmma:Same as mmm。

5.2 旧资料的误区 ​

历史上 mma/mmma 常被解释为“同时构建依赖”。当前 Soong 构建图本身会追踪依赖,mm 正在变得 更接近 mma,最终两者成为同一脚本。

阅读旧文章时,应以当前 tag 下脚本为准,不要根据命令名推断依赖策略。

6. Soong UI ​

6.1 动作选择约束 ​

buildActionConfig 定义三个动作:

FlagBuildAction
--all-modulesBUILD_MODULES
--modules-in-a-dirBUILD_MODULES_IN_A_DIRECTORY
--modules-in-dirsBUILD_MODULES_IN_DIRECTORIES

调用还必须提供 --dir,并且 dir 必须位于源码树内部。多个 action 同时出现、缺少 action 或目录 越界都会直接失败。

6.2 目标转换测试 ​

ui/build/config_test.go 覆盖了:

  • m 传入显式模块时保持模块参数;
  • mm 将当前目录转换为 MODULES-IN-*;
  • 深层目录找到父级构建文件;
  • mmm 将多个目录转换为多个目标;
  • directory:target1,target2 只选择显式模块;
  • 缺少构建文件时产生错误;
  • GET-INSTALL-PATH 使用独立目标前缀。

选择性运行 TestGetConfigArgsBuildModules* 已通过。它证明参数和目标转换,不证明所有编译器、 Kati、Ninja 或设备镜像任务都能成功。

7. 命令选择 ​

8. 常见错误与定位 ​

8.1 未执行 lunch ​

如果 TARGET_PRODUCT 未设置,构建无法确定产品配置。先检查:

bash
echo "$TARGET_PRODUCT"
echo "$TARGET_RELEASE"
echo "$TARGET_BUILD_VARIANT"

8.2 services目标 ​

m frameworks/base/services 会把参数当成模块目标,而不是目录。构建目录应使用 mm 或 mmm。

8.3 mm ​

mm 以当前 pwd 为目录入口。先运行:

bash
pwd
find .. -maxdepth 2 -name Android.bp -o -name Android.mk

8.4 mmm路径错误 ​

从子目录调用时,参数相对当前目录组合。跨模块构建优先:

bash
croot
mmm path/from/source/root

8.5 旧别名 ​

在 Android 17 中它们只是别名。依赖是否构建由模块图和 Ninja 决定。

8.6 设备更新 ​

这些命令把产物安装到 output tree,不会自动刷写设备。编译后如何定位产物、adb sync 或推送模块, 属于后续模块化编译专题。

9. 目标到产物 ​

m、mm 和 mmm 的差异在于如何形成初始目标,而不是“编译强度”:

入口初始信息后续共同路径
m <module>显式模块或顶层目标Soong/Kati 图展开、Ninja 增量与并行执行
mm当前目录对应的 MODULES-IN-*同上
mmm <dirs>一个或多个目录,或目录内显式目标同上

因此,一次局部编译要能解释四个映射:lunch 三元组选择了什么配置,命令产生了什么初始目标,构建图 展开了哪些依赖,最终产物落在哪个 output/install path。任一映射不清楚,都可能出现“命令成功但 编译错模块”或“产物生成但没有进入设备”的现象。

首个错误发生在目录转换、产品配置、Soong、Kati、Ninja 还是具体编译器,也应沿这条映射逐层定位。 产物定位与设备部署继续由 模块化编译 处理。

10. 两个目标转换断言 ​

在 build/soong/ui/build 运行 TestGetConfigArgsBuildModules、 TestGetConfigArgsBuildModulesInDirectory 和多目录变体,输入分别是显式模块、当前目录和多个 目录;断言生成的 BUILD_MODULES* 参数只包含预期目标,并在缺少 Android.bp 时失败。这组测试 证明的是命令到目标的转换与拒绝条件,不证明编译器或 Ninja action 成功。

读者还可以在真实树中执行 mm 与 mmm path/to/dir:target,保存 Soong UI 的目标摘要,再用 outmod <target> 检查安装产物。若两次摘要的目标集合不同,问题仍在入口或目录解析层;产物检查 只能证明输出存在,不能证明设备已经加载它。