Android这个庞大的系统,采用的是自己的Android Makefile来进行编译的。在编译系统时,我们通常需要命令行 source build/envsetup.sh 导入相关环境变量,之后 lunch 选择自己想要编译的版本,最后 make 才可进行系统的全局编译。首先我们来分析source build/envsetup.sh的具体执行流程。
编译脚本的导入
编译安卓系统之前,我们需要在shell环境中,进入安卓源码跟路径下,通过 source build/envsetup.sh 脚本导入安卓系统相关的环境变量。脚本文件提供了很多方法,下面我们来具体分析一下。
多个方法的声明
文件build/envsetup.sh是一个bash shell脚本,从它里面定义的函数hmm可以知道,它提供了lunch、m、mm和mmm等命令供我们初始化编译环境或者编译Android源码。
#!/usr/bin/env bash
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.
...
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
}
函数hmm主要完成三个工作:
- 调用另外一个函数gettop获得Android源码的根目录T
- 通过cat命令显示一个Here Document,说明$T/build/envsetup.sh文件加载到当前终端后所提供的主要命令
- 通过sed命令解析$T/build/envsetup.sh文件,并且获得在里面定义的所有函数的名称,这些函数名称就是$T/build/envsetup.sh文件加载到当前终端后提供的所有命令。
注意,sed命令是一个强大的文本分析工具,它以行为单位为执行文本替换、删除、新增和选取等操作。函数hmm通过执行以下的sed命令来获得在$T/build/envsetup.sh文件定义的函数的名称:
cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function \([a-z_]*\).*/\1/p"
1. cat读入$T/build/envsetup.sh脚本文件
2. 管道命令sed -n ".../p"一起使用,表示单行处理数据
3. 命令"/^[ \t]*function /s/function \([a-z_]*\).*/\1"表示匹配文本内容以若干制表符开头的连接字符"function"字符(声明的方法),使用"function ***"替换,其中"***"内容是匹配若干以"a-z"或者"_"为内容的字符串,即声明的方法规则必须是这两种情况
4. 最后那个"\1"为样式匹配,表示获取得到匹配并替换后的第一个符合字符串结果
上面的分析主要是获取脚本文件中声明的各种方法并打印出来。这里可以在控制台输入hmm,即可最后打印出脚本文件所有声明可用的方法。至于其他的方法,这里就不一一例举了,直接进入添加编译分支部分。
添加默认编译版本
build/envsetup.sh脚本中开头声明了众多函数,之间夹杂着添加默认编译版本的代码,具体如下:
# 滞空LUNCH_MENU_CHOICES变量值
unset 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)
}
# 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_MENU_CHOICES变量值用于重新赋值
- 声明add_lunch_combo方法,用于向环境变量LUNCH_MENU_CHOICES标识的列表中添加项
- 添加默认的几个编译版本
- 注意,${LUNCH_MENU_CHOICES[@]}表示数组LUNCH_MENU_CHOICES的所有元素
其中方法add_lunch_combo中,首先遍历获取LUNCH_MENU_CHOICES中的值,查看取出的值是否和添加的一致,不一致才会执行添加操作
检查当前shell版本
对当前的编译系统的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.
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
下面对上面代码进行解释:
- test -d device :查看device是否为文件夹,如果是文件夹返回ture。这里device显然是文件夹,返回true,通过 && 操作符连接后面的指令
- find -L device -maxdepth 4 -name ‘vendorsetup.sh’ 2> /dev/null :查找当前decice目录下最深层级4级下名为”vendorsetup.sh”的文件(-L表示跟随所有的符号连接),并且将错误信息丢弃
- | sort :附加管道命令,对查询结果进行排序
- echo “including $f”: 打印输出当前找到的 vendorsetup.sh 信息
- . $f: 导入脚本到当前环境中(. 后面有空格,导入当前shell环境,同source,不另开shell进程)
- unset f: 删除shell变量f
从上面分析,可以看出:脚本文件 build/envsetup.sh 被source到当前shell的过程中,会在vendor和device两个目录将厂商指定的 envsetup.sh 也source到当前shell当中,这样就可以获得厂商提供的产品配置信息。
例如如下厂商MSTAR定制的脚本信息:
--> /device/mstar/avocado/vendorsetup.sh
add_lunch_combo aosp_avocado-userdebug
add_lunch_combo aosp_avocado_dtmb-userdebug
addcompletions完成脚本的导入
在导入三方定制的初始化脚本信息后,便调用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版本,Bash版本名称为空直接返回。可通过echo ${BASH_VERSION}获取版本名
判断系统Bash主版本号是否小于3,是即直接返回
${BASH_VERSINFO}返回一个数组,这个数组含有6个元素,指示了安装的Bash版本的信息。 # BASH_VERSINFO[0] = 3 # 主版本号. # BASH_VERSINFO[1] = 00 # 次版本号. # BASH_VERSINFO[2] = 14 # 补丁级. # BASH_VERSINFO[3] = 1 # 编译版本. # BASH_VERSINFO[4] = release # 发行状态. # BASH_VERSINFO[5] = i386-redhat-linux-gnu # 结构体系
判断给定的”sdk/bash_completion”是否为文件夹,是文件夹变列出此文件夹下以[a-z]小写开头的.bash文件,错误信息直接抛弃,之后有的话直接打印输出,并通过”. $f”导入当前shell环境
到此,source导入脚本初始化编译环境部分就告一段落了。
lunch选择合适的编译版本
- 原本打算一次性将编译安卓系统的三步骤一起讲完,但由于具体分析脚本文件内容比较多,这里就按照编译步骤,也分为三部分。下面两节便分别具体分析一下lunch和make的具体流程。
说明
本内容针对 MASTR定制的安卓6.0电视系统,本人能力有限,在分析过程查找了相关的资料,比较有参考价值的在下面链接中提到。
老罗的Android之旅