Android编译过程分析(一)
Android编译过程分析
代码来自谷歌的开源代码:https://aosp.opersys.com/xref/android-12.0.0_r2/
按照google给出的编译步骤如下:
1> source build/envsetup.sh // 加载命令
2> lunch // 选择平台编译选项
3> make // 执行编译
按照这个流程,我们分析最原始的这几步到底做了什么?
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|arm64|x86_64] [eng|userdebug|user]
Sets up the build environment for building unbundled apps (APKs).
- banchan: banchan <module1> [<module2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
Sets up the build environment for building unbundled modules (APEXes).
- 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.
- ktgrep: Greps on all local Kotlin 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.
- rsgrep: Greps on all local Rust 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.
- gomod: Go to the directory containing a module.
- pathmod: Get the directory containing a module.
- outmod: Gets the location of a module's installed outputs with a certain extension.
- dirmods: Gets the modules defined in a given directory.
- installmod: Adb installs a module's built APK.
- refreshmod: Refresh list of modules for allmod/gomod/pathmod/outmod/installmod.
- syswrite: Remount partitions (e.g. system.img) as writable, rebooting if necessary.
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
}
------------------------------------------------------------------------
function hmm() # 显示帮助信息
function get_abs_build_var() # 获取绝对变量
function get_build_var() # 获取绝对变量
function check_product() # 检查product
function check_variant() # 检查变量
function setpaths() # 设置文件路径
function printconfig() # 打印配置
function set_stuff_for_environment() # 设置环境变量
function set_sequence_number() # 设置序号
function settitle() # 设置标题
function choosetype() # 设置type
function chooseproduct() # 设置product
function choosevariant() # 设置variant
function tapas() # 功能同choosecombo
function choosecombo() # 设置编译参数
function add_lunch_combo() # 添加lunch项目
function print_lunch_menu() # 打印lunch列表
function lunch() # 配置lunch
function m() # make from top
function findmakefile() # 查找makefile
function mm() # make from current directory
function mmm() # make the supplied directories
function croot() # 回到根目录
function cproj()
function pid()
function systemstack()
function gdbclient()
function jgrep() # 查找java文件
function cgrep() # 查找c/cpp文件
function resgrep()
function tracedmdump()
function runhat()
function getbugreports()
function startviewserver()
function stopviewserver()
function isviewserverstarted()
function smoketest()
function runtest()
function godir () # 跳到指定目录 405
------------------------------------------------------------------------
validate_current_shell
source_vendorsetup
addcompletions
function hmm() 这个函数列出来主要是它介绍了这个脚本的一些功能,第一行cat <<EOP是一个HERE文档,意思就是把EOF后面到下一个EOF前面的内容当做一个文件,然后cat 会接收这个文件的内容,而cat默认的输出是标准输出,也就是这个文件的内容会被打印到屏幕上来。
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
}
gettop函数一开始定义了一个局部变量TOPFILE,并且给他赋了值,然后是一个测试语句:if [ -n “ T O P " − a − f " TOP" -a -f " TOP"−a−f"TOP/$TOPFILE” ] ; then,这里-n 是判断 $TOP是否不为空, -a 就是and的意思,和C语言中的&&相同, -f是判断给定的变量是不是文件,那么,这个测试语句就是如果 $TOP不为空 切同时 T O P / TOP/ TOP/TOPFILE文件存在,就执行下面的代码:
(cd $TOP; PWD= /bin/pwd)
也就是进入到TOP目录下,并且给PWD变量赋一个pwd命令的返回值,也就是当前目录的路劲。我试着在这个脚本中搜索TOP变量,发现它并没有出现并且赋值,所以,这里应该执行else部分。else中,首先判断build/core/envsetup.mk这个文件是否存在,当在源码顶层目录下的时候,这个文件是存在的,那么这里为真,然后PWD变量就是android代码的根目录。所以如果souce build/envsetup.sh的时候,如果处于android源码的顶级目录,那么这个函数就返回了。关于shell函数的返回值问题,还需要留意一下,当一个函数没有返回任何内容的时候,默认返回的是最后一条命令的执行结果,也就是这里的/bin/pwd的结果。那当然就是android源码的顶级目录了。这个时候如果不在顶级目录,build/core/envsetup.mk应该不存在,这个时候就会while循环不断的进入道上层目录,然后判断$TOPFILE是否存在,并且判断是否到达根目录了,如果这个文件不存在且没有到达根目录,那么就会一个往上一级目录查找。最终如果找到了这个文件,就意味着找到了android源码的顶层目录,并把这个路劲返回。前面的两次判断如果都成立的话也没有返回任何东西,是因为,当前目录肯定就是源码的顶级目录了。也就是说,这个函数就是找到源码的顶级目录,如果当前目录就是顶级目录,就什么也不返回,如果当前目录不是顶级目录,就返回顶级目录的路劲。
再回过头来看check_product函数,可以看到在获取到android源码的顶级目录以后,就会判断这个T是不是空值,空的的话就说明没有获取到顶级目录,这个时候这个函数就直接返回了。如果一切正常,那么就会定义几个变量。
add_lunch_combo
function add_lunch_combo()
{
if [ -n "$ZSH_VERSION" ]; then
echo -n "${funcfiletrace[1]}: "
else
echo -n "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: "
fi
echo "add_lunch_combo is obsolete. Use COMMON_LUNCH_CHOICES in your AndroidProducts.mk instead."
}
add_lunch_combo函数被多次调用,就是它来添加Android编译选项(lunch展示菜单)
其中COMMON_LUNCH_CHOICES是AndroidProducts.mk中用户添加的
build/make/target/product/AndroidProducts.mk
COMMON_LUNCH_CHOICES := \
aosp_arm64-eng \
aosp_arm-eng \
aosp_x86_64-eng \
aosp_x86-eng \
这样我们在lunch时,会有很多菜单选项。
build/envsetup.sh文件最后有三个函数调用分别为:validate_current_shell、source_vendorsetup、addcompletions
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环境是bash还是zsh,因为bash和zsh数组起始点不一样。
source_vendorsetup
# Execute the contents of any vendorsetup.sh files we can find.
# Unless we find an allowed-vendorsetup_sh-files file, in which case we'll only
# load those.
#
# This allows loading only approved vendorsetup.sh files
function source_vendorsetup() {
unset VENDOR_PYTHONPATH
local T="$(gettop)"
allowed=
for f in $(cd "$T" && 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="$T/$f"
done
allowed_files=
[ -n "$allowed" ] && allowed_files=$(cat "$allowed")
for dir in device vendor product; do
for f in $(cd "$T" && 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"; . "$T/$f"
else
echo "ignoring $f, not in $allowed"
fi
done
done
}
执行能够寻找到的任何vendorsetup.sh 文件
执行一下source build/envsetup.sh,会打印一下内容:
including device/qcom/common/cuttlestone/vendorsetup.sh
including vendor/qcom/opensource/core-utils/vendorsetup.sh
including vendor/qcom/proprietary/common/vendorsetup.sh
including vendor/qcom/proprietary/prebuilt_HY11/vendorsetup.sh
addcompletions
function addcompletions()
{
local f=
# Keep us from trying to run in something that's neither bash nor zsh.
if [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; then
return
fi
# Keep us from trying to run in bash that's too old.
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
)
# Completion can be disabled selectively to allow users to use non-standard completion.
# e.g.
# ENVSETUP_NO_COMPLETION=adb # -> disable adb completion
# ENVSETUP_NO_COMPLETION=adb:bit # -> disable adb and bit 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 pathmod
complete -F _complete_android_module_names gomod
complete -F _complete_android_module_names outmod
complete -F _complete_android_module_names installmod
complete -F _complete_android_module_names m
}
因此,总结来说,envsetup.sh脚本做了这样的事情:
lunch
function lunch()
{
local answer
# 测试参数传入的合法性
if [[ $# -gt 1 ]]; then
echo "usage: lunch [target]" >&2
return 1
fi
#如果lunch后面带参数,直接确定机型,无需再显示lunch选项
if [ "$1" ]; then
answer=$1
else
#显示lunch后面的菜单选项
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
#choices中保存了所有的菜单项
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
if [ $answer -le ${#choices[@]} ]
then
#判断是不是zsh,如果是zsh,数据是从1开始的
# array in zsh starts from 1 instead of 0.
if [ -n "$ZSH_VERSION" ]
then
#根据命令选择的序号,在choices数据中选择
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名字,如ice-userdebug中的ice
product=${selection%%-*} # Trim everything after first dash
#模式选择,如ice-userdebug中的userdebug(有user、userdebug、eng三种)
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
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || echo
set_stuff_for_environment
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || printconfig
destroy_build_var_cache
}
当输入的是lunch无参数时,if循环就会执行 print_lunch_menu 函数,打印下面的语句。函数解析在下方。
You're building on Linux
Lunch menu... pick a combo:
1. aosp_arm-eng
2. aosp_arm64-eng
3. ...
58. silvermont-eng
59. taro-userdebug
60. uml-userdebug
61. yukawa-userdebug
62. yukawa_sei510-userdebug
Which would you like? [aosp_arm-eng]
print_lunch_menu
function print_lunch_menu()
{
local uname=$(uname)
local choices
#获取所有默认和用户添加的菜单选项
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
}
在 print_lunch_menu和lunch中都调用了get_build_var 函数,我们分析一下产品都是如何获取的。
get_build_var
# Get the exact value of a build variable.
function get_build_var()
{
if [ "$BUILD_VAR_CACHE_READY" = "true" ]
then
eval "echo \"\${var_cache_$1}\""
return 0
fi
local T=$(gettop)
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP." >&2
return 1
fi
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode $1)
}
soong_ui.bash
build/soong/soong_ui.bash
#!/bin/bash -eu
# To track how long we took to startup. %N isn't supported on Darwin, but
# that's detected in the Go code, which skips calculating the startup time.
export TRACE_BEGIN_SOONG=$(date +%s%N)
# Function to find top of the source tree (if $TOP isn't set) by walking up the
# tree.
function gettop
{
local TOPFILE=build/soong/root.bp
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
}
# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash
# $1: name of the requested binary
# $2: package name 可能是 build/soong/cmd/soong_ui/Android.bp文件
soong_build_go soong_ui android/soong/cmd/soong_ui
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"
soong_build_go
build/soong/scripts/microfactory.bash
# Set of utility functions to build and run go code with microfactory
#
# Inputs:
# ${TOP}: The top of the android source tree
# ${OUT_DIR}: The output directory location (defaults to ${TOP}/out)
# ${OUT_DIR_COMMON_BASE}: Change the default out directory to
# ${OUT_DIR_COMMON_BASE}/$(basename ${TOP})
# Ensure GOROOT is set to the in-tree version.
case $(uname) in
Linux)
export GOROOT="${TOP}/prebuilts/go/linux-x86/"
;;
Darwin)
export GOROOT="${TOP}/prebuilts/go/darwin-x86/"
;;
*) echo "unknown OS:" $(uname) >&2 && exit 1;;
esac
# Find the output directory
function getoutdir
{
local out_dir="${OUT_DIR-}"
if [ -z "${out_dir}" ]; then
if [ "${OUT_DIR_COMMON_BASE-}" ]; then
out_dir="${OUT_DIR_COMMON_BASE}/$(basename ${TOP})"
else
out_dir="out"
fi
fi
if [[ "${out_dir}" != /* ]]; then
out_dir="${TOP}/${out_dir}"
fi
echo "${out_dir}"
}
# Bootstrap microfactory from source if necessary and use it to build the
# requested binary.
#
# Arguments:
# $1: name of the requested binary
# $2: package name
function soong_build_go
{
BUILDDIR=$(getoutdir) \
SRCDIR=${TOP} \
BLUEPRINTDIR=${TOP}/build/blueprint \
EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
build_go $@
}
source ${TOP}/build/blueprint/microfactory/microfactory.bash
Android.bp
build/soong/cmd/soong_ui/Android.bp
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
blueprint_go_binary {
name: "soong_ui",
deps: [
"soong-ui-build",
"soong-ui-logger",
"soong-ui-terminal",
"soong-ui-tracer",
],
srcs: [
"main.go",
],
}
microfactory.bash
build/blueprint/microfactory/microfactory.bash
# Set of utility functions to build and run go code with microfactory
#
# Inputs:
# ${GOROOT}
# ${BUILDDIR}
# ${BLUEPRINTDIR}
# ${SRCDIR}
# Bootstrap microfactory from source if necessary and use it to build the
# requested binary.
#
# Arguments:
# $1: name of the requested binary
# $2: package name
# ${EXTRA_ARGS}: extra arguments to pass to microfactory (-pkg-path, etc)
function build_go
{
# Increment when microfactory changes enough that it cannot rebuild itself.
# For example, if we use a new command line argument that doesn't work on older versions.
local mf_version=3
local mf_src="${BLUEPRINTDIR}/microfactory"
local mf_bin="${BUILDDIR}/microfactory_$(uname)"
local mf_version_file="${BUILDDIR}/.microfactory_$(uname)_version"
local built_bin="${BUILDDIR}/$1"
local from_src=1
if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then
if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then
from_src=0
fi
fi
local mf_cmd
if [ $from_src -eq 1 ]; then
# `go run` requires a single main package, so create one
local gen_src_dir="${BUILDDIR}/.microfactory_$(uname)_intermediates/src"
mkdir -p "${gen_src_dir}"
sed "s/^package microfactory/package main/" "${mf_src}/microfactory.go" >"${gen_src_dir}/microfactory.go"
mf_cmd="${GOROOT}/bin/go run ${gen_src_dir}/microfactory.go"
else
mf_cmd="${mf_bin}"
fi
rm -f "${BUILDDIR}/.$1.trace"
# GOROOT must be absolute because `go run` changes the local directory
GOROOT=$(cd $GOROOT; pwd) ${mf_cmd} -b "${mf_bin}" \
-pkg-path "github.com/google/blueprint=${BLUEPRINTDIR}" \
-trimpath "${SRCDIR}" \
${EXTRA_ARGS} \
-o "${built_bin}" $2
if [ $? -eq 0 ] && [ $from_src -eq 1 ]; then
echo "${mf_version}" >"${mf_version_file}"
fi
}
关于make编译的部分留在接下来的分析。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)