Skip to content

lunch目标与编译变体

解释 lunch 的 product、release、variant 三元组,比较 user、userdebug、eng,并给出目标选择与错误诊断方法。

基于android-17.0.0_r1
AndroidAOSPlunch构建系统

lunch目标与编译变体 ​

lunch 不执行编译,它选择后续构建使用的配置。Android 17 中,这个配置由三个相互独立的维度组成:

text
TARGET_PRODUCT + TARGET_RELEASE + TARGET_BUILD_VARIANT
  • Product 决定产品、设备和分区内容从哪些配置继承;
  • Release 决定当前发布配置与 feature flags;
  • Variant 决定 user、userdebug 或 eng 调试能力和附加内容。

很多旧资料仍使用 <product>-<variant>,或者把 release、variant 顺序混在一起。Android 17 当前 envsetup.sh 的优先接口是多参数形式,单字符串形式只是兼容入口,而且要求完整的 <product>-<release>-<variant>。

AOSP目录结构全景 已说明 build/ 的位置, repo初始化与同步 已说明如何固定源码版本。本文只讨论配置选择; 真正执行 m、mm、mmm 的流程放在后续构建命令专题。

本文面向已经完成源码同步、知道 shell 环境和产品配置文件位置,但还不清楚 product、release 与 variant 如何分别拥有配置的读者。本文不讲完整产品继承、Soong 图生成或镜像构建;主线是 lunch 入口如何解析三元组、由哪些配置校验它、最终由 m 和 Soong 消费哪些环境变量。读者读完 应能定位 _lunch_meat、解释三种坐标的生效时机,并在不启动完整构建的情况下验证选择结果。

前置的目录与版本关系见 AOSP目录结构全景;完成配置选择后,继续 阅读 m-mm-mmm编译命令,它追踪这些变量如何被构建入口消费。

1. lunch 前约束 ​

1.1 lunch ​

lunch 是 build/envsetup.sh 定义的 shell function,需要先加载:

bash
source build/envsetup.sh

这一步会增加构建辅助命令和补全,并准备查询 build variables 的函数。它不会自动选择产品,也不会 开始编译。

1.2 TARGET_PRODUCT ​

维度环境变量回答的问题
ProductTARGET_PRODUCT构建哪个产品配置
ReleaseTARGET_RELEASE应用哪组发布配置和 flags
VariantTARGET_BUILD_VARIANT使用 user、userdebug 还是 eng

三者共同决定最终配置。只写“构建 aosp_arm64”并不能完整说明 release 与调试变体。

2. lunch参数 ​

2.1 lunch参数入口 ​

bash
lunch TARGET_PRODUCT [TARGET_RELEASE [TARGET_BUILD_VARIANT]]

例如:

bash
lunch aosp_arm64 trunk_staging userdebug

如果省略 release 和 variant:

bash
lunch aosp_arm64

当前实现默认:

text
TARGET_RELEASE=trunk_staging
TARGET_BUILD_VARIANT=eng

只省略 variant 时:

bash
lunch aosp_arm64 trunk_staging

variant 仍默认为 eng。

2.2 _lunch_usage ​

bash
lunch aosp_arm64-trunk_staging-userdebug

Android 17 的 _lunch_usage 将它称为 legacy format,要求:

text
<product>-<release>-<variant>

不要继续使用旧的 aosp_arm64-userdebug 心智模型。产品、发布配置和变体已经是三个字段。

2.3 参数解析源码 ​

源码文件:build/make/envsetup.sh

相关函数/类型:lunch

bash
function lunch()
{
    if [[ $# -eq 0 ]]; then
        echo "No target specified. See lunch --help" 1>&2
        return 1
    fi
    if [[ $# -gt 3 ]]; then
        echo "Too many parameters given. See lunch --help" 1>&2
        return 1
    fi

    local product release variant

    # 说明:单参数且包含连字符时,按兼容三元组解析。
    local legacy=$(echo $1 | grep "-")
    if [[ $# -eq 1 && -n $legacy ]]; then
        IFS="-" read -r product release variant <<< "$1"
        if [[ -z "$product" ]] || [[ -z "$release" ]] || [[ -z "$variant" ]]; then
            echo "Invalid lunch combo: $1" 1>&2
            # ...
            return 1
        fi
    fi

    # 说明:多参数形式缺省release和variant时设置当前默认值。
    if [[ -z $legacy ]]; then
        product=$1
        release=$2
        if [[ -z $release ]]; then
            release=trunk_staging
        fi
        variant=$3
        if [[ -z $variant ]]; then
            variant=eng
        fi
    fi

    _lunch_meat $product $release $variant
    _lunch_store_leftovers $product $release $variant
}

这段代码证明 lunch 首先完成参数拆分,再把三元组交给 _lunch_meat 做真实配置校验。

2.4 受控解析实验 ​

为了只验证参数解析而不启动完整构建图,可以在隔离 shell 中把 _lunch_meat 替换成参数记录器。 三种调用得到:

调用ProductReleaseVariant
lunch aosp_arm64aosp_arm64trunk_stagingeng
lunch aosp_arm64 trunk_staging userdebugaosp_arm64trunk_staginguserdebug
lunch aosp_arm64-trunk_staging-userdebugaosp_arm64trunk_staginguserdebug

实验只证明解析和默认值,不证明产品配置一定完整或镜像能够成功构建。

3. 配置提交 ​

参数解析完成后,真正决定 lunch 是否成功的是 _lunch_meat。它把三元组临时放入环境,调用 build_build_var_cache 让 Make/产品配置验证 product、release 和 variant;只有验证成功,才把缓存中的 值导出为当前 shell 的构建环境。

源码文件:build/make/envsetup.sh

相关函数/类型:_lunch_meat

bash
function _lunch_meat()
{
    local product=$1
    local release=$2
    local variant=$3

    TARGET_PRODUCT=$product \
    TARGET_RELEASE=$release \
    TARGET_BUILD_VARIANT=$variant \
    TARGET_BUILD_APPS= \
    build_build_var_cache
    if [ $? -ne 0 ]
    then
        return 1
    fi
    export TARGET_PRODUCT=$(_get_build_var_cached TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(_get_build_var_cached TARGET_BUILD_VARIANT)
    export TARGET_RELEASE=$release
    export TARGET_BUILD_TYPE=release
    set_stuff_for_environment
    destroy_build_var_cache
}

这里的提交边界很重要:校验失败时,函数在导出当前选择之前返回;成功时,set_stuff_for_environment 才把 产品相关工具路径等派生状态写入 shell。destroy_build_var_cache 负责清理本次解析的缓存,避免后续命令 继续读取已经失效的中间值。

读者可以用一个失败输入验证这条边界:运行 lunch not_a_real_product trunk_staging userdebug,断言命令 返回非零;再检查 echo "$TARGET_PRODUCT",不能把失败的候选值当成已提交配置。这个实验只证明 envsetup.sh 的选择与提交边界,不证明某个产品的完整 Soong 图或镜像一定可编译。

4. Product来源 ​

4.1 AndroidProducts ​

产品通常通过 AndroidProducts.mk 暴露:

makefile
# 示例结构
PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/aosp_example.mk

COMMON_LUNCH_CHOICES := \
    aosp_example-user \
    aosp_example-userdebug \
    aosp_example-eng

PRODUCT_MAKEFILES 定义产品名与产品 makefile,COMMON_LUNCH_CHOICES 提供常用组合信息。

4.2 <product>:<path> ​

core/product_config.mk 读取各 AndroidProducts.mk,把产品规范化为 <product>:<path>,并校验 COMMON_LUNCH_CHOICES:

源码文件:build/make/core/product_config.mk

相关函数/类型:_read-ap-file

makefile
define _read-ap-file
  $(eval PRODUCT_MAKEFILES :=) \
  $(eval COMMON_LUNCH_CHOICES :=) \
  $(eval include $(f)) \
  $(foreach p,$(PRODUCT_MAKEFILES),\
    $(eval ap_product_paths += $(call _product-spec,$(p)))) \
  $(eval ap_common_lunch_choices := $(COMMON_LUNCH_CHOICES)) \
  $(eval _products := $(call _first,$(ap_product_paths),:)) \

  # 说明:lunch choice 引用的product必须在当前文件定义。
  $(eval _bad := $(filter-out $(_products),\
    $(call _first,$(ap_common_lunch_choices),-))) \
  $(if $(_bad),$(error COMMON_LUNCH_CHOICES contains \
    products(s) not defined in this file: $(_bad))) \

  # 说明:允许的variant只有eng、userdebug和user。
  $(eval _bad := $(filter-out %-eng %-userdebug %-user,\
    $(ap_common_lunch_choices))) \
  $(if $(_bad),$(error invalid variant in COMMON_LUNCH_CHOICES: $(_bad)))
endef

这说明 lunch 名称不是任意字符串。product 必须能解析到产品配置,variant 也必须属于合法集合。

5. 环境配置 ​

5.1 TARGET_RELEASE ​

TARGET_RELEASE 选择的是 build release config。它可以控制一组 feature flags、aconfig values 和 构建变量。trunk_staging 是当前缺省 release 名之一,但不应被解释为最终产品版本字符串。

Release 配置可以来自 build/release,也可以由产品或 vendor 的 release 目录扩展。Soong 在配置 早期读取与 TARGET_RELEASE 对应的环境默认值。

5.2 Product ​

同一个 product 可以在不同 release 配置下构建;同一个 release 也可以服务多个 product。是否允许 某个组合,由产品配置和 release config 的约束共同决定。

6. user、userdebug ​

6.1 合法 variant ​

core/main.mk 明确限制:

源码文件:build/make/core/main.mk

相关函数/类型:variant validation

makefile
INTERNAL_VALID_VARIANTS := user userdebug eng

ifneq ($(filter-out $(INTERNAL_VALID_VARIANTS),\
                    $(TARGET_BUILD_VARIANT)),)
  $(info Invalid variant: $(TARGET_BUILD_VARIANT))
  $(info Valid values are: $(INTERNAL_VALID_VARIANTS))
  $(error stopping)
endif

不要把 debug、release、production 等普通软件项目术语直接填入 TARGET_BUILD_VARIANT。

6.2 变体测试 ​

源码文件:build/make/core/main.mk

相关函数/类型:tags_to_install

makefile
tags_to_install :=

ifeq ($(TARGET_BUILD_VARIANT),userdebug)
  # 说明:userdebug在user基础上增加debug内容。
  tags_to_install := debug
endif

ifeq ($(TARGET_BUILD_VARIANT),eng)
  # 说明:eng同时选择debug和eng内容。
  tags_to_install := debug eng
endif

Soong 侧还把 variant 投影为布尔配置:

源码文件:build/make/core/soong_config.mk

makefile
$(call add_json_bool, Debuggable,\
    $(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
$(call add_json_bool, Eng,\
    $(filter eng,$(TARGET_BUILD_VARIANT)))

由当前实现可以建立以下稳定矩阵:

Variantdebug tagseng tagsSoong DebuggableSoong Eng常见用途
user否否falsefalse面向正式发布约束
userdebug是否truefalse接近 user 的系统调试
eng是是truetrue平台开发与快速调试

具体系统属性、adb root、SELinux、预装工具和安全限制还可能受 product、release、overlay 和设备配置 影响。不能只根据 variant 名称推导所有最终行为。

6.3 变体选择 ​

7. lunch结果 ​

7.1 lunch校验 ​

源码文件:build/make/envsetup.sh

相关函数/类型:_lunch_meat

bash
function _lunch_meat()
{
    local product=$1
    local release=$2
    local variant=$3

    # 说明:先用三元组查询完整build变量,失败时不应继续当作有效目标。
    TARGET_PRODUCT=$product \
    TARGET_RELEASE=$release \
    TARGET_BUILD_VARIANT=$variant \
    TARGET_BUILD_APPS= \
    build_build_var_cache
    if [ $? -ne 0 ]
    then
        # ...
        return 1
    fi

    # 说明:导出的是构建系统解析后的product和variant。
    export TARGET_PRODUCT=$(_get_build_var_cached TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(_get_build_var_cached TARGET_BUILD_VARIANT)
    export TARGET_RELEASE=$release
    export TARGET_BUILD_TYPE=release
    export TARGET_BUILD_APPS=

    set_stuff_for_environment
    # ...
    printconfig
    # ...
    destroy_build_var_cache
}

TARGET_BUILD_TYPE 在这里固定为字符串 release,它不是 user/userdebug/eng 的同义词。 真正的变体仍由 TARGET_BUILD_VARIANT 表示。

7.2 常用环境确认 ​

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

printconfig

不要只看 shell 命令返回码。应确认三元组和 printconfig 都符合预期,再开始长时间构建。

8. 常见错误与诊断 ​

8.1 旧variant ​

错误:

bash
lunch aosp_arm64-userdebug

当前单参数兼容形式要求三项。推荐改为:

bash
lunch aosp_arm64 trunk_staging userdebug

8.2 variant配置 ​

如果 product 形如 aosp_arm64_userdebug,_lunch_meat 会提示是否应使用连字符。variant 不应编码 进 product 名。

8.3 Product不存在 ​

检查:

bash
list_products
rg 'PRODUCT_MAKEFILES|COMMON_LUNCH_CHOICES' device product vendor \
  -g 'AndroidProducts.mk'

8.4 Release限制 ​

使用 list_releases <product> 查询候选。某些 release config 可以存在,但明确禁止作为 TARGET_RELEASE 使用。

8.5 Variant 非法 ​

bash
list_variants

当前合法值只有 user userdebug eng。

8.6 构建失败 ​

lunch 会调用 Soong/build variable 查询。缺失 host prebuilts、Go 工具、产品 project 或配置依赖时, 三元组解析可能正确,但完整配置仍会失败。应根据首个缺失路径补齐固定 project,而不是把错误归因 于 variant。

9. 三元组配置 ​

lunch 的结果不是一个方便显示的名字,而是后续构建共同消费的配置坐标:

坐标主要决定出错时优先检查
product设备、分区、包集合与产品继承PRODUCT_MAKEFILES、设备目录和产品继承链
release发布配置、flags 与允许组合list_releases 和 release config
variantdebug/eng 内容与 debuggable 投影user、userdebug、eng 的合法性

完成 lunch 后,TARGET_PRODUCT、TARGET_RELEASE、TARGET_BUILD_VARIANT 与 printconfig 应描述同一 组配置。如果 shell 变量与 printconfig 不一致,应先处理环境和产品配置,而不是进入长时间构建。

Product 决定构建什么,release 决定采用哪组发布配置,variant 决定调试内容与构建模式。把三者分开 记录后,后续 m、mm、mmm、Soong 和 Ninja 的输出才具有可比较的配置前提。

10. 三元组验证 ​

先用受控的 _lunch_meat 替身记录 lunch aosp_arm64、显式三元组和单字符串三种输入,断言默认 值分别为 trunk_staging 与 eng,显式输入按原值传递;这证明的是参数解析,不是产品可构建。 然后在真实 checkout 中运行 printconfig,比较 TARGET_PRODUCT、TARGET_RELEASE 和 TARGET_BUILD_VARIANT,并故意传入非法 variant,断言配置阶段拒绝它而不继续进入构建。前一个实验 覆盖入口到参数所有者,后一个覆盖校验失败边界;两者都不能外推到镜像产物或设备行为。