源码基于:Android R
0. 前言
今天在编译项目的时候,想看看 envsetup.sh 中变化了些什么,才想起来编译专栏中好像没有详解该脚本,索性现在空余时间比较多,整理一下方便以后查看。
Android envsetup.sh 为编译前的准备工作,提供 lunch、m、mm等命令函数定义,是整个Android 编译系统的第一步。该文件一共1700 行左右,简单总结出两件事情:
- 函数定义
- 生成编译配置列表
本文会结合代码对 envsetup.sh 进行详细的剖析,由于函数比较多,本文会进行长期地补充和维护。
1. source envsetup.sh
当运行该脚本的时候终端提示:
including vendor/xxx/common/vendorsetup.sh
clang compdb: ln -s out/soong/development/ide/compdb/compile_commands.json $ANDROID_BUILD_TOP
including vendor/qqq/opensource/core-utils/vendorsetup.sh
including vendor/qqq/proprietary/common/vendorsetup.sh
那执行该脚本的时候到底做了些什么呢?
build/envsetup.sh
validate_current_shell
source_vendorsetup
addcompletions
执行该程序共运行三个函数,分别是 validate_current_shell、source_vendorsetup、addcompletions。下面来看下这三个函数做了些什么事情?
1.1 validate_current_shell
function validate_current_shell() {
local current_sh="$(ps -o command -p $$)"
case "$current_sh" in
*bash*)
function check_type() { type -t "$1"; }
;;
*zsh*)
function check_type() { type "$1"; }
enable_zsh_completion ;;
*)
echo -e "WARNING: Only bash and zsh are supported.\nUse of other shell would lead to erroneous results."
;;
esac
}
主要是测试下当前的shell 环境是否可用,对于 zsh 环境需要特殊处理,其他环境会其实warning。
1.2 source_vendorsetup
function source_vendorsetup() {
unset VENDOR_PYTHONPATH
allowed=
for f in $(find -L device vendor product -maxdepth 4 -name 'allowed-vendorsetup_sh-files' 2>/dev/null | sort); do
if [ -n "$allowed" ]; then
echo "More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"
echo " $allowed"
echo " $f"
return
fi
allowed="$f"
done
allowed_files=
[ -n "$allowed" ] && allowed_files=$(cat "$allowed")
for dir in device vendor product; do
for f in $(test -d $dir && \
find -L $dir -maxdepth 4 -name 'vendorsetup.sh' 2>/dev/null | sort); do
if [[ -z "$allowed" || "$allowed_files" =~ $f ]]; then
echo "including $f"; . "$f"
else
echo "ignoring $f, not in $allowed"
fi
done
done
}
- 首先,查找 device、vendor、product 目录下最大深度为4 的子目录中,是否存在 allowed-vendorsetup_sh-files 文件并排序,但这个文件只能1个,不然会报错退出;
- 接着,在device、vendor、product 中同样查找最大深度为 4 的子目录中,是否存在 vendorsetup.sh,打印 includeing ... ,然后运行该 vendorsetup.sh 脚本,这些脚本大多是配置一些环境变量;
例如上面运行 envsetup.sh 时打印 including vendor/xxx/common/vendorsetup.sh,就是在 vendor/xxx/common 目录下找到了 vendorsetup.sh,运行的时候会配置一些环境变量,并打印:
clang compdb: ln -s out/soong/development/ide/compdb/compile_commands.json
1.3 addcompletions
function addcompletions()
{
local T dir f
#避免不在 bash 或 zsh 的环境中运行
#需要提前指定这两个环境变量,如果没有指定,该函数return
if [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; then
return
fi
#避免运行在太久的版本中,这里是小于 3 的版本
if [ -n "$BASH_VERSION" -a ${BASH_VERSINFO[0]} -lt 3 ]; then
return
fi
local completion_files=(
system/core/adb/adb.bash
system/core/fastboot/fastboot.bash
tools/asuite/asuite.sh
)
#确认上面几个脚本是否存在,通过should_add_completion确认该脚本是否在白名单中
for f in ${completion_files[*]}; do
if [ -f "$f" ] && should_add_completion "$f"; then
. $f
fi
done
if should_add_completion bit ; then
complete -C "bit --tab" bit
fi
if [ -z "$ZSH_VERSION" ]; then
# Doesn't work in zsh.
complete -o nospace -F _croot croot
fi
complete -F _lunch lunch
complete -F _complete_android_module_names dumpmod
complete -F _complete_android_module_names pathmod
complete -F _complete_android_module_names gomod
complete -F _complete_android_module_names m
}
详细的 complete 命令可以另外查找,最后就是把一些命令进行补齐的设置。
2. 辅助函数
2.1 hmm
hmm 函数输出 envsetupsh.sh 的帮助说明,执行 build/envsetup.sh 后可以调用的操作总结:
function hmm() {
cat <<EOF
Run "m help" for help with the build system itself.
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch: lunch <product_name>-<build_variant>
Selects <product_name> as the product to build, and <build_variant> as the variant to
build, and stores those selections in the environment to be read by subsequent
invocations of 'm' etc.
- tapas: tapas [<App1> <App2> ...] [arm|x86|mips|arm64|x86_64|mips64] [eng|userdebug|user]
- croot: Changes directory to the top of the tree, or a subdirectory thereof.
- m: Makes from the top of the tree.
- mm: Builds and installs all of the modules in the current directory, and their
dependencies.
- mmm: Builds and installs all of the modules in the supplied directories, and their
dependencies.
To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma: Same as 'mm'
- mmma: Same as 'mmm'
- provision: Flash device with all required partitions. Options will be passed on to fastboot.
- cgrep: Greps on all local C/C++ files.
- ggrep: Greps on all local Gradle files.
- gogrep: Greps on all local Go files.
- jgrep: Greps on all local Java files.
- resgrep: Greps on all local res/*.xml files.
- mangrep: Greps on all local AndroidManifest.xml files.
- mgrep: Greps on all local Makefiles and *.bp files.
- owngrep: Greps on all local OWNERS files.
- sepgrep: Greps on all local sepolicy files.
- sgrep: Greps on all local source files.
- godir: Go to the directory containing a file.
- allmod: List all modules. or List all modules inside certain directory.
- gomod: Go to the directory containing a module.
- dumpmod: Get all info of specific modules from \$ANDROID_PRODUCT_OUT/module-info.json
- pathmod: Get the directory containing a module.
- refreshmod: Refresh list of modules for allmod/gomod.
- ninja-build: Bypass ninja generate procedure, directly use ninja to build a target or module, also build droid is support too by no param given.
- ninja-query: Query input and output of a ninja target, this is the most powerful tool when digging the compile steps.
- ninja-commands: Print all build commands, like a --just-print version of ninja-build, also known as the --dry-run purpose
- mod: Analyze $OUT/module-info.json with parameters, see mod -h for help
Environment options:
- SANITIZE_HOST: Set to 'address' to use ASAN for all host modules.
- ANDROID_QUIET_BUILD: set to 'true' to display only the essential messages.
Look at the source to view more functions. The complete list is:
EOF
local T=$(gettop)
local A=""
local i
for i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
A="$A $i"
done
echo $A
}
最后还列出了完整的函数名,通过:
sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p"
但其操作有一个 bug,用于匹配函数的正则表达式 function \([a-z_]*\).* 会漏掉函数 is64bit();
将匹配模式从 function \([a-z_]*\).*/ 修改为 function \([a-z_]\w*\).* 就可以匹配文件中的所有函数了。
2.2 gettop
gettop 函数,从指定的 $TOP 目录或者当前目录开始查找 build/make/core/envsetup.mk,并将能找到该文件的目录返回个调用函数作为操作的根目录:
function gettop
{
local TOPFILE=build/make/core/envsetup.mk
#如果编译环境已经设置了 $TOP,就检查 $TOP/build/make/core/envsetup.mk文件是否存在
if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
# The following circumlocution ensures we remove symlinks from TOP.
#跳转到$TOP 目录,并pwd将$TOP 目录指向的真实路径存放到PWD中
(cd $TOP; PWD= /bin/pwd)
else
#如果当前路径下能找到 build/make/core/envsetup.mk文件,则将当前目录的真实路径存放到PWD中
if [ -f $TOPFILE ] ; then
PWD= /bin/pwd
else
#如果当前目录下无法找到build/make/core/envsetup.mk文件,则不断返回到外层目录查找,
#直至到根目录为止
local HERE=$PWD
local T=
while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
\cd ..
T=`PWD= /bin/pwd -P`
done
#查找完后回到之前操作的路径
\cd $HERE
#如果目录$T包含了build/make/core/envsetup.mk,则说明$T是编译的根目录
if [ -f "$T/$TOPFILE" ]; then
echo $T
fi
fi
fi
}
2.3 croot
croot 命令用以将当前目录切换到当前编译环境的根目录。
当然croot 之后可以跟一个参数标记切到根目录之后的下一级目录,例如 croot device 命令。
详细可以查看代码:
function croot()
{
local T=$(gettop)
if [ "$T" ]; then
if [ "$1" ]; then
\cd $(gettop)/$1
else
\cd $(gettop)
fi
else
echo "Couldn't locate the top of the tree. Try setting TOP."
fi
}
2.4 cproj
cproj 命令用于切换到当前模块的编译目录下(含Android.mk的目录下)
function cproj()
{
local TOPFILE=build/make/core/envsetup.mk
local HERE=$PWD #临时保存当前目录
local T=
#当前目录下build/make/core/envsetup.mk不存在,即当前不是编译根目录,且
#当前目录不是系统根目录
while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
T=$PWD
if [ -f "$T/Android.mk" ]; then #如果该目录下有Android.mk文件,则cd过去
\cd $T
return
fi
\cd ..
done
\cd $HERE #恢复之前的目录
echo "can't find Android.mk"
}
如上述命令,如果无法找到该模块下的 Android.mk,就会提示:
can't find Android.mk
例如在 packages/apps/Launcher3/protos/ 目录下运行 cproj 命令,则会退到 packages/apps/Launcher3/ 目录下。
2.5 getprebuilt
function getprebuilt
{
get_abs_build_var ANDROID_PREBUILTS
}
function get_abs_build_var()
{
if [ "$BUILD_VAR_CACHE_READY" = "true" ]
then
eval "echo \"\${abs_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
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode --abs $1)
}
函数 get_abs_build_var() 用于查找编译时变量值,这里通过该函数查找变量是 ANDROID_PREBUILTS 这个绝对路径为 $TOP/prebuilt/linux-x86
2.6 setpaths
主要是配置一些环境变量,在lunch 函数或者 choosecombo 函数最后调用:
function setpaths()
{
local T=$(gettop)
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP."
return
fi
##################################################################
# #
# Read me before you modify this code #
# #
# This function sets ANDROID_BUILD_PATHS to what it is adding #
# to PATH, and the next time it is run, it removes that from #
# PATH. This is required so lunch can be run more than once #
# and still have working paths. #
# #
##################################################################
# Note: on windows/cygwin, ANDROID_BUILD_PATHS will contain spaces
# due to "C:\Program Files" being in the path.
# out with the old
if [ -n "$ANDROID_BUILD_PATHS" ] ; then
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)
# 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
;;
x86_64) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin
;;
arm) toolchaindir=arm/arm-linux-androideabi-$targetgccversion/bin
;;
arm64) toolchaindir=aarch64/aarch64-linux-android-$targetgccversion/bin;
toolchaindir2=arm/arm-linux-androideabi-$targetgccversion2/bin
;;
mips|mips64) toolchaindir=mips/mips64el-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
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
# Append llvm binutils prebuilts path to ANDROID_BUILD_PATHS.
local ANDROID_LLVM_BINUTILS=$(get_abs_build_var ANDROID_CLANG_PREBUILTS)/llvm-binutils-stable
ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_LLVM_BINUTILS
# Set up ASAN_SYMBOLIZER_PATH for SANITIZE_HOST=address builds.
export ASAN_SYMBOLIZER_PATH=$ANDROID_LLVM_BINUTILS/llvm-symbolizer
# 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:
export PATH=$ANDROID_BUILD_PATHS$PATH
# 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_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
}
首先确认 gettop 是否能找到编译的根目录,如果找不到,则退出该函数;
接着export 一些环境变量:
- TARGET_GCC_VERSION
- ANDROID_DEV_SCRIPTS
- ANDROID_TOOLCHAIN_2ND_ARCH
- ANDROID_BUILD_PATHS
- ASAN_SYMBOLIZER_PATH
- ANDROID_EMULATOR_PREBUILTS
- PYTHONPATH
- ANDROID_PYTHONPATH
- ANDROID_JAVA_HOME
- JAVA_HOME
- ANDROID_JAVA_TOOLCHAIN
- ANDROID_PRE_BUILD_PATHS
- ANDROID_PRODUCT_OUT
- OUT
- ANDROID_HOST_OUT
- ANDROID_HOST_OUT_TESTCASES
- ANDROID_TARGET_OUT_TESTCASES
2.7 set_sequence_number
function set_sequence_number()
{
export BUILD_ENV_SEQUENCE_NUMBER=13
}
指定环境变量 BUILD_ENV_SEQUENCE_NUMBER,后面 buildspec.mk 中会确认该环境变量的值与CORRECT_BUILD_ENV_SEQUENCE_NUMBER 相等。
2.8 set_stuff_for_environment
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'
}
调用 2.6 和 2.7 节的函数,并设定环境变量 ANDROID_BUILD_TOP 和 GCC_COLORS。
之后调用函数 gettop 打印出来的路径,通过 ANDROID_BUILD_TOP 就可以获知。
该函数通常是在choosetype、chooseproduct、lunch 函数中调用。
2.9 printconfig
function printconfig()
{
local T=$(gettop)
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP." >&2
return
fi
get_build_var report_config
}
调用get_build_var 函数将lunch 之后的编译配置信息:
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=11
TARGET_PRODUCT=shift
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=cortex-a55
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv8-a
TARGET_2ND_CPU_VARIANT=cortex-a55
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-4.15.0-142-generic-x86_64-Ubuntu-16.04.5-LTS
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=RD2A.211001.002
OUT_DIR=out
PRODUCT_SOONG_NAMESPACES=vendor/qqq/opensource/commonsys/packages/apps/Bluetooth vendor/qqq/opensource/commonsys/system/bt/conf external/v4l2_codec2
============================================
通常在 lunch 函数设定完环境变量后会调用该函数进行 config 的打印。详细看 lunch 函数。
2.10 gettargetarch
function gettargetarch
{
get_build_var TARGET_ARCH
}
通过 get_build_var 函数获取编译目标系统的 CPU 架构,如arm64
2.11 godir
function godir () {
if [[ -z "$1" ]]; then
echo "Usage: godir <regex>"
return
fi
local T=$(gettop)
local FILELIST
if [ ! "$OUT_DIR" = "" ]; then
mkdir -p $OUT_DIR
FILELIST=$OUT_DIR/filelist
else
FILELIST=$T/filelist
fi
if [[ ! -f $FILELIST ]]; then
echo -n "Creating index..."
(\cd $T; find . -wholename ./out -prune -o -wholename ./.repo -prune -o -type f > $FILELIST)
echo " Done"
echo ""
fi
local lines
lines=($(\grep "$1" $FILELIST | sed -e 's/\/[^/]*$//' | sort | uniq))
if [[ ${#lines[@]} = 0 ]]; then
echo "Not found"
return
fi
local pathname
local choice
if [[ ${#lines[@]} > 1 ]]; then
while [[ -z "$pathname" ]]; do
local index=1
local line
for line in ${lines[@]}; do
printf "%6s %s\n" "[$index]" $line
index=$(($index + 1))
done
echo
echo -n "Select one: "
unset choice
read choice
if [[ $choice -gt ${#lines[@]} || $choice -lt 1 ]]; then
echo "Invalid choice"
continue
fi
pathname=${lines[@]:$(($choice-1)):1}
done
else
pathname=${lines[@]:0:1}
fi
\cd $T/$pathname
}
这是一个很使用的函数,可以快速的跳转到所指向的目标目录。该命令需要一个目标文件名作为命令行参数。
- 首先需要创建一个工程文件的filelist,该filelist 默认是放在 gettop 函数得到的工程根目录。当然如果有 OUT_DIR 指定了,那么filelist 会放在该目录下;
- 如果 filelist 文件不存在,会创建并打印 "Creating index...";
- filelist 创建好后,通过 grep和sed 命令从 filelist 中查找所需要指向的目标文件所在的目录,并进行排序;
- 如果没有找到指定的目标文件,则会打印 "Not found" 并返回;
- 如果查找到有指定目标文件,则通过 while 循环将包含该文件的目录打印出来,并打印 "Select one: ",提醒工程师选择;
- 选择 pathname,cd TOP/pathname;
3. 编译函数
3.1 build_build_var_cache
该函数会在 set_stuff_for_environment 函数之前调用,set_stuff_for_environment 详细看第 2.8 节。
该函数用以将 envsetup.sh 中使用函数 get_build_var、get_abs_build_var 查询的变量都收集到 cached_vars 和 cached_abs_vars 中,然后通过 soong_ui.bash 脚本创建键值对。
function build_build_var_cache()
{
local T=$(gettop)
# Grep out the variable names from the script.
cached_vars=(`cat $T/build/envsetup.sh | tr '()' ' ' | awk '{for(i=1;i<=NF;i++) if($i~/get_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
cached_abs_vars=(`cat $T/build/envsetup.sh | tr '()' ' ' | awk '{for(i=1;i<=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
# Call the build system to dump the "<val>=<value>" pairs as a shell script.
build_dicts_script=`\builtin cd $T; build/soong/soong_ui.bash --dumpvars-mode \
--vars="${cached_vars[*]}" \
--abs-vars="${cached_abs_vars[*]}" \
--var-prefix=var_cache_ \
--abs-var-prefix=abs_var_cache_`
local ret=$?
if [ $ret -ne 0 ]
then
unset build_dicts_script
return $ret
fi
# Execute the script to store the "<val>=<value>" pairs as shell variables.
eval "$build_dicts_script"
ret=$?
unset build_dicts_script
if [ $ret -ne 0 ]
then
return $ret
fi
BUILD_VAR_CACHE_READY="true"
}
如果在 eval 之前将 $build_dicts_script 打印出来,结果大致如下:
var_cache_2ND_TARGET_GCC_VERSION='4.9'
var_cache_ANDROID_BUILD_PATHS='/home/justinwei/work/myproduct/android/out/soong/host/linux-x86/bin:/home/justinwei/work/myproduct/android/out/host/linux-x86/bin'
var_cache_COMMON_LUNCH_CHOICES='C3MN-userdebug aosp_arm-eng aosp_arm64-eng aosp_blueline_car-userdebug aosp_bonito_car-userdebug aosp_car_arm-userdebug aosp_car_arm64-userdebug aosp_car_x86-userdebug aosp_car_x86_64-userdebug aosp_cf_arm64_auto-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_coral_car-userdebug aosp_crosshatch_car-userdebug aosp_flame_car-userdebug aosp_x86-eng aosp_x86_64-eng arm_krait-eng arm_v7_v8-eng armv8-eng armv8_kryo385-eng car_x86_64-userdebug shift-userdebug shift_global-userdebug shift_in-userdebug shiftevb-userdebug qemu_trusty_arm64-userdebug silvermont-eng'
var_cache_HOST_PREBUILT_TAG='linux-x86'
var_cache_TARGET_ARCH='arm64'
var_cache_TARGET_BUILD_VARIANT='user'
var_cache_TARGET_DEVICE='shift'
var_cache_TARGET_GCC_VERSION='4.9'
var_cache_TARGET_PLATFORM_VERSION='RP1A'
var_cache_TARGET_PRODUCT='shift'
var_cache_print=''
var_cache_report_config='============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=11
TARGET_PRODUCT=shift
TARGET_BUILD_VARIANT=user
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=cortex-a55
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv8-a
TARGET_2ND_CPU_VARIANT=cortex-a55
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-5.10.102.1-microsoft-standard-WSL2-x86_64-Ubuntu-18.04.2-LTS
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=RD2A.211001.002
OUT_DIR=out
PRODUCT_SOONG_NAMESPACES=vendor/qcom/opensource/commonsys/packages/apps/Bluetooth vendor/qcom/opensource/commonsys/system/bt/conf
============================================'
abs_var_cache_ANDROID_CLANG_PREBUILTS='/home/justinwei/work/myproduct/android/prebuilts/clang/host/linux-x86'
abs_var_cache_ANDROID_GCC_PREBUILTS='/home/justinwei/work/myproduct/android/prebuilts/gcc/linux-x86'
abs_var_cache_ANDROID_JAVA_HOME='/home/justinwei/work/myproduct/android/prebuilts/jdk/jdk11/linux-x86'
abs_var_cache_ANDROID_JAVA_TOOLCHAIN='/home/justinwei/work/myproduct/android/prebuilts/jdk/jdk11/linux-x86/bin'
abs_var_cache_ANDROID_PREBUILTS='/home/justinwei/work/myproduct/android/prebuilt/linux-x86'
abs_var_cache_HOST_OUT='/home/justinwei/work/myproduct/android/out/host/linux-x86'
abs_var_cache_HOST_OUT_TESTCASES='/home/justinwei/work/myproduct/android/out/host/linux-x86/testcases'
3.2 get_abs_build_var
function get_abs_build_var()
{
if [ "$BUILD_VAR_CACHE_READY" = "true" ]
then
eval "echo \"\${abs_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
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode --abs $1)
}
通过 soog_ui.bash 解析需要查找的变量,该变量已经在之前的 build_build_var_cache 函数中创建了键值对,详细看第 3.1 节。
这些变量都是以 abs_var_cache_ 作为前缀,详细看第 3.1 节中打印的情况。
3.3 get_build_var
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
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode $1)
}
该函数与上面的 get_abs_build_var 差不多的,区别在于 get_abs_build_var 指定的变量都是以 abs_var_cache_ 为前缀,而get_build_var 指定的变量都是以 var_cache_ 为前缀。
3.4 print_lunch_menu
function print_lunch_menu()
{
local uname=$(uname)
local choices=$(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES)
echo
echo "You're building on" $uname
echo
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
}
打印 lunch 可选的 combo:
You're building on Linux
Lunch menu... pick a combo:
1. C3MN-userdebug
2. aosp_arm-eng
3. aosp_arm64-eng
4. aosp_blueline_car-userdebug
5. aosp_bonito_car-userdebug
6. aosp_car_arm-userdebug
7. aosp_car_arm64-userdebug
8. aosp_car_x86-userdebug
9. aosp_car_x86_64-userdebug
10. aosp_cf_arm64_auto-userdebug
11. aosp_cf_arm64_phone-userdebug
12. aosp_cf_x86_64_phone-userdebug
13. aosp_cf_x86_auto-userdebug
14. aosp_cf_x86_phone-userdebug
15. aosp_cf_x86_tv-userdebug
16. aosp_coral_car-userdebug
17. aosp_crosshatch_car-userdebug
18. aosp_flame_car-userdebug
3.5 lunch
function lunch()
{
local answer
if [ "$1" ] ; then
answer=$1
else
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
if [ -z "$answer" ]
then
selection=aosp_arm-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
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
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
TARGET_PRODUCT=$product \
TARGET_BUILD_VARIANT=$variant \
TARGET_PLATFORM_VERSION=$version \
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
echo
set_stuff_for_environment
printconfig
destroy_build_var_cache
}
函数是android 系统编译之前的核心操作,大致流程如下:
- lunch 有参数否?没有的话 print_lunch_menu,工程师进行选择;
- 根据选择,确定 build variant;
- 根据选择,确定 build product;
- build_build_var_cache 函数调用,配置编译所需变量,详细看第 3.1 节;
- 设置环境变量 TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_PLATFORM_VERSION、TARGET_BUILD_TYPE;
- set_stuff_for_environment 设置其他环境变量,详细看第 2.8 节;
- printconfig 将 lunch配置信息打印出来,详细看第 2.9 节;
- destroy_build_var_cache 销毁build_build_var_cache 创建的变量键值对;
3.6 _trigger_build
在分析 m、mm、mmm 等函数之前,需要了解下 _trigger_build 这个函数:
function _trigger_build()
(
local -r bc="$1"; shift
if T="$(gettop)"; then
_wrap_build "$T/build/soong/soong_ui.bash" --build-mode --${bc} --dir="$(pwd)" "$@"
else
echo "Couldn't locate the top of the tree. Try setting TOP."
fi
)
主要调用 $TOP/build/soong/soong_ui.bash 脚本进行编译,然后通过 _wrap_build 函数对结果进行特殊显示,详细见下面。
3.6.1 _wrap_build
function _wrap_build()
{
if [[ "${ANDROID_QUIET_BUILD:-}" == true ]]; then
"$@"
return $?
fi
local start_time=$(date +"%s")
"$@"
local ret=$?
local end_time=$(date +"%s")
local tdiff=$(($end_time-$start_time))
local hours=$(($tdiff / 3600 ))
local mins=$((($tdiff % 3600) / 60))
local secs=$(($tdiff % 60))
local ncolors=$(tput colors 2>/dev/null)
if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
color_failed=$'\E'"[0;31m"
color_success=$'\E'"[0;32m"
color_reset=$'\E'"[00m"
else
color_failed=""
color_success=""
color_reset=""
fi
echo
if [ $ret -eq 0 ] ; then
echo -n "${color_success}#### build completed successfully "
else
echo -n "${color_failed}#### failed to build some targets "
fi
if [ $hours -gt 0 ] ; then
printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs
elif [ $mins -gt 0 ] ; then
printf "(%02g:%02g (mm:ss))" $mins $secs
elif [ $secs -gt 0 ] ; then
printf "(%s seconds)" $secs
fi
echo " ####${color_reset}"
echo
return $ret
}
主要处理结果,以及结果的颜色效果、编译用时。
3.7 m 和mm系列
function m()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "all-modules" "$@"
)
function mm()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "modules-in-a-dir-no-deps" "$@"
)
function mmm()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "modules-in-dirs-no-deps" "$@"
)
function mma()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "modules-in-a-dir" "$@"
)
function mmma()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "modules-in-dirs" "$@"
)
- m,从根部开始编译所有的模块,m 也可以指定module,编译直接用 m 命令即可;
- mm,编译当前目录下所有的模块,当前目录下需要有Androd.mk,否则会往上一级找,只编译当前模块,不会编译依赖模块;
- mmm,同mm,编译指定目录下的所有模块;
- mma,同mm,需要编译依赖模块;
- mmma,同mmm,需要编译依赖模块;
4. 搜索函数
function ggrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.gradle" \
-exec grep --color -n "$@" {} +
}
function gogrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.go" \
-exec grep --color -n "$@" {} +
}
function jgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.java" \
-exec grep --color -n "$@" {} +
}
function cgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
-exec grep --color -n "$@" {} +
}
function resgrep()
{
local dir
for dir in `find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -name res -type d`; do
find $dir -type f -name '*\.xml' -exec grep --color -n "$@" {} +
done
}
function mangrep()
{
find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -type f -name 'AndroidManifest.xml' \
-exec grep --color -n "$@" {} +
}
function owngrep()
{
find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -type f -name 'OWNERS' \
-exec grep --color -n "$@" {} +
}
function sepgrep()
{
find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -name sepolicy -type d \
-exec grep --color -n -r --exclude-dir=\.git "$@" {} +
}
function rcgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.rc*" \
-exec grep --color -n "$@" {} +
}
搜索的几个函数基本相同,都是使用的 find 命令和 grep 命令结合。
首先, find 会排除一些文件夹:
- .repo
- .git
- out
接着,find 会指定文件类型:
- ggrep 针对 *.gradle 文件
- gogreap 针对 *.go 文件
- jgrep 针对 *.java 文件
- cgrep 针对 *.c *.cc *.cpp *.h *.hpp
- resgrep 针对目录中含有 *.xml
- mangrep 针对 AndroidManifest.xml 文件
- owngrep 针对 OWNERS 文件
- sepgrep 针对名为 sepolicy 的目录
- rcgrep 针对 *.rc 的文件
最后,find 通过 -exec 执行 grep 命令