lunch目标与编译变体
lunch 不执行编译,它选择后续构建使用的配置。Android 17 中,这个配置由三个相互独立的维度组成:
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,需要先加载:
source build/envsetup.sh这一步会增加构建辅助命令和补全,并准备查询 build variables 的函数。它不会自动选择产品,也不会 开始编译。
1.2 TARGET_PRODUCT
| 维度 | 环境变量 | 回答的问题 |
|---|---|---|
| Product | TARGET_PRODUCT | 构建哪个产品配置 |
| Release | TARGET_RELEASE | 应用哪组发布配置和 flags |
| Variant | TARGET_BUILD_VARIANT | 使用 user、userdebug 还是 eng |
三者共同决定最终配置。只写“构建 aosp_arm64”并不能完整说明 release 与调试变体。
2. lunch参数
2.1 lunch参数入口
lunch TARGET_PRODUCT [TARGET_RELEASE [TARGET_BUILD_VARIANT]]例如:
lunch aosp_arm64 trunk_staging userdebug如果省略 release 和 variant:
lunch aosp_arm64当前实现默认:
TARGET_RELEASE=trunk_staging
TARGET_BUILD_VARIANT=eng只省略 variant 时:
lunch aosp_arm64 trunk_stagingvariant 仍默认为 eng。
2.2 _lunch_usage
lunch aosp_arm64-trunk_staging-userdebugAndroid 17 的 _lunch_usage 将它称为 legacy format,要求:
<product>-<release>-<variant>不要继续使用旧的 aosp_arm64-userdebug 心智模型。产品、发布配置和变体已经是三个字段。
2.3 参数解析源码
源码文件:build/make/envsetup.sh
相关函数/类型:lunch
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 替换成参数记录器。 三种调用得到:
| 调用 | Product | Release | Variant |
|---|---|---|---|
lunch aosp_arm64 | aosp_arm64 | trunk_staging | eng |
lunch aosp_arm64 trunk_staging userdebug | aosp_arm64 | trunk_staging | userdebug |
lunch aosp_arm64-trunk_staging-userdebug | aosp_arm64 | trunk_staging | userdebug |
实验只证明解析和默认值,不证明产品配置一定完整或镜像能够成功构建。
3. 配置提交
参数解析完成后,真正决定 lunch 是否成功的是 _lunch_meat。它把三元组临时放入环境,调用 build_build_var_cache 让 Make/产品配置验证 product、release 和 variant;只有验证成功,才把缓存中的 值导出为当前 shell 的构建环境。
源码文件:build/make/envsetup.sh
相关函数/类型:_lunch_meat
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 暴露:
# 示例结构
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/aosp_example.mk
COMMON_LUNCH_CHOICES := \
aosp_example-user \
aosp_example-userdebug \
aosp_example-engPRODUCT_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
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
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
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
endifSoong 侧还把 variant 投影为布尔配置:
源码文件:build/make/core/soong_config.mk
$(call add_json_bool, Debuggable,\
$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
$(call add_json_bool, Eng,\
$(filter eng,$(TARGET_BUILD_VARIANT)))由当前实现可以建立以下稳定矩阵:
| Variant | debug tags | eng tags | Soong Debuggable | Soong Eng | 常见用途 |
|---|---|---|---|---|---|
user | 否 | 否 | false | false | 面向正式发布约束 |
userdebug | 是 | 否 | true | false | 接近 user 的系统调试 |
eng | 是 | 是 | true | true | 平台开发与快速调试 |
具体系统属性、adb root、SELinux、预装工具和安全限制还可能受 product、release、overlay 和设备配置 影响。不能只根据 variant 名称推导所有最终行为。
6.3 变体选择
7. lunch结果
7.1 lunch校验
源码文件:build/make/envsetup.sh
相关函数/类型:_lunch_meat
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 常用环境确认
echo "$TARGET_PRODUCT"
echo "$TARGET_RELEASE"
echo "$TARGET_BUILD_VARIANT"
echo "$TARGET_BUILD_TYPE"
printconfig不要只看 shell 命令返回码。应确认三元组和 printconfig 都符合预期,再开始长时间构建。
8. 常见错误与诊断
8.1 旧variant
错误:
lunch aosp_arm64-userdebug当前单参数兼容形式要求三项。推荐改为:
lunch aosp_arm64 trunk_staging userdebug8.2 variant配置
如果 product 形如 aosp_arm64_userdebug,_lunch_meat 会提示是否应使用连字符。variant 不应编码 进 product 名。
8.3 Product不存在
检查:
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 非法
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 |
| variant | debug/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,断言配置阶段拒绝它而不继续进入构建。前一个实验 覆盖入口到参数所有者,后一个覆盖校验失败边界;两者都不能外推到镜像产物或设备行为。
