Android编译流程(一):envsetup.sh文件解析
目录
(一)开发环境
CPU:Freescale I.MX 8M Mini
Android平台版本:Android 9.0.0(Pie)
kernel版本:4.14.98
uboot版本:2018.03
Android repo源码版本:imx-p9.0.0_2.0.0-ga
(二)概述
当需要编译Android源码时,第一步基本上都是:
source build/envsetup.sh
这一步的目的是将build/envsetup.sh这个文件中的环境变量和函数导出到shell终端环境,在这个文件中,绝大部分的代码都是关于函数的,使用以下命令:
sed -n "/^[[:blank:]]*function /s/function \([a-z_]\w*\).*/\1/p" < build/envsetup.sh | wc -l
75
能得到函数个数为75个。逐个函数分析太费时费力了,本文挑部分个人觉得重要的进行讲解。
(三)函数
在envsetup.sh中,最值得关注的几个函数分别是lunch、mm、mmm,这些都是在编译调试的时候经常用到的,但是除此之外的一些函数在我们调试过程中也能够提供很大的帮助,以下是一些介绍。
hmm
hmm函数是打印帮助信息的函数,函数定义如下:
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.
- m: Makes from the top of the tree.
- mm: Builds all of the modules in the current directory, but not their dependencies.
- mmm: Builds all of the modules in the supplied directories, but not their dependencies.
To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma: Builds all of the modules in the current directory, and their dependencies.
- mmma: Builds all of the modules in the supplied directories, and their dependencies.
- 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.
- 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 files.
- sepgrep: Greps on all local sepolicy files.
- sgrep: Greps on all local source files.
- godir: Go to the directory containing a file.
Environment options:
- SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that
ASAN_OPTIONS=detect_leaks=0 will be set by default until the
build is leak-check clean.
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
}
在终端输入hmm命令后,除了会打印部分函数的帮助信息外,还会打印envsetup.sh文件里面包括的函数:
Look at the source to view more functions. The complete list is:
mgrep sgrep treegrep _lunch _wrap_build add_lunch_combo addcompletions atest build_build_var_cache cgrep check_product check_variant choosecombo chooseproduct choosetype choosevariant core coredump_enable coredump_setup cproj croot destroy_build_var_cache findmakefile get_abs_build_var get_build_var get_make_command getbugreports getlastscreenshot getprebuilt getscreenshotpath getsdcardpath gettargetarch gettop ggrep godir hmm is isviewserverstarted jgrep key_back key_home key_menu lunch m make mangrep mm mma mmm mmma pez pid print_lunch_menu printconfig provision qpid rcgrep resgrep runhat runtest sepgrep set_sequence_number set_stuff_for_environment setpaths settitle smoketest stacks startviewserver stopviewserver systemstack tapas tracedmdump
gettop
获取源码根目录,函数定义如下:
function gettop
{
local TOPFILE=build/make/core/envsetup.mk
if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
# The following circumlocution ensures we remove symlinks from TOP.
(cd $TOP; PWD= /bin/pwd)
else
if [ -f $TOPFILE ] ; then
# The following circumlocution (repeated below as well) ensures
# that we record the true directory name and not one that is
# faked up with symlink names.
PWD= /bin/pwd
else
local HERE=$PWD
local T=
while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
\cd ..
T=`PWD= /bin/pwd -P`
done
\cd $HERE
if [ -f "$T/$TOPFILE" ]; then
echo $T
fi
fi
fi
}
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实现打印,在终端输入printconfig,会打印出:
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=9
TARGET_PRODUCT=evk_8mm
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=cortex-a53
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a9
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-4.15.0-101-generic-x86_64-Ubuntu-16.04.6-LTS
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=2.0.0-ga-rc4
OUT_DIR=out
============================================
该函数能非常方便地查看当前配置。
findmakefile
查找当前模块的Android.mk文件,函数定义如下:
function findmakefile()
{
local TOPFILE=build/make/core/envsetup.mk
local HERE=$PWD
local T=
while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
T=`PWD= /bin/pwd`
if [ -f "$T/Android.mk" -o -f "$T/Android.bp" ]; then
echo $T/Android.mk
\cd $HERE
return
fi
\cd ..
done
\cd $HERE
}
对于模块化编译的情况,修改文件后不知道需要在什么路径编译时,可以用这个函数迅速查找到Android.mk文件。
get_make_command
source build/envsetup.sh之后make有两种情况:一种是系统的可执行文件,还有一种是envsetup.sh定义的make。get_make_command可以返回make命令执行的脚本,函数定义如下:
function get_make_command()
{
# If we're in the top of an Android tree, use soong_ui.bash instead of make
if [ -f build/soong/soong_ui.bash ]; then
# Always use the real make if -C is passed in
for arg in "[email protected]"; do
if [[ $arg == -C* ]]; then
echo command make
return
fi
done
echo build/soong/soong_ui.bash --make-mode
else
echo command make
fi
}
在终端输入命令得:
$ get_make_command
$ build/soong/soong_ui.bash --make-mode
make
在source build/envsetup.sh后,我们使用make编译实际上是调用到了envsetup.sh文件中定义的make函数了,函数定义如下:
function make()
{
_wrap_build $(get_make_command "[email protected]") "[email protected]"
}
因为是调用了_wrap_build函数,_wrap_build函数的定义如下:
function _wrap_build()
{
local start_time=$(date +"%s")
"[email protected]"
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
}
所以编译时会打印时间戳,以及成功还是失败的提示。
xxgrep
xxgrep是泛指,代指几种同类型函数,这类函数一般用于在特定文件中查找内容,详情如下:
ggrep
在当前目录以及子目录下所有.gradle文件中查找内容,函数定义如下:
function ggrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.gradle" \
-exec grep --color -n "[email protected]" {} +
}
jgrep
在当前目录以及子目录下所有.java文件中查找内容,函数定义如下:
function jgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.java" \
-exec grep --color -n "[email protected]" {} +
}
cgrep
在当前目录以及子目录下所有.c/.cc/.cpp/.h/.hpp文件中查找内容,函数定义如下:
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 "[email protected]" {} +
}
resgrep
在当前目录以及子目录下所有res/目录下的所有.xml文件中查找内容,函数定义如下:
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 "[email protected]" {} +
done
}
mangrep
在当前目录以及子目录下所有AndroidManifest.xml文件中查找内容,函数定义如下:
function mangrep()
{
find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -type f -name 'AndroidManifest.xml' \
-exec grep --color -n "[email protected]" {} +
}
sepgrep
在当前目录以及子目录下对名为sepolicy目录中的文件查找内容,函数定义如下:
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 "[email protected]" {} +
}
rcgrep
在当前目录以及子目录下所有.rc文件中查找内容,函数定义如下:
function rcgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.rc*" \
-exec grep --color -n "[email protected]" {} +
}
sgrep
在所有资源文件中查找内容,函数定义如下:
case `uname -s` in
Darwin)
function sgrep()
{
find -E . -name .repo -prune -o -name .git -prune -o -type f -iregex '.*\.(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)' \
-exec grep --color -n "[email protected]" {} +
}
;;
*)
function sgrep()
{
find . -name .repo -prune -o -name .git -prune -o -type f -iregex '.*\.\(c\|h\|cc\|cpp\|S\|java\|xml\|sh\|mk\|aidl\|vts\)' \
-exec grep --color -n "[email protected]" {} +
}
;;
esac
mgrep treegrep
mgrep:在当前目录以及子目录下所有makefile文件(包括Makefile、.mk文件等)中查找内容
treegrep:在当前目录以及子目录下所有.(c|h|cpp|S|java|xml)文件中查找内容
函数定义如下:
case `uname -s` in
Darwin)
function mgrep()
{
find -E . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o \( -iregex '.*/(Makefile|Makefile\..*|.*\.make|.*\.mak|.*\.mk|.*\.bp)' -o -regex '(.*/)?soong/[^/]*.go' \) -type f \
-exec grep --color -n "[email protected]" {} +
}
function treegrep()
{
find -E . -name .repo -prune -o -name .git -prune -o -type f -iregex '.*\.(c|h|cpp|S|java|xml)' \
-exec grep --color -n -i "[email protected]" {} +
}
;;
*)
function mgrep()
{
find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o \( -regextype posix-egrep -iregex '(.*\/Makefile|.*\/Makefile\..*|.*\.make|.*\.mak|.*\.mk|.*\.bp)' -o -regextype posix-extended -regex '(.*/)?soong/[^/]*.go' \) -type f \
-exec grep --color -n "[email protected]" {} +
}
function treegrep()
{
find . -name .repo -prune -o -name .git -prune -o -regextype posix-egrep -iregex '.*\.(c|h|cpp|S|java|xml)' -type f \
-exec grep --color -n -i "[email protected]" {} +
}
;;
esac
godir
在编译路径下搜索匹配的路径,并且根据选择跳转,如:
$ godir evb_8mm
[1] ./device/fsl/imx8m/evb_8mm
[2] ./device/fsl/imx8m/evb_8mm/bluetooth
[3] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/core/res/res/drawable-nodpi
[4] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/core/res/res/drawable-sw600dp-nodpi
[5] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/core/res/res/drawable-sw720dp-nodpi
[6] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/core/res/res/values
[7] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/core/res/res/xml
[8] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/packages/SettingsProvider/res/values
[9] ./device/fsl/imx8m/evb_8mm/overlay/frameworks/base/packages/SystemUI/res/values
[10] ./device/fsl/imx8m/evb_8mm/overlay/packages/apps/Settings/res/values
[11] ./device/fsl/imx8m/evb_8mm/seccomp
[12] ./device/fsl/imx8m/evb_8mm/sepolicy
[13] ./device/fsl/imx8m/evb_8mm/sepolicy_drm
[14] ./hardware/broadcom/libbt/conf/fsl/evb_8mm
[15] ./hardware/broadcom/libbt/include
Select one: 1
如上,选择1后则会跳转到./device/fsl/imx8m/evb_8mm目录。函数定义如下:
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))]}
done
else
pathname=${lines[0]}
fi
\cd $T/$pathname
}
print_lunch_menu
读取LUNCH_MENU_CHOICES列表,打印可执行lunch的选项,函数定义如下:
function print_lunch_menu()
{
local uname=$(uname)
echo
echo "You're building on" $uname
echo
echo "Lunch menu... pick a combo:"
local i=1
local choice
for choice in ${LUNCH_MENU_CHOICES[@]}
do
echo " $i. $choice"
i=$(($i+1))
done
echo
}
add_lunch_combo
将提供的编译选项参数添加到LUNCH_MENU_CHOICES列表中,函数定义如下:
unset LUNCH_MENU_CHOICES
function add_lunch_combo()
{
local new_combo=$1
local c
for c in ${LUNCH_MENU_CHOICES[@]} ; do
if [ "$new_combo" = "$c" ] ; then
return
fi
done
LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}
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
if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
then
selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
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
}
m mm mmm mma mmma
这几个函数实际上都是执行make操作,区别如下:
m:不管当前路径如何,编译所有的模块,相当于在根目录下执行make操作
mm:查找当前目录下的Android.mk文件,若找到,编译当前目录下的所有模块
mmm:需要指定路径,并且根据指定的路径下查找Android.mk文件,找到后编译该路径下所有模块
mma:与mm类似,但会将有依赖的模块一起编译
mmma:与mmm类似,但会将有依赖的模块一起编译
(四)非函数部分
将函数部分去除,可以得到以下很少量的非函数部分如下:
...
VARIANT_CHOICES=(user userdebug eng)
...
这部分是设置VARIANT_CHOICES变量为user userdebug和eng三种。
...
# add the default one here
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng
...
添加默认lunch选项。
...
complete -F _lunch lunch
...
将lunch函数命令关联_lunch函数,_lunch为lunch的补全函数,也就是说当关联之后,在终端输入lunch命令后,按tab键可以进行参数的补全。示例如下:
$ lunch
aiy_8mq-eng aosp_cf_x86_auto-userdebug aosp_x86-eng evk_6sl-eng m_e_arm-userdebug sabreauto_6sx-userdebug
aiy_8mq-userdebug aosp_cf_x86_phone-userdebug aosp_x86_64-eng evk_6sl-userdebug m_e_mips-userdebug sabresd_6dq-eng
aosp_arm-eng aosp_cf_x86_tablet-userdebug cf_x86_64_phone-userdebug evk_7ulp-eng m_e_mips64-eng sabresd_6dq-userdebug
aosp_arm64-eng aosp_cf_x86_tablet_3g-userdebug cf_x86_64_tablet-userdebug evk_7ulp-userdebug mek_8q-eng sabresd_6dq_car-eng
aosp_blueline-userdebug aosp_cf_x86_tv-userdebug cf_x86_64_tablet_3g-userdebug evk_8mm-eng mek_8q-userdebug sabresd_6dq_car-userdebug
aosp_car_arm-userdebug aosp_cf_x86_wear-userdebug cf_x86_64_tv-userdebug evk_8mm-userdebug mek_8q_car-eng sabresd_6sx-eng
aosp_car_arm64-userdebug aosp_crosshatch-userdebug cf_x86_64_wear-userdebug evk_8mm_drm-eng mek_8q_car-userdebug sabresd_6sx-userdebug
aosp_car_x86-userdebug aosp_marlin-userdebug cf_x86_auto-userdebug evk_8mm_drm-userdebug mek_8q_car2-eng sabresd_7d-eng
aosp_car_x86_64-userdebug aosp_marlin_svelte-userdebug cf_x86_phone-userdebug evk_8mq-eng mek_8q_car2-userdebug sabresd_7d-userdebug
aosp_cf_x86_64_auto-userdebug aosp_mips-eng cf_x86_tablet-userdebug evk_8mq-userdebug mini_emulator_arm64-userdebug uml-userdebug
aosp_cf_x86_64_phone-userdebug aosp_mips64-eng cf_x86_tablet_3g-userdebug evk_8mq_drm-eng mini_emulator_x86-userdebug
aosp_cf_x86_64_tablet-userdebug aosp_sailfish-userdebug cf_x86_tv-userdebug evk_8mq_drm-userdebug mini_emulator_x86_64-userdebug
aosp_cf_x86_64_tablet_3g-userdebug aosp_taimen-userdebug cf_x86_wear-userdebug hikey-userdebug sabreauto_6q-eng
aosp_cf_x86_64_tv-userdebug aosp_walleye-userdebug evb_8mm-eng hikey64_only-userdebug sabreauto_6q-userdebug
aosp_cf_x86_64_wear-userdebug aosp_walleye_test-userdebug evb_8mm-userdebug hikey960-userdebug sabreauto_6sx-eng
...
if [ "x$SHELL" != "x/bin/bash" ]; then
case `ps -o command -p $$` in
*bash*)
;;
*)
echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"
;;
esac
fi
...
检查当前执行的shell环境是不是bash,如果不是,会输出警告信息。
...
# Execute the contents of any vendorsetup.sh files we can find.
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
`test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
`test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
echo "including $f"
. $f
done
unset f
...
在device、vendor、product目录下查找vendorsetup.sh文件,并且执行这个文件。一般vendorsetup.sh文件中是使用add_lunch_combo函数将lunch选项加入列表。
...
addcompletions
...
调用addcompletions函数,基于sdk/bash_completion目录下的.bash文件内容添加补全规则。
(五)总结
source build/envsetup.sh的流程实际上是环境变量以及各种函数设置的流程,是为了下一步设置与厂商相关的变量以及编译做铺垫。这一步将公共部分剥离,并且指定了厂商环境的路径,这样做的话,如果需要添加厂商自己的环境,只需要在指定目录(device、product、vendor)下添加vendorsetup.sh文件,将lunch选项添加,第一步的环境变量即可设置完成。
上一篇: eslint的使用小技巧