代码来自谷歌的开源代码: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()
function check_variant()
function setpaths()
function printconfig()
function set_stuff_for_environment()
function set_sequence_number()
function settitle()
function choosetype()
function chooseproduct()
function choosevariant()
function tapas()
function choosecombo()
function add_lunch_combo()
function print_lunch_menu()
function lunch()
function m()
function findmakefile()
function mm()
function mmm()
function croot()
function cproj()
function pid()
function systemstack()
function gdbclient()
function jgrep()
function cgrep()
function resgrep()
function tracedmdump()
function runhat()
function getbugreports()
function startviewserver()
function stopviewserver()
function isviewserverstarted()
function smoketest()
function runtest()
function godir ()
------------------------------------------------------------------------
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
(cd "$TOP"; PWD= /bin/pwd)
else
if [ -f $TOPFILE ] ; then
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
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=
if [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; then
return
fi
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
)
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
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 [[ $
echo "usage: lunch [target]" >&2
return 1
fi
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 ${
then
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%%-*}
variant_and_version=${selection
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
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
export TRACE_BEGIN_SOONG=$(date +%s%N)
function gettop
{
local TOPFILE=build/soong/root.bp
if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
(cd $TOP; PWD= /bin/pwd)
else
if [ -f $TOPFILE ] ; then
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
}
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash
soong_build_go soong_ui android/soong/cmd/soong_ui
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"
soong_build_go
build/soong/scripts/microfactory.bash
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
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}"
}
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
function build_go
{
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
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=$(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编译的部分留在接下来的分析。
|