Android源码解析之——Build系统

一、概述

一般Android App Developer,是无需关心Build系统是如何运作的,因为Android StudioGradle工具已经帮我们简化了这些操作。但如果你想成为一个Android Framework Engineer,就必须对此有所了解,因为无论是你想在Aosp源码中添加自己的设备,还是想新增系统应用,这些知识都是必要的。

可是对于新手,往往对不知道Build系统如何入门,遇到问题也只能Google和Baidu,但能否解决问题,这通常跟系统版本密切相关。所以:当遇到编译的问题时,阅读Build系统源码才是王道。

Android的Build系统是基于GNU Make和Shell构建的一套编译环境。Android的Build系统可分为3大模块:

  • 1、位于build/core目录下的文件,这是Android Build系统的框架和核心。
  • 2、位于device目录下的文件,存放的是具体产品的配置文件。
  • 3、模块的编译文件:Android.mk,位于模块的源文件目录下。

我们在上一章已经知道编译系统镜像的步骤:

  • 1、建立Android编译环境 $ source ./build/envsetup.sh

  • 2、根据用户输入或选择的产品名来设置与具体产品相关的环境变量 $ lunch <product_name>-<build_variant> 或者 $ lunch <序号> 或者 $ lunch #打印出产品菜单项 $ 输入序号

  • 3、开始编译 $ make update-api -j4 $ make -j4

我们可以通过上述步骤,去逐步分析Build系统是如何去编译这个系统源码的。

这里,我们采用android-5.0.2_r1的分支源码,其余分支源码也类似。

二、系统镜像编译流程分析

首先,我们执行./build/envsetup.sh脚本文件,该脚本中定义了一些有用的shell命令,可通过hmm命令进行查看:

sh 复制代码
$ source ./build/envsetup.sh
$ hmm

Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:   lunch <product_name>-<build_variant>
- tapas:   tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
- croot:   Changes directory to the top of the tree.
- m:       Makes from the top of the tree.
- mm:      Builds all of the modules in the current directory, but not their dependencies.
- mmm:     Builds all of the modules in the supplied directories, but not their dependencies.
           To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:     Builds all of the modules in the current directory, and their dependencies.
- mmma:    Builds all of the modules in the supplied directories, and their dependencies.
- cgrep:   Greps on all local C/C++ files.
- ggrep:   Greps on all local Gradle files.
- jgrep:   Greps on all local Java files.
- resgrep: Greps on all local res/*.xml files.
- sgrep:   Greps on all local source files.
- godir:   Go to the directory containing a file.

Look at the source to view more functions. The complete list is:
addcompletions add_lunch_combo cgrep check_product check_variant choosecombo chooseproduct  choosetype choosevariant cproj croot findmakefile gdbclient gdbwrapper get_abs_build_var getbugreports get_build_var getdriver getlastscreenshot get_make_command getprebuilt getscreenshotpath getsdcardpath get_symbols_directory gettargetarch gettop ggrep godir hmm is isviewserverstarted jgrep key_back key_home key_menu lunch _lunch m make mangrep mgrep mm mma mmm mmma pez pid printconfig print_lunch_menu qpid resgrep runhat runtest sepgrep set_java_home setpaths set_sequence_number set_stuff_for_environment settitle sgrep smoketest stacks startviewserver stopviewserver systemstack tapas tracedmdump treegrep

其中常用的命令有lunchmmmmammmmmmacroot,其中lunch命令是我们接下来分析的重点,而后5个命令在hmm中已给出了简单清晰的解释,故不赘述。

当执行source ./build/envsetup.sh时,会自动执行以下代码:

sh 复制代码
......省略部分代码......
# add the default one here
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng

......省略部分代码......
# Execute the contents of any vendorsetup.sh files we can find.
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
    echo "including $f"
    . $f
done
unset f

可以清楚发现,其主要功能包括:

  • 1、利用add_lunch_combo函数添加aosp设备默认编译选项。
  • 2、去device、vendor、product三个目录下搜寻vendorsetup.sh文件,并include进来。

vendorsetup.sh文件通常内容也只是add_lunch_combo xxxxx,用来添加自定义设备编译选项。

那我们接下来分析add_lunch_combo命令:

sh 复制代码
function add_lunch_combo()
{
    local new_combo=$1
    local c
    for c in ${LUNCH_MENU_CHOICES[@]} ; do
        if [ "$new_combo" = "$c" ] ; then
            return
        fi
    done
    LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}

add_lunch_combo命令会将所传递的参数存放到一个全局的数组变量LUNCH_MENU_CHOICES中。执行lunch命令时,打印的菜单项正是该数组的内容。

当所有设备编译选项都被add_lunch_combo添加后,就可以执行lunch命令对编译环境进行正式的配置。

sh 复制代码
function lunch()
{
    local answer

    // 如果lunch命令参数非空,则给变量answer赋值,否则打印LUNCH_MENU_CHOICES数组
    if [ "$1" ] ; then
        answer=$1
    else
        print_lunch_menu
        echo -n "Which would you like? [aosp_arm-eng] "
        read answer
    fi

    local selection=

    // 用户依旧不选择lunch项,则将selection设置为aosp_arm-eng
    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        // 如果变量answer是数字,则将selection设置为LUNCH_MENU_CHOICES数组中对应项
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    // 如果变量answer非数字,判断其格式是否符合规范
    elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
    then
        selection=$answer
    fi

    if [ -z "$selection" ]
    then
        echo
        echo "Invalid lunch combo: $answer"
        return 1
    fi

    export TARGET_BUILD_APPS=

    // 把变量selection中的字符串用"-"分成两部分,前部分赋值给变量product,并调用函数check_product去检查是否存变量product所对应的产品配置文件
    local product=$(echo -n $selection | sed -e "s/-.*$//")
    check_product $product
    if [ $? -ne 0 ]
    then
        echo
        echo "** Don't have a product spec for: '$product'"
        echo "** Do you have the right repo manifest?"
        product=
    fi

    // 后部分赋值给变量variant,并调用函数check_variant去检查这个值是否是"eng"、"user"、"userdebug"之一
    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
    check_variant $variant
    if [ $? -ne 0 ]
    then
        echo
        echo "** Invalid variant: '$variant'"
        echo "** Must be one of ${VARIANT_CHOICES[@]}"
        variant=
    fi

    if [ -z "$product" -o -z "$variant" ]
    then
        echo
        return 1
    fi

    // 环境变量赋值
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_TYPE=release

    echo

    //设置更多的环境变量
    set_stuff_for_environment
    //打印配置信息
    printconfig
}

其中整个lunch命令核心代码如下,用于配置所需编译的设备镜像。

sh 复制代码
export TARGET_PRODUCT=$product
export TARGET_BUILD_VARIANT=$variant
export TARGET_BUILD_TYPE=release

lunch执行完后,系统会打印出当前配置所生成的环境变量。

ini 复制代码
pujh@dell:~/Tiny4412/android-5.0.2$ lunch full_tiny4412-eng

============================================
PLATFORM_VERSION_CODENAME=REL                          \\平台版本名称
PLATFORM_VERSION=5.0.2                                 \\Android平台版本号
TARGET_PRODUCT=full_tiny4412                           \\所编译的产品名称
TARGET_BUILD_VARIANT=eng                               \\编译的产品类型
TARGET_BUILD_TYPE=release                              \\编译的类型
TARGET_BUILD_APPS=                                     \\当编译整个系统时,变量为null;当编译单个模块时,变量为模块的路径
TARGET_ARCH=arm                                        \\编译目标的CPU架构
TARGET_ARCH_VARIANT=armv7-a-neon                       \\编译目标的CPU架构版本
TARGET_CPU_VARIANT=cortex-a9                           \\编译目标的CPU代号
TARGET_2ND_ARCH=                                       \\编译目标的第二CPU架构
TARGET_2ND_ARCH_VARIANT=                               \\编译目标的第二CPU架构版本
TARGET_2ND_CPU_VARIANT=                                \\编译目标的第二CPU代号
HOST_ARCH=x86_64                                       \\编译平台的CPU架构
HOST_OS=linux                                          \\编译平台的操作系统
HOST_OS_EXTRA=Linux-4.4.0-21-generic-x86_64-with-Ubuntu-16.04-xenial          \\编译平台操作系统的一些额外信息
HOST_BUILD_TYPE=release
BUILD_ID=LRX22G                                        \\该值会出现在编译的版本信息中,可以利用整个环境变量来定义公司特有的标识
OUT_DIR=out                                            \\编译结果输出目录
============================================

三、make分析

Makefile文件主要由3种内容构成:变量定义、函数定义和目标依赖规则,其详细使用教程参考跟我一起写Makefile

当用户执行make命令时,会调用当前目录下Makefile文件,Android源码根目录Makefile文件内容只有一行,用于指向build/core/main.mk文件:

makefile 复制代码
include build/core/main.mk

main.mk是Android Build系统的主控文件。从main.mk开始,将通过include命令将其余所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个集合相当于一个巨大的Makefile文件。其大致包含关系如下:

其中config.mk会调用Android.mk中常用的mk定义,例如:

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_RAW_STATIC_LIBRARY := $(BUILD_SYSTEM)/raw_static_library.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_RAW_EXECUTABLE:= $(BUILD_SYSTEM)/raw_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_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_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_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

以及C/C++代码编译时的参数以及系统常用包的后缀名。

加快编译速度CCache

预编译模块的目标定义:

brk:调整堆的高地址边界 mmap:在堆区和栈区之间寻找一块合适的内存空间 munmap mremap

内存分配器------dlmalloc dlmalloc 2.8.6 源码详解

相关推荐
NRatel10 分钟前
Unity 游戏提升 Android TargetVersion 相关记录
android·游戏·unity·提升版本
叽哥3 小时前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走3 小时前
创建自定义语音录制View
android·前端
用户2018792831673 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831673 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
Kapaseker5 小时前
你一定会喜欢的 Compose 形变动画
android
QuZhengRong5 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil6 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌13 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
书弋江山14 小时前
flutter 跨平台编码库 protobuf 工具使用
android·flutter