Android 编译系统 的简单理解
一,Makefile 入门 :简单来说,Makefile提供了一种机制,让使用者可以有效的组织“工作”,这里说的是“工作”,而不是“编译”,因为Makefile并一定是用来完成编译工作,事实上它本身只是一种“规则”的执行者,而使用者具体使用它来做什么没有任何限制,比如可以用它来架构编系统,也能用来生成文档,或者打印log信息等。
所以理解makefile的规则是学习的重点,mafefile是一个脚本,有make程序来解析,它是通过以下基础的规则扩展起来的。
TARGET:PREREQUISITES
COMMANDS
每个COMMANDS前都必须有个TAB制表符。
这个看起来非常简单的规则,经过一次次的扩展修饰后,便构成最终我们看到的各种庞大工程的编译系统。在这个规则中,TARGET 是需要生成的目标文件,PREREQUISITES 代表了目标所依赖的所有文件。当PREREQUISITES文件中的任何一个比TARGET新时,都会触发下面COMMAND命令的执行。
以一个简单的例子来讲解这个规则的使用方法。
示例包含的主要文件:
main.c 主函数所在文件
utility.h 提供了一个测试函数(getNumber)的声明
utility.c 提供了函数getNumber的实现
Makefile 用于编译整个工程文件
各文件的主要源码实现:
main.c
int main(int args,char *argv[]){
printf("hello getNumber = %d \n",getNumber())
return 0;
}
utility.h
int getNumber();
utility.c
int getNumber(){
return 100;
}
Makefile
SimpleMakefile:main.o utility.o
gcc -o SimpleMakefile main.o utility.o
main.o:main.c
gcc -c main.c
utility.o:utiltiy.c
gcc -c utility.c
这里有三个TARGET ,分别是SimpleMakefile main.o utility.o,其中SimpleMakefile 依赖于后两者,main.o utility.o由对应的C文件编译生成。
当然在实际的makefile文件的编写中,会非常的简洁,因为make工具本身非常的强大,它由很多的显示规则,隐晦规则,还有自动推导功能。
二 Android的编译系统
1,makefile依赖树的概念 前面的simpleMakefil中个TARGET的依赖关系可以组成一棵树,称之为“makefile依赖树”。
这种树形结构为我们由上而下的分析Android编译系统提供了可能。
2,树根节点droid ,树根节点即可能是整个编译系统的最终目标,也可能不是。
从android源码工程的根目录分析,其下的makefile文件是编译系统的起点,他只是一个简单的文件转向,引用了另一个makefile文件
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
这个main.mk 文件有千行代码,其中引用了很多其他脚本文件。我们依据依赖树的概念,要去找它的树根节点。首先看一些make工作原来的知识点:
(1)编译系统中往往有不止一棵依赖树存在。在没有显示指定编译目标的情况下(执行make命令时不带任何参数来执行编译,当然make后面是可以跟参数的,这个参数指明你要编译那个TARGET),第一个符合要求的目标会被Make作为默认的依赖树根节点。
(2)make程序会对makefile中的内容按顺序进行逐条解析,一个典型的解析过程会分为三大步骤:
A ,变量赋值,环境检测等初始化
B,按规则生成所有的依赖树
C,根据用户选择的依赖树,从叶子到根逐步生成目标文件
根据这些规则,对照main.mk 可以找到make的依赖树的根节点 droid,
build/core/main.mk
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):
。。。。。
只不过这里的droid是一个空的“规则”,这里只是保证它是第一个出现的目标,后续内容还有很多地方对droid进行了定义。
ifneq ($(TARGET_BUILD_APPS),)
。。。 只编译app
.PHONY: apps_only
apps_only: $(unbundled_build_modules)
droid: apps_only
。。。
else # TARGET_BUILD_APPS
$(call dist-for-goals, droidcore, \
$(INTERNAL_UPDATE_PACKAGE_TARGET) \
$(INTERNAL_OTA_PACKAGE_TARGET) \
$(SYMBOLS_ZIP) \
$(INSTALLED_FILES_FILE) \
$(INSTALLED_BUILD_PROP_TARGET) \
$(BUILT_TARGET_FILES_PACKAGE) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(INSTALLED_RAMDISK_TARGET) \
$(INSTALLED_FACTORY_RAMDISK_TARGET) \
$(INSTALLED_FACTORY_BUNDLE_TARGET) \
)
。。。 编译整个系统
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files
endif # TARGET_BUILD_APPS
3,main.mk的解析 main.mk 很大一部分内容是为了完成以下工作:
a,对编译环境的检测
b,前期的一些必要处理,比如整个项目工程是不是要先进行清理工作,工具的安装
c,引用其他的makefile文件,比如config.mk cleanbuild.mk
d,设置全局变量,这些变量决定了编译的具体实现过程
e,各种函数的实现,比如my-dir可以知道当前所在的路径,
build/core/definitions.mk
# 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
整个编译过程起主导作用的是main.mk 其中涉及的makefile文件如下:
config.mk 产品配置的主导文件
base_rules.mk 编译系统需要遵循的基础规则定义
definitions.mk 提供了大量使用函数的定义
envsetup.mk 配置编译时的环境变量
。。。
4,droidcore节点,依赖于如下几个prerequisites:
droidcore: files \ 代表其所依赖的先决条件的集合,没有实际意义
systemimage \ 将生成system.img
$(INSTALLED_BOOTIMAGE_TARGET) \ 将生成boot.img
$(INSTALLED_RECOVERYIMAGE_TARGET) \将生成recovery.img
$(INSTALLED_USERDATAIMAGE_TARGET) \将生成userdata.img
$(INSTALLED_CACHEIMAGE_TARGET) \将生成cache.img
$(INSTALLED_VENDORIMAGE_TARGET) \将生成vendor.img
$(INSTALLED_FILES_FILE)将生成installed-files.txt 用于记录当前系统中预安装的程序,库等模块。
5,android.mk 的编写规则
android系统是由非常多的子项目组成的,它有非常好的兼容性,后期的动态扩展性。Android.mk在整个源码工程中随处可见,这么多的文件,是如何整合进庞大的编译系统而保证不出错的,下面是将所有android.mk添加进编译系统的处理过程 main.mk
subdir_makefiles := \
$(shell build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git $(subdirs) Android.mk)
$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
系统找到所有的Android.mk会先存入subdir_makefiles变量中,随后一次性include进整个编译文件中。
以system/core/adb/Android.mk为例子详细解释Android.mk的编写规则:
#
# Android.mk for adb
#
LOCAL_PATH:= $(call my-dir) LOCAL_PATH的位置先与CLEAR_VARS
include $(CLEAR_VARS) CLEAR_VARS定义在build/core/clear_vars.mk中,它清楚了很多的除LOCAL_PATH变量,因而CLEAR_VARS被认为是一个编译模块的开始。
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES定义了本模块编译所涉及到的所有源文件,
adb.c \
console.c \
transport.c \
transport_local.c \
LOCAL_CFLAGS += -O2 -g -DADB_HOST=1 -Wall -Wno-unused-parameter -Werror
LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
上面两行用于添加编译标志
LOCAL_MODULE := adb 所要生成的模块的名称
LOCAL_STATIC_LIBRARIES := libzipfile libunz libcrypto_static $(EXTRA_STATIC_LIBS)编译过程中要用到的库
include $(BUILD_HOST_EXECUTABLE) 这是整个Android.mk的重点,BUILD_HOST_EXECUTABLE表示我们希望生成一个HOST可执行文件,这里可以根据需要选择其他的BUILD***变量,每个编译模块是从CLEARS_VARS开始,到这里结束。
Android.mk中常用变量解析:
LOCAL_PATH 用于确定源码所在的目录,
CLEAR_VARS 清空很多以LOCAL_ 开头的变量
LOCAL_MODULE_PATH 模块的输出路径
LOCAL_MODULE 模块名,保证整个编译系统中唯一,而且中间不可以有空格
LOCAL_STATIC_LIBARARIES 编译所需要静态库列表
LOCAL_JAVA_LIBRARIES 编译时所需要的java库
BUILD_*** 各种形式编译模块,如生成静态库,动态链接库,可执行文件,文档等
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
新版本如果不想生成.jack,需要jar包,可以添加:
LOCAL_JACK_ENABLED := disabled