重要说明
Android 7.0以前,系统主要使用GNU make和shell编译系统,模块都使用Android.mk来定义。但是后来呢,大家都明白,Android工程越来越大,make编译所耗费的时间达到足以让程序员发狂,这时google认为这是不正常的现象,咋能编译时间还超过实际调试时间呢?因此在Android 7.0以后,google使用ninja取代make进行系统的编译,由于Android.mk数据实在巨大,Google又加入了两个工具,一个叫kati,用来专门将Android.mk转换成ninja的构建规则文件buildxxx.ninja,然后再使用ninja来进行构建工作;另一个叫blueprint,用于处理Android.bp,编译生成*.ninja文件,用于做ninja的处理。
综上,Android 7.0以后编译要用到的东西:ninja、kati、blueprint。
当然,重点是,我们要介绍的内容主要围绕Android 7.0 以后的系统版本。好,继续!
编译命令
从亲民、众所周知的命令入手,先说说编译的三条命令到底是个什么玩意儿,背后搞了那些事情。
初始化环境变量
source build/envsetup.sh #或 . build/envsetup.sh #.后跟空格
认真地讲,这个脚本定义了一大堆函数,但立马执行的命令只有三行。
validate_current_shell #检查当前使用的shell,说得只支持bash和zsh。但我感觉在放狗pi,之前用zsh编译了三个小时报错了,bash就正常的。这里读者自己权衡 source_vendorsetup #查找vendorsetup.sh脚本,这个名字的脚本作用是调用add_lunch_combo将它们各自的产品添加到 LUNCH_MENU_CHOICES 变量里 addcompletions #包含一些脚本,另外为lunch、m等函数建立自动补全
听起来似乎可有可无,但如果不执行这一步,你会惊喜地发现lunch会lunch个寂寞!那是怎么回事呢?
这需要追究一个关于shell的一个小细节。
在shell,执行脚本的命令有四种,区别如下:
- $PWD/XXX.sh
- bash $PWD/XXX.sh
- source $PWD/XXX.sh
- . $PWD/XXX.sh
第一种是使用路径直接执行一个脚本,一般脚本第一行需要指定使用的shell类型,如:#!/bin/sh,如果没指定,会使用默认shell,估计是sh,反正不是指用户默认使用的shell。
第二种使用用户指定的shell去执行脚本,会无视脚本第一行指定的shell。
第三种和第四种等价,是在当前shell执行脚本。
好了,说到这里,估计大家还是不清楚区别,连我也觉得,这TM说了个啥???哈哈哈,但是博主向来不太喜欢修修改改,所以,总结如下:
后两种方式会在当前shell执行,脚本即便执行完成后,脚本中的变量、函数都会保存到当前shell,直到用户关闭shell
(总结个pi总结,分都没有怎么总)
具体效果请看图:
小伙伴,明白了没????????????????????????????????????????
所以,画重点,我们的第二条命令lunch,是envsetup.sh的一个小小的函数。
另,强调一下,包括make,都是envsetup.sh的一个函数,而不是我们熟知的查找Makefile的命令make。
envsetup.sh抛出函数总结如下(认真的):
命令 | 说明 |
lunch | lunch <product_name>-<build_variant>
选择<product_name>作为要构建的产品,<build_variant>作为要构建的变体,并将这些选择存储在环境中,以便后续调用“m”等读取。 |
tapas | 交互方式:tapas [<App1> <App2> …] [arm|x86|mips|arm64|x86_64|mips64] [eng|userdebug|user] |
croot | 将目录更改到树的顶部或其子目录。 |
m | 编译整个源码,可以不用切换到根目录 |
mm | 编译当前目录下的源码,不包含他们的依赖模块 |
mmm | 编译指定目录下的所有模块,不包含他们的依赖模块 例如:mmm dir/:target1,target2. |
mma | 编译当前目录下的源码,包含他们的依赖模块 |
mmma | 编译指定目录下的所模块,包含他们的依赖模块 |
provision | 具有所有必需分区的闪存设备。选项将传递给fastboot。 |
cgrep | 对系统本地所有的C/C++ 文件执行grep命令 |
ggrep | 对系统本地所有的Gradle文件执行grep命令 |
jgrep | 对系统本地所有的Java文件执行grep命令 |
resgrep | 对系统本地所有的res目录下的xml文件执行grep命令 |
mangrep | 对系统本地所有的AndroidManifest.xml文件执行grep命令 |
mgrep | 对系统本地所有的Makefiles文件执行grep命令 |
sepgrep | 对系统本地所有的sepolicy文件执行grep命令 |
sgrep | 对系统本地所有的source文件执行grep命令 |
godir | 根据godir后的参数文件名在整个目录下查找,并且切换目录 |
allmod | 列出所有模块 |
gomod | 转到包含模块的目录 |
pathmod | 获取包含模块的目录 |
refreshmod | 刷新allmod/gomod的模块列表 |
选择并配置项目
lunch $TARGET
使用这个命令可以根据用户的输入来设置与具体产品相关的环境变量。如果你也像我一样每次lunch都在想“哪个东西叫啥???”,也是可以直接lunch然后再选的哦!
这个我不知道有啥好讲的,总感觉脚本写得很清楚,是吧?硬要说的话,envsetup.sh设置了公共的变量、函数,lunch引入了针对项目的变量(说了个pi)。
那好,lunch结果参考如下:
lunch结果 | 说明 |
PLATFORM_VERSION_CODENAME=REL | 表示平台版本的名称 |
PLATFORM_VERSION=10 | Android平台的版本号 |
TARGET_PRODUCT=aosp_arm | 所编译的产品名称 |
TARGET_BUILD_VARIANT=userdebug | 所编译产品的类型 |
TARGET_BUILD_TYPE=release | 编译的类型,debug和release |
TARGET_ARCH=arm | 表示编译目标的CPU架构 |
TARGET_ARCH_VARIANT=armv7-a-neon | 表示编译目标的CPU架构版本 |
TARGET_CPU_VARIANT=generic | 表示编译目标的CPU代号 |
HOST_ARCH=x86_64 | 表示编译平台的架构 |
HOST_2ND_ARCH=x86 | 表示编译平台的第二CPU架构 |
HOST_OS=linux | 表示编译平台的操作系统 |
HOST_OS_EXTRA=Linux-4.15.0-112-generic-x86_64-Ubuntu-16.04.6-LTS | 编译系统之外的额外信息 |
HOST_CROSS_OS=windows | |
HOST_CROSS_ARCH=x86 | |
HOST_CROSS_2ND_ARCH=x86_64 | |
HOST_BUILD_TYPE=release | 编译类型 |
BUILD_ID=QQ1D.200205.002 | BUILD_ID会出现在版本信息中,可以利用 |
OUT_DIR=out | 编译结果输出的路径 |
完毕,下一个!
正式编译
make
make在source build/envsetup.sh的前后对比:
所以!所以!make第一步不是执行Makefile!
继续,get_make_command() 如下:
function get_make_command() { # If we're in the top of an Android tree, use soong_ui.bash instead of make if [ -f build/soong/soong_ui.bash ]; then # Always use the real make if -C is passed in for arg in "$@"; do if [[ $arg == -C* ]]; then echo command make return fi done echo build/soong/soong_ui.bash --make-mode else echo command make fi }
所以,最终我们可以理解为,编译开始的命令是
build/soong/soong_ui.bash --make-mode $@
发现新大陆,soong_ui.bash才是编译入口诶!!那我们就继续分析soong_ui.bash这个脚本。
function gettop { local TOPFILE=build/soong/root.bp if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then # The following circumlocution ensures we remove symlinks from TOP. (cd $TOP; PWD= /bin/pwd) else if [ -f $TOPFILE ] ; then # The following circumlocution (repeated below as well) ensures # that we record the true directory name and not one that is # faked up with symlink names. 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 } # Save the current PWD for use in soong_ui 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" "$@"
代码看着难受,那你看我这样总结对不对(战术总结)
- 脚本source了一个microfactory.bash,该脚本主要得到一些函数命令,如soong_build_go
- 编译build/soong/cmd/soong_ui/main.go,生成 out/soong_ui这个可执行程序
- 回到根目录
- 执行命令:out/soong_ui –make-mode ,真正的构建,执行了make命令,会把”build/make/core/main.mk” 加到构建环境中,同时启动kati、blueprint-soong、ninja的编译。