1. Make
Make是一款自动化编译工具,在执行编译任务时会解析在Makefile文件中定义的编译规则,然后依据编译规则对源文件进行编译。
1.1 为什么要使用它?
在编译小项目时,我们可以使用gcc手动的编译每一个源文件,但是在编译大项目时(比如Android),如果再使用gcc手动编译,那么工作量是不能想象的。这时就需要使用自动化编译工具。使用make还有一个好处,避免不必要的重新编译,当修改了部分源文件时,它可以监测到哪些文件被修改了,然后只重新编译被修改的源文件。
1.2 编译规则
Makefile中定义的编译规则包括有源文件、编译的目标、模块之间的依赖关系以及编译条件等。
make的基础规则非常简单,但是经过扩展修饰后,就可以编译各种庞大的工程。基础编译规则如下:
TARGET: PREREQUISITES
COMMAND
- TARGET:编译目标,通常是最后需要生成的文件名或者是中间文件名,比如编译C时的
可执行文件和.o文件。 - PREREQUISITES:先决条件,也就是编译的目标文件所依赖的文件。
- COMMAND:编译目标时需要执行的命令。
注意:COMMOD前必须有一个制表符[TAB]
1.3 示例
main.c
#include <stdio.h>
#include "utils.h"
int main(int argc, char* argv[]) {
char* word= getWord();
printf("hello %s\n", word);
return 0;
}
utils.h
char* getWord();
utils.c
#include "utils.h"
char* getWord() {
return "Make";
}
Makefile
hello: main.o utils.o
gcc -o hello main.o utils.o
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
clean:
rm -rf main.o utils.o hell
编写完源码以及Makefile后,执行make命令,它会解析Makefile文件。首先make会读取到第一条规则,查看目标是否有依赖,如果有依赖就先生成它的依赖目标,然后在生成自身。当然,如果对应的依赖也有自己的依赖,那么会先去生成依赖的依赖,这就形成了一个树形结构。最后,完成整个项目的编译。
在执行make 命令时,也可以指定编译目标,比如`make utils.o`, 这样就只会执行utils.o所在规则对应的命令,将utils.c编译成utils.o。
注意到`clean`它虽然也是目标但是没有为它生成目标文件,这种叫做伪目标,在执行 make 时可以指定它来执行对应的的命令。
2. Android源码编译流程
对Make做了简单了解后,现在来说一下Android源码的编译流程。
Android源码的编译步骤分为3部,每一步都做了什么?
source build/envsetup.sh
lunch qssi-userdebug-m2181
make -j4
2.1 执行envsetup.sh
envsetup.sh中定义了很多变量和函数,执行envsetup.sh之后,就可以在当前终端中使用这些函数和变量。
- hmm:列出函数及其介绍
- lunch: 选择要编译的目标产品和版本
- croot: 切换到源码根目录
- m: 整编源码
- mm: 编译当前目录下的所有模块(不会编译它们的依赖)
- mmm: 编译指定目录下所有的模块(不会编译它们的依赖)
- mma: 编译当前目录下所有的模块以及它们所依赖的模块
- mmma: 编译指定目录下所有的模块以及它们所依赖的模块
- provision: 将设备所有需要的分区刷入,选项将传递给fastboot
- cgrep: 在C/C++文件中搜索指定关键字
- ggrep: 在gradle文件中搜索指定关键字
- Jgrep: 在java文件中搜索指定关键字
- resgrep: 在资源xml文件中搜索指定关键字
- mangrep: 在AndroidManifest.xml文件中搜索指定关键字
- mgrep:在Makefiles和android.mk文件中搜索指定关键字
- sepgrep: 在sepolicy文件中搜索指定关键字
- sgrep: 在所有本地文件中搜索指定关键字
- godir: 切换到包含某个文件的目录下
- cproj: 向上切换到最近包含Android.mk的目录下
- findmakefile: 打印当前目录所在工程的Android.mk的文件路径
- getsdcardpath: 获取Sd卡路径
- getscreenshotpath: 获取屏幕截图的路径
- getlastscreenshot: 获取最后一张截图,导出到当前目录下
- getbugreports: 将bug报告从设备上导出到本地,bug报告存放于目录/sdcard/bugreports
- gettop: 获取Android源码根目录
- pid: pid processname 查看某个可执行程序对应的进程id
- key_back: 模拟按返回键
- key_home: 模拟按Home键envsetup.sh
- key_menu: 模拟按菜单键
2.2 lunch
function lunch()
{
local answer
#执行lunch时,如果输入参数就获取,没有的话就调用print_lunch_menu()
if [ "$1" ] ; then
answer=$1
else
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
if [ -z "$answer" ]
then
#如果没有选取product就采用默认的
selection=aosp_arm-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
#如果answer记录的是编号,这里通过get_build_var获取所有的product,并根据编号找到对应的product字符串
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
if [ $answer -le ${#choices[@]} ]
then
# array in zsh starts from 1 instead of 0.
if [ -n "$ZSH_VERSION" ]
then
selection=${choices[$(($answer))]}
else
selection=${choices[$(($answer-1))]}
fi
fi
else
selection=$answer
fi
export TARGET_BUILD_APPS=
local product variant_and_version variant version
#从selection中获取product、version等信息
product=${selection%%-*} # Trim everything after first dash
variant_and_version=${selection#*-} # Trim everything up to first dash
if [ "$variant_and_version" != "$selection" ]; then
variant=${variant_and_version%%-*}
if [ "$variant" != "$variant_and_version" ]; then
version=${variant_and_version#*-}
fi
fi
if [ -z "$product" ]
then
echo
echo "Invalid lunch combo: $selection"
return 1
fi
TARGET_PRODUCT=$product \
TARGET_BUILD_VARIANT=$variant \
TARGET_PLATFORM_VERSION=$version \
build_build_var_cache
if [ $? -ne 0 ]
then
return 1
fi
#导出上面得到的product、variant、version等信息
export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
if [ -n "$version" ]; then
export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
else
unset TARGET_PLATFORM_VERSION
fi
export TARGET_BUILD_TYPE=release
在print_lunch_menu函数中首先会获取COMMON_LUNCH_CHOICES变量,然后输出所有product
function print_lunch_menu()
{
local uname=$(uname)
local choices=$(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES)
echo
echo "You're building on" $uname
echo
echo "Lunch menu... pick a combo:"
local i=1
local choice
for choice in $(echo $choices)
do
echo " $i. $choice"
i=$(($i+1))
done
echo
}
COMMON_LUNCH_CHOICES定义在AndroidProducts.mk中
device/qcom/qssi/AndroidProducts.mk
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/qssi.mk
COMMON_LUNCH_CHOICES := \
qssi-userdebug
2.3 make
执行make命令后,会解析在源码根目录中的Makefile,可以看到只是包含了main.mk
### DO NOT EDIT THIS FILE ###
include build/make/core/main.mk
### DO NOT EDIT THIS FILE ###
在main.mk中包含了build/make/core/config.mk和build/make/core/definitions.mk。
...
BUILD_SYSTEM := $(TOPDIR)build/make/core
...
# Set up various standard variables based on configuration
# and host information.
include $(BUILD_SYSTEM)/config.mk
...
# Bring in standard build system definitions.
include $(BUILD_SYSTEM)/definitions.mk
...
config.mk中定义了一系列编译需要用到的变量,比如常用的CLEAR_VARS、BUILD_PACKAGE。这些变量记录着对应脚本的路径。每一个.mk完成一个基本功能,比如,CLEAR_VARS对应的build/make/core/clear_vars.mk是清除编译的临时变量,BUILD_PACKAGE对应的build/make/core/package.mk是编译APK。
# Set up efficient math functions which are used in make.
# Here since this file is included by envsetup as well as during build.
include $(BUILD_SYSTEM)/math.mk
# Various mappings to avoid hard-coding paths all over the place
include $(BUILD_SYSTEM)/pathmap.mk
# Allow projects to define their own globally-available variables
include $(BUILD_SYSTEM)/project_definitions.mk
# ###############################################################
# Build system internal files
# ###############################################################
BUILD_COMBOS:= $(BUILD_SYSTEM)/combo
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_HEADER_LIBRARY:= $(BUILD_SYSTEM)/header_library.mk
BUILD_AUX_STATIC_LIBRARY:= $(BUILD_SYSTEM)/aux_static_library.mk
BUILD_AUX_EXECUTABLE:= $(BUILD_SYSTEM)/aux_executable.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_RRO_PACKAGE:= $(BUILD_SYSTEM)/build_rro_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_APIDIFF:= $(BUILD_SYSTEM)/apidiff.mk
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_NATIVE_BENCHMARK := $(BUILD_SYSTEM)/native_benchmark.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk
BUILD_FUZZ_TEST := $(BUILD_SYSTEM)/fuzz_test.mk
BUILD_HOST_FUZZ_TEST := $(BUILD_SYSTEM)/host_fuzz_test.mk
BUILD_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/shared_test_lib.mk
BUILD_HOST_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/host_shared_test_lib.mk
BUILD_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/static_test_lib.mk
BUILD_HOST_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/host_static_test_lib.mk
BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
BUILD_HOST_TEST_CONFIG := $(BUILD_SYSTEM)/host_test_config.mk
BUILD_TARGET_TEST_CONFIG := $(BUILD_SYSTEM)/target_test_config.mk
而definitions.mk中用define也定义了一系列变量,比如常用的my-dir、all-subdir-makefiles、all-subdir-java-files等,这些编译主要用于处理目录相关的功能,比如my-dir是获取当前目录路径,all-subdir-makefiles调用所有子目录的android.mk。
define print-vars
define true-or-empty
define gcno-touch-rule
define my-dir
define all-makefiles-under
define first-makefiles-under
define all-subdir-makefiles
define all-named-subdir-makefiles
define all-named-dirs-under
define all-subdir-named-dirs
define all-named-files-under
define all-subdir-named-files
define all-java-files-under
define all-subdir-java-files
define all-c-files-under
define all-subdir-c-files
define all-cpp-files-under
define all-subdir-cpp-files
define all-Iaidl-files-under
define all-subdir-Iaidl-files
define all-vts-files-under
define all-subdir-vts-files
define all-logtags-files-under
define all-proto-files-under
...
在上面提到make在解析Makefile时,会形成一个树型结构。那么对于Android来说,这个树型结构的根是谁呢?在`main.mk`中可以看到,定义的默认target为`droid`,而`droid`又依赖`droid_targets`。这里定义的`droid_targets`只是一个空的规则,就相当于先占一个位置,后面有真正的定义。
build/make/core/main.mk
...
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets
.PHONY: droid_targets
droid_targets:
...
根据TARGET_BUILD_APPS值的不同,这里分成了单编和整编。
ifneq ($(TARGET_BUILD_APPS),)
#单编
...
apps_only: $(unbundled_build_modules)
droid_targets: apps_only
...
else # TARGET_BUILD_APPS
#编译整个系统
# Building a full system-- the default is to build droidcore
droid_targets: droidcore dist_files
对于编译整个系统来说,droid_targets依赖droidcore和dist_files。
先看一下droid_core。通过droidcore可以生成分区镜像以及各种程序包,modules_to_install这个变量描述了系统需要安装的模块,比如framework、Settings。
# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
#生成system.img
$(INSTALLED_SYSTEMIMAGE_TARGET) \
#生成ramdisk.img
$(INSTALLED_RAMDISK_TARGET) \
#生成boot.img
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RADIOIMAGE_TARGET) \
$(INSTALLED_DEBUG_RAMDISK_TARGET) \
$(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_VBMETAIMAGE_TARGET) \
...
dist_files的作用是在out目录下创建dist文件夹,用于存储多种分发包。
3. Android.mk
上面提到在执行make时,会定义很多变量以及函数,我们在Android.mk中就可以使用这些函数和变量。
Android.mk用于在构建模块时,向构建系统描述源文件以及使用到的共享库。构建的模块可以是静态库、共享库、可执行文件、jar或apk。
3.1 示例
3.1.1 编译动态jar
HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Andoird.mk
#LOCAL_PATH用于指定项目路径
#my-dir是一个函数,可以调用这个函数直接获得当前路径
LOCAL_PATH := $(call my-dir)
#引入CLEAR_VARS指向的脚本,该脚本用于给 LOCAL_XXX 变量赋空值
include $(CLEAR_VARS)
#指定模块名
LOCAL_MODULE := HelloWorld
#指定源文件
LOCAL_SRC_FILES := HelloWorld.java
#将编译的目标指定为java库,BUILD_EXECUTABLE指向的脚本会收集LOCAL_XXX变量中提供的模块的所有相关信息,以及确定如何根据列出的源文件构建目标可执行文件。
include $(BUILD_JAVA_LIBRARY)
执行mm进行编译
3.1.2 编译APK
APK源码放在附件MaterialMe.tar.gz中
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#指定在什么版本下编译此模块, debug | eng | user | development | optional(默认)
#optional:所有版本都编译
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
#指定资源文件根目录
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
#指定AndroidManifest.xml
LOCAL_MANIFEST_FILE := AndroidManifest.xml
#项目名称
LOCAL_PACKAGE_NAME := MaterialMe
#安装在system_ext分区
LOCAL_SYSTEM_EXT_MODULE := true
#如果设置为true,则安装在system_ext/priv-app目录下,
#如果不设置或者设置为false,则安装在system_ext/app下面,当然对应的权限也会有变化
LOCAL_PRIVILEGED_MODULE := true
#使用sdk中已经hide的api来编译
LOCAL_PRIVATE_PLATFORM_APIS := true
#指定签名类型为platform,需要给AndroidManifest.xml中的manifest标签添加属性android:sharedUserId="android.uid.system
LOCAL_CERTIFICATE := platform
#aapt 是编译和打包资源的工具。而aapt2是在aapt上做了优化
LOCAL_USE_AAPT2 := true
#给appt指定编译参数,auto-add-overlay:自动添加overlays资源
LOCAL_AAPT_FLAGS := --auto-add-overlay
#指定依赖的Android库
LOCAL_STATIC_ANDROID_LIBRARIES := androidx.appcompat_appcompat \
com.google.android.material_material \
androidx.recyclerview_recyclerview \
androidx.cardview_cardview \
#指定依赖的静态库
LOCAL_STATIC_JAVA_LIBRARIES := glide-full-4.6.1 \
android-support-v4
#打包成apk
include $(BUILD_PACKAGE)
#项目使用到了开源库glide,而Android源码中没有这个库,这时就需要先对这个开源库进行预编译
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := glide-full-4.6.1:libs/glide-full-4.6.1.jar
include $(BUILD_MULTI_PREBUILT)
3.2 常用函数和变量
常用获取源文件的方法
#获取指定目录下的所有 Java 文件。
$(call all-java-files-under, src)
$(call all-subdir-java-files)
#获取指定目录下的所有 C 语言文件。
$(call all-c-files-under, src)
#获取指定目录下的所有 AIDL 文件。
$(call all-Iaidl-files-under, src)
#获取指定目录下的所有 Makefile 文件。
$(call all-makefiles-under, folder)
编译目标类型
#主机上的二进制动态库
BUILD_HOST_STATIC_LIBRARY
#主机上的二进制静态库
BUILD_HOST_SHARED_LIBRARY
#主机上的可执行程序
BUILD_HOST_EXECUTABLE
#目标设备上的二进制静态库,源码为C/C++,编译成.a文件
BUILD_STATIC_LIBRARY
#目标设备上的二进制动态库,源码为C/C++,编译成.so文件
BUILD_SHARED_LIBRARY
#目标设备上的可执行程序
BUILD_EXECUTABLE
#APK程序
BUILD_PACKAGE
#目标设备上的共享Java库
BUILD_JAVA_LIBRARY
#目标设备上的静态Java库
BUILD_STATIC_JAVA_LIBRARY
#主机上的Java共享库
BUILD_HOST_JAVA_LIBRARY
变量及其含义
#指定该子项目的绝对路径
LOCAL_PATH
Android.bp
#指定该项目所包含的所有源文件
LOCAL_SRC_FILES
#是否使用Android Asset Packaging Tool工具,取值true | false
LOCAL_USE_AAPT2
#指定该项目的名称
LOCAL_PACKAGE_NAME
#指定是否使用sdk中已经hide的api来编译,取值true | flase
LOCAL_PRIVATE_PLATFORM_APIS
#指定签名,取值testkey | platform | shared | media
# testkey:普通APK,默认情况下使用。
# platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。android:sharedUserId=”android.uid.system”
# shared:该APK需要和home/contacts进程共享数据。AndroidManifest.xml的manifest节点中增加android:sharedUserId=”android.uid,shared”
# media:该APK是media/download系统中的一环。 AndroidManifest.xml的manifest节点中增加 android:sharedUserId=”android.media”
LOCAL_CERTIFICATE
#安装在system_ext分区。默认安装在system分区
LOCAL_SYSTEM_EXT_MODULE := true
#安装到product分区 LOCAL_PRODUCT_MODULE := true
#指定编译后的安装位置
#如果设置为true,则安装在priv-app目录下,如果不设置或者设置为false,则安装在app下面,当然对应的权限也会有变化
LOCAL_PRIVILEGED_MODULE
#指定依赖的动态共享Java类库
LOCAL_JAVA_LIBRARIES
#指定依赖的静态Java类库
LOCAL_STATIC_JAVA_LIBRARIES
#指定资源文件路径
LOCAL_RESOURCE_DIR
#指定该项目所包含的头文件路径
LOCAL_C_INCLUDES
#指定依赖的C动态库
LOCAL_SHARED_LIBRARIES
#指定依赖的C静态库
LOCAL_STATIC_LIBRARIES
#指定编译目标为32位或64位
LOCAL_MULTILIB
#指定该项目的名称
LOCAL_MODULE
#指定C的编译参数,指定例如警告开关、优化和增加全局宏定义等设置
LOCAL_CFLAGS
#Cpp的编译参数
LOCAL_CPPFLAGS
#Java编译参数
LOCAL_JAVACFLAGS
#指定在什么类型的版本下编译, debug | eng | user | development | optional(默认)
#optional:所有版本都编译
LOCAL_MODULE_TAGS
#指定SDK版本
LOCAL_SDK_VERSION
#指定共享JNI 库的名称
LOCAL_JNI_SHARED_LIBRARIES
#指定要依赖的android 包
LOCAL_STATIC_ANDROID_LIBRARIES
#声明用到的注解器
LOCAL_ANNOTATION_PROCESSOR_CLASSES
#混淆配置,enable | disabled
LOCAL_PROGUARD_ENABLED
#指定混淆规则配置文件
LOCAL_PROGUARD_FLAG_FILES
Android.mk其实还是Makefile文件,那么Android.mk自然也支持定义变量以及使用条件语句
变量名=值列表
ifeq ($(变量), true)
else
endif
ifneq ($(变量), true)
endif
3.3 静态库动态库的区别及使用场景
动态库编译后会放在`out/target/product/qssi/system/framework`;而静态库编译后会放在`out/target/product/qssi/obj/JAVA_LIBRARIES`
引入了静态jar的模块实际上是把jar包里用到class都包含到引用的模块里了,会增大APK占用的空间;而共享jar在系统中仅有一份,使用到时,由系统加载提供。
什么时候使用动态jar,什么时候又使用静态jar呢?
一般打包系统库的时候,都会打包成动态jar,比如services.jar,framework.jar等。
在引用第三方库时,会打包成静态jar。
4. Android.bp
4.1 构建系统简介
Ninja
Ninja 是一个专注于速度的小型构建系统,它被设计为尽可能快地运行构建。Ninja与其他构建系统的区别在于,它的输入文件被设计为由更高级的构建系统生成;其他构建系统基于高级语言,而Ninja基于汇编。
ninja的构建文件虽然是可读的(human-readable),但是编写不是特别方便,因此就需要一些ninja构建文件的生成器。这些生成器就是一些元构建系统(meta-build system),例如Blueprint、CMake等等。
Blueprint
Blueprint 是一个元构建系统,该系统读取Blueprint文件来描述需要构建的模块,并生成一个Ninja清单来描述需要运行的命令及其依赖项。 Blueprint是ninja构建文件的生成器。android编译系统soong集成了Blueprint,Blueprint可将我们编写的android.bp解析生成一个ninja构建文件。
Kati
kati就是一个转换工具,它可以将Makefile和.mk文件转换为ninja。
Soong
在android 6.0版本之前,编译android源码采用的是基于make的编译系统,由于make在编译时表现出效率不够高、增量编译速度慢等问题,Google在android 7.0版本引进了编译速度更快的soong来替代make。
Soong集成了Ninja, 而Ninja专注于速度,没有条件或流程控制语句,也不支持逻辑运算。但它允许以其它语言如来维护这些复杂的编译流程和逻辑。例如,我们可以继续采用makefile, 或者采用go语言来维护编译流程和逻辑。上面已经提到了Ninja,Blueprint, kati等等好几种工具,为了完整、快速的构建一个android系统,就需要一个“管家”来协调这些工具。这个选择转换工具、选择解析框架、解析维护构建逻辑的“管家”就是soong。
androidmk
soong中还集成了一个非常有用的工具androidmk。androidmk可以将android.mk转换成android.bp。注意:androidmk工具可以转换变量,模块,注释和某些条件,但是自定义的Makefile规则,复杂的条件语句或其它的额外的include语句,必须手动转换。
4.2 services的Android.bp
Android.bp的格式:
模块类型 {
name: "",
属性名1: "属性值1",
属性名2: "属性值2",
...
}
开头为模块类型,比如`java_library`的意思就是将次模块编译成动态jar。后面跟着的是一个类似Json的结构,它定义着模块的属性。每个模块都必须有一个 name属性,也就是模块的名称。
services模块
java_library {
name: "services",
installable: true,
dex_preopt: {
app_image: true,
profile: "art-profile",
},
#源文件
srcs: [":services-main-sources"],
#依赖的静态jar
// The convention is to name each service module 'services.$(module_name)'
static_libs: [
"services.core",
"services.accessibility",
"services.appprediction",
"services.appwidget",
"services.autofill",
"services.backup",
"services.companion",
"services.contentcapture",
"services.contentsuggestions",
"services.coverage",
"services.devicepolicy",
"services.midi",
"services.net",
"services.people",
"services.print",
"services.restrictions",
"services.startop",
"services.systemcaptions",
"services.usage",
"services.usb",
"services.voiceinteraction",
"services.wifi",
"service-blobstore",
"service-jobscheduler",
"android.hidl.base-V1.0-java",
],
#依赖的动态jar
libs: [
"android.hidl.manager-V1.0-java",
"framework-tethering.stubs.module_lib",
],
// Uncomment to enable output of certain warnings (deprecated, unchecked)
//javacflags: ["-Xlint"],
}
可以看到`srcs`属性的值并不是文件路径,它是引用的一个filegroup
filegroup {
name: "services-main-sources",
srcs: ["java/**/*.java"],
path: "java",
visibility: ["//visibility:private"],
}
另外在Android.bp中还可以定义默认模块,默认模块可用于在多个模块中重复使用相同的属性。定义方法是将模块类型写为xxx_defaults,比如`java_defaults`。引用时,可以给其他模块添加defaults属性,比如`defaults: ["services_defaults"]`。默认模块还有cc_defaults, java_defaults, doc_defaults, stub_defaults等。
java_defaults {
name: "services_defaults",
plugins: [
"error_prone_android_framework",
],
}
services/wifi的Android.bp中就使用了这个默认模块
java_library_static {
name: "services.wifi",
defaults: ["services_defaults"],
srcs: [
":services.wifi-sources",
],
}
4.3 模块类型
在build/soong/androidmk/cmd/androidmk/android.go可以看到Android.bp支持的模块类型以及预编译类型
var moduleTypes = map[string]string{
"BUILD_SHARED_LIBRARY": "cc_library_shared",
"BUILD_STATIC_LIBRARY": "cc_library_static",
"BUILD_HOST_SHARED_LIBRARY": "cc_library_host_shared",
"BUILD_HOST_STATIC_LIBRARY": "cc_library_host_static",
"BUILD_HEADER_LIBRARY": "cc_library_headers",
"BUILD_EXECUTABLE": "cc_binary",
"BUILD_HOST_EXECUTABLE": "cc_binary_host",
"BUILD_NATIVE_TEST": "cc_test",
"BUILD_HOST_NATIVE_TEST": "cc_test_host",
"BUILD_NATIVE_BENCHMARK": "cc_benchmark",
"BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host",
"BUILD_JAVA_LIBRARY": "java_library",
"BUILD_STATIC_JAVA_LIBRARY": "java_library_static",
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
}
var prebuiltTypes = map[string]string{
"SHARED_LIBRARIES": "cc_prebuilt_library_shared",
"STATIC_LIBRARIES": "cc_prebuilt_library_static",
"EXECUTABLES": "cc_prebuilt_binary",
"JAVA_LIBRARIES": "prebuilt_java_library",
}
4.4 常用属性
模块名称 {
name: "test",
//指定AndroidManifest.xml路径
manifest: "AndroidManifest.xml",
//源码路径,支持通配符
srcs: [
"src/**/*.java",
],
//资源路径
resource_dirs: [
"res",
],
//依赖的静态Jar
static_libs: [
"androidx.appcompat_appcompat",
"androidx.recyclerview_recyclerview",
],
//静态库
static_libs: ["xxx", "xxx", ...]
//动态库
shared_libs: ["xxx", "xxx", ...]
//依赖的头文件库
header_libs: ["xxx", "xxx", ...]
#依赖的动态jar
libs: [
"android.hidl.manager-V1.0-java",
"framework-tethering.stubs.module_lib",
],
//签名
certificate: "platform",
optimize: {
//#指定混淆规则配置文件
proguard_flags_files: ["proguard.flags"],
},
sdk_version: "system_current",
cflags: ["xxx", "xxx", ...]
cppflags: ["xxx", "xxx", ...]
javacflags: ["xxx", "xxx", ...]
//指定安装此模块时,需要安装的模块
required: ["privapp_whitelist_com.android.documentsui"],
}
4.5 与Android.mk的差异
- Android.bp支持单行
//和多行/**/注释 - Android.bp也可以定义变量,通过
=赋值,变量定义后,它的值是不可变的,除了使用+=改变值,但也仅限在使用之间。 - Android.mk文件通常可以包含多个同名模块(例如,用于库的静态(static)和共享(shared)版本,用于不同主机(host)的版本,用于不同设备(device)版本);Android.bp中的每一个模块都需要唯一的,但是单个模块可以构建为多个变体。
将不同变体添加到arch中,这样为arm平台构建时,将构建generic.cpp和arm.cpp。 在为x86平台构建时,将构建generic.cpp和x86.cpp。
cc_library {
...
srcs: ["generic.cpp"],
arch: {
arm: {
srcs: ["arm.cpp"],
},
x86: {
srcs: ["x86.cpp"],
},
},
}
4.6 示例
MaterialMe的Android.bp
android_app {
name: "MaterialMe",
//指定AndroidManifest.xml路径
manifest: "AndroidManifest.xml",
//源码路径,支持通配符
srcs: [
"src/**/*.java",
],
//资源路径
resource_dirs: [
"res",
],
//依赖的静态Jar
static_libs: [
"androidx.appcompat_appcompat",
"com.google.android.material_material",
"androidx.recyclerview_recyclerview",
"androidx.cardview_cardview",
"glide-full-4.6.1",
"android-support-v4",
],
aaptflags: ["--auto-add-overlay"]
system_ext_specific: true,
privileged: true,
platform_apis: true,
certificate: "platform",
}
java_import {
name: "glide-full-4.6.1",
jars: ["libs/glide-full-4.6.1.jar"],
}