使用Android编译系统
编译完整的Android
根据Android官网的文档,需要编译完整的Android系统,其步骤如下:
a. 进入需要的编译的Android工程源码树顶层目录
b. . build/envsetup.sh // 配置环境变量
c. lunch // 从交互界面的列表中选择想要编译的产品
d. make // 开始编译
模块单独编译
模块单独编译的前提是系统已经进行一次完整的编译,因为这里面到包括依赖问题以及一些基本的工具制作问题,在后面的剖析中会讲到。
模块编译的方式有两种:
第一种方法,进入需要编译的模块所在的目录,执行
mm,该命令仅仅只会编译模块本身,并不会解决依赖问题
mma,改命令会检查模块依赖的其它模块是否需要编译,如果需要的话,则会先行编译依赖然后再编译模块本身.
第二种方法,在Android工程源码目录下面的任意位置,执行
mmm 相对路径 // 所要编译的模块相对当前位置的相对路径
mmma,同mma,会先解决依赖问题
SHELL环境的初始化
Android的编译系统有一部分工作是shell完成的,这样能简化makefile工程的使用,同时还能提高不同场景下使用编译系统的灵活性。
SHELL环境的初始化工作主要通过以下两个步骤完成,这也是编译android的先决条件
. build/envsetup.sh
lunch
解析envsetup.sh脚本
该脚本的工作主要可分为两个部分:
第一部分,它定义了一系列shell函数,来辅助我们编译开发android系统,包括了前面提到的lunch以及mm/mmm/mma/mmma函数。另外还有几个常用的函数如下:
croot // 切换至android源码树顶层目录
cgrep // 在当前目录下的所有c/c++/h文件上执行grep
jgrep // 在当前目下所有的java文件上执行grep
mgrep // 在当前目录下所有的makefile/mk/mak/make文件上执行grep
除此之外,还有许多不经常用的函数,有兴趣可以自行阅读脚本源码。
注意,envsetup.sh里面定义的函数,都要求在TOP及子目录下面使用。
第二部分,envsetup.sh在导入到当前shell环境时,还直接执行了以下几个步骤:
1629 if [ "x$SHELL" != "x/bin/bash" ]; then
1630 case `ps -o command -p $$` in
1631 *bash*)
1632 ;;
1633 *)
1634 echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"
1635 ;;
1636 esac
1637 fi
1638
1639 # Execute the contents of any vendorsetup.sh files we can find.
1640 for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null` \
1641 `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null`
1642 do
1643 echo "including $f"
1644 . $f
1645 done
1646 unset f
1647
1648 addcompletions
1629-1637行表示android编译依赖HOST的shell是bash,非bash的shell可能导致错误。
1648行完成envsetup.sh所定义的函数的自动补全功能。
1640-1645行,会从device和vendor目录下寻找vendorsetup.sh,如果找到,则将其内容导入到当前shell环境中来。这个过程就是添加各个硬件厂商自己定义的产品信息,通过这样的方式,android实际上就把编译系统的核心规则和需要由各个厂商来实现的具体产品相关的配置信息给剥离开来,从而形成一个松散的结构。这样的话,如果需要添加一个新的产品将变得容易很多,并且也不会影响到核心规则。
以高通平台为例,观察vendorsetup.sh所做的事情:
文件名为device/qcom/s410/vendorsetup.sh
add_lunch_combo s410-eng
add_lunch_combo s410-userdebug
add_lunch_combo s410-user
很简单,调用了三次add_lunch_combo函数,分别传入了三个参数。add_lunch_combo函数在build/envsetup.sh中实现的:
557 function add_lunch_combo()
558 {
559 local new_combo=$1
560 local c
561 for c in ${LUNCH_MENU_CHOICES[@]} ; do
562 if [ "$new_combo" = "$c" ] ; then
563 return
564 fi
565 done
566 LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
567 }
该函数就是把传进来的参数加入到数组LUNCH_MENU_CHOICES里面。关于LUNCH_MENU_CHOICES的作用,则需要从lunch函数入手,这里先记住我们往数组里面添加了三个元素:s410-eng, s410-userdebug,s410-user.
关于add_lunch_combo函数,还有一点需要说明一下,除了vendorsetup.sh调用了以外,envsetup.sh自己也调用了:
570 add_lunch_combo aosp_arm-eng
571 add_lunch_combo aosp_arm64-eng
572 add_lunch_combo aosp_mips-eng
573 add_lunch_combo aosp_mips64-eng
574 add_lunch_combo aosp_x86-eng
575 add_lunch_combo aosp_x86_64-eng
看到aosp就明白了,这是原生android内置的一组默认产品,因此在这里调用也算合情合理。
导入envsetup.sh以后,我们得到了两个结果:一组shell函数和一个shell数组。
lunch函数的实现
首先看运行lunch的效果,lunch可以有两种用法:
a. 如果直接执行lunch,不带参数,那么lunch会给出一个menu来与用户交互,比如:
You’re building on Linux
Lunch menu… pick a combo:
1. aosp_arm-eng
2. aosp_arm64-eng
3. aosp_mips-eng
4. aosp_mips64-eng
5. aosp_x86-eng
6. aosp_x86_64-eng
7. aosp_manta-userdebug
8. mini_emulator_arm64-userdebug
9. mini_emulator_mips-userdebug
10. m_e_arm-userdebug
11. mini_emulator_x86-userdebug
12. mini_emulator_x86_64-userdebug
13. aosp_shamu-userdebug
14. aosp_flo-userdebug
15. aosp_grouper-userdebug
16. full_fugu-userdebug
17. aosp_fugu-userdebug
18. aosp_deb-userdebug
19. aosp_tilapia-userdebug
20. aosp_hammerhead-userdebug
21. aosp_mako-userdebug
22. s410-eng
23. s410-userdebug
24. s410-user
Which would you like? [aosp_arm-eng]
然后等待我们输入,这里可以输入列表项或者项对应的数字编号。
b. 我们也可以直接运行一个带参数的lunch,比如lunch s410-eng或者lunch 22
效果和上面两步合起来的效果是一样的。
lunch执行后,除了会在背后做一系列动作,同时还会dump出一些信息给我们:
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=5.0.2
TARGET_PRODUCT=s410
TARGET_BUILDSPEC=pc3
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a53
HOST_ARCH=x86_64
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.16.0-38-generic-x86_64-with-Ubuntu-14.04-trusty
HOST_BUILD_TYPE=release
BUILD_ID=LRX22G
OUT_DIR=out
============================================
先不解释这个结果,往下面继续分析到相关处,再来回过头看这些信息。
在分析lunch函数前,先分析两个在后续过程中会频繁使用的函数:
get_build_var
get_abs_build_var
以get_build_var为例分析:
51 function get_build_var()
52 {
53 T=$(gettop)
54 if [ ! "$T" ]; then
55 echo "Couldn't locate the top of the tree. Try setting TOP." >&2
56 return
57 fi
58 (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
59 command make --no-print-directory -f build/core/config.mk dumpvar-$1)
60 }
实际上是执行了一条make命令,目标是dumpvar-XXX,比如dumpvar-TARGET_DEVICE.这条目标实际上是在core/dumpvar.mk里面定义的:
28 dumpvar_goals := \
29 $(strip $(patsubst dumpvar-%,%,$(filter dumpvar-%,$(MAKECMDGOALS))))
…
48 DUMPVAR_VALUE := $($(dumpvar_goals))
49 dumpvar_target := dumpvar-$(dumpvar_goals)
…
53 $(dumpvar_target):
54 @echo $(DUMPVAR_VALUE)
还是以dumpvar-TARGET_DEVICE为例,dumpvar_target实际上就是目标dumpvar-TARGET_DEVICE,而DUMPVAR_VALUE内容为$(TARGET_DEVICE),这是一个makefile变量,是通过厂商定义的PRODUCT_DEVICE变量得到的,在分析编译流程时会详细描述这个过程。
这个函数的作用就是把一个make变量转成同名的shell变量。这么做的原因是,这些变量的值都定义在makefile里面,因为整个android编译系统的主体工程还是makefile工程,所以如果shell里面需要用到某些变量的话,通过这个巧妙的转换就可以获得了。
get_abs_build_var这个函数作用差不多,其make的目标是dumpvar-abs-XXX. 如果shell想要的值是一个路径的话,get_build_var返回的可能是相对路径,这取决于makefile里面的同名变量,而get_abs_build_var会把相对路径转换成绝对路径,它是这么做的:
41 ifneq ($(filter /%,$($(dumpvar_goals))),)
42 DUMPVAR_VALUE := $($(dumpvar_goals))
43 else
44 DUMPVAR_VALUE := $(PWD)/$($(dumpvar_goals))
45 endif
这里的相对路径是针对TOP而言的,这里使用PWD转换的。因为在执行make命令时,有这样的动作:
58 (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
59 command make --no-print-directory -f build/core/config.mk dumpvar-$1)
所以make运行的所在路径其实就是TOP。
开始分析lunch函数
596 function lunch()
597 {
598 local answer
599
// 如果带参数,直接把参数记录在answer里面,否则等待