Android Build System

[TOC]

与其他编译系统的比较

了解Android build system的为什么这样设计可以读一读 Recursive Make Considered Harmfulbuild/core/buildsystem.html

下面是一些总结:

  • 不像linux kernel那样,有一个顶层的makefile会递归地调用子目录的makefiles。

    Android build system用脚本搜索所有的文件夹,直到找到Android.mk文件,并停止继续搜索这个Android.mk文件所在的目录的子目录,除非该Android.mk文件有写include $(call all-subdir-makefiles)或者include $(LOCAL_PATH)/xxx/Android.mk之类的。

One Android.mk == One module, 一个Android.mk对应一个编译模块。 一共有多少Android.mk呢?在Android 6.0上:

$ find . -name Android.mk | wc -l
2646
  • .o等编译中间文件和.c等源文件不在同一个目录下,所有编译生成的文件都在out目录下。

  • 不像linux kernel那样可以配置的选项非常多,android能配置的只有:envsetup.sh, lunch, buildspec.mk。

    想配置enable, disable某个模块请看下面的“编译和安装”部分。

架构

编译命令

但是会消耗更多的时间来检查依赖的模块是否都编译成功。

Android.mk

Android.mk 文件通常以以下两行代码作为开头:

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

这两行代码的作用是:

  1. 设置当前模块的编译路径为当前文件夹路径。

  2. 清理(可能由其他模块设置过的)编译环境中用到的变量。

这两行代码的顺序不能倒过来哦!

这是为什么呢?请看下面的分析:

首先我们要知道$(CLEAR_VARS)的值是build/core/clear_vars.mk;

然后再要去理解my-dir函数,定义在build/core/definitions.mk里:

###########################################################
## Retrieve the directory of the current makefile
## Must be called before including any other makefile!!
###########################################################

# Figure out where we are.
define my-dir
$(strip \
  $(eval LOCAL_MODULE_MAKEFILE := $$(lastword $$(MAKEFILE_LIST))) \
  $(if $(filter $(BUILD_SYSTEM)/% $(OUT_DIR)/%,$(LOCAL_MODULE_MAKEFILE)), \
    $(error my-dir must be called before including any other makefile.) \
   , \
    $(patsubst %/,%,$(dir $(LOCAL_MODULE_MAKEFILE))) \
   ) \
 )
endef

my-dir函数通过$$(lastword $$(MAKEFILE_LIST))拿到最后读取的makefile,然后通过$(patsubst %/,%,$(dir $(LOCAL_MODULE_MAKEFILE)))得到最后读取的makefile的路径。

最后要知道gnu make 会自动将所有读取的makefile路径都会加入到MAKEFILE_LIST变量中,而且是按照读取的先后顺序添加。

那么分析开始了,在运行本makefile文件时,$(MAKEFILE_LIST)字符串中最后一个makefile肯定是最后读取的makefile,即$(lastword $(MAKEFILE_LIST))则会返回当前路径/Android.mk,my-dir函数则会得到当前路径。

如果我们在include $(CLEAR_VARS)之后,再调用my-dir函数,那么$$(lastword $$(MAKEFILE_LIST))肯定就会返回build/core/clear_vars.mk,my-dir函数得到的值就是build/core,而不是当前的路径了。

这么一来得到的LOCAL_PATH的值就是错误的值,依赖LOCAL_PATH的其他变量也就更加不可能是正确的了!所以说 ,LOCAL_PATH必须要在任何including $(CLEAR_VARS))之前定义 。

编译静态库、动态库、apk、签名版apk等模块的Android.mk要怎么写请看Android.mk写法实例

为了方便模块的编译,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或者optional。其中optional 是默认标签。标签是提供给编译类型TARGET_BUILD_VARIANT使用的。不同的编译类型会安装包含不同标签的模块,关于编译类型的说明下面来讲。

编译和安装

首先我们要知道并不是所有编译了的模块都会安装打包进img,有的模块是只编译不安装的哦!

module 在单独mmm编译的时候,是可以安装到out中的system对应位置的,最后能够打包进系统的system.img。 但是如果整体的 make -j 编译系统,那么module就会先生成在out下的*symbols/system对应的位置,最后会不会打包进系统system.img要看module 的LOCAL_MODULE_TAGS和当前的编译的TARGET_BUILD_VARIANT没有满足下面表格中的规则。

  • 三种编译类型TARGET_BUILD_VARIANT与安装模块的规则

odex的作用:系统制作会把.odex 和 apk 一起放到system/app 下,由系统来调度使用,如果想盗版apk,单独copy出去.apk是不能用的,这样可以起到一定程度上的保护作用!

总结: 这里可以看到对module的安装控制级别最高的是 PRODUCT_PACKAGES 这个变量。

不满足的规则的module只会被编译,并不会被install 的。

如果需要安装打包进system.img则可以按照上面表格中的规则,修改module的 LOCAL_MODULE_TAGS 或者在 PRODUCT_PACKAGES 中添加 module。

比如: LOCAL_MODULE_TAGS的默认值是optional,则该module会被编译但不会install。如果希望install的话可以:

  1. PRODUCT_PACKAGES += 该module名称

  2. 修改LOCAL_MODULE_TAGS := 对应的TARGET_BUILD_VARIANT(eng/debug)

常见模块名称

暂时就写这么多啦,以后有需要再补充~ 欢迎指导和提意见

Written with StackEdit.

Last updated