QCM 8155 Android R的编译
一、环境
平台:QCM
版本:Android 11
二、相关概念
2.1 Treble架构
由于Android各个层级之间的耦合性大,Android系统更新成本高,导致Android系统版本虽然已经释放了很久,但是市面上Android的系统依然存在滞后、碎片化的情况。Android 8.0 重新设计了 Android 操作系统框架(名为“Treble”的项目),以便让制造商能够以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。
Android 8.0 及更高版本提供了一个稳定的新供应商接口。设备制造商访问的是 Android 代码中特定于硬件的部分,而不会依赖供应商的私有接口。并且供应商会使用独立的vendor分区。这样,设备制造商在更新 Android 操作系统框架,只需要单独升级system分区。同时稳定的供应商接口会保证兼容性。
2.2 Soong
Soong 构建系统是在 Android 7.0 (Nougat) 中引入的,旨在取代 Make。它利用 Kati GNU Make 克隆工具和 Ninja 构建系统组件来加速 Android 的构建。
Soong是由Go语言写的一个项目,从Android 7.0开始,在prebuilts/go/目录下新增了Go语言所需的运行环境,Soong在编译时使用,解析Android.bp,将之转化为Ninja文件,完成Android的选择编译,解析配置工作等。故Soong相当于Makefile编译系统的核心,即build/make/core下面的内容。
2.3 Blueprint
Blueprint由Go语言编写,是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。
2.4 KATI
kati是Google专门为了Android而开发的一个小项目,基于Golang和C++。目的是为了把Android中的Makefile,转换成Ninja文件。
在最新的Android R(11)中,Google已经移除了/build/kati目录
,只保留了一个预先编译出来的可执行文件:prebuilts/build-tools/linux-x86/bin/ckati,这意味着Google在逐渐从编译系统中移除kati,预计1-2个Android大版本,.mk文件全部都切换成.bp文件后,kati将会正式退出Android历史舞台
。
2.5 Ninja
Ninja 是Google的一名程序员推出的注重速度的构建工具。一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。
Ninja是一个致力于速度的小型编译系统(类似于Make),如果把其他编译系统比做高级语言的话,Ninja就是汇编语言。通常使用Kati或soong把makefile转换成Ninja files,然后用Ninja编译。
ninja核心是由C/C++编写的,同时有一部分辅助功能由python和shell实现。由于其开源性,所以可以利用ninja的开源代码进行各种个性化的编译定制。
2.6 Makefile
Makefile是一个文本文件,是GNU make程序在执行的时候默认读取的配置文件。其关系到了整个工程的编译规则。一个工程中的源文件按类型、功能、模块分别放在若干个目录中,makefile定义了一系列规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
其好处在于:写好makefile之后,只需要一个“make”命令,整个工程就能完全自动编译,极大地提高了软件开发的效率。
三、高通编译
3.1 编译指令
编译指令 | 备注 |
---|---|
source build/envsetup.sh lunch qssi-userdebug | 配置环境变量 |
./build.sh dist -j32 | 全编译,并打包 |
./build.sh dist -j32 --qssi_only | 单独编译,qssi模块 |
./build.sh dist -j32 --merge_only | 打包操作,打包ota zip and super.img |
./build.sh -j32 --merge_only | 打包操作,打包super.img |
3.2 编译脚本
- qssi模块,qcom single system image,就类似system分区,编译生成路径:out\target\product\qssi;
- target模块,就类似vendor分区,编译生成路径:out\target\product\bengal;
- AndroidQ以前只用lunch自己项目的target就可以进行编译了,现在随着Treble架构的强制推行,system和vendor要强制解耦了。
@android/build.sh
function build_qssi_only () {//编译qssi
command "source build/envsetup.sh"
command "$QTI_BUILDTOOLS_DIR/build/kheaders-dep-scanner.sh"
command "lunch ${TARGET_QSSI}-${TARGET_BUILD_VARIANT}"
command "make $QSSI_ARGS"
}
function build_target_only () {//编译target
command "source build/envsetup.sh"
command "$QTI_BUILDTOOLS_DIR/build/kheaders-dep-scanner.sh"
command "lunch ${TARGET}-${TARGET_BUILD_VARIANT}"
QSSI_ARGS="$QSSI_ARGS SKIP_ABI_CHECKS=$SKIP_ABI_CHECKS"
command "make $QSSI_ARGS"
command "run_qiifa"
}
function merge_only () {//打包
# DIST/OTA specific operations:
if [ "$DIST_ENABLED" = true ]; then
generate_ota_zip
fi
# Handle dynamic partition case and generate images
if [ "$BOARD_DYNAMIC_PARTITION_ENABLE" = true ]; then
generate_dynamic_partition_images
fi
}
function full_build () {//全编译
build_qssi_only
build_target_only
# Copy Qssi system|product.img to target folder
# so that all images can be picked up from one folder
command "cp $QSSI_OUT/system.img $OUT/"
if [ -f $QSSI_OUT/product.img ]; then
command "cp $QSSI_OUT/product.img $OUT/"
fi
if [ -f $QSSI_OUT/system_ext.img ]; then
command "cp $QSSI_OUT/system_ext.img $OUT/"
fi
merge_only
}
四、Android-Make指令
从高通的编译脚本来看,其最终是调用make指令,来实现Android的编译。那么问题来了,这个make指令实现在哪里呢?具体做了什么事情呢?
4.1 Android编译指令
Android项目的编译,不同的ODM厂商会根据自身的编译规则,定制不同的编译脚本、打包脚本,但是归根到底,都是基于如下命令进行客制化:
source build/envsetup.sh //step 1.初始化编译环境
lunch xxx //step 2.选择编译目标
make -j8 //step 3.执行编译
make snod //step 4.打包生成镜像
4.2 source env
envsetup.sh脚本:主要是定义了make、mm、lunch、cgrep等相关函数,为Android系统的编译提供支持。
@LINUX\android\build\envsetup.sh
...
function mmm()
(
call_hook ${FUNCNAME[0]} $@
if [ $? -ne 0 ]; then
return 1
fi
_trigger_build "modules-in-dirs-no-deps" "$@"
)
...
function make()
{
_wrap_build $(get_make_command "$@") "$@"
}
...
常用指令:
4.3 lunch target
lunch命令:主要作用是根据用户输入或者选择的产品名来设置与具体产品相关的环境变量。
lunch后,生成的编译环境变量信息:
4.4 Make
make命令:主要用来编译Android系统,生成对应的编译文件。其中,Android10及之后,通过soong构建系统执行编译。
4.4.1 编译流程图
4.4.2 shell脚本部分
1. make指令入口
@LINUX\android\build\envsetup.sh
function make()
{
_wrap_build $(get_make_command "$@") "$@"
}
2. 获取构建方式
以前通过make构建,后续更换成soong方式
,通过判断soong_ui.bash文件是否存在,来决定系统构建方式。
@LINUX\android\build\envsetup.sh
function get_make_command()
{
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
}
3. 执行构建指令
打印构建时间、构建结果
@LINUX\android\build\envsetup.sh
function _wrap_build()
{
...
local start_time=$(date +"%s")
"$@" //执行构建指令
local ret=$?
local end_time=$(date +"%s")
local tdiff=$(($end_time-$start_time))
local hours=$(($tdiff / 3600 ))
local mins=$((($tdiff % 3600) / 60))
local secs=$(($tdiff % 60))
local ncolors=$(tput colors 2>/dev/null)
...
echo
if [ $ret -eq 0 ] ; then
echo -n "${color_success}#### build completed successfully " //打印构建成功结果
else
echo -n "${color_failed}#### failed to build some targets " //打印构建失败结果
fi
if [ $hours -gt 0 ] ; then
printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs //打印构建时间
...
return $ret
}
构建指令如下:
高通平台指令: build/soong/soong_ui.bash --make-mode dist -j32 ENABLE_AB=true
SYSTEMEXT_SEPARATE_PARTITION_ENABLE=true
BOARD_DYNAMIC_PARTITION_ENABLE=true ENABLE_VIRTUAL_AB=false
SHIPPING_API_LEVEL=29
4.4.3 goLang脚本部分
1. 执行soong_ui.bash脚本
soong_ui.bash脚本主要做了两件事:
1. 根据"android/soong/cmd/soong_ui/"内容,生成soong_ui的go可执行程序,生成路径:out\soong_ui
2. 执行soong_ui程序,进入soong_ui
@build\soong\soong_ui.bash
...
# 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//构建soong_ui执行程序
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"//执行soong_ui程序,启动构建
2. soong入口
soong_ui是个go程序,至此进入soong构建的系统。
@android\build\soong\cmd\soong_ui\main.go
func main() {
...
c, args := getCommand(os.Args)
...
f := build.NewSourceFinder(buildCtx, config)
defer f.Shutdown()
build.FindSources(buildCtx, config, f)//遍历整个项目,记录所有的mk、bp等文件
c.run(buildCtx, config, args, logsDir)//启动构建
}
3. soong构建系统
soong构建系统最核心的步骤。其主要通过将bp、mk文件,解析成ninja文件,再通过ninja去实现系统构建任务。
@android\build\soong\ui\build\build.go
func Build(ctx Context, config Config, what int) {
...
runSoong(ctx, config)//step 1.处理bp文件
...
runKatiBuild(ctx, config)//step 2.处理mk文件
...
createCombinedBuildNinjaFile(ctx, config)//step 3.整合ninja文件
...
runNinja(ctx, config)//step 4.构建
...
}
1. runSoong
runSoong 对工具进行编译,先编译出blueprint等编译工具, 再把*.bp 编译成 out/soong/build.ninja
。
@android\build\soong\ui\build\soong.go
func runSoong(ctx Context, config Config) {
...
ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")//1.主要用于生成out/soong/.minibootstrap目录相关文件
...
ctx.BeginTrace(metrics.RunSoong, "environment check")//2.初始环境检查
...
ctx.BeginTrace(metrics.RunSoong, "minibp")//3.创建minibp可执行程序
...
ctx.BeginTrace(metrics.RunSoong, "bpglob")//4.创建bpglob可执行程序
...
ninja("minibootstrap", ".minibootstrap/build.ninja")//5.主要用于生成out/soong/.bootstrap/build.ninja文件
ninja("bootstrap", ".bootstrap/build.ninja")//6.生成out/soong/build.ninja
}
- 创建 out/soong/.minibootstrap/目录并在这个目录下创建一系列文件,包括
out/soong/.minibootstrap/build.ninja
这个文件。该路径下的内容,会参与到bootstrap阶段的构建; - 检查soong构建的环境、工具是否存在&正常;
- 通过microfactory生成out/soong/.minibootstrap/minibp可执行程序,会参与到bootstrap阶段的构建;
- 通过microfactory生成out/soong/.minibootstrap/bpglob可执行程序,会参与到bootstrap阶段的构建;
- 通过步骤三编译生成的minibp程序,生成out/soong/.bootstrap/build.ninja文件,该文件会参与到bootstrap阶段的构建。
- bootstrap表示从无到有创建Soong,该阶段会先生成bootstrap相关的工具程序:./out/soong/.bootstrap/bin/*,再使用编译生成的soong_build程序,生成out/soong/build.ninja文件。用于后续参与Ninja编译构建工作,out/soong/build.ninja文件罗列了项目上所有的bp模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。
2. runKatiBuild
runKatiBuild, 加载 build/make/core/main.mk, 搜集所有的Android.mk文件生成out/build-xxx.ninja文件
@android\build\soong\ui\build\kati.go
func runKatiBuild(ctx Context, config Config) {
ctx.BeginTrace(metrics.RunKati, "kati build")
...
args := []string{
"--writable", config.OutDir() + "/",
"-f", "build/make/core/main.mk",
}
...
runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})//执行ckati指令,构建mk
...
}
- runKati函数最终会引用cKati指令,加载main.mk文件,生成
ninja
文件。 - 从
build/make/core/main.mk
开始,通过include命令将其所有需要的.mk文件包含进来
,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。网上有个图,可以很好的描述其关系:
文件 | 说明 |
---|---|
build/make/core/main.mk | Build的主控文件,主要作用是包含其他mk,以及定义几个最重要的编译目标,同时检查编译工具的版本,例如gcc、clang、java等 |
build/make/core/config.mk | Build的配置文件,主要是区分各个产品的配置,并将这些编译器参数引入产品配置 BoardConfig.mk,同时也配置了一些编译器的路径等 |
build/make/core/clang/config.mk | clang编译的配置文件 |
build/make/core/definitions.mk | 最重要的 Make 文件之一 ,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等 |
build/make/core/dex_preopt.mk | 定义了dex优化相关的路径和参数 |
build/make/core/pdk_config.mk | 编译pdk的配置文件 |
build/make/core/Makefile | 系统最终编译完成所需要的各种目标和规则 |
build/make/core/envsetup.mk | 包含进product_config.mk文件并且根据其内容设置编译产品所需要的环境变量,并检查合法性,指定输出路径等 |
build/make/core/combo/select.mk | 根据当前编译器的平台选择平台相关的 Make 文件 |
build/make/core/ninja_config.mk | 解析makefile的的列表,传给kati,配置传给ninja和kati的目标 |
build/make/core/soong_config.mk | 配置soong的环境变量,建立go变量和mk变量的json映射关系,让go变量可以获取到mk中定义的变量值 |
- 在soong启动时,便会去搜索项目中所有的Android.mk文件,并记录于out/.module_paths/Android.mk.list文件。在main.mk里面,便可以根据这个文件,将所有的内容include进来。所以在该项目下定义的任一Android.mk都可以被引用。
@android/build/make/core/main.mk
...
subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list) \
$(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk
subdir_makefiles_total := $(words int $(subdir_makefiles) post finish)
.KATI_READONLY := subdir_makefiles_total
//遍历相关mk文件
$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))
- main.mk加载完成后,最终生成out/build-xxx.ninja文件,用于后续参与Ninja编译构建工作。out/build-xxx.ninja文件罗列了项目上所有的mk模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。
3. createCombinedBuildNinjaFile
为了方便统一管理,Soong将out/soong/build.ninja文件 、out/build-.ninja文件和out/build--package.ninja文件, 合成为out/combined-*.ninja
文件,由该文件记录所有待执行ninja文件。
@android\build\soong\ui\build\build.go
var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
builddir = {{.OutDir}}
{{if .UseRemoteBuild }}pool local_pool
depth = {{.Parallel}}
{{end -}}
pool highmem_pool
depth = {{.HighmemParallel}}
build _kati_always_build_: phony
{{if .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}//追加文件out/build-*.ninja
subninja {{.KatiPackageNinjaFile}}//追加文件out/build-*-package.ninja
{{end -}}
subninja {{.SoongNinjaFile}}//追加文件out/soong/build.ninja
`))
func createCombinedBuildNinjaFile(ctx Context, config Config) {
...
file, err := os.Create(config.CombinedNinjaFile())//创建combined-*.ninja文件
...
if err := combinedBuildNinjaTemplate.Execute(file, config); //执行合并动作
...
}
out/combined-qssi.ninja文件,如下:
4. runNinja
runNinja,运行Ninja命令, 解析combined-*.ninja
,执行编译过程
@android\build\soong\ui\build\ninja.go
func runNinja(ctx Context, config Config) {
...
executable := config.PrebuiltBuildTool("ninja")//获取ninja指令
args := []string{
"-d", "keepdepfile",
"-d", "keeprsp",
"--frontend_file", fifo,
}
...
args = append(args, "-f", config.CombinedNinjaFile())//配置组合的ninja文件
args = append(args,
"-w", "dupbuild=err",
"-w", "missingdepfile=err"
cmd := Command(ctx, config, "ninja", executable, args...)//初始化ninja指令参数
...
ctx.Status.Status("Starting ninja...")
cmd.RunAndStreamOrFatal()//执行ninja指令
}
4.4.4 soong编译创建文件
文件 | 备注 |
---|---|
android/out/soong.log | soong模块打印内容 |
android/out/verbose.log | 控制台编译日志 |
android/out/dumpvars-verbose.log | lunch的log信息 |
android/out/.ninja_log | ninja模块编译log |
android/out/soong_ui | go可执行程序,执行soong编译 |
android/out/soong/build.ninja | 项目上所有bp模块的编译规则 |
android/out/build-*.ninja | 项目上所有mk模块的编译规则 |
android/out/combined-*.ninja | 项目上所有模块的编译规则组合 |
android/out/soong/host/linux-x86/bin/androidmk | mk文件转bp文件的指令 |
五. 模块编译速度优化
随着Android版本的更迭,尤其是Androd10和Android11以上源码的编译,单编模块的时间也特别慢,每次都需要小半个小时甚至更长,因为每次单编都会重新加载所有mk文件,再生成ninja编译,此过程很慢,实际编译过程很快。
如下指令是AOSP的快编指令,用于快速编译单个模块:
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-bengal.ninja SnapdragonCamera -j32
./prebuilts/build-tools/linux-x86/bin/ninja
指定了要使用的构建工具,即Ninja构建系统。该指令通过在AOSP预构建工具链目录下找到Ninja可执行文件进行调用。
out/combined-aosp_arm-eng.ninja
指定了Ninja构建系统要使用的构建文件的路径和名称。在AOSP编译过程中,生成的构建文件会存储在out目录中,且命名通常包含目标设备的相关信息。
SnapdragonCamera
指定了要构建的目标模块或子模块的名称。
-j32
指定了并发构建的线程数。-j32表示同时使用32个线程进行构建,以加快构建速度。
其他常用的指令,如:
#编译Settings./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-bengal.ninja Settings -j32
#编译selinux./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-bengal.ninja selinux_policy -j32
#编译Framework./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-bengal.ninja frameworks -j32
#全编译./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-bengal.ninja -j32 2>&1 |tee ninja_build.log
六、参考资料
https://blog.xiaobaicai.fun/android/75346.html
Treble架构:
https://segmentfault.com/a/1190000021550665?sort=newest
Android-Make指令:
https://blog.csdn.net/yiranfeng/article/details/109084082
https://blog.csdn.net/yiranfeng/article/details/109148537
https://zhuanlan.zhihu.com/p/342303212
https://zhuanlan.zhihu.com/p/342817768
https://blog.csdn.net/m0_37624402/article/details/91409900
编译优化:
https://blog.csdn.net/lontano_0406/article/details/131162119
http://wed.xjx100.cn/news/268761.html?action=onClick
https://blog.csdn.net/weixin_36389889/article/details/128469488