Android Build System(1)
本文说明Android编译系统的整体架构,编译过程
编译系统综述
发展历史
-
Android 7 之前,Android 使用GNU Make和Shell编译
-
Android 7 Google引入ninja用来替代make,并且提供了kati工具,将Android.mk转换为ninja的构建规则文件
-
Android 8 Google引入了Soong,soong是整个Android构建系统的核心,他调用kati、Make、Ninja构建系统,完成整个构建
-
下一代构建分布式编译系统bazel,到Android 14 尚未正式发布,将大幅提高编译速度
| Android版本 | 编译系统 |
|---|---|
| 7.0 之前 | Make 、Shell |
| 7.0 | 引入ninjia、kati |
| 8.0 | 引入soong和blueprint,Android.mk逐渐作废 |
| NA | Google正在开发全新的bazel分布式编译系统 |
编译系统架构

soong
整个系统的核心,soong调用kati/ckati、ninja、make等,完成对整个Android的编译。源码目录:build/soong
kati/ckati
将Android.mk转换为ninja的构建规则文件,kati使用go语言开发,后续为了提高性能使用c++重写,即ckati
blueprint
bluprint是go语言开发的生成ninja manifest的库,官方解释参考blueprint reference:
Blueprint is a meta-build system that reads in Blueprints files that describe modules that need to be built, and produces a Ninja manifest describing the commands that need to be run and their dependencies. Where most build systems use built-in rules or a domain-specific language to describe the logic for converting module descriptions to build rules, Blueprint delegates this to per-project build logic written in Go. For large, heterogenous projects this allows the inherent complexity of the build logic to be maintained in a high-level language, while still allowing simple changes to individual modules by modifying easy to understand Blueprints files.
Blueprint是一个元编译系统,Blueprint读入描述需要编译的模块的 Blueprints files ,输出ninja manifest文件,manifest文件中包含模块的依赖和编译模块所需要的命令。
大部分编译系统使用内置的规则或特定领域的语言,来描述如何将模块定义转换为编译规则的逻辑,Blueprint的目的就是代理实现这种逻辑。
source 执行流程
soource执行的build/ensetup.sh,首先会执行最下面的三行语句
validate_current_shell
source_vendorsetup
addcompletions
主要是source_vendorsetup函数,此函数的作用是 查找device vendor product下的vendorsetup.sh并执行,查找的最大深度是4。例如输出如下:
including device/qcom/common/cuttlestone/vendorsetup.sh
including vendor/long/common/vendorsetup.sh
including vendor/qcom/opensource/core-utils/vendorsetup.sh
including vendor/qcom/proprietary/common/vendorsetup.sh
including vendor/qcom/proprietary/prebuilt_HY11/vendorsetup.sh
所以:vendor可以通过vendorsetup.sh来做一些编译前的准备工作,比如创建软连接之类的。
luncher 执行流程
在luncher函数的实现中,是调用print_lunch_menu函数输出找到的产品选项
print_lunch_menu的实现如下:
function print_lunch_menu()
{
local uname=$(uname)
local choices
choices=$(TARGET_BUILD_APPS= TARGET_PRODUCT= TARGET_BUILD_VARIANT= get_build_var COMMON_LUNCH_CHOICES 2>/dev/null)
local ret=$?
local i=1
local choice
for choice in $(echo $choices)
do
echo " $i. $choice"
i=$(($i+1))
done
}
核心逻辑是通过get_build_var COMMON_LUNCH_CHOICES 2 获取所有的编译选项。
get_build_var实现如下:
# Get the exact value of a build variable.
function get_build_var()
{
if [ "$BUILD_VAR_CACHE_READY" = "true" ]
then
eval "echo \"\${var_cache_$1}\""
return 0
fi
local T=$(gettop)
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP." >&2
return 1
fi
(\cd $T; build/soong/soong_ui.bash --dumpvar-mode $1)
}
get_build_var主要是调用build/soong/soong_ui.bash –dumpvar-mode COMMON_LUNCH_CHOICES 来获取值。
build/soong/soong_ui.bash
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_build_go mk2rbc android/soong/mk2rbc/cmd
soong_build_go rbcrun rbcrun/cmd
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"
@startmindmap
* soong_ui.bash
** build/soong/scripts/microfactory.bash
*** setup GOROOT
*** 定义soong_build_go
*** build/blueprint/microfactory/microfactory.bash
**** 定义build_go
** soong_build_go soong_ui android/soong/cmd/soong_ui\nsoong_build_go mk2rbc android/soong/mk2rbc/cmd\nsoong_build_go rbcrun rbcrun/cmd
*** 编译soong_ui等可执行程序
** exec "$(getoutdir)/soong_ui" "$@"
*** 通过soong_ui执行命令
@endmindmap
product_config.mk的处理
android_products_makefiles保存的是所有的AndroidProducts.mk文件列表:
# Products are defined in AndroidProducts.mk files:
android_products_makefiles := $(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
$(SRC_TARGET_DIR)/product/AndroidProducts.mk
下面这段代码用于根据第一个参数是否包含冒号来设置 _product-spec 变量的值。如果参数中包含冒号,则变量值将设置为参数本身。否则,变量值将设置为文件名和参数的组合,中间用冒号分隔。
# Products are defined in AndroidProducts.mk files:
_product-spec=$(strip \
$(if $(findstring :,$(1)), \
$(1), \
$(basename $(notdir $(1))):$(1) \
) \
)
对每个AndroidProducts.mk文件,执行下面的操作:
# Build cumulative lists of all product specs/lunch choices/Starlark-based products.
product_paths :=
common_lunch_choices :=
products_using_starlark_config :=
$(foreach f,$(android_products_makefiles), \
$(call _read-ap-file,$(f)) \
$(eval product_paths += $(ap_product_paths)) \
$(eval common_lunch_choices += $(ap_common_lunch_choices)) \
$(eval products_using_starlark_config += $(ap_products_using_starlark_config)) \
)
_read-ap-file的定义如下,主要是读取AndroidProducts.mk,并进行初步的校验,校验失败则报错。$(f) 代表一个AndroidProducts.mk文件
# Reads given AndroidProduct.mk file and sets the following variables:
# ap_product_paths -- the list of <product>:<path> pairs
# ap_common_lunch_choices -- the list of <product>-<build variant> items
# ap_products_using_starlark_config -- the list of products using starlark config
# In addition, validates COMMON_LUNCH_CHOICES and STARLARK_OPT_IN_PRODUCTS values
define _read-ap-file
$(eval PRODUCT_MAKEFILES :=) \
$(eval COMMON_LUNCH_CHOICES :=) \
$(eval STARLARK_OPT_IN_PRODUCTS := ) \
$(eval ap_product_paths :=) \
# 清空上面几个变量的值
$(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
# 设置LOCAL_DIR的值
$(eval include $(f)) \
# include f代表的makefile
$(foreach p, $(PRODUCT_MAKEFILES),$(eval ap_product_paths += $(call _product-spec,$(p)))) \
# $(PRODUCT_MAKEFILES)是某个AndroidProduct.mk中定义的变量,所以遍历$(PRODUCT_MAKEFILES) 中的每个item,然后获取到_product-spec,即每一个都代表一个规格
$(eval ap_common_lunch_choices := $(COMMON_LUNCH_CHOICES)) \
# AndroidMakefile中定义的COMMON_LUNCH_CHOICES变量
$(eval ap_products_using_starlark_config := $(STARLARK_OPT_IN_PRODUCTS)) \
$(eval _products := $(call _first,$(ap_product_paths),:)) \
# 当前AndroidProduct.mk中定义的所有产品
$(eval _bad := $(filter-out $(_products),$(call _first,$(ap_common_lunch_choices),-))) \
# 校验1:根据-拆分COMMON_LUNCH_CHOICES中的所有字符串,从_products中过滤掉,如果过滤结果还有值,说明有配置存在错误
$(if $(_bad),$(error COMMON_LUNCH_CHOICES contains products(s) not defined in this file: $(_bad))) \
$(eval _bad := $(filter-out %-eng %-userdebug %-user,$(ap_common_lunch_choices))) \
# 校验2:校验variant是否正常
$(if $(_bad),$(error invalid variant in COMMON_LUNCH_CHOICES: $(_bad)))
$(eval _bad := $(filter-out $(_products),$(ap_products_using_starlark_config))) \
$(if $(_bad),$(error STARLARK_OPT_IN_PRODUCTS contains product(s) not defined in this file: $(_bad)))
endef
@startmindmap
* product_config.mk
** $(BUILD_SYSTEM)/node_fns.mk
*** 定义了一组var相关的操作函数
** $(BUILD_SYSTEM)/product.mk
*** 定义产品Product相关Makefile变量
**** 这些变量都以 PRODUCT_ 开头
**** _product_single_value_vars(只能保存一个值)
**** _product_list_vars(可以保存多个值)
*** 定义Product 相关Makefile函数
**** inherit-product
**** inherit-product-if-exists
** $(BUILD_SYSTEM)/device.mk
*** 定义device 相关Makefile变量 \nDEVICE_NAME\nDEVICE_BOARD\nDEVICE_REGION
*** 定义Product 相关Makefile函数 \ninherit-device\ndump-device\nimport-devices
@endmindmap
BoardConfig.mk
BoardConfig.mk是必须的,因为如果device或者vendor/$(PRODUCT_DEVICE)/BoardConfig.mk找不到,则lunch命令会报错,不报错如下:
In file included from build/make/core/config.mk:356:
In file included from build/make/core/envsetup.mk:371:
build/make/core/board_config.mk:228: error: No config file found for TARGET_DEVICE orange_x86_64.
16:50:34 dumpvars failed with: exit status 1
build/make/core/board_config.mk 228行的代码如下:
# Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
# or under vendor/*/$(TARGET_DEVICE). Search in both places, but
# make sure only one exists.
# Real boards should always be associated with an OEM vendor.
ifdef TARGET_DEVICE_DIR
ifneq ($(origin TARGET_DEVICE_DIR),command line)
$(error TARGET_DEVICE_DIR may not be set manually)
endif
board_config_mk := $(TARGET_DEVICE_DIR)/BoardConfig.mk
else
board_config_mk := \
$(strip $(sort $(wildcard \
$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
$(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
$(shell test -d vendor && find -L vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
)))
ifeq ($(board_config_mk),)
$(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))
endif
ifneq ($(words $(board_config_mk)),1)
$(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))
endif
TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))
.KATI_READONLY := TARGET_DEVICE_DIR
endif
其中TARGET_DEVICE_DIR是在make/core/product_config.mk中赋值的,相关代码如下:
# Quick check and assign default values
TARGET_DEVICE := $(PRODUCT_DEVICE)
Reference


还可以