一、概述
一般Android App Developer
,是无需关心Build系统
是如何运作的,因为Android Studio
的Gradle
工具已经帮我们简化了这些操作。但如果你想成为一个Android Framework Engineer
,就必须对此有所了解,因为无论是你想在Aosp源码中添加自己的设备,还是想新增系统应用,这些知识都是必要的。
可是对于新手,往往对不知道Build系统
如何入门,遇到问题也只能Google和Baidu,但能否解决问题,这通常跟系统版本密切相关。所以:当遇到编译的问题时,阅读Build系统源码才是王道。
Android的Build系统是基于GNU Make和Shell构建的一套编译环境。Android的Build系统可分为3大模块:
- 1、位于build/core目录下的文件,这是Android Build系统的框架和核心。
- 2、位于device目录下的文件,存放的是具体产品的配置文件。
- 3、模块的编译文件:Android.mk,位于模块的源文件目录下。
我们在上一章已经知道编译系统镜像的步骤:
-
1、建立Android编译环境
$ source ./build/envsetup.sh
-
2、根据用户输入或选择的产品名来设置与具体产品相关的环境变量
$ lunch <product_name>-<build_variant>
或者$ lunch <序号>
或者$ lunch #打印出产品菜单项
$ 输入序号
-
3、开始编译
$ make update-api -j4
$ make -j4
我们可以通过上述步骤,去逐步分析Build系统是如何去编译这个系统源码的。
这里,我们采用android-5.0.2_r1
的分支源码,其余分支源码也类似。
二、系统镜像编译流程分析
首先,我们执行./build/envsetup.sh
脚本文件,该脚本中定义了一些有用的shell命令,可通过hmm
命令进行查看:
sh
$ source ./build/envsetup.sh
$ hmm
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.
- sgrep: Greps on all local source files.
- godir: Go to the directory containing a file.
Look at the source to view more functions. The complete list is:
addcompletions add_lunch_combo cgrep check_product check_variant choosecombo chooseproduct choosetype choosevariant cproj croot findmakefile gdbclient gdbwrapper get_abs_build_var getbugreports get_build_var getdriver getlastscreenshot get_make_command getprebuilt getscreenshotpath getsdcardpath get_symbols_directory gettargetarch gettop ggrep godir hmm is isviewserverstarted jgrep key_back key_home key_menu lunch _lunch m make mangrep mgrep mm mma mmm mmma pez pid printconfig print_lunch_menu qpid resgrep runhat runtest sepgrep set_java_home setpaths set_sequence_number set_stuff_for_environment settitle sgrep smoketest stacks startviewserver stopviewserver systemstack tapas tracedmdump treegrep
其中常用的命令有lunch
、mm
、mma
、mmm
、mmma
,croot
,其中lunch
命令是我们接下来分析的重点,而后5个命令在hmm
中已给出了简单清晰的解释,故不赘述。
当执行source ./build/envsetup.sh
时,会自动执行以下代码:
sh
......省略部分代码......
# 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
......省略部分代码......
# 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` \
`test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
echo "including $f"
. $f
done
unset f
可以清楚发现,其主要功能包括:
- 1、利用
add_lunch_combo
函数添加aosp设备默认编译选项。 - 2、去
device、vendor、product
三个目录下搜寻vendorsetup.sh
文件,并include
进来。
而vendorsetup.sh
文件通常内容也只是add_lunch_combo xxxxx
,用来添加自定义设备编译选项。
那我们接下来分析add_lunch_combo
命令:
sh
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_lunch_combo
命令会将所传递的参数存放到一个全局的数组变量LUNCH_MENU_CHOICES
中。执行lunch
命令时,打印的菜单项正是该数组的内容。
当所有设备编译选项都被add_lunch_combo
添加后,就可以执行lunch
命令对编译环境进行正式的配置。
sh
function lunch()
{
local answer
// 如果lunch命令参数非空,则给变量answer赋值,否则打印LUNCH_MENU_CHOICES数组
if [ "$1" ] ; then
answer=$1
else
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
// 用户依旧不选择lunch项,则将selection设置为aosp_arm-eng
if [ -z "$answer" ]
then
selection=aosp_arm-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
// 如果变量answer是数字,则将selection设置为LUNCH_MENU_CHOICES数组中对应项
if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
then
selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
fi
// 如果变量answer非数字,判断其格式是否符合规范
elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
then
selection=$answer
fi
if [ -z "$selection" ]
then
echo
echo "Invalid lunch combo: $answer"
return 1
fi
export TARGET_BUILD_APPS=
// 把变量selection中的字符串用"-"分成两部分,前部分赋值给变量product,并调用函数check_product去检查是否存变量product所对应的产品配置文件
local product=$(echo -n $selection | sed -e "s/-.*$//")
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
// 后部分赋值给变量variant,并调用函数check_variant去检查这个值是否是"eng"、"user"、"userdebug"之一
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
// 环境变量赋值
export TARGET_PRODUCT=$product
export TARGET_BUILD_VARIANT=$variant
export TARGET_BUILD_TYPE=release
echo
//设置更多的环境变量
set_stuff_for_environment
//打印配置信息
printconfig
}
其中整个lunch命令核心代码如下,用于配置所需编译的设备镜像。
sh
export TARGET_PRODUCT=$product
export TARGET_BUILD_VARIANT=$variant
export TARGET_BUILD_TYPE=release
lunch
执行完后,系统会打印出当前配置所生成的环境变量。
ini
pujh@dell:~/Tiny4412/android-5.0.2$ lunch full_tiny4412-eng
============================================
PLATFORM_VERSION_CODENAME=REL \\平台版本名称
PLATFORM_VERSION=5.0.2 \\Android平台版本号
TARGET_PRODUCT=full_tiny4412 \\所编译的产品名称
TARGET_BUILD_VARIANT=eng \\编译的产品类型
TARGET_BUILD_TYPE=release \\编译的类型
TARGET_BUILD_APPS= \\当编译整个系统时,变量为null;当编译单个模块时,变量为模块的路径
TARGET_ARCH=arm \\编译目标的CPU架构
TARGET_ARCH_VARIANT=armv7-a-neon \\编译目标的CPU架构版本
TARGET_CPU_VARIANT=cortex-a9 \\编译目标的CPU代号
TARGET_2ND_ARCH= \\编译目标的第二CPU架构
TARGET_2ND_ARCH_VARIANT= \\编译目标的第二CPU架构版本
TARGET_2ND_CPU_VARIANT= \\编译目标的第二CPU代号
HOST_ARCH=x86_64 \\编译平台的CPU架构
HOST_OS=linux \\编译平台的操作系统
HOST_OS_EXTRA=Linux-4.4.0-21-generic-x86_64-with-Ubuntu-16.04-xenial \\编译平台操作系统的一些额外信息
HOST_BUILD_TYPE=release
BUILD_ID=LRX22G \\该值会出现在编译的版本信息中,可以利用整个环境变量来定义公司特有的标识
OUT_DIR=out \\编译结果输出目录
============================================
三、make分析
Makefile文件主要由3种内容构成:变量定义、函数定义和目标依赖规则,其详细使用教程参考跟我一起写Makefile。
当用户执行make
命令时,会调用当前目录下Makefile
文件,Android源码根目录Makefile
文件内容只有一行,用于指向build/core/main.mk
文件:
makefile
include build/core/main.mk
main.mk
是Android Build系统的主控文件。从main.mk
开始,将通过include命令将其余所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个集合相当于一个巨大的Makefile文件。其大致包含关系如下:
其中config.mk会调用Android.mk中常用的mk定义,例如:
mk
# ###############################################################
# Build system internal files
# ###############################################################
BUILD_COMBOS:= $(BUILD_SYSTEM)/combo
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_RAW_STATIC_LIBRARY := $(BUILD_SYSTEM)/raw_static_library.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_RAW_EXECUTABLE:= $(BUILD_SYSTEM)/raw_executable.mk
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk
BUILD_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/shared_test_lib.mk
BUILD_HOST_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/host_shared_test_lib.mk
BUILD_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/static_test_lib.mk
BUILD_HOST_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/host_static_test_lib.mk
BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
以及C/C++代码编译时的参数以及系统常用包的后缀名。
加快编译速度CCache
预编译模块的目标定义:
brk:调整堆的高地址边界 mmap:在堆区和栈区之间寻找一块合适的内存空间 munmap mremap
内存分配器------dlmalloc dlmalloc 2.8.6 源码详解