看了好多篇关于build/envsetup.sh 的介绍,记下的总结
每次开始编译开始的第一个命令便是source build/envsetup.sh,其中source命令就是用于运行shell脚本命令,功能等价于”.”,因此该命令也等价于. build/envsetup.sh。在文件envsetup.sh声明了当前会话终端可用的命令,这里需要注意的是当前会话终端,也就意味着每次新打开一个终端都必须再一次执行这些指令。build/envsetup.sh文件存在的意义就是,设置一些环境变量和shell函数为后续的编译工作做准备。 接下来我们分析下
envsetup.sh.
导入envsetup.sh系统会都出一些命令,先看下命令的作用
常用的命令
编译指令 | 解释 |
---|---|
mm | 编译当前路径下所有模块,但不包含依赖 |
mmm [module_path] | 编译指定路径下所有模块,但不包含依赖 |
mma | 编译当前路径下所有模块,且包含依赖 |
mmma [module_path] | 编译指定路径下所有模块,且包含依赖 |
make [module_name] | 无参数,则表示编译整个Android代码 |
cgrep | 当前目录下所有C/C++文件执行搜索操作 |
jgrep | 当前目录下所有Java文件执行搜索操作 |
ggrep | 当前目录下所有Gradle文件执行搜索操作 |
mangrep [keyword] | 当前目录下所有AndroidManifest.xml文件执行搜索操作 |
mgrep [keyword] | 当前目录下所有Android.mk文件执行搜索操作 |
sepgrep [keyword] | 所有sepolicy文件执行搜索操作 |
resgrep [keyword] | 当前目录下所有本地res/*.xml文件执行搜索操作 |
sgrep [keyword] | 当前目录下基于(c |
croot | 切换至Android根目录 |
cproj | 切换至工程的根目录 |
godir [filename] | 跳转到包含某个文件的目录 |
hmm | 查询所有的指令help信息 |
Tips: Android源码非常庞大,直接采用grep来搜索代码,不仅方法笨拙、浪费时间,而且搜索出很多无意义的混淆结果。根据具体需求,来选择合适的代码搜索指令,能节省代码搜索时间,提高搜索结果的精准度,方便定位目标代码。
godir [filename] 第一次执行速度较慢,之后运作效率比find命令高(具体见后面解析)
envsetup.sh 定义了很多函数,除此之外还执行了其它操作,以下是除去函数部分的代码::
VARIANT_CHOICES=(user userdebug eng)# 提前定义3种编译模式,供后面使用
# Clear this variable. It will be built up again when the vendorsetup.sh
# files are included at the end of this file.
#LUNCH_MENU_CHOICES是供用户选择的prodcut列表,
#每次source build/envsetup.sh时需重置变量LUNCH_MENU_CHOICES
#不然后续的include vendor/cm/vendorsetup.sh时会继续添加产品至变量LUNCH_MENU_CHOICES里,
#导致出现很多重复产品
unset LUNCH_MENU_CHOICES
#处理逻辑就是先从LUNCH_MENU_CHOICES中循环查找,看存不存在要添加的板型,如果存在就直接返回,如果不存在就添加到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)
}
#默认会加载如下6个板型选项
# 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和_lunch函数,这行代码的意思是通过_lunch函数实现lunch命令的自动补全功能
complete -F _lunch lunch
#shell检查和警告,这里只支持bash,如果是其他的shell会发出这个WARNING
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
# Execute the contents of any vendorsetup.sh files we can find.
#source vendor和device下能找到的所有vendorsetup.sh
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`
do
echo "including $f"
. $f
done
unset f
addcompletions
最最后看看addcompletions干了啥
function addcompletions()
{
local T dir f
# Keep us from trying to run in something that isn't bash.
if [ -z "${BASH_VERSION}" ]; then
return
fi
# Keep us from trying to run in bash that's too old.
if [ ${BASH_VERSINFO[0]} -lt 3 ]; then
return
fi
dir="sdk/bash_completion"
if [ -d ${dir} ]; then
for f in `/bin/ls ${dir}/[a-z]*.bash 2> /dev/null`; do
echo "including $f"
. $f
done
fi
}
如果BASH的版本为空或者小于3都直接返回,否则打印android/sdk/bash_completion/目录下的以.bash结尾的所有文件,执行.bash文件,实现Linux终端下adb命令的补全
至此, source build/envsetup.sh的过程就分析完了。
在source流程之后,紧接着就是执行lunch操作,lunch操作执行的其实就是build/envsetup.sh脚本中的lunch函数,直接看代码:
function lunch()
{
local answer
#如果lunch命令后跟有参数,则直接赋给answer变量;
#如果lunch命令后没有参数,则调用函数print_lunch_menu打印出板型列表供用户选择,并将用户的选择存储在answer变量中。
if [ "$1" ] ; then
answer=$1
else
print_lunch_menu #函数的作用就是打印数组LUNCH_MENU_CHOICES的内容
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
#如果answer为空,则selection默认赋值为aosp_arm-eng;
#如果answer是纯数字,则将answer作为数组下标从LUNCH_MENU_CHOICES数组中取出板型名称;
#如果anwser是字符串,并且字符串使用”-”连接,而且”-”连接的前后两个子串中都没有”-”,则认为是板型名称字符串,直接赋给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
elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
then
selection=$answer
fi
#如果selection仍然为空,则直接报错并退出
if [ -z "$selection" ]
then
echo
echo "Invalid lunch combo: $answer"
return 1
fi
export TARGET_BUILD_APPS=
#从selection中取出“-”前面的字符串到product,
local product=$(echo -n $selection | sed -e "s/-.*$//")
#检查product的合法性,涉及到make 不展开
check_product $product
if [ $? -ne 0 ]
then
echo
echo "** Don't have a product spec for: '$product'"
echo "** Do you have the right repo manifest?"
product=
fi
#再从selection中取出“-”后面的字符串到variant,然后调用函数check_variant判断variant是否符合要求。check_variant函数很简单,主要就是用到前文source流程中初始化为"user userdebug eng"的VARIANT_CHOICES数组,判断variant是否是数组成员之一,是则返回0,不是则返回1。
local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
check_variant $variant
if [ $? -ne 0 ]
then
echo
echo "** Invalid variant: '$variant'"
echo "** Must be one of ${VARIANT_CHOICES[@]}"
variant=
fi
if [ -z "$product" -o -z "$variant" ]
then
echo
return 1
fi
#导出以下准备好的宏变量供整个shell环境使用
export TARGET_PRODUCT=$product
export TARGET_BUILD_VARIANT=$variant
export TARGET_BUILD_TYPE=release
echo
#设置PROMPT_COMMAND,ANDROID_BUILD_PATHS,JAVA_HOME和BUILD_ENV_SEQUENCE_NUMBER等等环境变量
set_stuff_for_environment
#打印最终准备好的环境变量
printconfig
}
Tips:set_stuff_for_environment 里面的set_java_home函数,可以根据实际情况修改java路径等,这样可以避免编译不同的android版本还需手动切换java版本
实现lunch命令的补全的步骤:
首先定义了lunch函数
然后定义了_lunch函数
使用complete命令
# Tab completion for lunch.
function _lunch()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=( $(compgen -W "${LUNCH_MENU_CHOICES[*]}" -- ${cur}) )
return 0
}
++complete -F _lunch lunch++ 是上面代码中最关键的一行。
当bash在遇到lunch这个词的时候,会调用_lunch函数。
该函数会传入三个参数:要补全的命令名、当前的光标所在的词、当前光标所在的词的前一个词。
补全的结果需要存储到COMPREPLY变量中,以待bash获取。
function gettop
{
local TOPFILE=build/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
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
}
-n 变量是否存在
-a 逻辑与
-f 文件是否存在
PWD 打印当前目录
这个函数主要是获取Android源码的根目录,根据根目录下的build/core/envsetup.mk文件来判断的。
注意:调用这个函数的时候有个要求:就是你必须在源码根目录的子目录中,如果你处在根目录的上级目录则调用失败,因为这个函数是通过不停的调用cd ..命令,然后通过文件build/core/envsetup.mk来判断源码根目录的。
function hmm() {
cat <<EOF
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch: lunch <product_name>-<build_variant>
- tapas: tapas [<App1> <App2> ...] [arm|x86|mips|armv5|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.
- 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.
- sepgrep: Greps on all local sepolicy files.
- sgrep: Greps on all local source files.
- godir: Go to the directory containing a file.
Environemnt 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
T=$(gettop)
local A
A=""
for i in `cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
A="$A $i"
done
echo $A
}
帮助函数,cat $T/build/envsetup.sh | sed -n “/^[ \t]function /s/function ([a-z_]).*/\1/p” | sort | uniq 这句脚本主要是获取envsetup.sh里面的以function开头的函数,函数名都是字母,并将函数名打印出来。
sed -n “/^[ \t]*function /p” 是打印出以function 开头的行
s/function ([a-z_])./\1 这部分是将函数名提取出来
sort 排序
uniq 去重
function mm()
{
local T=$(gettop)
local DRV=$(getdriver $T)
# If we're sitting in the root of the build tree, just do a
# normal make.
if [ -f build/core/envsetup.mk -a -f Makefile ]; then
$DRV make $@
else
# Find the closest Android.mk file.
local M=$(findmakefile)
local MODULES=
local GET_INSTALL_PATH=
local ARGS=
# Remove the path to top as the makefilepath needs to be relative
local M=`echo $M|sed 's:'$T'/::'`
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP."
return 1
elif [ ! "$M" ]; then
echo "Couldn't locate a makefile from the current directory."
return 1
else
for ARG in $@; do
case $ARG in
GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;
esac
done
if [ -n "$GET_INSTALL_PATH" ]; then
MODULES=
ARGS=GET-INSTALL-PATH
else
MODULES=all_modules
ARGS=$@
fi
ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS
fi
fi
}
如果当前处在源码根目录下,且存在Makefile文件,直接执行make $@命令。
如果不是在源码根目录下:循环调用cd .. 调用findmakefile函数查找Android.mk文件,直到根目录,将Android.mk文件的绝对路径返回给M.
如找到了:M=/home/admin/android/frameworks/base/Android.mk,调用: local M=echo $M|sed 's}'$T'/}}'
这句就是将源码根目录/home/admin/android/去掉,取得相对目录后,M=frameworks/base/Android.mk
最后执行:ONE_SHOT_MAKEFILE=frameworks/base/android.mk make -C /home/admin/android all_modules
相当于make -C /home/admin/android all_modules ONE_SHOT_MAKEFILE=frameworks/base/android.mk
接下来会调用到:源码根目录下的Makefile中,我们发现内容是include build/core/main.mk,即走到了build/core/main.mk文件中。后面就设计到Makefile的问题,我们后面会分析。现在只需要知道mm命令是怎么调用makefile里面去就行
mmm 命令就是从指定目录下开始编译所有模块
function mmm()
{
local T=$(gettop)
local DRV=$(getdriver $T)
if [ "$T" ]; then
local MAKEFILE=
local MODULES=
local ARGS=
local DIR TO_CHOP
local GET_INSTALL_PATH=
local DASH_ARGS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^-.*$/')
local DIRS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^[^-].*$/')
for DIR in $DIRS ; do
MODULES=`echo $DIR | sed -n -e 's/.*:\(.*$\)/\1/p' | sed 's/,/ /'`
if [ "$MODULES" = "" ]; then
MODULES=all_modules
fi
DIR=`echo $DIR | sed -e 's/:.*//' -e 's:/$::'`
if [ -f $DIR/Android.mk ]; then
local TO_CHOP=`(\cd -P -- $T && pwd -P) | wc -c | tr -d ' '`
local TO_CHOP=`expr $TO_CHOP + 1`
local START=`PWD= /bin/pwd`
local MFILE=`echo $START | cut -c${TO_CHOP}-`
if [ "$MFILE" = "" ] ; then
MFILE=$DIR/Android.mk
else
MFILE=$MFILE/$DIR/Android.mk
fi
MAKEFILE="$MAKEFILE $MFILE"
else
case $DIR in
showcommands | snod | dist | incrementaljavac | *=*) ARGS="$ARGS $DIR";;
GET-INSTALL-PATH) GET_INSTALL_PATH=$DIR;;
*) echo "No Android.mk in $DIR."; return 1;;
esac
fi
done
if [ -n "$GET_INSTALL_PATH" ]; then
ARGS=$GET_INSTALL_PATH
MODULES=
fi
ONE_SHOT_MAKEFILE="$MAKEFILE" $DRV make -C $T -f build/core/main.mk $DASH_ARGS $MODULES $ARGS
else
echo "Couldn't locate the top of the tree. Try setting TOP."
return 1
fi
}
首先T=$(gettop)获取源码根目录,
local DASH_ARGS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^-.*$/')
根据mmm后面的参数获取以空格隔开,以-开头的字符串,如:mmm -B frameworks/base 则DASH_ARGS = -B
local DIRS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^[^-].*$/')
根据mmm后面参数获取以空格隔开,不以-开头的字符串。如:DIRS=frameworks/base/
DIR=`echo $DIR | sed -e 's:/$::'`
去掉DIR结尾的/,DIR=frameworks/base
if [ -f $DIR/Android.mk ] ;then
TO_CHOP=`(cd -P -- $T && pwd -P) | wc -c | tr -d ' '`
跳到源码根目录,pwd -P打印出来,获取到字符串长度如:/home/admin/androids TO_CHOP就是20
TO_CHOP=`expr $TO_CHOP + 1`
START=`PWD= /bin/pwd`
MFILE=`echo $START | cut -c${TO_CHOP}-`
获取当前目录字符串,去掉前面21个字符,如:当前目录为/home/admin/androids,则MFILE=”“
if [ "$MFILE" = "" ] ; then
MFILE=$DIR/Android.mk
else
MFILE=$MFILE/frameworks/base/Android.mk
fi
这里进行判断当前目录是否就是源码目录,如果是的话,MFILE为空,所以MFILe=frameworks/base/Android.mk
否则:MFILE=$MFILE/frameworks/base/Android.mk
总之:就是把当前目录的前面源码根目录去了,然后加上后面mmm -B **参数部分相对的目录,最后加上Android.mk就是,最后Android.mk文件的相对路径。
ONE_SHOT_MAKEFILE=”MAKEFILE” make -C T T DASH_ARGS all_modules $ARGS
相当于:make -C /home/admin/android -B all_modules ONE_SHOT_MAKEFILE=frameworks/base/Android.mk
function godir () {
#如果没有带参数直接提示错误返回
if [[ -z "$1" ]]; then
echo "Usage: godir <regex>"
return
fi
T=$(gettop)
#如果OUT_DIR 不等于空创建OUT_DIR这个目录,设置FILELIST文件路径
if [ ! "$OUT_DIR" = "" ]; then
mkdir -p $OUT_DIR
FILELIST=$OUT_DIR/filelist
else
FILELIST=$T/filelist
fi
#判断filelist是否存在,不存在新建
if [[ ! -f $FILELIST ]]; then
echo -n "Creating index..."
# 使用find命令从编译的根目录下查找文件"-type f"(目录out和.repo除外),并将结果输出到$FILELIST文件中
# out目录和.repo目录的排除选项分别为:
# - "-wholename ./out -prune"
# - "-wholename ./.repo -prune"
# 关于find的"-wholename pattern"选项,其行为跟"-path pattern"基本一样,具体可以查看find的帮助信息
# 因此filelist文件保存了除out和.repo目录外其余目录的完整文件名
(\cd $T; find . -wholename ./out -prune -o -wholename ./.repo -prune -o -type f > $FILELIST)
echo " Done"
echo ""
fi
local lines
# 根据传入godir的参数,在filelist中搜索,并用sed处理后将结果存放在lines中
# 操作"sed -e 's/\/[^/]*$//'"仅保留完整文件名的路径部分
lines=($(\grep "$1" $FILELIST | sed -e 's/\/[^/]*$//' | sort | uniq))
# 检查lines中的结果,即filelist通过grep和sed操作后,是否还有匹配的目录
if [[ ${#lines[@]} = 0 ]]; then
echo "Not found"
return
fi
local pathname
local choice
# 如果lines的结果多于1行,则对各行进行编号并输出
if [[ ${#lines[@]} > 1 ]]; then
while [[ -z "$pathname" ]]; do
# 从1开始编号
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中
pathname=${lines[0]}
fi
跳转到
\cd $T/$pathname
}
godir 是在编译路径下搜索匹配模式的目录,然后跳转到此目录,同时可以用来快速查找文件的路径
如像查找frameworks/base/core/res/res/values在那些地方被overlay 了直接godir frameworks/base/core/res/res/values/ 查找包含这个路径的所有文件夹,及所有可能被覆盖的地方
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)' -print0 | xargs -0 grep --color -n "$@"
}
;;
*)
function sgrep()
{
# 排除 .repo, .git目录
# 并对后缀(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)的文件执行grep模式搜索
find . -name .repo -prune -o -name .git -prune -o -type f -iregex '.*\.\(c\|h\|cc\|cpp\|S\|java\|xml\|sh\|mk\|aidl\)' -print0 | xargs -0 grep --color -n "$@"
}
;;
esac
这里为什么没有把out目录去了表示无法理解
其他代码搜索原理类似就不一一列举