一、说明
android build system是一个非常庞大的系统,要编译Android工程、修改或新增Android模块都需要对这个编译系统有一定的了解。但是由于它实在是太庞大了,大家往往是不知道从哪里切入进去,对Android的编译系统进行一个系统的学习。
下面我们尝试从一个小模块逐步对android build system做一个深入剖析。选择的这个模块名字叫做acp ,源码位于build\tools\acp目录。
后续很多模块的编译都需要使用到acp,根据编译依赖一般会先编译本模块。当然它也需要依赖到其他文件,需要的时候我们再进行阐述。
二、acp Android.mk初探
acp的Android.mk比较简单,去掉的无用代码后,如下面所示:
- LOCAL_PATH:= $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES := acp.c
- LOCAL_STATIC_LIBRARIES := libhost
- LOCAL_C_INCLUDES := build/libs/host/include
- LOCAL_MODULE := acp
- LOCAL_ACP_UNAVAILABLE := true
- include $(BUILD_HOST_EXECUTABLE)
上面的语句大致的意思就是,使用当前路径下的acp.c源码,引用的include和链接的library都是host模块,最终编译生成一个可在当前主机运行的可执行文件,名字为acp(linux环境)。。
这里我们先不谈每一个变量的具体含义和使用,我们先大概看一下一个Android.mk的基本组成。
三、 Android.mk基本组成
- LOCAL_PATH 定义了当前模块的相对路径,必须出现在所有的编译模块之前
- 每个编译模块由include $(CLEAR_VARS) 开始,由include $(BUILD_XXX) 结束
- include $(CLEAR_VARS) 是一个编译模块的开始,它会清空除LOCAL_PATH之外的所有LOCA_XXX变量
- include $(BUILD_XXX) 描述了编译目标
- LOCAL_SRC_FILES 定义了本模块编译使用的源文件,采用的是基于LOCAL_PATH的相对路径
- LOCAL_MODULE 定义了本模块的模块名
编译acp还需要了几个可选的变量:
- LOCAL_STATIC_LIBRARIES 表示编译本模块时需要链接的静态库
- LOCAL_C_INCLUDES 表示了本模块需要引用的include文件
- LOCAL_ACP_UNAVAILABLE 表示是否支持acp,如果支持acp,则使用acp进行拷贝,否则使用linux cp拷贝,本模块编译acp,当然是不支持acp了
四、编译目标
上面我们用到include
$(CLEAR_VARS)和include
$(BUILD_HOST_EXECUTABLE),那么他们是在哪里定义的呢?除了BUILD_HOST_EXECUTABLE还有哪些BUILD_XXX目标呢?
它们的定义位于build/core/config.mk文件,当然config.mk文件定义的编译目标也很多,下面列举几个常用的目标:
编译目标 | 说明 |
BUILD_HOST_STATIC_LIBRARY | 主机上的静态库 |
BUILD_HOST_SHARED_LIBRARY | 主机上的动态库 |
BUILD_HOST_EXECUTABLE | 主机上的可执行文件 |
BUILD_STATIC_LIBRARY | 目标设备上的静态库 |
BUILD_SHARED_LIBRARY | 目标设备上的动态库 |
BUILD_EXECUTABLE | 目标设备上的可执行文件 |
BUILD_JAVA_LIBRARY | JAVA库 |
BUILD_STATIC_JAVA_LIBRARY | 静态JAVA库 |
BUILD_HOST_JAVA_LIBRARY | 主机上的JAVA库 |
BUILD_PACKAGE | APK程序 |
二、LOCAL_PATH
有人就问了,在本Android.mk中又没有使用到LOCAL_PATH,为什么先 要定义这么一个变量呢?为什么规定必须放在所有的include $(CLEAR_VARS)之前呢?
在Android.mk中我们发现有LOCAL_SRC_FILES := acp.的定义,NDK文件中对LOCAL_SRC_FILES 的说明如下:
This is a list of source files that will be built for your module. Only list the files that will be passed to a compiler, since the build system automatically computes dependencies for you.
Note that source files names are all relative to LOCAL_PATH and you can use path components .
Note that source files names are all relative to LOCAL_PATH and you can use path components .
因此在定义LOCAL_SRC_FILES 时已经间接的使用到了LOCAL_PATH变量,即定义LOCAL_SRC_FILES是用的基于当前路径的相对路径。
我们接着看看为什么LOCAL_PATH的定义必须要放到所有的include $(CLEAR_VARS)之前。
LOCAL_PATH通过调用my-dir函数来获取当前的路径,my-dir函数的定义位于core/definitions.mk文件:
- <span style="font-size: small;"># Figure out where we are.
- define my-dir
- $(strip \
- $(eval md_file_ := $$(lastword $$(MAKEFILE_LIST))) \
- $(if $(filter $(CLEAR_VARS),$(md_file_)), \
- $(error LOCAL_PATH must be set before including $$(CLEAR_VARS)) \
- , \
- $(patsubst %/,%,$(dir $(md_file_))) \
- ) \
- )
- endef</span>
请注意,这里明确的说明了LOCAL_PATH的定义必须要放在任何include $(CLEAR_VARS)语句之前,如果不这么做的话,编译就直接报错,停止不干了。
可是它是怎么判断LOCAL_PATH的定义是在任何include $(CLEAR_VARS)语句之前呢,我们看到有这么一句话:
- $(if $(filter $(CLEAR_VARS),$(md_file_))
三、CLEAR_VARS
在build/core/config.mk中有如下明确的定义:
- CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
而
BUILD_SYSTEM的定义在build/core/main.mk文件中:
BUILD_SYSTEM := $(TOPDIR)build/core
- BUILD_SYSTEM := $(TOPDIR)build/core
紧接着:
- TOPDIR :=
得到了CLEAR_VARS变量的值,我们再回到my-dir函数中。
四、my-dir
根据gnu make定义,gnu make 会自动将所有读取的makefile路径都会加入到MAKEFILE_LIST变量中,而且是按照读取的先后顺序添加。
那么,在运行本makefile文件时,$(MAKEFILE_LIST)字符串中最后一个makefile肯定是最后读取的makefile,即$(lastword $(MAKEFILE_LIST))则会返回build/tools/acp/Android.mk,此字符串经过$(eval md_file_ := $$(lastword $$(MAKEFILE_LIST))运算,赋值给了临时变量md_file_。
判断md_file_中是否包含CLEAR_VARS变量的值$(if $(filter $(CLEAR_VARS),$(md_file_)),肯定也就会返回失败,再通过$(patsubst %/,%,$(dir $(md_file_)))函数,则会得到当前路径,即build/tools/acp
如果我们在include $(CLEAR_VARS)之后,再调用my-dir函数,那么$$(lastword $$(MAKEFILE_LIST))肯定就会返回$(BUILD_SYSTEM)/clear_vars.mk,同时,$(patsubst %/,%,$(dir $(md_file_)))
也就会返回$(BUILD_SYSTEM)的值build/core,而不是当前的路径build/tools/acp。
这么一来得到的LOCAL_PATH的值就是错误的值,依赖LOCAL_PATH的其他变量也就更加不可能是正确的了!所以说 ,LOCAL_PATH必须要在任何including $(CLEAR_VARS))之前定义 。
讨论完LOCAL_PATH,我们紧接着来看看LOCAL_SRC_FILES。
LOCAL_SRC_FILES变量的意思见名知意,很明显是用来记录当前模块的源文件列表的一个变量。
而HOST_OUT_release的定义如下:
一、LOCAL_SRC_FILES
- LOCAL_SRC_FILES := acp.c
这里是他的赋值,我们下面来看看他的使用的地方。在build/core/binary.mk中有如下的部分:
- ###########################################################
- ## C: Compile .c files to .o.
- ###########################################################
- c_arm_sources := $(patsubst %.c.arm,%.c,$(filter %.c.arm,$(LOCAL_SRC_FILES)))
- c_arm_objects := $(addprefix $(intermediates)/,$(c_arm_sources:.c=.o))
- c_normal_sources := $(filter %.c,$(LOCAL_SRC_FILES))
- c_normal_objects := $(addprefix $(intermediates)/,$(c_normal_sources:.c=.o))
- $(c_arm_objects): PRIVATE_ARM_MODE := $(arm_objects_mode)
- $(c_arm_objects): PRIVATE_ARM_CFLAGS := $(arm_objects_cflags)
- $(c_normal_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
- $(c_normal_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
- c_objects := $(c_arm_objects) $(c_normal_objects)
- ifneq ($(strip $(c_objects)),)
- $(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c $(yacc_cpps) $(proto_generated_headers) $(LOCAL_ADDITIONAL_DEPENDENCIES)
- $(transform-$(PRIVATE_HOST)c-to-o)
- -include $(c_objects:%.o=%.P)
- endif
分析上面的代码,
1. 我们首先从LOCAL_SRC_FILES中得到所有的C文件
c_normal_sources value acp.c
2. 定义一个变量,c_normal_objects,用来表示生成的.o文件的路径
c_normal_objects value out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
其中,$(c_normal_sources:.c=.o)会返回acp.o,那么c_normal_objects的关键就是$(intermediates)变量
二、intermediates
通过查找,我们可以发现在以下地方有intermediates的赋值:
- build/core/host_java_library.mk:intermediates := $(call local-intermediates-dir)
- build/core/base_rules.mk:intermediates := $(call local-intermediates-dir)
- build/core/dynamic_binary.mk:guessed_intermediates := $(call local-intermediates-dir)
- build/core/java.mk:intermediates := $(call local-intermediates-dir)
即,他们的都是调用local-intermediates-dir函数获取当前的intermediates的值
在build/core/definitions.mk中有local-intermediates-dir函数的定义:
- # Uses LOCAL_MODULE_CLASS, LOCAL_MODULE, and LOCAL_IS_HOST_MODULE
- # to determine the intermediates directory.
- #
- # $(1): if non-empty, force the intermediates to be COMMON
- define local-intermediates-dir
- $(strip \
- $(if $(strip $(LOCAL_MODULE_CLASS)),, \
- $(error $(LOCAL_PATH): LOCAL_MODULE_CLASS not defined before call to local-intermediates-dir)) \
- $(if $(strip $(LOCAL_MODULE)),, \
- $(error $(LOCAL_PATH): LOCAL_MODULE not defined before call to local-intermediates-dir)) \
- $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),$(LOCAL_MODULE),$(LOCAL_IS_HOST_MODULE),$(1)) \
- )
- endef
根据注释,我们知道 local-intermediates-dir的定义会依赖于LOCAL_MODULE_CLASS, LOCAL_MODULE, 和 LOCAL_IS_HOST_MODULE这三个变量的值。
我们先通过添加打印的方式,得到如下三个变量的值:
LOCAL_MODULE_CLASS value EXECUTABLES
LOCAL_MODULE value acp
LOCAL_IS_HOST_MODULE value true
LOCAL_MODULE value acp
LOCAL_IS_HOST_MODULE value true
其中,LOCAL_MODULE 的值是acp,这个肯定的是没问题的,因为在acp模块的Android.mk文件中有明确的定义:LOCAL_MODULE := acp
那LOCAL_MODULE_CLASS 为什么是EXECUTABLES呢?我们这里先猜一下,估计是和最后一句include $(BUILD_HOST_EXECUTABLE )有关,
同样,LOCAL_IS_HOST_MODULE 为true,估计也是和include $(BUILD_HOST _EXECUTABLE)有关了。
我们还是先回到 local-intermediates-dir函数中,其中前两个判断是判断是否定义LOCAL_MODULE_CLASS和LOCAL_MODULE,如果未空,则直接报错,这里也就说明了在每一个编译模块中(即include $(CLEAR_VARS)和include $(BUILD_XXX)之间)必须定义LOCAL_MODULE的值。
因此,如果LOCAL_MODULE_CLASS和LOCAL_MODULE都不为空时,则执行intermediates-dir-for这个函数,$(LOCAL_MODULE_CLASS),$(LOCAL_MODULE),$(LOCAL_IS_HOST_MODULE)还有
local-intermediates-dir函数的第一个参数 $(1)会作为参数传给intermediates-dir-for函数。根据上面intermediates的赋值部分的代码,我们知道调用local-intermediates-dir函数时没有传递任何参数,因此此时的$(1)即为空,传给intermediates-dir-for函数的也就只有上述的3个参数。
三、 intermediates-dir-for
下面我们来看intermediates-dir-for
- ###########################################################
- ## The intermediates directory. Where object files go for
- ## a given target. We could technically get away without
- ## the "_intermediates" suffix on the directory, but it's
- ## nice to be able to grep for that string to find out if
- ## anyone's abusing the system.
- ###########################################################
- # $(1): target class, like "APPS"
- # $(2): target name, like "NotePad"
- # $(3): if non-empty, this is a HOST target.
- # $(4): if non-empty, force the intermediates to be COMMON
- define intermediates-dir-for
- $(strip \
- $(eval _idfClass := $(strip $(1))) \
- $(if $(_idfClass),, \
- $(error $(LOCAL_PATH): Class not defined in call to intermediates-dir-for)) \
- $(eval _idfName := $(strip $(2))) \
- $(if $(_idfName),, \
- $(error $(LOCAL_PATH): Name not defined in call to intermediates-dir-for)) \
- $(eval _idfPrefix := $(if $(strip $(3)),HOST,TARGET)) \
- $(if $(filter $(_idfPrefix)-$(_idfClass),$(COMMON_MODULE_CLASSES))$(4), \
- $(eval _idfIntBase := $($(_idfPrefix)_OUT_COMMON_INTERMEDIATES)) \
- , \
- $(eval _idfIntBase := $($(_idfPrefix)_OUT_INTERMEDIATES)) \
- ) \
- $(_idfIntBase)/$(_idfClass)/$(_idfName)_intermediates \
- )
- endef
根据注释,我们可以知道$(1),$(2),$(3),$(4)这4个参数的意义。
而以下三行则是使用$(1),$(2),$(3)来定义三个临时变量:_idfClass,_idfName 和_idfPrefix
$(eval _idfClass := $(strip $(1)))
$(eval _idfName := $(strip $(2)))
$(eval _idfPrefix := $(if $(strip $(3)),HOST,TARGET))
在本例中这三个临时变量的值则为:EXECUTABLES、acp和HOST
下面关键的一个临时变量是_idfIntBase ,我们发现无论$(if $(filter $(_idfPrefix)-$(_idfClass),$(COMMON_MODULE_CLASSES))$(4)是真还是假,
_idfIntBase 的值不是$(HOST_OUT_COMMON_INTERMEDIATES)就是$(HOST_OUT_INTERMEDIATES),因为上面调用时$(4)为空,在这里估计判断结果 应该为假,即应该执行
- $(eval _idfIntBase := $($(_idfPrefix)_OUT_INTERMEDIATES))
下面我就看看这个$(HOST_OUT_INTERMEDIATES )到底是一个什么变量。
在build/core/envsetup.mk中有明确的定义:
- HOST_OUT_INTERMEDIATES := $(HOST_OUT)/obj
HOST_OUT也在本文件中定义:
- HOST_OUT := $(HOST_OUT_$(HOST_BUILD_TYPE))
HOST_BUILD_TYPE默认值为release:
- # the host build defaults to release, and it must be release or debug
- ifeq ($(HOST_BUILD_TYPE),)
- HOST_BUILD_TYPE := release
- endif
因此,
- HOST_OUT := $(HOST_OUT_release)
- HOST_OUT_release := $(HOST_OUT_ROOT_release)/$(HOST_OS)-$(HOST_ARCH)
HOST_OUT_ROOT_release的定义:
- HOST_OUT_ROOT_release := $(OUT_DIR)/host
在Linux上编译,因此HOST_OS := linux ,而我们的机器采用的是Intel Xeon X3440 CPU,即x86架构,因此HOST_ARCH:= x86
经过上述分析,
$(HOST_OUT_INTERMEDIATES ) =out/host/linux-x86/obj
intermediates-dir-for
函数返回out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
local-intermediates-dir
函数返回out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
intermediates
变量的值为out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
c_normal_objects
变量的值为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
四、c_objects
下面我们回到LOCAL_SRC_FILES的编译部分
- c_objects := $(c_arm_objects) $(c_normal_objects)
- ifneq ($(strip $(c_objects)),)
- $(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c $(yacc_cpps) $(proto_generated_headers) $(LOCAL_ADDITIONAL_DEPENDENCIES)
- $(transform-$(PRIVATE_HOST)c-to-o)
- -include $(c_objects:%.o=%.P)
- endif
其中c_arm_objects为空,c_normal_objects 的值为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
所以c_objects 的值也为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
执行c_objects目标时,依赖$(intermediates)/%.o、 $(TOPDIR)$(LOCAL_PATH)/%.c、$(yacc_cpps) 、$(proto_generated_headers)和$(LOCAL_ADDITIONAL_DEPENDENCIES)
执行如下操作: $(transform-$(PRIVATE_HOST)c-to-o)
$(transform-$(PRIVATE_HOST)c-to-o)函数的具体操作,我们暂时不讲,留待后续分析 。