envsetup.sh 中的 lunch 函数分析
- 作者:汪辰
- 联系方式:[email protected] / [email protected]
文章大纲
- 1.
lunch()
函数整体分析 - 2.
print_lunch_menu()
函数分析 - 3.
get_build_var
函数分析 - 4.
set_stuff_for_environment()
函数分析 - 5.
setpaths()
函数分析
注:本文的分析基于 AOSP 12, tag android-12.0.0_r2
function lunch()
{
local answer
if [ "$1" ] ; then
answer=$1
else
# 这里是在屏幕上打印所有的 product-variable
# 注意这里通过调用 print_lunch_menu 函数,后面有专门介绍
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
# 这里首先判断 answer变量是不是为0,如果为零的话,selection 变量就会赋值
# 为默认值 "aosp_arm-eng"
if [ -z "$answer" ]
then
selection=aosp_arm-eng
# 如果不为零的话,首先会输出 answer 的值,使用 echo -n $answer, "-n"
# 选项的作用是的输出的时候不输出换行符
# answer 变量的值并没有输出到屏幕上,而是通过管道传给了后面一条命令:
# grep -q -e "^[0-9][0-9]*$"
# 这条命令从 answer 变量中搜寻以两位数字开头的字符串
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
# 如果找到,就认为是输入的是数字。然后进一步对这个数字做有没有越界的检查。
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
# 如果这个数字小于 LUNCH_MENU_CHOICES 的大小,就会把 LUNCH_MENU_CHOICES
# 的第 $answer-1 项复制给 selection 变量。
if [ $answer -le ${#choices[@]} ]
then
# array in zsh starts from 1 instead of 0.
if [ -n "$ZSH_VERSION" ]
then
selection=${choices[$(($answer))]}
else
selection=${choices[$(($answer-1))]}
fi
fi
else
selection=$answer
fi
# 最终得到的 selection 其实就是一个 product-varient 模式的字符串。
export TARGET_BUILD_APPS=
local product variant_and_version variant version
product=${selection%%-*} # Trim everything after first dash
variant_and_version=${selection#*-} # Trim everything up to first dash
if [ "$variant_and_version" != "$selection" ]; then
variant=${variant_and_version%%-*}
if [ "$variant" != "$variant_and_version" ]; then
version=${variant_and_version#*-}
fi
fi
if [ -z "$product" ]
then
echo
echo "Invalid lunch combo: $selection"
return 1
fi
# 至此得到我们的 product 和 variant
# 以下是为环境变量赋值,主要是针对 TARGET 的,即设备的
TARGET_PRODUCT=$product \
TARGET_BUILD_VARIANT=$variant \
TARGET_PLATFORM_VERSION=$version \
# 注意 build_build_var_cache 函数的实现,本质是调用了 "soong_ui --dumpvars-mode"
build_build_var_cache
if [ $? -ne 0 ]
then
return 1
fi
export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
if [ -n "$version" ]; then
export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
else
unset TARGET_PLATFORM_VERSION
fi
export TARGET_BUILD_TYPE=release
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || echo
# 至此从用户提取 product 和 variant 的工作告一段落
# set_stuff_for_environment 是一个重点函数,后面重点看一下
set_stuff_for_environment
# 打印重要的 config 信息
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || printconfig
destroy_build_var_cache
}
简单总结一下 lunch 的基本流程
- 打印所有的 product-variable(
print_lunch_menu()
) - 读入用户的选择,如果没有则缺省为 "aosp_arm-eng"
- 解析用户的选择后得到 product 和 variable
- 根据 product 和 variable 得到基本的产品相关环境变量
- TARGET_PRODUCT
- TARGET_BUILD_VARIANT
- TARGET_PLATFORM_VERSION
- 获取所有的和 build 相关的环境变量(包括其绝对路径形式)并记录到 cache 中(
build_build_var_cache()
) - 导出 product 相关变量
- TARGET_PRODUCT
- TARGET_BUILD_VARIANT
- TARGET_PLATFORM_VERSION
- TARGET_BUILD_TYPE
- 设置 build 过程中需要用到的环境变量(
set_stuff_for_environment()
),其中主要是通 过setpaths()
设置一些编译工具的路径 - 输出主要的 config 信息(
printconfig()
)
在此过程中,lunch 函数会调用 print_lunch_menu()
、set_stuff_for_environment()
这些重要的函数,而这些函数,譬如 set_stuff_for_environment()
又会调用 setpaths()
, 进而 get_build_var()
。关系大致如下图所示,后面我们专门对这些函数再做分析。
综上所述,lunch 可以有两种用法:
- 如果直接执行 lunch,不带参数,那么 lunch 会给出一个 menu 来与用户交互,提示
Which would you like? [aosp_arm-eng]
, 直接回车则默认为aosp_arm-eng
, 或者输入数字选择。 - 我们也可以直接运行一个带参数的 lunch,比如
lunch s410-eng
或者lunch 22
,效果和上面是一样的。
一个输出的示例,参考 Setup Android 12 on RISC-V,运行 lunch sdk_phone64_riscv64
后
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=12
TARGET_PRODUCT=sdk_phone64_riscv64
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_ARCH=riscv64
TARGET_ARCH_VARIANT=riscv64
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH_VARIANT=riscv64
TARGET_2ND_CPU_VARIANT=generic
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-5.4.0-100-generic-x86_64-Ubuntu-20.04.4-LTS
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=SP1A.210812.016
OUT_DIR=out
PRODUCT_SOONG_NAMESPACES=device/generic/goldfish device/generic/goldfish-opengl hardware/google/camera hardware/google/camera/devices/EmulatedCamera device/generic/goldfish device/generic/goldfish-opengl
============================================
这个函数的逻辑本身很简单,值得注意的是这个函数会执行 get_build_var
这个函数。
function print_lunch_menu()
{
local uname=$(uname)
local choices
# 注意这里 get_build_var COMMON_LUNCH_CHOICES 的调用
# 有关 get_build_var 的分析见下面对该函数的分析。
choices=$(TARGET_BUILD_APPS= TARGET_PRODUCT= TARGET_BUILD_VARIANT= get_build_var COMMON_LUNCH_CHOICES 2>/dev/null)
local ret=$?
echo
echo "You're building on" $uname
echo
if [ $ret -ne 0 ]
then
echo "Warning: Cannot display lunch menu."
echo
echo "Note: You can invoke lunch with an explicit target:"
echo
echo " usage: lunch [target]" >&2
echo
return
fi
echo "Lunch menu... pick a combo:"
local i=1
local choice
for choice in $(echo $choices)
do
echo " $i. $choice"
i=$(($i+1))
done
echo
}
get_build_var()
这个函数会编译生成 soong_ui 这个小程序,然后执行该程序,带入 --dumpvar-mode
选项以及 $1。所以调用 get_build_var COMMON_LUNCH_CHOICES
就等价于调用 build/soong/soong_ui.bash --dumpvar-mode COMMON_LUNCH_CHOICES
,这个命令会打印出 $COMMON_LUNCH_CHOICES
,即所有注册到 lunch 菜单中的 product 名字。
function get_build_var()
{
if [ "$BUILD_VAR_CACHE_READY" = "true" ]
then
eval "echo \"\${var_cache_$1}\""
return
fi
local T=$(gettop)
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP." >&2
return
fi
# 下面这条指令会确保生成 ./out/soong_ui 并执行之
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode $1)
}
所以我们知道在 envsetup.sh
中有很多形如执行 get_build_var AAA
的代码, 相当于执行 ./out/soong_ui --dumpvar-mode AAA
有关 soong_ui
这个函数的分析,可以参考另外一篇笔记 《代码走读:对 soong_ui 的深入理解》
function set_stuff_for_environment()
{
setpaths
set_sequence_number
export ANDROID_BUILD_TOP=$(gettop)
# With this environment variable new GCC can apply colors to warnings/errors
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
}
set_stuff_for_environment()
中最关键的函数是 setpaths
,下面有一章节专门看一下。
其他代码,譬如 set_sequence_number()
的工作只是导出了 BUILD_ENV_SEQUENCE_NUMBER
这个环境变量。其他的工作是导出了 ANDROID_BUILD_TOP
、GCC_COLORS
这些环境变量。
function setpaths()
{
# 此处省略 ......
# 下面的工作主要是会设置两个重要的环境变量
# ANDROID_BUILD_PATHS
# PATH
# 此处省略 ......
# out with the old
if [ -n "$ANDROID_BUILD_PATHS" ] ; then
# 这个语法很奇妙,作用是会将 $ANDROID_BUILD_PATHS 从 PATH 中删除
export PATH=${PATH/$ANDROID_BUILD_PATHS/}
fi
if [ -n "$ANDROID_PRE_BUILD_PATHS" ] ; then
export PATH=${PATH/$ANDROID_PRE_BUILD_PATHS/}
# strip leading ':', if any
export PATH=${PATH/:%/}
fi
# and in with the new
local prebuiltdir=$(getprebuilt)
local gccprebuiltdir=$(get_abs_build_var ANDROID_GCC_PREBUILTS)
# 获取 gcc 的工具链的版本,定义在 make/core/config.mk 中
# defined in core/config.mk
local targetgccversion=$(get_build_var TARGET_GCC_VERSION)
local targetgccversion2=$(get_build_var 2ND_TARGET_GCC_VERSION)
export TARGET_GCC_VERSION=$targetgccversion
# The gcc toolchain does not exists for windows/cygwin. In this case, do not reference it.
export ANDROID_TOOLCHAIN=
export ANDROID_TOOLCHAIN_2ND_ARCH=
local ARCH=$(get_build_var TARGET_ARCH)
local toolchaindir toolchaindir2=
case $ARCH in
x86) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin
;;
# 此处省略 ......
*)
echo "Can't find toolchain for unknown architecture: $ARCH"
toolchaindir=xxxxxxxxx
;;
esac
if [ -d "$gccprebuiltdir/$toolchaindir" ]; then
export ANDROID_TOOLCHAIN=$gccprebuiltdir/$toolchaindir
fi
if [ "$toolchaindir2" -a -d "$gccprebuiltdir/$toolchaindir2" ]; then
export ANDROID_TOOLCHAIN_2ND_ARCH=$gccprebuiltdir/$toolchaindir2
fi
<b>// 至此得到工具的路径 ANDROID_TOOLCHAIN 和 ANDROID_TOOLCHAIN_2ND_ARCH 并导出</b>
export ANDROID_DEV_SCRIPTS=$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin
# add kernel specific binaries
case $(uname -s) in
Linux)
export ANDROID_DEV_SCRIPTS=$ANDROID_DEV_SCRIPTS:$T/prebuilts/misc/linux-x86/dtc:$T/prebuilts/misc/linux-x86/libufdt
;;
*)
;;
esac
ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN
if [ -n "$ANDROID_TOOLCHAIN_2ND_ARCH" ]; then
ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCH
fi
ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_DEV_SCRIPTS:
export ANDROID_BUILD_PATHS
<b>// 更新 ANDROID_BUILD_PATHS,此时追加 ANDROID_TOOLCHAIN、</b>
<b>// ANDROID_TOOLCHAIN_2ND_ARCH 和 ANDROID_DEV_SCRIPTS </b>
# If prebuilts/android-emulator/<system>/ exists, prepend it to our PATH
# to ensure that the corresponding 'emulator' binaries are used.
case $(uname -s) in
Darwin)
ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/darwin-x86_64
;;
Linux)
ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/linux-x86_64
;;
*)
ANDROID_EMULATOR_PREBUILTS=
;;
esac
if [ -n "$ANDROID_EMULATOR_PREBUILTS" -a -d "$ANDROID_EMULATOR_PREBUILTS" ]; then
ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS$ANDROID_EMULATOR_PREBUILTS:
export ANDROID_EMULATOR_PREBUILTS
fi
# Append asuite prebuilts path to ANDROID_BUILD_PATHS.
local os_arch=$(get_build_var HOST_PREBUILT_TAG)
local ACLOUD_PATH="$T/prebuilts/asuite/acloud/$os_arch:"
local AIDEGEN_PATH="$T/prebuilts/asuite/aidegen/$os_arch:"
local ATEST_PATH="$T/prebuilts/asuite/atest/$os_arch:"
export ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS$ACLOUD_PATH$AIDEGEN_PATH$ATEST_PATH
# 还在追加 ANDROID_BUILD_PATHS
# 差不多 ANDROID_BUILD_PATHS 就绪了,添加 ANDROID_BUILD_PATHS 到 PATH
export PATH=$ANDROID_BUILD_PATHS$PATH
# 下面是其他的环境变量导出,和 ANDROID_BUILD_PATHS 已经没什么关系了
# out with the duplicate old
if [ -n $ANDROID_PYTHONPATH ]; then
export PYTHONPATH=${PYTHONPATH//$ANDROID_PYTHONPATH/}
fi
# and in with the new
export ANDROID_PYTHONPATH=$T/development/python-packages:
if [ -n $VENDOR_PYTHONPATH ]; then
ANDROID_PYTHONPATH=$ANDROID_PYTHONPATH$VENDOR_PYTHONPATH
fi
export PYTHONPATH=$ANDROID_PYTHONPATH$PYTHONPATH
export ANDROID_JAVA_HOME=$(get_abs_build_var ANDROID_JAVA_HOME)
export JAVA_HOME=$ANDROID_JAVA_HOME
export ANDROID_JAVA_TOOLCHAIN=$(get_abs_build_var ANDROID_JAVA_TOOLCHAIN)
export ANDROID_PRE_BUILD_PATHS=$ANDROID_JAVA_TOOLCHAIN:
export PATH=$ANDROID_PRE_BUILD_PATHS$PATH
unset ANDROID_PRODUCT_OUT
export ANDROID_PRODUCT_OUT=$(get_abs_build_var PRODUCT_OUT)
export OUT=$ANDROID_PRODUCT_OUT
unset ANDROID_HOST_OUT
export ANDROID_HOST_OUT=$(get_abs_build_var HOST_OUT)
unset ANDROID_SOONG_HOST_OUT
export ANDROID_SOONG_HOST_OUT=$(get_abs_build_var SOONG_HOST_OUT)
unset ANDROID_HOST_OUT_TESTCASES
export ANDROID_HOST_OUT_TESTCASES=$(get_abs_build_var HOST_OUT_TESTCASES)
unset ANDROID_TARGET_OUT_TESTCASES
export ANDROID_TARGET_OUT_TESTCASES=$(get_abs_build_var TARGET_OUT_TESTCASES)
# needed for building linux on MacOS
# TODO: fix the path
#export HOST_EXTRACFLAGS="-I "$T/system/kernel_headers/host_include
}
总结一下 setpaths 的工作, export 如下变量
- PATH
- TARGET_GCC_VERSION
- ANDROID_TOOLCHAIN:这个是形如
- ANDROID_TOOLCHAIN_2ND_ARCH
- ANDROID_DEV_SCRIPTS: 形如
$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin
, 如果是在 Linux 上编译还会补上:$T/prebuilts/misc/linux-x86/dtc:$T/prebuilts/misc/
- ANDROID_EMULATOR_PREBUILTS
- ANDROID_BUILD_PATHS: 这个路径是一个所有和 build 有关的路径的全集,包括:
- ANDROID_BUILD_PATHS
- ANDROID_TOOLCHAIN
- ANDROID_TOOLCHAIN_2ND_ARCH
- ANDROID_DEV_SCRIPTS
- ANDROID_EMULATOR_PREBUILTS
- ACLOUD_PATH
- AIDEGEN_PATH
- ATEST_PATH 注意:ANDROID_BUILD_PATHS 还会被添加到 PATH 中 下面是 python 相关路径
- ANDROID_PYTHONPATH
- PYTHONPATH 下面是 java 相关路径
- ANDROID_JAVA_HOME
- JAVA_HOME
- ANDROID_JAVA_TOOLCHAIN
- ANDROID_PRE_BUILD_PATHS
- 将 ANDROID_PRE_BUILD_PATHS 加入 PATH 设置 aosp 的 out 路径
- ANDROID_PRODUCT_OUT
- OUT
- ANDROID_HOST_OUT
- ANDROID_HOST_OUT_TESTCASES
- ANDROID_TARGET_OUT_TESTCASES
一个例子:参考 $T/out/soong.log
2ND_TARGET_GCC_VERSION
ANDROID_BUILD_PATHS /home/wangchen/ws/aosp/aosp-riscv/aosp/out/soong/host/linux-x86/bin:/home/wangchen/ws/aosp/aosp-riscv/aosp/out/host/linux-x86/bin
COMMON_LUNCH_CHOICES aosp_arm-eng aosp_arm64-eng aosp_blueline-userdebug aosp_bonito-userdebug aosp_car_arm-userdebug aosp_car_arm64-userdebug aosp_car_x86-userdebug aosp_car_x86_64-userdebug aosp_cf_arm64_phone-userdebug aosp_cf_x86_64_phone-userdebug aosp_cf_x86_auto-userdebug aosp_cf_x86_phone-userdebug aosp_cf_x86_tv-userdebug aosp_crosshatch-userdebug aosp_marlin-userdebug aosp_riscv64-eng aosp_sailfish-userdebug aosp_sargo-userdebug aosp_taimen-userdebug aosp_walleye-userdebug aosp_walleye_test-userdebug aosp_x86-eng aosp_x86_64-eng beagle_x15-userdebug fuchsia_arm64-eng fuchsia_x86_64-eng hikey-userdebug hikey64_only-userdebug hikey960-userdebug hikey960_tv-userdebug hikey_tv-userdebug ice910-userdebug m_e_arm-userdebug mini_emulator_arm64-userdebug mini_emulator_x86-userdebug mini_emulator_x86_64-userdebug poplar-eng poplar-user poplar-userdebug qemu_trusty_arm64-userdebug uml-userdebug
HOST_PREBUILT_TAG linux-x86
print
TARGET_ARCH riscv64
TARGET_BUILD_VARIANT eng
TARGET_DEVICE generic_riscv64
TARGET_GCC_VERSION 8.1
TARGET_PLATFORM_VERSION QP1A
TARGET_PRODUCT aosp_riscv64
ANDROID_GCC_PREBUILTS prebuilts/gcc/linux-x86
ANDROID_JAVA_HOME prebuilts/jdk/jdk9/linux-x86
ANDROID_JAVA_TOOLCHAIN prebuilts/jdk/jdk9/linux-x86/bin
ANDROID_PREBUILTS prebuilt/linux-x86
HOST_OUT out/host/linux-x86
HOST_OUT_TESTCASES out/host/linux-x86/testcases
print
PRODUCT_OUT out/target/product/generic_riscv64
TARGET_OUT_TESTCASES out/target/product/generic_riscv64/testcases
PLATFORM_VERSION_CODENAME REL
PLATFORM_VERSION 10
TARGET_PRODUCT aosp_riscv64
TARGET_BUILD_VARIANT eng
TARGET_BUILD_TYPE release
TARGET_BUILD_APPS
TARGET_ARCH riscv64
TARGET_ARCH_VARIANT riscv64
TARGET_CPU_VARIANT generic
TARGET_2ND_ARCH
TARGET_2ND_ARCH_VARIANT
TARGET_2ND_CPU_VARIANT
HOST_ARCH x86_64
HOST_2ND_ARCH x86
HOST_OS linux
HOST_OS_EXTRA Linux-4.15.0-144-generic-x86_64-Ubuntu-18.04.6-LTS
HOST_CROSS_OS windows
HOST_CROSS_ARCH x86
HOST_CROSS_2ND_ARCH x86_64
HOST_BUILD_TYPE release
BUILD_ID QP1A.191105.004
AUX_OS_VARIANT_LIST
TARGET_BUILD_PDK
PDK_FUSION_PLATFORM_ZIP
PRODUCT_SOONG_NAMESPACES