Android编译系统
1. makefile入门
- makefile本质是创建的一种“规则”,根据“规则”按指定顺序一步步执行,可以用它来编译系统、生成文档、打印log等
- makefile也是一种脚本,和shell、python等类似,由make程序来解析
- make解析程序种类有很多,android中采用的是GNU make程序来编译系统
- 不同的make解析程序对应的makefile语法也有差异,但是这些makefile语法都是根据基础规则扩展起来的
其基本规则如下:
target : prerequisites
command //每个command前都必须有一个TAB制表符
- target: 是指需要生成的目标文件,它可以是可执行文件,还可以是一个标签(Label),如果target是标签则表示“伪目标”
- prerequisites:要生成那个target所依赖的所有文件
- command:表示其生成规则定义,即prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的规则命令就会被执行
虽然写好一个Makefile并不简单,但是可以先给出简单列子来对Makefile语法有个初步认识。
文件名 | 描述 |
---|---|
main.c | 主函数 |
base.h | 相关头文件,为用到的方法进行声明 |
base.c | 为主函数提供需要用的方法 |
Makefile | 编译规则 |
这个例子主要功能就是打印字符,main.c源码如下:
#include<stdio.h>
#include"base.h"
int main()
{
printf("hello World,getNumber method can get the value is:%d",getNumber());
return 0;
}
base.c源码如下:
#include"base.h"
int getNumber()
{
return 7;
}
base.h源码如下:
int getNumber();
Makefile建立编译规则,其源码如下:
SimpleMakefile: main.o base.o //规则一: 它依赖main.o base.o2个文件,即它依赖规则二和规则三的结果
gcc -o SimpleMakefile main.o base.o
main.o:main.c //规则二: 它依赖main.c文件
gcc -o main.c
base.o:base.c //规则三: 它依赖 base.c文件
gcc -c base.c
部分makefile语法总结
1. 符号%
表示一到多个文件
2. :=
是makefile中的一种赋值语法,它相比于普通的赋值语法=
,会禁止前面变量使用后面的变量。
如:
Y:=$(X)bar //此时 X为空,Y为bar
X:=foo //此时 X为foo
Y:=$(X)bar //此时Y为foobar
X:=later //此时 X为later
- 用
#
定义空格:
nullString:=
space:=$(nullString) //eng of line nullString表示一个空变量,space的值是一个空格
?=
是表示变量是否被定义过,如果没有定义过则该变量就为后面的值
Foo?=bar //如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做
//其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
- 变量替换,其格式为
$(var:a=b)
,表示把变量【var】中所有以a字符结尾的替换成b字符串
foo := a.o b.o c.o //先定义了一个“$(foo)”变量
bar := $(foo:.o=.c) //把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c” bar := a.c b.c c.c
make在读取Makefile时就计算条件表达的值,并根据条件表达的值来选择语句。而自动化变量(如
$@
等)是在运行时才有的,所以Makefile不允许把整个条件语句分成两部分放在不同文件中命令执行,make解析程序会一条一条的执行其后面的命令,如果上一条命令的结果应用在下一条命令时,可用分号
;
隔开两条命令
//示例一:
exec:
cd /home/hchen
pwd //cd没有作用,pwd会打印出当前的Makefile目录
//示例二:
exec:
cd /home/hchen; pwd //cd起作用了,pwd会打印出“/home/hchen
- foreach 函数,类似于shell中的for循环,其格式为
$(foreach <var>,<list>,<text>)
把参数<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。返回的字符串所组成的整个字符(以空格分隔)作为foreach
函数的返回值。
<var>
最好是一个变量名,<list>
可以是一个表达式,而<text>
中一般会使用<var>
这个参数来依次枚举<list>
中的单词。举个例子:
names := a b c d
files := $(foreach n,$(names),$(n).o) // 等价于files :=a.o b.o c.o d.o
- Makefile中的函数,很像变量的使用,以
$
符号标识,其语法如$(<function> <arguments>)
。<function>
就是函数名,make支持的函数不多。<arguments>
是函数的参数,参数间以逗号,
分隔,而函数名和参数之间以“空格”分隔。
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo)) //函数“subst”是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串
//替换后的结果是 bar:=a,b,c
- if函数,很类似于shell中的ifeq,其语法是
$(if <condition>,<then-part> )
或者是$(if <condition>,<then-part>,<else-part> )
。判定条件是<condition>
是否为空字符串 - 执行指定Makefile,一般默认的Makefile执行顺序:
- ① GNUmakefile
- ② makefile
- ③ Makefile
也可以给make命令指定一个特殊名字的Makefile,需要使用make的“-f”或是“–file”参数。例如有个makefile的名字是“hchen.mk”,执行命令如下:
make –f hchen.mk
- call函数 ,用来创建新的参数化的函数,其语法是
$(call <expression>,<parm1>,<parm2>,<parm3>...)
,如:
reverse := $(1) $(2)
foo := $(call reverse,a,b) //foo:=a b
reverse := $(2) $(1)
foo := $(call reverse,a,b) //foo:=b a
2. Android编译系统
android 编译系统构建原则:
1. 同一套代码支持编译多个目标
2. 用唯一的Makefile组织编译所以文件
3. 单独模块可用独立编译
4. 中间文件、源码、编译结果在存储目录上分离
对于Android的编译系统,主要由如下4步构成,其中编译系统的核心是有效的构建依赖树。
通过下面系统源码可以看到,该编译系统的根节点droid。但实际上droid只是一个伪目标,在编译系统中相当于有先占一个位置,声明一下依赖树的根节点,具体的编译依赖树还需要根绝传入的参数TARGET_BUILD_APPS
来确定。
#build/core/main.mk
#(此处省略部分代码.......)
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):
#(此处省略部分代码.......)
ifneq ($(TARGET_BUILD_APPS),)
# If this build is just for apps, only build apps and not the full system by default.
unbundled_build_modules :=
ifneq ($(filter all,$(TARGET_BUILD_APPS)),)
# If they used the magic goal "all" then build all apps in the source tree.
unbundled_build_modules := $(foreach m,$(sort $(ALL_MODULES)),$(if $(filter APPS,$(ALL_MODULES.$(m).CLASS)),$(m)))
else
unbundled_build_modules := $(TARGET_BUILD_APPS)
endif
# Dist the installed files if they exist.
apps_only_installed_files := $(foreach m,$(unbundled_build_modules),$(ALL_MODULES.$(m).INSTALLED))
$(call dist-for-goals,apps_only, $(apps_only_installed_files))
# For uninstallable modules such as static Java library, we have to dist the built file,
# as <module_name>.<suffix>
apps_only_dist_built_files := $(foreach m,$(unbundled_build_modules),$(if $(ALL_MODULES.$(m).INSTALLED),,\
$(if $(ALL_MODULES.$(m).BUILT),$(ALL_MODULES.$(m).BUILT):$(m)$(suffix $(ALL_MODULES.$(m).BUILT)))\
$(if $(ALL_MODULES.$(m).AAR),$(ALL_MODULES.$(m).AAR):$(m).aar)\
))
$(call dist-for-goals,apps_only, $(apps_only_dist_built_files))
ifeq ($(EMMA_INSTRUMENT),true)
$(EMMA_META_ZIP) : $(apps_only_installed_files)
$(call dist-for-goals,apps_only, $(EMMA_META_ZIP))
endif
$(PROGUARD_DICT_ZIP) : $(apps_only_installed_files)
$(call dist-for-goals,apps_only, $(PROGUARD_DICT_ZIP))
$(SYMBOLS_ZIP) : $(apps_only_installed_files)
$(call dist-for-goals,apps_only, $(SYMBOLS_ZIP))
.PHONY: apps_only
apps_only: $(unbundled_build_modules)
droid: apps_only
# Combine the NOTICE files for a apps_only build
$(eval $(call combine-notice-files, \
$(target_notice_file_txt), \
$(target_notice_file_html), \
"Notices for files for apps:", \
$(TARGET_OUT_NOTICE_FILES), \
$(apps_only_installed_files)))
else # TARGET_BUILD_APPS
$(call dist-for-goals, droidcore, \
$(INTERNAL_UPDATE_PACKAGE_TARGET) \
$(INTERNAL_OTA_PACKAGE_TARGET) \
$(BUILT_OTATOOLS_PACKAGE) \
$(SYMBOLS_ZIP) \
$(INSTALLED_FILES_FILE) \
$(INSTALLED_BUILD_PROP_TARGET) \
$(BUILT_TARGET_FILES_PACKAGE) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(INSTALLED_RAMDISK_TARGET) \
)
# Put a copy of the radio/bootloader files in the dist dir.
$(foreach f,$(INSTALLED_RADIOIMAGE_TARGET), \
$(call dist-for-goals, droidcore, $(f)))
ifneq ($(ANDROID_BUILD_EMBEDDED),true)
ifneq ($(TARGET_BUILD_PDK),true)
$(call dist-for-goals, droidcore, \
$(APPS_ZIP) \
$(INTERNAL_EMULATOR_PACKAGE_TARGET) \
$(PACKAGE_STATS_FILE) \
)
endif
endif
ifeq ($(EMMA_INSTRUMENT),true)
$(EMMA_META_ZIP) : $(INSTALLED_SYSTEMIMAGE)
$(call dist-for-goals, dist_files, $(EMMA_META_ZIP))
endif
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files
endif # TARGET_BUILD_APPS
在代码中,可以看到根绝参数TARGET_BUILD_APPS
是否为空可以分成2条不同分支,具体如下图所示。
在编译整个android系统时,从droid: droidcore dist_files
中可以看到,它分别依赖droidcore和dist_files2个目标。
目标droidcore
它又分别依赖其他的子目标:
# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: files \
systemimage \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_FILES_FILE)
字段 | 描述 |
---|---|
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) | 将生成install_files.txt,记录当前系统预安装的程序、库等模块 |
此处可以推测出,通过目标droidcore可以生成系统的所有可运行程序包。
目标dist_files
该目标主要是在out目录下产生专门的dist文件夹,用于存储多种分发包,相关描述如下所示:
# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files
3. Java编译链(java android compiler kit)
在android6.0之后,系统采用了全新的java编译链Jack(java android compiler kit),它的主要任务是取代以前版本中的javac、ProGuard、jarjar、dx等工具,它的优势是编译速度快内建shrinking、obfuscation、repackaging、multidex的工具,并且开源。
4. SDK的编译过程
5. Android GDB调试过程
参考:《深入理解android内核设计思想》