Andoird编译系统分析(一)

来源:互联网 发布:淘宝店级别怎么分 编辑:程序博客网 时间:2024/05/17 09:29

使用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" ]; then1630     case `ps -o command -p $$` in1631         *bash*)1632             ;;1633         *)1634             echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"1635             ;;1636     esac1637 fi1638 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 do1643     echo "including $f"1644     . $f1645 done1646 unset f1647 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-engadd_lunch_combo s410-userdebugadd_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里面,否则等待用户选择,并将读到的结果记录在answer里。 600     if [ "$1" ] ; then 601         answer=$1 602     else             // print_lunch_menu 就是把LUNCH_MENU_CHOICES内容打印出来,这样就能让用户知道,有哪些产品候选 603         print_lunch_menu 604         echo -n "Which would you like? [aosp_arm-eng] " 605         read answer 606     fi 607  608     local selection= 609          // 如果不带参数,也没有选择而直接回车的话,answer为空,选择默认产品 610     if [ -z "$answer" ] 611     then 612         selection=aosp_arm-eng         // 检查answer的值是否为数字 613     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$") 614     then             // 如果数字小于数组长度,则将对应元素记录在selection当中 615         if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ] 616         then 617             selection=${LUNCH_MENU_CHOICES[$(($answer-1))]} 618         fi         // 如果是字符串,则检查格式是否为:xxx-yyy 619     elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$") 620     then 621         selection=$answer 622     fi 623          // 到这一步,可以了解到LUNCH_MENU_CHOICES记录的xxx-xxx格式的产品信息,其直接作用就是方便在lunch时与用户进行交互。但是需要注意的是,vendorsetup.sh可不是随便添加信息的,必须是要确实有这样一个产品,才可以合法地使用,关于这一点,下面很快就会看到了         // 没有找到合适的候选产品,则直接报错退出了 624     if [ -z "$selection" ] 625     then 626         echo 627         echo "Invalid lunch combo: $answer" 628         return 1 629     fi 630  631     export TARGET_BUILD_APPS= 632         // 这里先说一下xxx-xxx的格式,比如s410-eng,分为两个部分,以‘-’分隔,前半部分是指的产品名;后半部分是module_tag,可选的有:eng/userdebug/user,这个三个tag决定最终编出来的系统有很大的不一样,分析编译的时候再细说,一般最终产品的发布版本user类型的        // 取出产品名,通过check_product函数进行验证,该函数通过判断TARGET_DEVICE变量是否为空来表征产品是否合法,而TARGET_DEVICE的表征是通过makefile工程完成的,在后面会详说 633     local product=$(echo -n $selection | sed -e "s/-.*$//") 634     check_product $product 635     if [ $? -ne 0 ] 636     then 637         echo 638         echo "** Don't have a product spec for: '$product'" 639         echo "** Do you have the right repo manifest?" 640         product= 641     fi 642          // 取出  TAG,并进行检查,check_variant比较简单,就是把tag和三个候选项进行比对,确定其是三个当中的一个 643     local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//") 644     check_variant $variant 645     if [ $? -ne 0 ] 646     then 647         echo 648         echo "** Invalid variant: '$variant'" 649         echo "** Must be one of ${VARIANT_CHOICES[@]}" 650         variant= 651     fi 652  653     if [ -z "$product" -o -z "$variant" ] 654     then 655         echo 656         return 1 657     fi 658  659     export TARGET_PRODUCT=$product 660     export TARGET_BUILD_VARIANT=$variant 661     export TARGET_BUILD_TYPE=release 662          // 这是高通自己添加的功能,就是支持在product下面再细分设备,不做过多描述 663     echo 664     choosebuildspec $2 665  666     echo 667  668     set_stuff_for_environment         // 就是前面看到的lunch运行完成后dump出来的信息 669     printconfig 670 }
 231 function set_stuff_for_environment() 232 {          // 设置PROMPT_COMMAND值,这是一个bash内置变量,与主线关系不大,不作说明 233     settitle         // 检查JAVA_HOME,开发5.0以上版本,linux系统都被要求通过包管理软件安装openjdk7.0,开发者不必自己再单独配置JAVA环境 234     set_java_home 235     setpaths  236     set_sequence_number   237    238     export ANDROID_BUILD_TOP=$(gettop) 239     # With this environment variable new GCC can apply colors to warnings/errors 240     export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' 241     export ASAN_OPTIONS=detect_leaks=0 242 }

setpaths函数的实现比较啰嗦,但结果很明确,就是export出一组变量共编译、开发期间使用。所以不再贴代码了,直接说明一下这组变量。

TARGET_GCC_VERSION
64位的GCC版本号,通过get_build_var TARGET_GCC_VERSION得到,其定义在core/combo/TARGET_HOSTOS-ARCH.mk里面,具体的生成过程在编译一节里面讲述。

ANDROID_TOOLCHAIN
64位toolchain的路径,由下列步骤导出,以arm64为例:

gccprebuiltdir=$(get_abs_build_var ANDROID_GCC_PREBUILTS)toolchaindir=aarch64/aarch64-linux-android-$targetgccversion/binANDROID_TOOLCHAIN=$gccprebuiltdir/$toolchaindir

其中ANDROID_GCC_PREBUILTS定义在build/core/dumpvar.mk当中;targetgccversion就是TARGET_GCC_VERSION.

ANDROID_TOOLCHAIN_2ND_ARCH
32位的toolchain的路径,导出过程和ANDROID_TOOLCHAIN一致,仅仅替换toolchaindir为toolchaindir2,以arm为例:

toolchaindir2=arm/arm-linux-androideabi-$targetgccversion2/bin

其中targetgccversion2由get_build_var 2ND_TARGET_GCC_VERSION得到,其定义在core/combo/TARGET_HOSTOS-ARCH.mk里面,具体的生成过程在编译一节里面讲述。
ANDROID_KERNEL_TOOLCHAIN_PATH // 编译arm架构的kernel时,需要单独指定toolchain,这里用的是arm-eabi

ANDROID_DEV_SCRIPTS // 用到的一些脚本的路径
ANDROID_PRE_BUILD_PATHS // 就是java程序的路径
ANDROID_PRODUCT_OUT // 编译产出路径,就是out
ANDROID_HOST_OUT // host工具的路径,即out/host

心得总结
通篇看下来,lunch函数里面虽然做了很多初始化的工作,但我认为这些工作其实和make的关系不是很大,也就是说,没有运行lunch和envsetup.sh,也是可以用make来编译完整android镜像的(对于单个模块而言,如果能找到真正的目标名,理论上也可以直接编译)。
我在aosp的代码下做了如下实验:
不执行envsetup.sh和lunch函数,直接进行下列步骤:
export TARGET_PRODUCT=aosp_arm64
export TARGET_BUILD_VARIANT=eng
make systemimage
发现最终就可以编译出system.img了,也就是说,lunch的结果,直接影响make编译的也就是两个变量:
TARGET_PRODUCT
TARGET_BUILD_VARIANT
原因是,通过TARGET_PRODUCT,make可以在device/vendor目录下,找到产品对应的AndroidProducts.mk,有了这个文件,顺藤摸瓜,编译一款产品的完整信息就都能获得了。关于TARGET_PRODUCT的用法,在下章product_config.mk里面会看到;而TARGET_BUILD_VARIANT的主要是在envsetup.mk里面会进行检查,空值也是非法的。

不过这个方法在s410上面会失败,原因是s410有一个buildspec,android默认也有,但是没有处理,qcom在这部分改动比较大,还有牵扯到了perl脚本之类的。因为属于定制化的东西,我也没多分析,不过这些都是在lunch里面执行的,所以s410,不lunch是无法直接编译的。

说了这么多,就是想理顺一下android到底在shell里面做了什么?其实在AOSP系统里面,没做什么特别必要的东西,也就是搞了一些工具,方便开发者使用,以及粗略地检查了一下编译环境;不过不同的厂商,可能会利用lunch做一些自己的定制,所以,我们还是应该尽量按照官方步骤来编译android.

Makefile环境的初始化
SHELL环境初始化完成以后,在TOP下面执行make就会开始进入Makefile部分。
TOP/Makefile内容如下:

   ### DO NOT EDIT THIS FILE ###   include build/core/main.mk   ### DO NOT EDIT THIS FILE ###

所以整个编译流程的主入口转到了main.mk里面,后面的分析还是继续以高通平台的s410为例。
main.mk从上往下分为三大部分:解析配置文件,检查环境,定义目标规则。本章讲述前两个部分。
先列出本章涉及到的主要文件:
(TARGETDEVICEDIR)/BoardConfig.mk(TARGET_DEVICE_DIR)/AndroidProducts.mk
以上三个定义在厂商路径下,s410的就是device/qcom/s410. 其余的文件都在build/core下面:
(BUILDSYSTEM)/main.mk(BUILD_SYSTEM)/envsetup.mk
(BUILDSYSTEM)/config.mk(BUILD_SYSTEM)/product_config.mk
$(BUILD_SYSTEM)/select.mk

解析配置文件
定义变量:BUILD_SYSTEM := $(TOPDIR)build/core
被依次导入的几个重要mk文件:

include $(BUILD_SYSTEM)/help.mkinclude $(BUILD_SYSTEM)/config.mkinclude $(BUILD_SYSTEM)/cleanbuild.mk

help.mk里面主要定义了一个目标:help,其动作就是dump一个usage.
cleanbuild.mk定义了与清除编译产物相关的目标,源码当中的各个模块自定义的CleanSpec.mk也会在这里被导入进来。

解析config.mk
整个Android用到的Makefile变量,可以分成两类,一类是与编译对象相关的,比如:编译目标,编译模块,编译文件等等,这类变量主要定义在各个模块的Android.mk当中,最后在main.mk里面被汇总表征出来,这部分变量和编译相关性较大,将放在后续章节中讲述;而另一类变量是基础性,比如要编译的产品,该产品用到的特性,用到的工具,需要编译哪些模块等更加宏观,全局的变量,这是本章要讲述的重点。

下面列出了config.mk及其内部导入的mk文件的层次结构:

$(BUILD_SYSTEM)/config.mk>>  $(BUILD_SYSTEM)/pathmap.mk  $(BUILD_SYSTEM)/envsetup.mk  >>    $(BUILD_SYSTEM)/version_defaults.mk    $(BUILD_SYSTEM)/product_config.mk       $(BUILD_SYSTEM)/node_fns.mk       $(BUILD_SYSTEM)/product.mk       $(BUILD_SYSTEM)/device.mk    $(board_config_mk)  即BoardConfig.mk  <<  $(BUILD_SYSTEM)/combo/select.mk  >>    $(BUILD_COMBOS)/$(combo_target)$(combo_os_arch).mk  <<  $(BUILD_SYSTEM)/javac.mk  $(BUILD_SYSTEM)/clang/config.mk  $(BUILD_SYSTEM)/dumpvar.mk<<

另外还有两个编外人员:

$(TARGET_DEVICE_DIR)/BoardConfig.mk$(TARGET_DEVICE_DIR)/AndroidProducts.mk

先讨论后面两个,这两个文件是由各个厂商自己定义的,其位置TARGET_DEVICE_DIR一般是在device目录或者vendor目录下面,s410的在device/qcom/s410.

BoardConfig.mk
定义的是板级相关的信息,也就是和硬件强相关的一些变量,比如SoC的架构,bootloader/kernel的相关信息,外设的相关信息,存储芯片及系统分区镜像的大小等等。比较关键,也常会涉及到的是:

TARGET_ARCH := arm64TARGET_ARCH_VARIANT := armv8-aTARGET_CPU_ABI := arm64-v8aTARGET_CPU_ABI2 :=TARGET_CPU_VARIANT := genericTARGET_2ND_ARCH := arm TARGET_2ND_ARCH_VARIANT := armv7-a-neonTARGET_2ND_CPU_ABI := armeabi-v7aTARGET_2ND_CPU_ABI2 := armeabiTARGET_2ND_CPU_VARIANT := cortex-a53

SoC的体系架构,这些变量主要是用于编译目标语言时的标准,Android5.0以后,系统可以同时兼容64/32位程序,因此引入了2ND_ARCH的概念,在arm平台,标准的搭配一般就是armv8(64) + armv7(32)这样的组合,系统原生的组件,比如system_server和其它本地服务都是64位的,其它模块可以在两者之间灵活组合,具体的实现方式在后续讨论中会陆续提到。

TARGET_USERIMAGES_USE_EXT4 := trueBOARD_BOOTIMAGE_PARTITION_SIZE := 0x02000000BOARD_RECOVERYIMAGE_PARTITION_SIZE := 0x02000000BOARD_SYSTEMIMAGE_PARTITION_SIZE := 1288491008BOARD_USERDATAIMAGE_PARTITION_SIZE := 1860648960BOARD_CACHEIMAGE_PARTITION_SIZE := 268435456BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4BOARD_PERSISTIMAGE_PARTITION_SIZE := 33554432BOARD_PERSISTIMAGE_FILE_SYSTEM_TYPE := ext4BOARD_FLASH_BLOCK_SIZE := 131072  存储分区的相关属性,一般是各个分区的大小和文件系统类型。BOARD_KERNEL_CMDLINE := console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom msm_rtb.filter=0x237 ehci-hcd.park=3 a oidboot.bootdevice=7824900.sdhci lpm_levels.sleep_disabled=1 earlyprintkBOARD_KERNEL_SEPARATED_DT := trueBOARD_KERNEL_BASE        := 0x80000000BOARD_KERNEL_PAGESIZE    := 2048BOARD_KERNEL_TAGS_OFFSET := 0x01E00000BOARD_RAMDISK_OFFSET     := 0x02000000TARGET_KERNEL_ARCH := arm64TARGET_KERNEL_HEADER_ARCH := arm64TARGET_KERNEL_CROSS_COMPILE_PREFIX := aarch64-linux-android-TARGET_USES_UNCOMPRESSED_KERNEL := true内核的相关属性,通过变量名字即可知道相应的意义。NUM_FRAMEBUFFER_SURFACE_BUFFERS := 3  Framebuffer可使用的最大buffer个数,被SurfaceFlinger使用。MALLOC_IMPL := dlmalloc  Bionic使用的内存分配器,默认为dlmalloc,还可以选择jemalloc.TARGET_USES_ION := trueTARGET_USES_NEW_ION_API :=true  与ION内存技术相关的变量,ION是android4.0以后引入的一种共享内存技术,多用于媒体部分。TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_msm  Recovery系统支持的厂商扩展功能库。

除此之外,各个厂商还会有一些自己定义的相关属性,就不做讨论了,有兴趣的可以自己去看。

AndroidProducts.mk

还是以s410为例,该文件内容:

PRODUCT_MAKEFILES := \    $(LOCAL_DIR)/s410.mk

相当于一个跳板,直接看s410.mk的内容。与BoardConfig.mk正相反,这里定义的变量基本上都是和软件相关的。

PRODUCT_DEVICE := s410

这个变量最终体现为TARGET_DEVICE,在编译系统里面会被频繁使用的一个变量。在第一章里面,check_product函数就是检查的这个值。

PRODUCT_COPY_FILES += \device/qcom/msm8916_32/audio_policy.conf:system/etc/audio_policy.conf \….

类似于overlay,在编译时用厂商自己的文件替换android默认的同名文件,配置类文件多用这种方法。

PRODUCT_PACKAGES += libGLES_android \                    …

需要参与整体编译的模块,这里主要添加厂商自己额外的部分,android系统部分的在build/core里面定义。这个变量就属于前面说的第一类变量,是表征参与编译的对象的,在本章不做讨论。

DEVICE_PACKAGE_OVERLAYS := device/qcom/s410/overlay

也是一种overlay,与PRODUCT_COPY_FILES不同,这里要进行覆盖的东西是将要参与编译的内容,比如源码,或者java的资源文件xml等,常见的覆盖文件都是frameworks/base/core/res/res/values/config.xml; 后者属于配置类,在打包镜像前替换即刻。

同样的,这里也有一部分厂商自定义的属性,用来控制编译期的某些选项。

product_config.mk
该文件主要作用就是找到对应产品的AndroidProducts.mk, 并解析PRODUCT_MAKEFILES所指向的文件,在这里也就是解析s410.mk的内容。在解析过程中,会使用到许多make函数,它们大部分定义在:

$(BUILD_SYSTEM)/node_fns.mk$(BUILD_SYSTEM)/product.mk$(BUILD_SYSTEM)/device.mk

有些函数的实现有些绕,不必深究它们,不影响我们的分析。

all_product_configs := $(get-all-product-makefiles) 

函数具体的过程比较简单,不分析了,最终的结果,就是把device/vendor目录下所有的AndroidProduct.mk里面的PRODUCT_MAKEFILES都列出来,并存放在all_product_configs里面。

得到all_product_configs,会进行如下处理:

# PRODUCT_MAKEFILES is set up in AndroidProducts.mks. # Format of PRODUCT_MAKEFILES: # <product_name>:<path_to_the_product_makefile> # If the <product_name> is the same as the base file name (without dir # and the .mk suffix) of the product makefile, "<product_name>:" can be # omitted. current_product_makefile :=all_product_makefiles :=$(foreach f, $(all_product_configs),\    $(eval _cpm_words := $(subst :,$(space),$(f)))\    $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\    $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\    $(if $(_cpm_word2),\        $(eval all_product_makefiles += $(_cpm_word2))\        $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\            $(eval current_product_makefile += $(_cpm_word2)),),\        $(eval all_product_makefiles += $(f))\        $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\            $(eval current_product_makefile += $(f)),)))_cpm_words :=_cpm_word1 :=_cpm_word2 :=current_product_makefile := $(strip $(current_product_makefile))all_product_makefiles := $(strip $(all_product_makefiles))

PRODUCT_MAKEFILES是个mk文件列表,里面的格式可以是两种,上面一段注释已经说得比较清楚了,这里不再赘述。
我们一般使用的都是后者,就是只有冒号的后半部分。

在第二章里面强调的比较多的TARGET_PRODUCT,在这里总归是派上用场了,它用来匹配PRODUCT_MAKEFILES列表,通过上面的注释可以知道列表中的名字必须要是product_name,因此匹配成功的那个product,就是我们想要编译的产品,这里就是s410,并将其存放在current_product_makefile变量里面。

$(call import-products, $(current_product_makefile)) 

这里就开始解析s410.mk的内容了,在product.mk里面定义了一个列表:

_product_var_list := \     PRODUCT_NAME \     PRODUCT_MODEL \     PRODUCT_LOCALES \     PRODUCT_AAPT_CONFIG \     PRODUCT_AAPT_PREF_CONFIG \     PRODUCT_AAPT_PREBUILT_DPI \     PRODUCT_PACKAGES \     PRODUCT_PACKAGES_DEBUG \     PRODUCT_PACKAGES_ENG \     PRODUCT_PACKAGES_TESTS \     PRODUCT_DEVICE \     PRODUCT_MANUFACTURER \     PRODUCT_BRAND \     PRODUCT_PROPERTY_OVERRIDES \     PRODUCT_DEFAULT_PROPERTY_OVERRIDES \     PRODUCT_CHARACTERISTICS \     PRODUCT_COPY_FILES \     PRODUCT_OTA_PUBLIC_KEYS \     PRODUCT_EXTRA_RECOVERY_KEYS \     PRODUCT_PACKAGE_OVERLAYS \     DEVICE_PACKAGE_OVERLAYS \     PRODUCT_SDK_ATREE_FILES \     PRODUCT_SDK_ADDON_NAME \     PRODUCT_SDK_ADDON_COPY_FILES \     PRODUCT_SDK_ADDON_COPY_MODULES \     PRODUCT_SDK_ADDON_DOC_MODULES \     PRODUCT_SDK_ADDON_SYS_IMG_SOURCE_PROP \     PRODUCT_DEFAULT_WIFI_CHANNELS \     PRODUCT_DEFAULT_DEV_CERTIFICATE \     PRODUCT_RESTRICT_VENDOR_FILES \     PRODUCT_VENDOR_KERNEL_HEADERS \     PRODUCT_BOOT_JARS \     PRODUCT_SUPPORTS_VERITY \     PRODUCT_OEM_PROPERTIES \     PRODUCT_SYSTEM_PROPERTY_BLACKLIST \     PRODUCT_SYSTEM_SERVER_JARS \     PRODUCT_VERITY_SIGNING_KEY \     PRODUCT_SYSTEM_VERITY_PARTITION \     PRODUCT_VENDOR_VERITY_PARTITION \     PRODUCT_DEX_PREOPT_MODULE_CONFIGS \     PRODUCT_DEX_PREOPT_DEFAULT_FLAGS \     PRODUCT_DEX_PREOPT_BOOT_FLAGS 

import-products函数从s410.mk里面找到这些变量,并将其记录在对应的$(PRODUCTS.$(INTERNAL_PRODUCT).XXX变量当中,这个转换过程有些繁琐,不过这不影响我们的整个分析过程,只需要知道两个结论就好:
a. $(PRODUCTS.$(INTERNAL_PRODUCT).XXX的内容就是XXX的内容,比如说:
$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES的内容就是PRODUCT_PACKAGES的内容。
b. INTERNAL_PRODUCT就是s410.mk的目录名(TOP相对路径)。

比较例外的是TARGET_DEVICE是对应的PRODUCT_DEVICE:
TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)

后面在编译目标时,$(PRODUCTS.$(INTERNAL_PRODUCT).XXX用的会比较多,我们就当做XXX来认为就好了。

所以说product_config.mk的主要工作时找到并解析AndroidProducts.mk及相关文件的内容,并将其记录在$(PRODUCTS.$(INTERNAL_PRODUCT).XXX里面。

envsetup.mk
顾名思义,这是配置环境相关的部分。

首先,该文件先导入BoardConfig.mk,如前面所述,这个文件里面记录许多板级相关的变量。

其次,配置了HOST/TARGET环境:
HOST:
host的情况主要根据uname工具计算出来,
HOST_OS可以是linux/osx/windows(cygwin)

# HOST_ARCHifneq (,$(findstring x86_64,$(UNAME)))  HOST_ARCH := x86_64  HOST_2ND_ARCH := x86  HOST_IS_64_BIT := trueelseifneq (,$(findstring x86,$(UNAME)))$(error Building on a 32-bit x86 host is not supported: $(UNAME)!)endifendif

因此HOST必须要是64位的系统。

TARGET:

# Set up configuration for target machine. # The following must be set: #       TARGET_OS = { linux } #       TARGET_ARCH = { arm | x86 | mips } TARGET_OS := linux # TARGET_ARCH should be set by BoardConfig.mk and will be checked later ifneq ($(filter %64,$(TARGET_ARCH)),) TARGET_IS_64_BIT := true endif 

根据注释内容,TARGET_OS必须为linux,而ARCH可以支持多个平台,但需要厂商自己在BoardConfig.mk里面定义。所以可以推断,android确实是基于linux系统的,这里可以说主要表现在系统调用、C库等方面;而且它还提供了arm/x86/mips三种架构的编译器。

最后,定义了out路径:

OUT_DIR := $(TOPDIR)outTARGET_OUT_ROOT_release := $(OUT_DIR)/targetTARGET_OUT_ROOT_debug := $(DEBUG_OUT_DIR)/targetTARGET_OUT_ROOT := $(TARGET_OUT_ROOT_$(TARGET_BUILD_TYPE))TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/productPRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)TARGET_COPY_OUT_SYSTEM := systemTARGET_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM)

所以最终的TARGET_OUT就是system目录,其它的目录也都用类似的方式表征出来

select.mk
config.mk有如下语句:

combo_target := TARGET_combo_2nd_arch_prefix :=include $(BUILD_SYSTEM)/combo/select.mk

select.mk根据combo_target和combo_2nd_arch_prefix导入架构的mk文件:

combo_os_arch := $($(combo_target)OS)-$($(combo_target)$(combo_2nd_arch_prefix)ARCH)即combo_os_arch := $(TARGET_OS)-$(TARGET_ARCH),根据上文BoardConfig.mk以及envsetup.mk介绍的,这里最后就是:TARGET_linux-arm64接着select.mk会导入include $(BUILD_COMBOS)/$(combo_target)$(combo_os_arch).mk, 这里就是TARGET_linux-arm64.mk, 这个文件里面会选择与架构对应的编译器:ifeq ($(strip $(TARGET_TOOLS_PREFIX)),)TARGET_TOOLCHAIN_ROOT := prebuilts/gcc/$(HOST_PREBUILT_TAG)/aarch64/aarch64-linux-android-$(TARGET_GCC_VERSION)TARGET_TOOLS_PREFIX := $(TARGET_TOOLCHAIN_ROOT)/bin/aarch64-linux-android-endifTARGET_CC := $(TARGET_TOOLS_PREFIX)gccTARGET_CXX := $(TARGET_TOOLS_PREFIX)g++TARGET_AR := $(TARGET_TOOLS_PREFIX)arTARGET_OBJCOPY := $(TARGET_TOOLS_PREFIX)objcopyTARGET_LD := $(TARGET_TOOLS_PREFIX)ldTARGET_READELF := $(TARGET_TOOLS_PREFIX)readelfTARGET_STRIP := $(TARGET_TOOLS_PREFIX)strip

并且选择与该编译器配套使用的其它部分,比如CFLAGS/LDFLAGS,默认链接的c库及头文件还有链接加载器等。

TARGET_C_INCLUDES := \    $(libc_root)/arch-arm64/include \    $(libc_root)/include \    $(KERNEL_HEADERS) \    $(libm_root)/include \    $(libm_root)/include/arm64 \TARGET_DEFAULT_SYSTEM_SHARED_LIBRARIES := libc libmTARGET_LINKER := /system/bin/linker64

这些TARGET_XXX工具,将在build工程的其它通过$(my_prefix)XXX引用,比如TARGET_CC就是$(my_prefix)CC,my_prefix在base_rules.mk里面定义。
这样的动作,config.mk一共进行了四次,也就是分别处理host/host_2nd/target/target_2nd四类编译器。

version_defaults.mk
定义了BUILD_ID, android版本,SDK版本,BUILD_NUMBER.

config.mk
在导入上述文件,处理完相关内容以后,config.mk自己还会做如下补充,主要是一些通用性的东西,或者不依赖厂商的配置自行可以决定的相关部分。

各个Android.mk使用的公共mk文件在此处定义:
CLEAR_VARS
BUILD_STATIC_LIBRARY
BUILD_SHARED_LIBRARY
BUILD_EXECUTABLE
BUILD_PACKAGE
BUILD_PREBUILT等。

HOST在编译,打包镜像等作业时会使用到的一些工具,比如:
LEX
BISON
AIDL
MKBOOTFS
MKBOOTIMG
MKYAFFS2等。

通用的CFLAGS/CPPFLAGS/LDFLAGS,会和LOCAL_XXX叠加使用的:

COMMON_GLOBAL_CFLAGS:= -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith COMMON_RELEASE_CFLAGS:= -DNDEBUG -UDEBUG COMMON_GLOBAL_CPPFLAGS:= $(COMMON_GLOBAL_CFLAGS) -Wsign-promo -std=gnu++11 COMMON_RELEASE_CPPFLAGS:= $(COMMON_RELEASE_CFLAGS) 

这些选项,host/target基本上通用。

Tips:a.  在makefile中定义一个有效的空字符,空格符,逗号17 # Utility variables. 18 empty := 19 space := $(empty) $(empty) 20 comma := ,b.  在makefile中定义一个有效的空行 21 # Note that make will eat the newline just before endef. 22 define newline 23  24  25 endefc.  在makefile中定义一个有效的’\’ 26 # Unfortunately you can't simply define backslash as \ or \\. 27 backslash := \a 28 backslash := $(patsubst %a,%,$(backslash))

环境检查
比较要注意的就两个:

工程路径检查
路径中不能含有空格字符.

JAVA版本检查

java_version_str := $(shell unset _JAVA_OPTIONS && java -version 2>&1)javac_version_str := $(shell unset _JAVA_OPTIONS && javac -version 2>&1)required_version := "1.7.x"required_javac_version := "1.7"java_version := $(shell echo '$(java_version_str)' | grep '^java .*[ "]1\.7[\. "$$]')javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.7[\. "$$]')

要求版本是1.7的。

# For Java 1.7, we require OpenJDK on linux and Oracle JDK on Mac OS.requires_openjdk := falseifeq ($(BUILD_OS),linux)requires_openjdk := trueendif

linux上面要求使用openjdk,在5.0以前的版本用的主要是oracle-jdk

到这里,makefile部分的环境初始化工作基本就完成了,后面的工作,就是读入所有的Android.mk文件,选择符合编译条件的,并组织起来,准备编译目标。

0 0