源码基于:Android R
0. 前言
今天在编译项目的时候,想看看 envsetup.sh 中变化了些什么,才想起来编译专栏中好像没有详解该脚本,索性现在空余时间比较多,整理一下方便以后查看。
Android envsetup.sh 为编译前的准备工作,提供 lunch、m、mm等命令函数定义,是整个Android 编译系统的第一步。该文件一共1700 行左右,简单总结出两件事情:
- 函数定义
- 生成编译配置列表
本文会结合代码对 envsetup.sh 进行详细的剖析,函数比较多,会进行长期地补充和维护。
1. source envsetup.sh
当运行该脚本的时候终端提示:
including vendor/xxx/common/vendorsetup.sh
clang compdb: ln -s out/soong/development/ide/compdb/compile_commands.json $ANDROID_BUILD_TOP
including vendor/qqq/opensource/core-utils/vendorsetup.sh
including vendor/qqq/proprietary/common/vendorsetup.sh
那执行该脚本的时候到底做了些什么呢?
build/envsetup.shvalidate_current_shell
source_vendorsetup
addcompletions
执行该程序共运行三个函数,分别是 validate_current_shell、source_vendorsetup、addcompletions。下面来看下这三个函数做了些什么事情?
1.1 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 环境是否可用,对于 zsh 环境需要特殊处理,其他环境会其实warning。
1.2 source_vendorsetup
function source_vendorsetup() {unset VENDOR_PYTHONPATHallowed=for f in $(find -L device vendor product -maxdepth 4 -name 'allowed-vendorsetup_sh-files' 2>/dev/null | sort); doif [ -n "$allowed" ]; thenecho "More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"echo " $allowed"echo " $f"returnfiallowed="$f"doneallowed_files=[ -n "$allowed" ] && allowed_files=$(cat "$allowed")for dir in device vendor product; dofor f in $(test -d $dir && \find -L $dir -maxdepth 4 -name 'vendorsetup.sh' 2>/dev/null | sort); doif [[ -z "$allowed" || "$allowed_files" =~ $f ]]; thenecho "including $f"; . "$f"elseecho "ignoring $f, not in $allowed"fidonedone
}
- 首先,查找 device、vendor、product 目录下最大深度为4 的子目录中,是否存在 allowed-vendorsetup_sh-files 文件并排序,但这个文件只能1个,不然会报错退出;
- 接着,在device、vendor、product 中同样查找最大深度为 4 的子目录中,是否存在 vendorsetup.sh,打印 includeing ... ,然后运行该 vendorsetup.sh 脚本,这些脚本大多是配置一些环境变量;
例如上面的打印 including vendor/xxx/common/vendorsetup.sh,就是在 vendor/xxx/common 目录下找到了 vendorsetup.sh,运行的时候会配置一些环境变量,并打印:
clang compdb: ln -s out/soong/development/ide/compdb/compile_commands.json
1.3 addcompletions
function addcompletions()
{local T dir f#避免不在 bash 或 zsh 的环境中运行#需要提前指定这两个环境变量,如果没有指定,该函数returnif [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; thenreturnfi#避免运行在太久的版本中,这里是小于 3 的版本if [ -n "$BASH_VERSION" -a ${BASH_VERSINFO[0]} -lt 3 ]; thenreturnfilocal completion_files=(system/core/adb/adb.bashsystem/core/fastboot/fastboot.bashtools/asuite/asuite.sh)#确认上面几个脚本是否存在,通过should_add_completion确认该脚本是否在白名单中for f in ${completion_files[*]}; doif [ -f "$f" ] && should_add_completion "$f"; then. $ffidoneif should_add_completion bit ; thencomplete -C "bit --tab" bitfiif [ -z "$ZSH_VERSION" ]; then# Doesn't work in zsh.complete -o nospace -F _croot crootficomplete -F _lunch lunchcomplete -F _complete_android_module_names dumpmodcomplete -F _complete_android_module_names pathmodcomplete -F _complete_android_module_names gomodcomplete -F _complete_android_module_names m
}
详细的 complete 命令可以另外查找,最后就是把一些命令进行补齐的设置。
2. 辅助函数
2.1 hmm
hmm 函数输出 envsetupsh.sh 的帮助说明,执行 build/envsetup.sh 后可以调用的操作总结:
function hmm() {
cat <<EOFRun "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 tobuild, and stores those selections in the environment to be read by subsequentinvocations 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, 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 theirdependencies.
- mmm: Builds and installs all of the modules in the supplied directories, and theirdependencies.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.
- 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.
- 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. or List all modules inside certain directory.
- gomod: Go to the directory containing a module.
- dumpmod: Get all info of specific modules from \$ANDROID_PRODUCT_OUT/module-info.json
- pathmod: Get the directory containing a module.
- refreshmod: Refresh list of modules for allmod/gomod.
- ninja-build: Bypass ninja generate procedure, directly use ninja to build a target or module, also build droid is support too by no param given.
- ninja-query: Query input and output of a ninja target, this is the most powerful tool when digging the compile steps.
- ninja-commands: Print all build commands, like a --just-print version of ninja-build, also known as the --dry-run purpose
- mod: Analyze $OUT/module-info.json with parameters, see mod -h for helpEnvironment 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:
EOFlocal T=$(gettop)local A=""local ifor i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; doA="$A $i"doneecho $A
}
最后还列出了完整的函数名,通过:
sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p"
但其操作有一个 bug,用于匹配函数的正则表达式 function \([a-z_]*\).* 会漏掉函数 is64bit();
将匹配模式从 function \([a-z_]*\).*/ 修改为 function \([a-z_]\w*\).* 就可以匹配文件中的所有函数了。
2.2 gettop
gettop 函数,从指定的 $TOP 目录或者当前目录开始查找 build/make/core/envsetup.mk,并将能找到该文件的目录返回个调用函数作为操作的根目录:
function gettop
{local TOPFILE=build/make/core/envsetup.mk#如果编译环境已经设置了 $TOP,就检查 $TOP/build/make/core/envsetup.mk文件是否存在if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then# The following circumlocution ensures we remove symlinks from TOP.#跳转到$TOP 目录,并pwd将$TOP 目录指向的真实路径存放到PWD中(cd $TOP; PWD= /bin/pwd)else#如果当前路径下能找到 build/make/core/envsetup.mk文件,则将当前目录的真实路径存放到PWD中if [ -f $TOPFILE ] ; thenPWD= /bin/pwdelse#如果当前目录下无法找到build/make/core/envsetup.mk文件,则不断返回到外层目录查找,#直至到根目录为止local HERE=$PWDlocal T=while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do\cd ..T=`PWD= /bin/pwd -P`done#查找完后回到之前操作的路径\cd $HERE#如果目录$T包含了build/make/core/envsetup.mk,则说明$T是编译的根目录if [ -f "$T/$TOPFILE" ]; thenecho $Tfififi
}
2.3 croot
croot 命令用以将当前目录切换到当前编译环境的根目录。
当然croot 之后可以跟一个参数标记切到根目录之后的下一级目录,例如 croot device 命令。
详细可以查看代码:
function croot()
{local T=$(gettop)if [ "$T" ]; thenif [ "$1" ]; then\cd $(gettop)/$1else\cd $(gettop)fielseecho "Couldn't locate the top of the tree. Try setting TOP."fi
}
2.4 cproj
cproj 命令用于切换到当前模块的编译目录下(含Android.mk的目录下)
function cproj()
{local TOPFILE=build/make/core/envsetup.mklocal HERE=$PWD #临时保存当前目录local T=#当前目录下build/make/core/envsetup.mk不存在,即当前不是编译根目录,且#当前目录不是系统根目录while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; doT=$PWDif [ -f "$T/Android.mk" ]; then #如果该目录下有Android.mk文件,则cd过去\cd $Treturnfi\cd ..done\cd $HERE #恢复之前的目录echo "can't find Android.mk"
}
2.5 getprebuilt
function getprebuilt
{get_abs_build_var ANDROID_PREBUILTS
}
function get_abs_build_var()
{if [ "$BUILD_VAR_CACHE_READY" = "true" ]theneval "echo \"\${abs_var_cache_$1}\""returnfilocal T=$(gettop)if [ ! "$T" ]; thenecho "Couldn't locate the top of the tree. Try setting TOP." >&2returnfi(\cd $T; build/soong/soong_ui.bash --dumpvar-mode --abs $1)
}
主要是通过函数 get_abs_build_var() 中查找变量值,这里查找的是 ANDROID_PREBUILTS 这个绝对路径为 $TOP/prebuilt/linux-x86
2.6 setpaths
主要是配置一些环境变量,在lunch 命令时调用,或者是在 choosecombo 函数中调用:
function setpaths()
{local T=$(gettop)if [ ! "$T" ]; thenecho "Couldn't locate the top of the tree. Try setting TOP."returnfi################################################################### ## Read me before you modify this code ## ## This function sets ANDROID_BUILD_PATHS to what it is adding ## to PATH, and the next time it is run, it removes that from ## PATH. This is required so lunch can be run more than once ## and still have working paths. ## #################################################################### Note: on windows/cygwin, ANDROID_BUILD_PATHS will contain spaces# due to "C:\Program Files" being in the path.# out with the oldif [ -n "$ANDROID_BUILD_PATHS" ] ; thenexport PATH=${PATH/$ANDROID_BUILD_PATHS/}fiif [ -n "$ANDROID_PRE_BUILD_PATHS" ] ; thenexport PATH=${PATH/$ANDROID_PRE_BUILD_PATHS/}# strip leading ':', if anyexport PATH=${PATH/:%/}fi# and in with the newlocal prebuiltdir=$(getprebuilt)local gccprebuiltdir=$(get_abs_build_var ANDROID_GCC_PREBUILTS)# defined in core/config.mklocal targetgccversion=$(get_build_var TARGET_GCC_VERSION)local targetgccversion2=$(get_build_var 2ND_TARGET_GCC_VERSION)export TARGET_GCC_VERSION=$targetgccversion# The gcc toolchain does not exists for windows/cygwin. In this case, do not reference it.export ANDROID_TOOLCHAIN=export ANDROID_TOOLCHAIN_2ND_ARCH=local ARCH=$(get_build_var TARGET_ARCH)local toolchaindir toolchaindir2=case $ARCH inx86) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin;;x86_64) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin;;arm) toolchaindir=arm/arm-linux-androideabi-$targetgccversion/bin;;arm64) toolchaindir=aarch64/aarch64-linux-android-$targetgccversion/bin;toolchaindir2=arm/arm-linux-androideabi-$targetgccversion2/bin;;mips|mips64) toolchaindir=mips/mips64el-linux-android-$targetgccversion/bin;;*)echo "Can't find toolchain for unknown architecture: $ARCH"toolchaindir=xxxxxxxxx;;esacif [ -d "$gccprebuiltdir/$toolchaindir" ]; thenexport ANDROID_TOOLCHAIN=$gccprebuiltdir/$toolchaindirfiif [ "$toolchaindir2" -a -d "$gccprebuiltdir/$toolchaindir2" ]; thenexport ANDROID_TOOLCHAIN_2ND_ARCH=$gccprebuiltdir/$toolchaindir2fiexport ANDROID_DEV_SCRIPTS=$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin# add kernel specific binariescase $(uname -s) inLinux)export ANDROID_DEV_SCRIPTS=$ANDROID_DEV_SCRIPTS:$T/prebuilts/misc/linux-x86/dtc:$T/prebuilts/misc/linux-x86/libufdt;;*);;esacANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAINif [ -n "$ANDROID_TOOLCHAIN_2ND_ARCH" ]; thenANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCHfiANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_DEV_SCRIPTS# Append llvm binutils prebuilts path to ANDROID_BUILD_PATHS.local ANDROID_LLVM_BINUTILS=$(get_abs_build_var ANDROID_CLANG_PREBUILTS)/llvm-binutils-stableANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_LLVM_BINUTILS# Set up ASAN_SYMBOLIZER_PATH for SANITIZE_HOST=address builds.export ASAN_SYMBOLIZER_PATH=$ANDROID_LLVM_BINUTILS/llvm-symbolizer# If prebuilts/android-emulator/<system>/ exists, prepend it to our PATH# to ensure that the corresponding 'emulator' binaries are used.case $(uname -s) inDarwin)ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/darwin-x86_64;;Linux)ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/linux-x86_64;;*)ANDROID_EMULATOR_PREBUILTS=;;esacif [ -n "$ANDROID_EMULATOR_PREBUILTS" -a -d "$ANDROID_EMULATOR_PREBUILTS" ]; thenANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_EMULATOR_PREBUILTSexport ANDROID_EMULATOR_PREBUILTSfi# Append asuite prebuilts path to ANDROID_BUILD_PATHS.local os_arch=$(get_build_var HOST_PREBUILT_TAG)local ACLOUD_PATH="$T/prebuilts/asuite/acloud/$os_arch"local AIDEGEN_PATH="$T/prebuilts/asuite/aidegen/$os_arch"local ATEST_PATH="$T/prebuilts/asuite/atest/$os_arch"export ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH:export PATH=$ANDROID_BUILD_PATHS$PATH# out with the duplicate oldif [ -n $ANDROID_PYTHONPATH ]; thenexport PYTHONPATH=${PYTHONPATH//$ANDROID_PYTHONPATH/}fi# and in with the newexport ANDROID_PYTHONPATH=$T/development/python-packages:if [ -n $VENDOR_PYTHONPATH ]; thenANDROID_PYTHONPATH=$ANDROID_PYTHONPATH$VENDOR_PYTHONPATHfiexport PYTHONPATH=$ANDROID_PYTHONPATH$PYTHONPATHexport ANDROID_JAVA_HOME=$(get_abs_build_var ANDROID_JAVA_HOME)export JAVA_HOME=$ANDROID_JAVA_HOMEexport ANDROID_JAVA_TOOLCHAIN=$(get_abs_build_var ANDROID_JAVA_TOOLCHAIN)export ANDROID_PRE_BUILD_PATHS=$ANDROID_JAVA_TOOLCHAIN:export PATH=$ANDROID_PRE_BUILD_PATHS$PATHunset ANDROID_PRODUCT_OUTexport ANDROID_PRODUCT_OUT=$(get_abs_build_var PRODUCT_OUT)export OUT=$ANDROID_PRODUCT_OUTunset ANDROID_HOST_OUTexport ANDROID_HOST_OUT=$(get_abs_build_var HOST_OUT)unset ANDROID_HOST_OUT_TESTCASESexport ANDROID_HOST_OUT_TESTCASES=$(get_abs_build_var HOST_OUT_TESTCASES)unset ANDROID_TARGET_OUT_TESTCASESexport ANDROID_TARGET_OUT_TESTCASES=$(get_abs_build_var TARGET_OUT_TESTCASES)# needed for building linux on MacOS# TODO: fix the path#export HOST_EXTRACFLAGS="-I "$T/system/kernel_headers/host_include
}
2.7 printconfig
function printconfig()
{local T=$(gettop)if [ ! "$T" ]; thenecho "Couldn't locate the top of the tree. Try setting TOP." >&2returnfiget_build_var report_config
}
调用get_build_var 函数将lunch 之后的编译配置信息:
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=11
TARGET_PRODUCT=shift
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=cortex-a55
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv8-a
TARGET_2ND_CPU_VARIANT=cortex-a55
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-4.15.0-142-generic-x86_64-Ubuntu-16.04.5-LTS
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=RD2A.211001.002
OUT_DIR=out
PRODUCT_SOONG_NAMESPACES=vendor/qqq/opensource/commonsys/packages/apps/Bluetooth vendor/qqq/opensource/commonsys/system/bt/conf external/v4l2_codec2
============================================
2.8