Android编译脚本,source、lunch以及make在编译时都做了什么

912 阅读14分钟

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会读取到第一条规则,查看目标是否有依赖,如果有依赖就先生成它的依赖目标,然后在生成自身。当然,如果对应的依赖也有自己的依赖,那么会先去生成依赖的依赖,这就形成了一个树形结构。最后,完成整个项目的编译。

20241015-101112.jpg

在执行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.mkbuild/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进行编译

20241015-142818.jpg

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)

20241015-142900.jpg

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"],
}

20241015-143002.jpg

附件