[Android Basics] - Deeply understand the Android Build system

Overview:

The Android Build system is a framework for compiling the Android system, Android SDK and related documents. In the Android system, the Android source code contains many modules. Different devices of different manufacturers have different customizations to the Android system. How to manage these modules in a unified manner, how to compile on different operating systems, how to support different hardware devices and different compilation types when compiling, and also provide customized extensions for each package, Android system How to solve these problems? This is the Android Build system we have to talk about.

Android source code directory structure:


Linux system make command

Before explaining the Android build system, we first need to understand the make command of the Linux system. In the Linux system, we can compile the code through the make command. When the Make command is executed, it will find a Makefile in the current directory by default, and then compile the code according to the instructions in the Makefile. Such as gcc, the shell commands cp, rm and so on in Linux systems.

Seeing this, some friends may say, what is the difference between the shell and make commands in a Linux system?

The make command actually accomplishes the task through the shell command, but its magic is that it can help us deal with the dependencies between files. For example, there is a file T, which depends on another file D, and it is required to regenerate the file T only when the content of the file D changes.

How does the Make command know that there is a dependency between two files, and how to deal with the target file when the dependent file changes? The answer is the Makefile mentioned earlier. The Makefile file is actually a script file, just like a normal shell script file, except that it follows the Makefile syntax. The most basic function of the Makefile file is to describe the dependencies between files and how to deal with these dependencies.

Android Build system core

The Android Build system is a part of the Android system and is mainly used to compile the Android system, Android SDK and related documents. The system is mainly composed of Make files, Shell scripts and Python scripts.

Android build category:

  •  Files in the build/core directory, which are the core of the system framework of Android Build;
  • The files in the device directory store specific product configuration files;
  • The compiled file of each module: Android.mk, located in the source file directory of the module.

Android Build system core

The core of the Android Build system is in the directory build/core. There are mk files, shell scripts, and per scripts in this directory, which form the foundation and architecture of the Android Build system.

In the core build/core, the system mainly does three things:

Commonly used commands:

source build / envsetup.sh

lunch

make

envsetup.sh

To compile the Android system, you must first execute the envsetup.sh script, which will establish the Android compiling environment. The specific implementation is to create a shell command and call the add_lunch_combo command. This command stores the passed parameters of the call to a global array variable LUNCH_MENU_CHOICES.

Common shell commands defined in the envsetup.sh script:

command Description
contact-button Specify the currently compiled product
croot Quickly switch to the root directory of the source code for easy compilation
m Compile the entire source code, but do not need to switch the current directory to the root directory of the source code
mm Compile all modules in the current directory, but do not compile their dependencies
mmm Compile all modules in the specified directory, but do not compile their dependencies
cgrep Execute grep command on all C/C++ files in the system
screech Execute grep command on all source files in the system

 

Compile Android system

The compilation environment of the Android system currently only supports two operating systems, Ubuntu and Mac OS. Before compiling the Android system, we need to obtain the complete Android source code. After opening the console, go to the root directory of the Android source code, and then execute the following command:

source build/envsetup.sh

lunch full-eng

make -j8

Regarding the meaning of these commands, we mentioned above.

The first step command "source build/envsetup.sh" introduces the build/envsetup.sh script, which is used to initialize the compilation environment and introduce some auxiliary Shell functions;

The second step command "lunch full-eng" is to call the lunch function and specify the parameter as "full-eng". The parameters of the lunch function are used to specify the target device for this compilation and the compilation type.

The third step is to command "make -j8" to actually start compiling. The parameter "-j" of make specifies the number of jobs to be compiled at the same time. This is an integer. The value is usually 1 or 2 times the total number of concurrent threads supported by the compilation host CPU. The complete compilation time depends on the configuration of the compilation host.

 

Build result

The most important of the build products are three image files, all of which are located in the /out/target/product// directory:

  • system.img: Contains the system files, libraries, executable files and preset applications of the Android OS, which will be mounted as the root partition.
  • ramdisk.img: It will be mounted as a read-only partition by the Linux kernel at startup, and it contains the /init file and some configuration files. It is used to mount other system images and start the init process.
  • userdata.img: Will be mounted as /data, which contains application-related data and user-related data.

Make file

The entry file of the entire Build system is a file named "Makefile" in the root directory of the source code tree. When the make command is invoked on the root directory of the source code, the make command will first read the file.

The content of the Makefile file has only one line: "include build/core/main.mk". The effect of this line of code is obvious: it contains the build/core/main.mk file. The main.mk file will contain other files, and other files will contain more files, thus introducing the entire Build system.

In the entire Build system, the relationship between Make files is quite complicated. Look at the main relationship diagram of a make file:

 

Make common files:

file name Description
main.mk

The main make file, in which the compilation environment is checked first, and other Make files are introduced at the same time. In addition, several major Make targets are defined in this file, such as droid, sdk, etc. (see "Make Target Description" below).

help.mk

Contains the definition of a Make target named help, which will list the main Make targets and their descriptions.

envsetup.mk Configure the environment variables required by the Build system, such as: TARGET_PRODUCT, TARGET_BUILD_VARIANT, HOST_OS, HOST_ARCH, etc. The currently compiled host platform information (such as operating system, CPU type, etc.) is determined in this file. In addition, the file also specifies the output path of various compilation results.
pathmap.mk Define the path of many header files as a mapping table by name and value, and provide include-path-for function to get
combo/select.mk

Select the platform-related Make file according to the platform of the current compiler.

dumpvar.mk

Before the build starts, the configuration information of this build is displayed.

config.mk The configuration file of the entire Build system, one of the most important Make files. The file mainly contains the following content: A lot of constants are defined to be responsible for the compilation of different types of modules. Define compiler parameters and common file suffixes, such as .zip, .jar, .apk. Configure product-related parameters according to the BoardConfig.mk file. Set the path of some common tools, such as flex, e2fsck, dx.
definitions.mk One of the most important Make files, in which a large number of functions are defined. These functions are used by other files of the Build system. For example: my-dri, all-subdir-makefiles, find-subdir-files, sign-package, etc. For the description of these functions, please refer to the code comments of each function.
distdir.mk Definition for the dist target. The dist target is used to copy files to the specified path.
dex_preopt.mk

Pre-optimized for starting jar package.

pdk_config.mk As the name suggests, it is a configuration file for pdk (Platform Developement Kit).
post_clean.mk Check the current Build configuration based on the previous Build, and perform necessary cleanup work.
legacy_prebuilts.mk Only the GRANDFATHERED_ALL_PREBUILT variable is defined in this file.
Makefile Contained by main.mk, the content of this file is some extra content that assists main.mk.

The Android source code contains many modules, and there are many types of modules, such as: Java libraries, C/C++ libraries, APK applications, executable files, etc. In addition, Java or C/C++ libraries can be classified as static or dynamic. The library or executable file may be device-specific ("device" in this article refers to the device where the Android system will be installed, such as a mobile phone with a certain signal. Or tablet) may also be aimed at the host (the "host" in this article refers to the development of the Android system machine, such as a PC with Ubuntu operating system or an iMac or Macbook with MacOS). The compilation steps and methods of different types of modules are different. In order to be able to perform the compilation of various types of modules in a consistent and convenient manner, many constants are defined in config.mk, each of which describes a type of module. The compilation method. Common ones are:

  • BUILD_HOST_STATIC_LIBRARY
  • BUILD_HOST_SHARED_LIBRARY
  • BUILD_STATIC_LIBRARY
  • BUILD_SHARED_LIBRARY
  • BUILD_EXECUTABLE
  • BUILD_BUILD_HOST_EXECUTABLE
  • BUILD_PACKAGE
  • BUILD_PREBUILT
  • BUILD_MULTI_PREBUILT
  • BUILD_HOST_PREBUILT
  • BUILD_JAVA_LIBRARY
  • BUILD_STATIC_JAVA_LIBRARY
  • BUILD_HOST_JAVA_LIBRARY

不同类型的模块的编译过程会有一些相同的步骤,例如:编译一个 Java 库和编译一个 APK 文件都需要定义如何编译 Java 文件。为了减少代码冗余,需要将共同的代码复用起来,复用的方式是将共同代码放到专门的文件中,然后在其他文件中包含这些文件的方式来实现的。模块的编译方式定义文件包含关系:

Make 编译镜像

make/make droid

如果在源码树的根目录直接调用 “make” 命令而不指定任何目标,则会选择默认目标:“driod”(在 main.mk 中定义)。因此,这和执行 “make droid” 效果是一样的。droid 目标将编译出整个系统的镜像。从源代码到编译出系统镜像,整个编译过程非常复杂。这个过程并不是在 droid 一个目标中定义的,而是 droid 目标会依赖许多其他的目标,这些目标的互相配合导致了整个系统的编译。那么需要编译出系统镜像,需要哪些依赖呢?

droid 所依赖的其他 Make 目标说明:

名称 说明
apps_only 该目标将编译出当前配置下不包含 user,userdebug,eng 标签(关于标签,请参见后文 “添加新的模块”)的应用程序。
droidcore 该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理。
dist_files 该目标用来拷贝文件到 /out/dist 目录。
files 该目标仅仅是所依赖的几个目标的组合,其本身不做跟多的处理。
prebuilt 该目标依赖于(ALL_PREBUILT),(ALL_PREBUILT)的作用就是处理所有已编译好的文件。
$(modules_to_install) modules_to_install 变量包好了当前配置下所有会被安装的模块(一个模块是否会被安装依赖于该产品的配置文件,模块的标签等信息),因此该目标将导致所有会被安装的模块的编译。
$(modules_to_check)

该目标用来确保我们定义的构建模块是没用冗余的。

$(INSTALLED_ANDROID_INFO_TXT_TARGET) 该目标会生成一个关于当前 Build 配置的设备信息的文件,该文件的生成路径是:out/target/product/android-info.txt。
systemimage 生成 system.img。

Build 系统中抱哈的其他的一些 Make 目标:

Make 目标说明 说明
make clean 执行清理,等同于:rm -rf out/
make sdk 编译出 Android 的 SDK
Make 目标说明 说明
make clean-sdk 清理 SDK 的编译产物
make update-api 更新 API。在 framework API 改动之后,需要首先执行该命令来更新 API,公开的 API 记录在 framworks/base/api 目录下。
make dist 执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录。
make all 编译所有内容,不管当前产品的定义宏是否会包含。
make help 帮助信息
make snod 从已经编译出的包快速重建系统镜像
make libandroid_runtime 编译所有 JNI framework 内容
make  framework 编译所有 Java  framework 内容
make services 编译系统服务和相关内容
make 编译一个指定的模块,local_target 为模块的名称
make clean- 清理一个指定模块的编译结果
make dump-products 显示所有产品的编译配置信息,例如:产品名,产品支持的地区语言,产品中会包含的模块等信息
make PRODUCT-xxx-yyy 编译某个指定的产品
make bootimage 生成 boot.img

定制 Build 系统中内容

当我们要开发一款新的 Andorid 产品的时候,我们首先就需要在 Build 系统中添加对于该产品的定义。在 Android Build 系统中对产品定义的文件通常位于 device 目录下,device 目录下可以公司名以及产品名分为二级目录,然后加入到系统中,如以前小米等基于 Android 深度定制的系统。通常,对于一个产品的定义通常至少会包括四个文件:

  • AndroidProducts.mk;
  • 产品版本定义文件;
  • BoardConfig.mk;
  • verndorsetup.sh;

AndroidProducts.mk:

该文件只需要定义一个变量,名称为 "PRODUCT_MAKEFILES"。

PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/full_string.mk \
    $(LOCAL_DIR)/stringray_emu.mk \
    $(LOCAL_DIR)/generic_stringray.mk

产品版本定义文件:

该文件中包含了对于特定产品版本的定义。该文件可能不止一个,因为同一个产品可能会有多种版本。通常情况下我们并不需要定义所有这些变量。Build 系统的已经预先定义好了一些组合,它们都位于 /build/target/product 下,每个文件定义了一个组合,我们只要集成这些预置的定义,然后再覆盖自己想要的变量定义即可。

# 继承 full_base.mk 文件中的定义
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
#覆盖其中已经定义的一些变量
PRODUCT_NAME := full_lt26
PRODUCT_DEIVCE := lt26
PRODUCT_BRAND := Android
PRODUCT_MODEL := Full Android on LT26

BoardConfig.mk:

该文件用来配置硬件主板,它其中定义的都是设备底层的硬件特性。例如:该设备的主板相关信息,WiFi 相关信息,还有 BootLoader,内核,radioimage 等信息。

vendorsetup.sh:

该文件中作用是通过 add_lunch_combo 函数在 lunch 函数中添加一个菜单选项。该函数的参数是产品名称加上编译类型,中间以 "-" 连接,例如:

add_lunch_combo full_lt26-userdebug

/build/envsetup.sh 会扫描所有 device 和 vendor 二级目录下的名称为 "vendorsetup.sh" 文件,并根据其中的内容来确定 lunch 函数的菜单选项。在配置了以上的文件之后,便可以编译出我们新添加的设备的系统镜像了。我们可以使用命令:

source build/envsetup.sh

来查看 Build 系统已经引入了刚刚添加的 vendorsetup.sh 文件。

添加新模块在源码树中

一个模块的所有文件通常都位于同一个文件夹中。为了将当前模块添加到整个 Build 系统中,每个模块都需要一个专门的 Make 文件,该文件的名称为 "Android.mk"。Build 系统会扫描名称为 "Android.mk" 的文件,并根据该文件中内容编译出相应的产物。

注:在 Android Build 系统中,编译是以模块(而不是文件)作为单位的,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。

对于已经编译好的库使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:当编译某个 Java 库需要依赖一些 Jar 包时,并不能直接指定 Jar 包的路径作为依赖,而必须先将这些 Jar 包定义为一个模块,然后在编译 Java 库的时候通过模块的名称来依赖这些 Jar 包。那么怎么编写 Android.mk 文件呢?Android.mk 文件通常以以下两行代码作为开头:

LOCAL_PATH := $(call my-dir) //设置单枪模块的编译路径为当前文件夹路径
    include $(CLEAR_VARS) //清理编译环境中用到的变量

为了方便模块的编译,Build 系统设置了很多的编译环境变量。要编译一个模块,只要在编译之前根据需要设置这些变量然后执行编译即可。常见的如:

  • LOCAL_SRC_FILES:当前模块包含的所有源代码文件。
  • LOCAL_MODULE:当前模块的名称,这个名称应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。
  • LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。
  • LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库的名称。
  • LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库的名称。
  • LOCAL_CFLAGS:提供给 C/C++ 编译器的额外的编译参数。
  • LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。
  • LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。
  • LOCAL_PACKAGE_NAME:当前 APK 应用的名称。
  • LOCAL_CERTIFICATE:签署当前应用的证书名称。
  • LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。

标签的值可能是 debug,eng,user,development 或者 optional。其中,optional 是默认标签。标签是提供给编译类型使用的,不同的编译类型会安装包含不同标签的模块。编译类型说明:

名称 说明
eng 默认类型,该编译类型适用于开发阶段。当选择这种类型时,编译结果将:安装包含 eng,debug,user,development 标签的模块;安装所有没有标签的非 APK 模块;安装所有产品定义文件中指定的 APK 模块。
user

该编译类型适合用于最终发布阶段。当选择这种类型时,编译结果将:安装所有带有 user 标签的模块;安装所有没有标签的非 APK 模块;安装所有产品定义文件中指定的 APK 模块,APK 模块的标签将被忽略。

userdebug 该编译类型适合用于 debug 阶段。该类型和 user 一样,除了:会安装包含 debug 标签的模块,编译出的系统具有 root 访问权限。

根据上表各种类型模块的编译方式,要执行编译,只需要引入表3 中对应的 Make 文件即可。例如,要编译一个 APK 文件,只需要在 Android.mk 文件中,加入 “include $(BUILD_PACKAGE)”。除此以外,Build 系统中还定义了一些便捷的函数以便在 Android.mk 中使用,包括:

  • $(call my-dir):获取当前文件夹路径。
  • $(call all-java-files-under):获取指定目录下的所有 Java 语言文件。
  • $(call all-c-files-under):获取指定目录下的所有 C 语言文件。
  • $(call all-laidl-files-under):获取指定目录下的所有 AIDL 文件。
  • $(call all-makefile-under):获取指定目录下的所有 Make 文件。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#获取所有子目录中的 Java 文件
LOCAL_SRC_FILES := $(call all-subdir-java-files)
#当前模块依赖的静态 Java 库,如果有多个以空格分隔
LOCAL_STATIC_JAVA_LIBRARIES := static-library
#当前模块的名称
LOCAL_PACKAGE_NAME := LocalPackage
#编译 APK 文件
include $(BUILD_PACKAGE)

编译一个 Java 的静态库:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

#获取所有子目录中的 Java 文件
LOCAL_SRC_FILES := $(call all-subdir-java-files)

#当前模块依赖的动态 Java 库名称
LOCAL_JAVA_LIBRARIES := android.test.runner

#当前模块的名称
LOCAL_MODULE := sample

#将当前模块编译成一个静态的 Java 库
include $(BUILD_STATIC_JAVA_LIBRARY)

 

Guess you like

Origin blog.csdn.net/u014674293/article/details/105158562