AndroidPQR编译系统
概述
Android编译环境的初始化过程,在编译环境初始化完成后,我们就可以⽤m/mm/mmm/make命令编译源代码了。当然,这要求每⼀个模块都有⼀个Android.mk⽂件。Android.mk实际上是⼀个Makefile脚本,⽤来描述模块编译信息。Android编译系统通过整合Android.mk⽂件完成编译过程。
mmm命令的编译过程,需要依赖会make命令全部模块编译完成,下⾯使⽤编译mmm命令分析Android源码的编译过程,如图1所⽰:
蘑菇OS > Android9.0编译系统 > 01.png
通过上图可以知道mmm命令执⾏流程,具体可以看下,本⽂主要研究他的编译原理,感兴趣根据上图追下流程.
Android最初是⽤ Android.mk 来定义模块的, Android.mk 本质上就是 Makefile。随着 Android ⼯程越来越⼤,包含的模块越来越多,以 Makefile 组织的项⽬编译花费的时间越来越多。google 在 Android 7.0 开始引⼊了 ninja 编译系统。相对于 make 来说 ninja 在⼤的项⽬管理中速度和并⾏⽅⾯有突出的优势,因此 google 采⽤了 ninja 来取代之前使⽤的 make。由于 Android.mk 的数量巨⼤且复杂,不可能把
所有的 Android.mk 改写成 ninja 的构建规则,因此 google 搞了个 kati ⼯具,⽤于将 Androd.mk 转换成 ninja 的构建规则⽂件 build.ninja,再使⽤ ninja 来进⾏构建⼯作。
Android 8.0 开始,google 引⼊了 Android.bp ⽂件来替代之前的Android.mk ⽂件,Android.bp 只是纯粹的配置⽂件,不包括分⽀、循环等流程控制, 本质上就是⼀个 json 配置⽂件。同时还引⼊ Soong 这个⼯具,⽤于将 Android.bp 转换为 ninja 的构建规则⽂件build.ninja,再使⽤ ninja 来进⾏构建⼯作。但之前的模块全部是⽤ Android.mk 来定义的,google 不可能⼀下⼦把所有模块都修改成Android.bp,只能逐步替换。⽆论是 Android.mk 还是 Android.bp 最后都是转化成 ninja 的构建规则,再进⾏编译的。Soong、Blueprint、Kati、Ninja之间的关系.
蘑菇OS > Android9.0编译系统 > 02.jpg
蘑菇OS > Android9.0编译系统 > 03.png
1.Kati构建编译系统
<1>.kati是专为Android开发的⼀个基于Golang和C++的⼯具,主要功能是把Android中的Android.mk⽂件转换成 Ninja⽂件。代码路径是build/kati,编译后的产物是ckati。
Kati代码是开源的,可以把它clone下来,简单分析下它的运⾏原理,在Android全编译以后,可以使
⽤ninja来编译已经⽣成的.ninja⽂件。
<2>.执⾏编译odex编译命令
2.Blueprint和Soong构建编译系统
Soong、Blueprint
Soong类似于之前的Makefile编译系统的核⼼,负责提供Android.bp语义解析,并将之转换成Ninja⽂件。Soong还会编译⽣成⼀个androidmk命令,⽤于将Android.mk⽂件转换为Android.bp⽂件,不过这个转换功能仅限于没有分⽀、循环等流程控制的Android.mk才有效。
Blueprint是⽣成、解析Android.bp的⼯具,是Soong的⼀部分。Soong负责Android编译⽽设计的⼯具,⽽Blueprint只是解析⽂件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项⽬,从Android 7.0,prebuilts/go/⽬录下新增Golang所需的运⾏环境,在编译时使⽤。
因为Soong和Blueprint是Google⾕歌为Android.bp特别定制的⼯具,所以不要摘出来单独来操作,下⾯是我编译时通过执⾏编译命令
#mmm extern/zgj/test_bp
在out⽬录下会⽣成3个以.ninja⽂件,进去查看下是ninja语法,通过ninja命令来编译.
build-sdm660_64-_test_bp_Android.mk-cleanspec.ninja
build-sdm660_64-_test_bp_Android.mk-cleanspec.ninja
combined-sdm660_64-_test_bp_Android.mk.ninja
具体运⾏可以分为4个步骤:
第⼀步:进⼊Makefile执⾏
#out/soong_ui --make-mode -j32 MODULES-IN-external-zgj-test_bp
第⼆步:如果没有build.ninja,则⽣成
#prebuilts/build-tools/linux-x86/bin/ckati --ninja --ninja_dir=out --ninja_suffix=-sdm660_64-_external_zgj_test_bp_Android.mk-cleanspec --regen --detect_a ndroid_echo --color_warnings --gen_all_targets --werror_find_emulator --u_find_emulator -f build/make/core/cleanbuild.mk BUILDING_WITH_NINJA=tru e SOONG_MAKEVARS_MK=out/soong/make_vars-sdm660_64.mk
第三步:⽣成⽬标ninja
#prebuilts/build-tools/linux-x86/bin/ckati --ninja --ninja_dir=out --ninja_suffix=-sdm660_64-_external_zgj_test_bp_Android.mk --regen --ignore_optional_incl ude=out/%.P --detect_android_echo --color_warnings --gen_all_targets --werror_find_emulator --kati_stats -f build/make/core/main.mk --u_find_emulato r BUILDING_WITH_NINJA=true SOONG_ANDROID_MK=out/soong/Android-sdm660_64.mk SOONG_MAKEVARS_MK=out/soong/make_vars-sdm660_64 .mk
第四步:编译⽣成odex
#prebuilts/build-tools/linux-x86/bin/ninja -d keepdepfile MODULES-IN-external-zgj-test_bp -j 32 -f out/combined-sdm660_64-_external_zgj_test_bp_Androi d.mk.ninja -w dupbuild=err
Android.bp
Android.bp的出现就是为了替换Android.mk⽂件。bp跟mk⽂件不同,它是纯粹的配置,没有分⽀、循环等流程控制,不能做算数逻辑运算。如果需要控制逻辑,那么只能通过Go语⾔编写。
编译android源码后,androidmk⼯具会⽣成在:out/soong/host/linux-x86/bin⽬录下,⽤于Android.mk
转换成Android.bp使⽤,如下转换命令
#androidmk Android.mk > Android.bp
Ninja
ninja是⼀个编译框架,会根据相应的ninja格式的配置⽂件进⾏编译,但是ninja⽂件⼀般不会⼿动修改,⽽是通过将Android.bp⽂件转换成ninja格⽂件来编译。环境搭建
下载依赖包
sudo apt-get install git-core gnupg flex bison gperf build-esntial
zip curl zlib1g-dev gcc-multilib g+±multilib libc6-dev-i386
lib32ncurs5-dev x11proto-core-dev libx11-dev lib32z-dev ccache
libgl1-mesa-dev libxml2-utils xsltproc unzip m4 libssl-dev libswitch-perl
编译源码的时候可能需要切换jdk
sudo update-alternatives --config java
sudo update-alternatives --config javac
如果没有将jdk路径添加进系统,需要先添加。
sudo update-alternatives --install /usr/bin/java java ~/tools/java/jdk1.6.0_45/bin/java 700
sudo update-alternatives --install /usr/bin/javac javac ~/tools/java/jdk1.6.0_45/bin/javac 700
sudo update-alternatives --install /usr/bin/jar jar ~/tools/java/jdk1.6.0_45/bin/jar 700
这⾥的700是优先级, ~/tools/java/jdk1.6.0_45/bin是⾃⼰安装jdk的⽬录
配置ccache
ccache是⼀个编译cache系统,可以加快c/c++代码编译的速度(编译过⼀次之后)。
有ccache的情况下,整个android编译的时间从超过1个⼩时下降到约半个⼩时。
配置⽅法见android⽹站说明:
/source/initializing.html#optimizing-a-build-environment
上述配置对kernel代码⽆效。kernel编译使⽤ccache请参考下述⽂章:
apuroth.gitbooks.io/android-study/content/20160701.html
编译安卓P、Q启⽤ccache⽅法:
sudo apt install ccache
vim ~/.bashrc
export USE_CCACHE=1
source ~/.bashrc
ccache -M 100G
缓存数据默认保持在~/ache,上⼀⾏指令可以⾃⼰设置缓存的最⼤值
编译
动态分区
在android Q 中加⼊了动态分区,为了在ota的时候能够灵活创建分区和修改分区⼤⼩,将system,vendor,odm,product合并成super分区,并在super分区上预留出⼀定量的free space,这样就可以动态调整这些分区的⼤⼩,解决了ota的时候分区不⾜,以及调整分区的风险.
在这⾥插⼊图⽚描述
qssi
为了配合project treble,将系统与odm相关的解耦,需要单独编译system.img和其他img。⽽编译systemimage的时候需要lunch qssi,⽽编译其他分区的时候需要lunch target.
编译
make命令来进⾏编译
source build/envtup.sh
编译 system.img
lunch qssi-urdebug
make target-files-package
编译除system.img外的其他img
lunch xx-urdebug
make target-files-package
⾼通提供的build.sh脚本进⾏编译
编译所有img,包括system和其它img
source build/envtup.sh
lunch XX-urdebug
./build.sh dist -j32
编译system.img,产物在qssi⽬录下
source build/envtup.sh
lunch xx-urdebug
./build.sh dist qssi_only -j32
编译super.img
source build/envtup.sh
lunch xx-urdebug
./build.sh dist merge_only -j32
编译其它img,例如vendorimage,如果不指定会编译其它所有img,产物在XX⽬录下
source build/envtup.sh
lunch xx-urdebug
./build.sh vendorimage dist target_only -j32
动态分区刷机的⽅法
Q版本将system和vendor分区合并为super分区,⽆法通过adb reboot bootloader模式单独刷动态分区⾥⾯的img,例如system,vendor,product,odm,只能刷s uper.img和其他的
但是fastboot模式下可以单独刷动态分区⾥⾯的img
推荐进⼊fastboot模式刷机:
adb reboot fastboot
fastboot getvar is-urspace
is-urspace: yes
Finished. Total time: 0.002s
fastboot flash vendor vendor.img
fastboot flash system system.img
fastboot flash vbmeta vbmeta.img
fastboot flash vbmeta_system vbmeta_system.img
fastbootd是⽤户空间的代码,因为动态的逻辑分区只能在应⽤空间识别
fastboot刷机出现权限问题,需要将fastboot的所有者属性改成root
sudo chown root:root fastboot
sudo chmod +s fastboot
ninja快速编译
在开发过程中经常遇到make、mm时候特别慢,原因有很多地⽅
全部编译的时候需要find所有Android.mk和Android.bp,耗时⽐较长,导致make时候电脑卡住,不能进⾏其它操作。
每次source/lunch完成,⼚商预装逻辑会动态⽣成⼀个app_config.mk,虽然内容不会变化但是会修改时间戳,导致make的时候需要重新⽣成ninja⽂件,很烦。
执⾏真正编译前,build系统需要检查ninja⽂件是否需要更新?soong环境是否正常?如果不是需要进⾏soong_ui相关初始化,例如查找全部的Android.mk/An droid.bp检查lastModifiedTime,检查microfactory/minibp/soong_ui/soong_build等是否完成,以及动态配置各种config.
ninja编译时不会重新解析mk⽂件,会使⽤上次⽣成的 ninja build ⽂件,因此⽐较省时间
要使⽤ninja 需要先⽤make/mm/mmm等编译过⼀次才⾏,如果更改了⽂件⽬录结构、增删⽂件、repo sync等操作,或者修改Makefile 或Android.bp⽂件,还需要再重新make⼀次
Android N:
第⼀次make的时候系统会将所有的Android.mk 通过kati 转换成 out/build-mido.ninja,然后通过ninja -f out/build-mido.ninja 进⾏最终编译。
操作步骤:
croot
cp prebuilts/ninja/linux-x86/ninja out/host/linux-x86/bin/ #将ninja 放到export的PATH⽬录下
ln -sf out/build-$(device).ninja build.ninja # 因为ninja默认执⾏build.ninja⽂件,所以通过⼀个软链接来指定。
ninja // 编译全部,如果不进⾏上⾯的ln软链接的,可以⼿动指定 ninja -f out/build-$(device).ninja 也是同样效果。
ninja rvices // 单独编译rvices模块,参数⼀般是LOCAL_MODULE的名字
Android O和R:
第⼀make的时候系统会将所有Android.mk 通过kati转换成 out/build-sagit.ninja,将所有的Android.bp通过soong转换成 out/soong/build.ninja,并合并到out/co mbined-$(device).ninja,进⾏最终编译。
操作步骤:
croot
cp prebuilts/build-tools/linux-x86/bin/ninja out/host/linux-x86/bin/
cp prebuilts/build-tools/linux-x86/lib64/libjemalloc.so out/host/linux-x86/lib64/
ln -sf out/combined-$(device).ninja build.ninja
ninja // 编译全部,⽀持 -j 8 指定多个线程编译。
ninja rvices // 单独编译rvices模块,参数⼀般是LOCAL_MODULE的名字
这种快速编译最⼤的好处是,编译单个模块速度快,⾃动查找编译相关依赖,不会出现mm时提⽰缺少依赖的问题
Framework快速编译
androidR之前单独编译framework和rvice命令为:
make -j8 framework
make -j8 rvices
androidR命令为:
make -j8 framework-minus-apex
make -j8 rvices
push framework 并删除设备中oat arm arm64 ⽬录
当前Q编译framework的时间⾮常久, 编译⼀次需要10~30分钟, 这样对于我们本地多次修改代码编译调试就效率⾮常低
原理:
只编译少量修改的代码⽂件, 单独编译到⼀个jar⾥, 运⾏时提前加载它, 覆盖原始的framework.jar的rvices.jar
不⽀持的ca: (以下类似修改需要更新framework.jar/rvices.jar)
类继承关系发⽣改变
如framework.jar⾥Canvas继承BaCanvas, testdebug.jar⾥修改为Canvas继承TestCanvas, TestCanvas继承BaCanvas,此时testdebug.jar的Canvas.java⽆法覆盖framework.jar的Canvas.java
具体操作:
整编过⼀次framework.jar和rvices.jar
这个整编过程耗时挺长, ⽬前暂⽆办法跳过, 后续还需改进
只编译修改的或者新加的代码
将quickbuild.zip解压到源码根⽬录, 将修改的或者新加的代码路径填⼊Android.bp中
ba/core的代码填在testdebug.jar, ba/rvices的代码填在testdebugrvices.jar
在quickbuild⽬录下mm快速编译出的testdebug.jar和testdebugrvices.jar, push到framework⽬录
将testdebug.jar和testdebugrvices.jar放⼊CLASSPATH, ⽐framework.jar和rvices.jar提前加载
从⼿机中pull出 (adb root; adb pull / .)
修改环境变量, 在framework.jar和rvices.jar前⾯分别添加⾃⼰的jar, 之后 push 到⼿机⽬录/system/etc/init/
BOOTCLASSPATH的/system/framework/framework.jar前⾯添加/system/framework/testdebug.jar
DEX2OATBOOTCLASSPATH的/system/framework/framework.jar前⾯添加/system/framework/testdebug.jar
SYSTEMSERVERCLASSPATH的/system/framework/rvices.jar:前⾯添加/system/framework/testdebugrvices.jar:
内核快速编译
全编
make showcommands -j6 bootimage > build.log
gzip -cd out/verbo. | grep “/bin/bash -c” | d ‘s/[./.] //g’> build_XXX1.sh
chmod +x build_XXX1.sh
./build_XXX1.sh
1
2
3
4
适合改动较⼩的重复验证
Android.bp条件编译
⼀个基本的雏形如下:
module类型 {
name:“module名字”,
srcs: [“依赖的源⽂件路径”]
}
注意name在整个Android编译系统必须全局唯⼀
如以下是⼀个hello world:
cc_binary {
name: “hello_world”,
src: [“main.c”],
}
cc_binary代表target是⼀个可执⾏⽂件,make hello_world之后在out⽬录就可以得到名为hello_world的可执⾏⽂件Android.bp中各种关键字的作⽤,请参考 Soong 构建系统
由于Android.bp是⼀个树形结构,不像Makefile是⼀套拥有完整功能的脚本语⾔,所以只能实现⼀些简单的逻辑控制,例如