Android是一个庞大的系统,包含太多的模块,各种模块的类型也有10多种。为了管理整套源码的编译,Android专门开发了自己的Build系统。从大的方面讲,Android的Build系统可分为3大块:第一块是位于build/core目录下的文件,这是Android Build系统的框架和核心;第二块是位于device目录下的文件,存放的是具体产品的配置文件;第三块是各模块的编译文件:Android.mk,位于模块的源文件目录下。
Android Build系统核心
核心位于build/core,这个目录中有几十个mk文件,以及一些shell脚本和perl脚本,构成了Android Build系统的基础和框架。
通常使用下面的命令来编译Android系统:
# . build/envsetup.sh # lunch # make
那么我们以此作为入口,进行解析。
编译环境的建立
1、envsetup.sh文件的作用
执行Android系统的编译,必须先运行envsetup.sh脚本,这个脚本会建立Android的编译环境。envsetup.sh文件定义了很多shell命令,这些命令在envsetup.sh脚本之后就可以从shell环境中调用了。envsetup.sh 脚本还定义了一些其他有用的shell 命令,这些命令可以单独使用。例如:
lunch 当前编译的产品 croot 跳转到根目录 m 编译整个源码 mm 编译当前目录所有模块,不编译依赖模块 mmm 编译指定模块,不编译依赖模块 mma 编译当前目录所有模块,编译依赖模块 mmma 编译指定模块,编译依赖模块 cgrep 对所有的c/c++文件执行grep命令 ggrep 对所有的Gradle文件执行grep命令 jgrep 对所有的java文件执行grep命令 resgrep 对所有的资源文件执行grep命令 sgrep 对所有的文件执行grep命令 godir 查找目录,并切换。
2、lunch命令的功能
lunch 命令没有参数,打印产品列表,以供选择;如果有名称,格式为“<product_name>--< build_variant >”,其中前半部分是产品名称,后面是“build_variant”必须是eng /user/userdebug 三者之一。
lunch主要作用就是根据用户输入或选择的产品的名来设置与产品相关的环境变量。这些环境变量与产品编译相关的主要有:
TARGET_PRODUCT:所编译的产品名称。 TARGET_BUILD_VARIANT:表示编译产品的类型。可能值有 eng ,user .userdebug。 TARGET_BUILD_TYPE:表示编译的类型,可选值为release和debug当选择debug版本时,系统会加入调式信息,方便追踪。
Build相关环境变量
PLATFORM_VERSION_CODENAME:平台版本名称,通常是AOSP(Android OpenSource Project的缩写)
PLATFORM_ VERSION:Android平台的版本号。
TARGET_PRODUCT:所编译的产品名称。
TARGET_BUILD_VARIANT:表示编译产品的类型,可能值有 eng、user、userdebug。
TARGET_BUILD_TYPE:表示编译的类型,可选值为release和debug。当选择debug版本时,系统会加入调式信息,方便追踪。
TARGET_BUILD_APPS:编译系统时,这个变量值为空,编译单个模块时,这个模块的变量值为模块的路径。
TARGET_ARCH:编译目标的CPU架构。
TARGET_ARCH_VARIANT:编译目标的CPU架构版本。
TARGET_CPU_VARIANT:编译目标的CPU代号。
TARGET_2ND_ARCH: 编译目标的第二CPU架构。
TARGET_2ND_ARCH _VARIANT: 编译目标的第二CPU架构版本。
TARGET_2ND_CPU_VARIANT:编译目标的第二CPU代号
HOST_ARCH:编译平台架构。
HOST_OS:编译平台使用的操作系统。
HOST_OS_EXTRA:编译平台操作系统的一些额外信息,包括版本号、产品名称、代号等。
BUILD_ID:编译版本信息,可以定义公司特有标识。
OUT_DIR:编译结果输出目录。
对这些环境变量的修改,可以放到产品的定义文件中。如果只是希望临时改变这些环境变量的值,可以通过在make命令中加入参数的方式完成。如:
make BUILD_ID='Android L'
Build系统的层次关系
设置好环境变量之后,就是用make命令开始执行编译的过程。编译产品的目的是生成用于“刷机”的各种img文件,因此,生成这些特殊格式的文件将是Build系统的主要功能。
Build系统主要编译脚本简介,如下:
main.mk:Android Build系统的主控文件。主要作用是包含进其他mk文件,以及定义几个最重要的编译目标,如droid、sdk、ndk等。同时检查编译工具的版本,如make、gcc、javac等。
help.mk:Android Build系统的帮助。文件中定义了一个名为help的编译目标,因此,输入“make help”会打印出Build系统的使用说明。
config.mk:Android Build系统的配置文件。主要定义了许多常量来负责不同类型模块的编译,定义编译器参数并引入产品的BoardConfig.mk文件来配置产品参数,同时也定义了一些编译工具的路径,如aapt、javajar等。
pathmap.mk:将许多头文件的路径通过名值对的方式定义为映射表,并通过include-path-for函数来获取。例如,通过 $(callinclude-path-for, frameworks-native)
便可以获取到 framework 本地代码需要的头文件路径。
envsetup.sh:包含进product_config.mk文件并根据其内容设置编译产品所需要的环境变量,如TARGET_PRODUCT等,并检查这些变量值的合法性,同时还指定了各种编译结果的输出路径。
product_config.mk:包含进了系统中所有AndroidProduct.mk文件,并根据当前产品的配置文件来设置产品编译相关的变量。
product.mk:定义product_config.mk文件中使用的各种函数。
version_default.mk:定义系统版本相关的变量。
build_id.mk:定义环境变量BUILD_ID。
Makefile:定义了系统最终编译完成所需要的各种目标和规则。
分析main.mk文件
main.mk是Android Build系统的主控文件。分段看下源码:
(1)、检查gnu make的版本号是否大于等于3.81,否则报错并停止编译。
# Check for broken versions of make. ifneq (1,$(strip $(shell expr $(MAKE_VERSION) \>= 3.81))) $(warning ********************************************************************************) $(warning * You are using version $(MAKE_VERSION) of make.) $(warning * Android can only be built by versions 3.81 and higher.) $(warning * see https://source.android.com/source/download.html) $(warning ********************************************************************************) $(error stopping) endif
(2)、定义缺省的编译目标为“droid”。因此,命令“make”相当于“make droid”:
# This is the default target. It must be the first declared target. .PHONY: droid DEFAULT_GOAL := droid $(DEFAULT_GOAL): droid_targets
(3)、引入几个make文件。这里注意“-include”和“include”的区别:前者包含的文件如果不存在不会报错,后者则会报错并停止编译。
# Targets that provide quick help on the build system. include $(BUILD_SYSTEM)/help.mk # Set up various standard variables based on configuration # and host information.基于配置和主机信息设置各种标准变量。 include $(BUILD_SYSTEM)/config.mk # This allows us to force a clean build - included after the config.mk # environment setup is done, but before we generate any dependencies. This # file does the rm -rf inline so the deps which are all done below will # be generated correctly include $(BUILD_SYSTEM)/cleanbuild.mk # Include the google-specific config -include vendor/google/build/config.mk VERSION_CHECK_SEQUENCE_NUMBER := 6 -include $(OUT_DIR)/versions_checked.mk
(4)、检查java的版本是否是1.8或1.7,不是则会报错退出。1.8或1.7版本的java,在Linux下还要求是openJDK的版本,否则要求是Oracle的JDK版本。
# Check for the correct version of java, should be 1.8 by # default and only 1.7 if LEGACY_USE_JAVA7 is set. ifeq ($(LEGACY_USE_JAVA7),) # if LEGACY_USE_JAVA7 == '' required_version := "1.8.x" required_javac_version := "1.8" java_version := $(shell echo '$(java_version_str)' | grep '[ "]1\.8[\. "$$]') javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.8[\. "$$]') else 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[\. "$$]') endif # if LEGACY_USE_JAVA7 == '' ifeq ($(strip $(java_version)),) $(info ************************************************************) $(info You are attempting to build with the incorrect version) $(info of java.) $(info $(space)) $(info Your version is: $(java_version_str).) $(info The required version is: $(required_version)) $(info $(space)) $(info Please follow the machine setup instructions at) $(info $(space)$(space)$(space)$(space)https://source.android.com/source/initializing.html) $(info ************************************************************) $(error stop) endif
# Check for the current JDK. # # For Java 1.7/1.8, we require OpenJDK on linux and Oracle JDK on Mac OS. requires_openjdk := false ifeq ($(BUILD_OS),linux) requires_openjdk := true endif
(5)、将变量VERSIONS_CHECKED和BUILD_EMULATOR写入文件out/version_checked.mk,下次build时会重新包含这个文件。
ifndef BUILD_EMULATOR # Emulator binaries are now provided under prebuilts/android-emulator/ BUILD_EMULATOR := false endif $(shell echo 'VERSIONS_CHECKED := $(VERSION_CHECK_SEQUENCE_NUMBER)' \ > $(OUT_DIR)/versions_checked.mk) $(shell echo 'BUILD_EMULATOR ?= $(BUILD_EMULATOR)' \ >> $(OUT_DIR)/versions_checked.mk) endif
(6)、再包含进3个mk文件:
# Bring in standard build system definitions. include $(BUILD_SYSTEM)/definitions.mk # Bring in dex_preopt.mk include $(BUILD_SYSTEM)/dex_preopt.mk # The pdk (Platform Development Kit) build include build/core/pdk_config.mk
(7)、如果变量ONE_SHOT_MAKEFILE的值不为空,将它定义的文件包含进来。当编译一个单独的模块时,ONE_SHOT_MAKEFILE的值会设为模块的make文件路径。如果值ONE_SHOT_MAKEFILE的值为空,说明正在编译整个系统,因此,调用findleayes.py脚本搜索系统里所有Android.mk文件并将它们包含进来。
(8)、根据编译类型来设置属性ro.secure的值。
(9)、包含进post_clean.mk和legacy_prebuilts.mk脚本。根据legacy_prebuilds.mk中定义的变量GRANDFATHERED_ALL_PREBUILT检查是由有不在这个列表中的prebuilt模块。如果有则报错退出。
# Now with all Android.mks loaded we can do post cleaning steps. include $(BUILD_SYSTEM)/post_clean.mk ifeq ($(stash_product_vars),true) $(call assert-product-vars, __STASHED) endif include $(BUILD_SYSTEM)/legacy_prebuilts.mk ifneq ($(filter-out $(GRANDFATHERED_ALL_PREBUILT),$(strip $(notdir $(ALL_PREBUILT)))),) $(warning *** Some files have been added to ALL_PREBUILT.) $(warning *) $(warning * ALL_PREBUILT is a deprecated mechanism that) $(warning * should not be used for new files.) $(warning * As an alternative, use PRODUCT_COPY_FILES in) $(warning * the appropriate product definition.) $(warning * build/target/product/core.mk is the product) $(warning * definition used in all products.) $(warning *) $(foreach bad_prebuilt,$(filter-out $(GRANDFATHERED_ALL_PREBUILT),$(strip $(notdir $(ALL_PREBUILT)))),$(warning * unexpected $(bad_prebuilt) in ALL_PREBUILT)) $(warning *) $(error ALL_PREBUILT contains unexpected files) endif
(10)、计算哪些模块应该在本次编译之后引入。
(11)、包含Makefile文件。至此,所有的编译文件都包含进来了。
# build/core/Makefile contains extra stuff that we don't want to pollute this # top-level makefile with. It expects that ALL_DEFAULT_INSTALLED_MODULES # contains everything that's built during the current make, but it also further # extends ALL_DEFAULT_INSTALLED_MODULES. ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install) include $(BUILD_SYSTEM)/Makefile modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
(12)、定义系统的编译目标。下面详细介绍。
Build系统的编译目标介绍
Android Build系统的缺省编译目标是droid。droid目标会依赖其他目标,所有这些目标共同组成了最终的产品。下面是droid目标定义。
# Building a full system-- the default is to build droidcore droid_targets: droidcore dist_files # Build files and then package it into the rom formats .PHONY: droidcore droidcore: files \ systemimage \ $(INSTALLED_BOOTIMAGE_TARGET) \ $(INSTALLED_RECOVERYIMAGE_TARGET) \ $(INSTALLED_USERDATAIMAGE_TARGET) \ $(INSTALLED_CACHEIMAGE_TARGET) \ $(INSTALLED_VENDORIMAGE_TARGET) \ $(INSTALLED_FILES_FILE) \ # All the droid stuff, in directories .PHONY: files files: prebuilt \ $(modules_to_install) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) .PHONY: prebuilt prebuilt: $(ALL_PREBUILT)
在droid的依赖目标中,droidcore、files和prebuilt是中间目标,其余目标的作用介绍如下:
目标 | 说明 |
dist_file | 用来复制文件到out/dist目录 |
systemimage | 用来产生system.img |
$(INSTALLED_BOOTIMAGE_TARGET) | 用来产生boot.img |
$(INSTALLED_RECOVERYIMAGE_TARGET) | 用来产生recovery.img |
$(INSTALLED_USERDATAIMAGE_TARGET) | 用来产生userdata.img |
$(INSTALLED_CACHEIMAGE_TARGET) | 用来产生cache.img |
$(INSTALLED_VENDORIMAGE_TARGET) | 用来产生vendor.img |
$(INSTALLED_FILES_FILE) | 用来产生名为installed-files.txt文件,该文件将会存放在out/target/product/<product_name>下。文件的内容是当前产品配置将要安装的所有文件列表。 |
$(modules_to_install) | modules_to_install变量是当前产品配置下所有将要安装的模块的列表。因此,该目标将导致所有这些模块的编译。 |
$(INSTALLED_ANDROID_INFO_TXT_TARGET) | 用来产生名为android_info.txt文件,该文件将会存放在out/target/product/<product_name>下。文件内容是当前产品的设备信息。 |
$(ALL_PREBUILT) | 用来产生所有在变量GRANDFATHERED_ALL_PREBUILT中的文件。 |
除了droid及其相关目标,Build系统里还有很多可以独立使用的目标。
目标 | 说明 |
make clean | 清除所有编译结果,相当于“rm -rf out” |
make snod | 快速打包system.img。 |
make help | 打印build系统简单的帮助信息。 |
make sdk | 生成Android SDK。 |
make framework | 编译出所有framework的jar包 |
make services | 编译出系统服务及相关的模块。 |
Android的产品配置文件
产品的配置文件的作用是按照Build系统的要求,将生成产品的各种image文件所需要的配置信息(如版本号、各种参数等)、资源(图片、字体、铃声等)、二进制文件(apk、jar包、so库等)有机地组织起来,同时进行剪裁,加入或去掉一些模块。
Android的产品配置文件位于源码的device目录下,但是产品配置文件也可以放在vendor目录下。这两个目录从Build系统的角度看没有太大的区别,Build系统中搜寻产品配置的关联文件时会同时在这两个目录下进行,但是在实际使用中,往往会让这两个目录配合使用,通常产品配置文件放在device目录下,而vendor目录下则存放一些硬件的HAL库。编译某一款手机的“刷机包”之前,需要将手机上一些不开源的HAL库(主要是so文件)、驱动等抽取出来,放在vendor目录下。
分析配置文件
通常device目录下有以下几个子目录。
(1)、common:用来存放各个产品通用的配置脚本、文件等。
(2)、sample:一个产品配置的例子,写一个新的产品配置时可以使用sample目录下的文件做为模板。
(3)、google:几个简单的模块。
(4)、generic:存放的是用于模拟器的产品,包括x86、arm、mips等。
(5)、zeusis:产品则放在对应目录下,各家公司的命名不同。如需添加新产品,可在device目录下新建一个目录。
下面介绍Build系统会包含的产品配置中的几个文件,这几个文件和Build系统关系最密切,也是关键文件。
1、vendorsetup.sh
vendorsetup.sh文件会在初始化编译环境被包含进去,主要作用就是调用add_lunch_combo命令来添加产品名称串。例如,pollux目录下的vendorsetup.sh文件的内容如下:
add_lunch_combo pollux-user add_lunch_combo pollux-userdebug add_lunch_combo pollux-eng
产品名称串的格式是<produce_name>-<goal>,前半部分是产品名称,后半部分是编译类型。产品的编译类型有三种:eng、user、userdebug。
2、AndroidProducts.mk
AndroidProducts.mk会在Build系统的product.mk文件中被包含进去,这个文件最重要的作用是定义了一个变量PRODUCT_MAKEFILES,它定义了本配置目录中所有编译入口文件,但是,每种产品编译时只会使用其中之一。例如,pollux目录下的AndroidProducts.mk文件内容如下:
PRODUCT_MAKEFILES := \ $(LOCAL_DIR)/pollux.mk
vendorsetup.sh文件中加入“选择列表”的是pollux,因此,实际能选用的文件只用pollux.mk。
3、BoardConfig.mk
BoardConfig.mk文件会被Build系统的envsetup.mk文件包含进来。主要定义了和设备硬件(CPU、WIFI、GPS等)相关的一些参数。看懂这个文件的关键是理解文件中使用的编译变量。下面简单介绍几种。
一些重要的编译变量总结下:
PRODUCT_COPY_FILE:格式为“原文件路径:目标文件路径”字串的集合。该变量能方便地将编译目录下的一个文件复制到目标文件系统中。
PRODUCT_PACKAGES:定义产品的模块列表,多有在模块列表中定义的模块都会被执行。
PRODUCT_AAPT_CONFIG:指定了系统中能够支持的屏幕密度类型(dip)。所谓支持,是指系统编译时,会将相应的资源文件添加到framework-res.apk文件中。
PRODUCT_AAPT_PREF_CONFIG:指定系统实际的屏幕密度类型。
DEVICE_PACKAGE_OVERLAYS:指定了系统的overlay目录。系统编译时会使用overlay目录下存放的资源文件替换系统或模块原有的资源文件。
PRODUCT_PROPERTY_OVERRIDES:定义系统的属性值。如果属性名称以“ro.”开头,就是只读属性。如果属性名称以“persist.”开头,当设置这个属性时,它的值将写入文件/data/property中。
编译类型eng、user、userdebug
产品的image文件
Android编译完成后会生成几个image文件,包括:boot.img、system.img、recovery.img和userdata.img。
boot.img
Boot header(文件头) | 1 page |
Kernel(gzip压缩的映像) | n pages |
Ramdisk(映像) | m pages |
Second stage(载入器程序) | o pages |
boot.img是一种Android自定义的文件格式。该格式包括了一个2*1024大小的文件头,后面是用gzip压缩过的kernel镜像,在后面是ramdisk映像,最后是一个加载器程序。
recovery.img
相当于一个小型文本界面的Linux系统,有自己的内核和文件系统,作用是恢复或升级系统。
system.img
是设备中system目录的镜像,包含了Android系统主要的目录和文件。
- app目录:一般的apk文件。
- bin目录:一些Linux工具。
- etc目录:系统的配置文件。
- framework目录:系统平台所有jar包和资源文件包
- lib目录:系统共享库
- media目录:系统的多媒体资源,主要是铃声
- priv-app目录:系统核心的apk文件
- usr目录:键盘布局、时间区域文件
- build.prop目录:系统属性的定义文件
- tts目录:系统的语音合成文件
userdata.img
是设备中data目录的镜像,初始时一般不包含任何文件。Android系统初始化时会在/data目录创建一些子目录和文件。