您的位置:首页 > 产品设计 > UI/UE

Android Makefile and build system 分析与梳理

2012-11-09 12:36 363 查看
Android Makefile无疑是打开系统架构的一扇窗户。但因Android的Makefile (build system)文件众多,架构复杂,分析起来较为困难。本文梳理了build system的大致脉络,希望对build system感兴趣的同学们有所帮助。

1. Android Makefile & build system 概述

Makefile文件用来告诉make命令需要怎么样的去编译和链接程序。在编译时,需要根据编译环境和编译目标选择编译工具,编译参数,以及选择编译安装哪些模块。同时Makefile 指定了构建目标所需的依赖性以及生成规则。 在Android中,主要的Makefile文件存在于build/core/目录下,它的表现形式为多个后缀为mk的文件组成,也称为build
system。Android build system 主要有两大部分构成:配置部分,目标构建部分。Build system的主流程文件为build/core/main.mk文件。

Android build system在设计中考虑了如下方面,具有良好的扩展性。

a) 增添子模块编译 b) 多CPU架构 – ARM/PPC(maybe)/X86(maybe) c) 多语言编译 – C/C++/Java

d) 多目标 – static lib/share lib/execute/Java/Java library e) 多发布版本

2. Build system 配置部分及特点

a) 基于Android 产品的配置(product config):选择构建安装的运行程序 (user package)

b) 设置target 等相关变量TARGET_ARCH, TARGET_OS, TARGET_BUILD_TYPE, TARGET_PREBUILT_TAG

c) 根据编译环境设置host等相关变量 HOST_OS, HOST_ARCH, HOST_BUILD_TYPE, HOST_PREBUILT_TAG

d) 编译target上运行程序所需的工具链及编译参数设置,如linux-arm-cc,cflag,include目录等。

e) 编译host上运行程序所需的工具链及编译参数设置。

-----多发布版本的支持

Android 会被不同的厂商所采用,他们内置的packages(应用程序)相应也会有差别。AndroidProducts.mk文件即为Android build system提供给厂商的接口文件。通过此文件即可定义所需编译和安装的packages(也即应用程序)。缺省选项是generic。为了更容易的扩展,Android定义了基本package(core.mk)和通用package(generic.mk),通用package包含基本package。同时Android还实现了一个继承函数(inherit-product)。通过继承通用package,可以很容易的配置所需编译和安装的package。

------多CPU架构的扩展

Config.mk文件中会设置combo_target为不同的变量,然后include select.mk文件。在select.mk文件中,会根据OS和CPU架构选择相应的mk文件,在这些相应的mk文件中,又定义了编译目标程序所需的工具链及编译参数。目前从combo目录下看,target上不支持PPC架构。但如果要想支持PPC架构的话,只需在combo目录下创建PPC的mk文件,在其中定义工具链和参数即可。

3. build system 目标构建部分

目标构建部分的主要工作就是选择所需构建的目标,确定它们所需依赖的目标,然后根据规则来构建最终的目标。说起来简单,可是在实际中就需要考虑很多问题了。

a) 作为一个设计优秀的Framework,如何方便的添加子模块?

b) 在Android系统中,有多种编程语言的存在,它们的编译工具各不相同。即使同一种语言,如C语言,也存在host 和target的差别,编译器也不同。如何选择不同的工具进行模块的构建?

c) 在Android系统中,存在不同种类的构建目标:有可执行文件,static library,dynamic library,java library,java.如何构建这些不同的目标?

在main.mk中,非常关键的一个步骤就是找到TOP目录下所有Android.mk文件,并include 它们。在Include的过程中,就会确定子模块的构建目标,类型,和规则。

Android.mk就是build system提供给子模块的借口文件。Android.mk有下面几个关键词:

LOCAL_SRC_FILES – 指定模块的源文件

LOCAL_MODULE – 指定所需构建的目标名

include 构建类型对应的文件 – 例如想构建在target上运行的可执行文件,那就执行语句include $(BUILD_EXECUTABLE)。通过此语句可有如下结果:

a) 指定构建目标的类型

b)确定构建此类型所需的工具及参数。

通过定义自己的Android.mk文件,再修改上述等变量,即可轻松的把自身模块放入至build system中。

Main.mk 文件471行: include $(subdir_makefiles) subdir_makefiles为TOP目录下所有Android.mk文件的集合。语句虽短,可确是整个build system中最为重要的一条语句。无论有多少子模块,无论构建模块的目标类型,无论它是什么语言所写,就这一条语句,完成了这些纷繁复杂的工作。

是否似曾相识?Android.mk 就类似于build system提供的基类,LOCAL_MODULE,LOCAL_SRC_FILES,include构建类型文件等类似于基类提供的虚函数。通过继承基类(Android.mk),重写虚函数(重新定义LOCAL_等变量),遍历子类集合调用虚函数(include $(subdir_makefiles)),完美的解决了本节开头的问题。为Android build system提供了良好的可扩展性。

Android Building System 分析

Android building system 包括几种重要的设定档

* Android.mk * AndroidProducts.mk * target_<os>-<arch>.mk, host_<os>-<arch>.mk and <os>-<arch>.mk * BoardConfig.mk * buildspec.mk

Android.mk 是 module 和 package 的设定档,每个 module/package 的目录下都会有一个 Android.mk。所谓的 module 是指系统的 native code ,相对于用 Java 写成的 Android application 称为 package。

AndroidProducts.mk 则设定 product 配置。 product 即特定系统版本,透过编译不同 product ,产生不同软件配置内容,安装不同的 application。 Product 可视为特定项目,产生特定规格系统

BoardConfig.mk 是为 product 主板做设定,像是 driver 选择、设定。*<os>-<arch>.mk 则是针对选择的操作系统和 CPU 架构,进行相关设定

buildspec.mk 是位于 source 根目录下,为进行编译者所做之额外设定。例如,可在此选择要产生的 product 、平台、额外的 module/package 等

参数

build/envsetup.sh 实作一个 mm 指令,以编译单一 module,不需编译整个 source tree。ONE_SHOT_MAKEFILE 这个 makefile 变量/参数就是用以实作这个功能。使用方法是在执行 make 时,将该变量指定为 module 的 Android.mk

* make ONE_SHOT_MAKEFILE=<path to Androiod.mk>

透过定义 CREATE_MODULE_INFO_FILE , building system 会将所有 module 信息列在 $(PRODUCT_OUT)/module-info.txt 档案里。

* make CREATE_MODULE_INFO_FILE=true

设定 BUILD_TINY_ANDROID=true , building system 产生一个简单的 image ,以测试硬件的可用度。此功能用于移植的早期阶段,以快速 bring up 。

HOST_BUILD_TYPE 和 TARGET_BUILD_TYPE 指定 building system 产生 binary 的目的为 debug 或 release 。透过设定此二变量,能产生包含 debug information 的 binry 。* debug * release

这些参数,也可设于 buildspec.mk 里,以避免开发过程不断的重新指定。

Goals

一般编辑整个 Android 系统,就是使用 droid 这个 goal。 droid 会产生一个完整的系统,包括 bootloader、kernel、系统程序、模块和应用程序

showcommands 和 droid 功能相同,但 droid 在编译过程不显示所使用的指令。透过 showcommands 这个 goal, building system 显示过程中每一个步骤的详细指令。

Makefile 的流程

* 初始化相关变数 * 侦测编译环境和目标环境 * 决定目标 product * 读取 product 的设定 * 读取 product 所指定之目标平台架构设定 o 选择 toolchain o 指定编译参数 (*<os>-<arch>.mk) * 清除输出目录 * 设定/检查版本编号 * 读取所有 BoardConfig.mk 档案 * 读取所有 module
的设定 * 根据设定,产生必需的 rule * 产生 image

以上的主要流程都是由 build/core/main.mk 所安排。

初始化和侦测

由 build/core/config.mk 所进行。 build/core/envsetup.mk 检查 developer 的设定 (buildspec.mk) ,并检查执行环境,以决定输出目录、项目。

build/core/config.mk 本身还依据参数,决定解译时的相关参数。像是 compiler 的路径、flags, lex 、yacc 的路径参数等。

关于 product 的相关设定,则是由 build/core/product_config.mk 所处理,使用 build/core/product.mk 提供之 macro 加载。根据 AndroidProduct.mk 的内容, product_config.mk 决定了

* PRODUCT_TAGS * OTA_PUBLIC_KEYS * PRODUCT_POLICY

Product 设定的读取

Android product 的设定来自于 build/target/product/AndroidProduct.mk 和 vendor 子目录下的 AndroidProduct.mk 。 building system 透过 find 指令,找出所有可能的 AndroidProduct.mk。 AndroidProduct.mk 里定义 PRODUCT_MAKEFILES 变量,列举所有实际定义 product 的 makefile。这些
makefile 各自定义独立的 product 。product 相关参数,存成 PRODUCTS.<path of makefile>.<variable> 形式的变数。并将 makefile 路径存在 PRODUCTS 变量。因此通过 PRODUCTS 能取得所有的 product 路径/名称,并通过 PRODUCTS.<path of makefile>.<variable> 形式的变量取得内容

Module 设定的读取

Module 是指 native code 的软件组件,而 Java application 则被称为 package。 build/core/definitions.mk 定义 module/package 相关 macro ,读取、检查 module/package 定义档;分散 source tree 各处的 Android.mk 档案。 build/core/main.mk 使用 find 指令,在这些子目录下找出所有 Android.mk
,并将路径存在 subdir_makefiles 变量里。最后,include 这些档案

这些 Android.mk 会 include 定义成变量 BUILD_SHARED_LIBRARY 、BUILD_PACKAGE 等,和其目的相配的 makefile。这些 makefile 会变 Android.mk 定义之内容,存成 ALL_MODULES.<path of Android.mk>.<variable> 形式。例如, Android.mk 定义了 LOCAL_MODULE_SUFFIX ,变会存成 ALL_MODULES.<path
of Android.mk>.LOCAL_MODULE_SUFFIX 。而 Android.mk 路径,当样会存于 ALL_MODULES 变量里

Search Android.mk 的路径,基本上会是整个 source tree 。但会依特定的 goal ,选择性只找寻特定目录。例如 SDK 只需特定目录下的 Android.mk 。

Board Level 设定

和目标平台主板相关之设定,例如使用了什么装置、driver 等,或是是否需要编译 bootloader 、 kernel 等,都是在 BoardConfig.mk 里设定。同样,每张主板可以有不同设定,存在不同目录下的 BoardConfig.mk ,以 find 寻找如下档案

* build/target/board/$(TARGET_DEVICE)/BoardConfig.mk * vendor/*/$(TARGET_DEVICE)/BoardConfig.mk

TARGET_DEVICE 是 product 所定义,因此同一个 BoardConfig.mk 可被多个 product 所使用。一个 TARGET_DEVICE ,通常只有一个 BoardConfig.mk 。 BoardConfig.mk 会被直接 include 到 building system 的 name space 里。因此,一些 module 的 enable/disable ,可以在 BoardConfig.mk 以对映不同的主板。

Rules

在 module 的定义档 Android.mk 里,可定义 module 的 tag, LOCAL_MODULE_TAGS,以分类这些 module。每一个 product 可以指定需要的 tag (PRODUCT_TAGS),使 building system 只编译标示这些 tag 的 module。在 build/core/main.mk 里,所有标示特定 tag 的 module 收集为 ALL_DEFAULT_INSTALLED_MODULES
,并 include build/core/Makefile 处理。

build/core/Makefile 为这些module产生rule ,并使产生 image 的goal depend on 这些 rule ,使这些 module 被编译。

Build环境初探

这里略过对android在手机上的文件系统框架的阐述(google或者baidu都能帮助你找到对应的信息),主要看google是如何把生成合适的rootfs的工作整合到它的build体系当中,同时,会顺带看一下CyanogenMod中对应各种机型的build机制。

首先,来看一下Android的build系统中,使用到的编译选项和相关工具

具体的目录在:mydroid/build/tools/下

|-- acp 这是一个稍微改良的cp命令,用来应付在windows/MAC/Linux下的cp命令的缺陷,其中的README很值得一看!

|-- adbs 这是一个用来查看crash问题的工具,详细请看《Android调试工具之adbs》

|-- Android.mk |-- apicheck 用来进行发布前的API检查(参见mydroid/build/core/tasks/apicheck.mk),是否新编译的系统中有破坏API兼容性或是非法的API

这里的代码是用Java写的用来检查编译时生成的API相关信息的xml文件(mydroid/framework/base/api/中),可以参考里面对于xml文件解析的代码

|-- apriori 实现prelink的工具,简单介绍参见(mydroid/bionic/linker/README.TXT)

|-- atree 为android SDK服务的一个工具,用来按照指定xxx.atree文件中的内容进行一些文件操作

|-- bin2asm 不太明白具体的用处,应该是用来应付mac上编译android一些与gcc相关的问题

|-- buildinfo.sh 生成target中的各种xxx.prop文件,如system.prop, build.prop等

|-- check_builds.sh 包装了diff,用来看2个发布版本之间变化

|-- check_prereq device上进行ota升级时的工具之一

|-- compare_fileslist.py 与check_builds.sh配合完成版本比较的脚本

|-- droiddoc Android更具javadoc的一些移植

|-- dump-package-stats 简单的查看一个jar/apk文件内的dex和其它文件的大小信息

|-- event_log_tags.py 处理event-log-tags的内容,关于event-log-tags文件的意义参见《Android学习之event-log-tags是神马》

|-- fileslist.py 简化的列出指定目录下所有文件及大小的脚本 -- 可以放入自己的工具库了使用:)

|-- findleaves.py 在指定目录中(可多个)找指定文件的脚本 -- 可以放入自己的工具库了使用:)

|-- fixlinebreaks.sh 把windows中的换行改为linux下的 -- 可以放入自己的工具库了使用:)

|-- fs_config 列出指定文件夹及文件的权限

|-- fs_get_stats 得到指定文件夹下文件的简单stats信息

|-- iself 判断文件是否是ELF格式

|-- isprelinked 判断文件是否是prelink过的

|-- java-event-log-tags.py 处理event-log-tags的内容,关于event-log-tags文件的意义参见《Android学习之event-log-tags是神马》

|-- kcm key character map的工具, 相关资料参照:http://www.kandroid.org/online-pdk/guide/keymaps_keyboard_input.html#androidKeymapKeyCharMap

|-- merge-event-log-tags.py 处理event-log-tags的内容,关于event-log-tags文件的意义参见《Android学习之event-log-tags是神马》

|-- mktarball.sh 与fs_get_stats配合而执行的打包工具

|-- print_module_licenses.sh 显示当前目录下所有module信息

|-- releasetools -- check_target_files_signatures |-- common.py |-- edify_generator.py |-- img_from_target_files |-- ota_from_target_files `-- sign_target_files_apks

|-- rgb2565 rgb转换工具 |-- signapk 命令行下对jar包签名的工具

|-- soslim Android定制的编译工具之一,简单介绍参见(mydroid/bionic/linker/README.TXT)

|-- warn.py 解析Android系统编译log的工具 `-- zipalign zipfile的对齐工具,参见该文件夹下的README.TXT

这里,目录在mydroid/build/core/tasks/有一些特别的task

|-- apicheck.mk, 判断api是否符合AOSP的规范

|-- cts.mk cts测试, 可以在代码根目录, make cts, 编译结束之后,进入out/host/linux-x86/bin/下,执行cts命令

|-- ide.mk IDE开发环境 |-- product-graph.mk `-- sdk-addon.mk

NDK的build环境没有包含在标注难得AOSP的/build/目录下,而是在mydroid/ndk/build下

最佳实践

1 如何显示每次编译所包含的所有xxx.mk文件

找到build/core/main.mk

  把include $(subdir_makefiles)替换为

  [plain] view plaincopy $(foreach subdir_makefile, $(subdir_makefiles), \

  $(info Including $(subdir_makefile)) \

  $(eval include $(subdir_makefile)) \

  )

  subdir_makefile :=

  如果遇见API相关的PACKAGING/checkapi-current-timestamp] Error 38

  需要执行:make update-api

2 如何在AOSP代码目录之外编译

  [plain] view plaincopy # Paths and settings

  TARGET_PRODUCT = generic

  ANDROID_ROOT = /home/karim/android/aosp-2.3.x

  BIONIC_LIBC = $(ANDROID_ROOT)/bionic/libc

  PRODUCT_OUT = $(ANDROID_ROOT)/out/target/product/$(TARGET_PRODUCT)

  CROSS_COMPILE = \

  $(ANDROID_ROOT)/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-

  # Tool names

  AS = $(CROSS_COMPILE)as

  AR = $(CROSS_COMPILE)ar

  CC = $(CROSS_COMPILE)gcc

  CPP = $(CC) -E

  LD = $(CROSS_COMPILE)ld

  NM = $(CROSS_COMPILE)nm

  OBJCOPY = $(CROSS_COMPILE)objcopy

  OBJDUMP = $(CROSS_COMPILE)objdump

  RANLIB = $(CROSS_COMPILE)ranlib

  READELF = $(CROSS_COMPILE)readelf

  SIZE = $(CROSS_COMPILE)size

  STRINGS = $(CROSS_COMPILE)strings

  STRIP = $(CROSS_COMPILE)strip

  export AS AR CC CPP LD NM OBJCOPY OBJDUMP RANLIB READELF \

  SIZE STRINGS STRIP

  # Build settings

  CFLAGS = -O2 -Wall -fno-short-enums

  HEADER_OPS = -I$(BIONIC_LIBC)/arch-arm/include \

  -I$(BIONIC_LIBC)/kernel/common \

  -I$(BIONIC_LIBC)/kernel/arch-arm

  LDFLAGS = -nostdlib -Wl,-dynamic-linker,/system/bin/linker \

  $(PRODUCT_OUT)/obj/lib/crtbegin_dynamic.o \

  $(PRODUCT_OUT)/obj/lib/crtend_android.o \

  -L$(PRODUCT_OUT)/obj/lib -lc -ldl

  # Installation variables

  EXEC_NAME = example-app

  INSTALL = install

  INSTALL_DIR = $(PRODUCT_OUT)/system/bin

  # Files needed for the build

  OBJS = example-app.o

  # Make rules

  all: example-app

  .c.o:

  $(CC) $(CFLAGS) $(HEADER_OPS) -c {1}lt;

  example-app: ${OBJS}

  $(CC) -o $(EXEC_NAME) ${OBJS} $(LDFLAGS)

  install: example-app

  test -d $(INSTALL_DIR) || $(INSTALL) -d -m 755 $(INSTALL_DIR)

  $(INSTALL) -m 755 $(EXEC_NAME) $(INSTALL_DIR)

  clean:

  rm -f *.o $(EXEC_NAME) core

  distclean:

  rm -f *~

  rm -f *.o $(EXEC_NAME) core

3 如何增加一个新的设备

  [plain] view plaincopy $ cd ~/android/aosp-2.3.x

  $ . build/envsetup.sh

  $ mkdir -p device/acme/coyotepad

  $ cd device/acme/coyotepad

  进入AndroidProducts.mk

  PRODUCT_MAKEFILES := \

  $(LOCAL_DIR)/full_coyotepad.mk

  对于full_coyotepad.mk

  $(call inherit-product, $(SRC_TARGET_DIR)/product/languages_full.mk)

  $(call inherit-product, $(SRC_TARGET_DIR)/product/full.mk)

  DEVICE_PACKAGE_OVERLAYS :=

  PRODUCT_PACKAGES +=

  PRODUCT_COPY_FILES +=

  PRODUCT_NAME := full_coyotepad

  PRODUCT_DEVICE := coyotepad

  PRODUCT_MODEL := Full Android on CoyotePad, meep-meep

  在BoardConfig.mk中

  TARGET_NO_KERNEL := true

  TARGET_NO_BOOTLOADER := true

  TARGET_CPU_ABI := armeabi

  BOARD_USES_GENERIC_AUDIO := true

  USE_CAMERA_STUB := true

  打开vendorsetup.sh

  add_lunch_combo full_coyotepad-eng
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: