Android.mk的使用方法
在上一篇Android编译系统入门(一)中我们只要介绍了Android系统使用make命令默认编译的依赖树是droid,而droid是一个伪目标,它有两个先决条件droidcore和dist_files,其中重点是droidcore,它主要用于编译系统所需的system.img,boot.img等。有了上一篇的基础,今天我们要分析一下Android.mk文件在整个编译系统中的地位和作用。
一棵大树的繁茂和枝叶的多少息息相关。一方面只有枝干足够茁壮才能托起枝叶,另一方面枝叶的光合作用也能促进枝干的生长。那么在Android编译系统中,droid就是这棵树中强有里的枝干,而Android.mk则是一片片的叶子,纵观整个Android平台Android.mk的数量在一千个以上。那么如此多的makefile文件又是在何时被整合进整个编译系统的呢?其实答案还是在main.mk中。
ifneq ($(ONE_SHOT_MAKEFILE),)
# We've probably been invoked by the "mm" shell function
# with a subdirectory's makefile.
include $(ONE_SHOT_MAKEFILE)
# Change CUSTOM_MODULES to include only modules that were
# defined by this makefile; this will install all of those
# modules as a side-effect. Do this after including ONE_SHOT_MAKEFILE
# so that the modules will be installed in the same place they
# would have been with a normal make.
CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))
FULL_BUILD :=
# Stub out the notice targets, which probably aren't defined
# when using ONE_SHOT_MAKEFILE.
NOTICE-HOST-%: ;
NOTICE-TARGET-%: ;
# A helper goal printing out install paths
.PHONY: GET-INSTALL-PATH
GET-INSTALL-PATH:
@$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED), \
echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))
else # ONE_SHOT_MAKEFILE
ifneq ($(dont_bother),true)
#
# Include all of the makefiles in the system
#
# Can't use first-makefiles-under here because
# --mindepth=2 makes the prunes not work.
subdir_makefiles := \
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
endif # dont_bother
ONE_SHOT_MAKEFILE变量和编译选项有关,当选择默认make命令进行整编的时候ONE_SHOT_MAKEFILE值为空,这是就会走下面这个分支
subdir_makefiles := \
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
其中就是通过findleaves.py这个脚本来查找所有的Android.mk文件但可能并不是所有的Android.mk都会被包好进来比如.repo .git下的就会被排除在外。这些排除选项由FIND_LEAVES_EXCLUDES决定。所有被包含进来的Android.mk的路径都会被追加到subdir_makefiles变量,接着通过一个foreach函数将所有的Android.mk文件都include进来。其中$(info including $(mk) ...)负责打印这些文件信息,如下
再通过eval函数执行include操作将Android.mk文件整合进整棵大树。
OK,到这里所有的Android.mk文件都被包含进来了,等整个大树被构建完成后make会从依赖树最外层的叶子开始往上执行所有的COMMANDS。
接下来我们选取Settings模块作为例子,详细的解释一下Android.mk的编写规则和一些注意事项。Settings模块的Android.mk内容如下
#LOCAL_PATH表示当前目录的地址,一般位于include $(CLEAR_VARS)之前
LOCAL_PATH:= $(call my-dir)
#CLEAR_VARS对应的是clean_vars.mk,用于清除除了LOCAL_PATH以外的所有LOCAL_打头的变量
include $(CLEAR_VARS)
#重定向java库文件
LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common
#重定向java静态库文件
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 jsr305
#模块tag为optional,表示不管是选择了什么模式都会编译该模块
LOCAL_MODULE_TAGS := optional
#重定向本地源码
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
src/com/android/settings/EventLogTags.logtags
#重定向本地资源文件
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
#模块名
LOCAL_PACKAGE_NAME := Settings
#模块证书签名
LOCAL_CERTIFICATE := platform
#是否是特权文件
LOCAL_PRIVILEGED_MODULE := true
#使用代码混淆
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
#判断是否进行增量编译
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_JACK_ENABLED := incremental
endif
#include三个makefile文件,进项相关变量赋值
include frameworks/opt/setupwizard/navigationbar/common.mk
include frameworks/opt/setupwizard/library/common.mk
include frameworks/base/packages/SettingsLib/common.mk
#开始编译Settings模块,对应package.mk文件。感兴趣的可以进一步研究apk是怎么被编译出来的,里面还是很复杂的
include $(BUILD_PACKAGE)
# 如果使用的是mm或mmm命令单编Settings模块的话会额外include test目录下的Android.mk,用于编译测试模块。
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif
通过上述对Android.mk文件的分析,我们可以看到需要编译一个模块要做的工作还是很少的,只要指定几个变量就可以了,这也得益与google的用心良苦,它把所有的公共操作都抽取了出来,做好了各种模板,如BUILD_PACKAGE等,我们要做的只是调用适当的模板就行了。
下面介绍一些Android.mk中常用的变量,以供读者参考。
变量名 | 说明 |
---|---|
LOCAL_PATH | 用于确定源码所在的目录,一般把它放在CLEAR_VARS变量引用的前面,因为它不会背清除,每个Android.mk只需要定义一次就行 |
CLAER_VARS | 清空很多LOCAL_开头的变量(LOCAL_PATH除外)。因为所有的Makefile都是在一个编译环境下执行,因此变量的定义理论上都是全局的,每个模块开始编译前进行清理工作是必不可少的 |
LOCAL_MODULE | 模块名,需要保证唯一存在且中间不能又空格 |
LOCAL_MODULE_PATH | 模块的输出路径 |
LOCAL_SRC_FILES | 模块编译所涉及的源文件。如果是java程序,可以考虑调用all-java-files-under添加java代码。因为有LOCAL_PATH,所以这里只需要给出文件名即可,如src |
LOCAL_CC | 用于指定C编译器 |
LOCAL_CXX | 用于指定C++编译器 |
LOCAL_CPP_EXTENSION | 用于指定特殊的C++文件后缀名 |
LOCAL_CFLAGS | C语言编译时的额外选项 |
LOCAL_CXXFLOAGS | C++编译时的额外选项 |
LOCAL_C_INCLUDES | 编译C和C++时需要的额外头文件 |
LOCAL_STATIC_LIBRARIES | 编译所需的静态库列表 |
LOCAL_SHARED_LIBRARIES | 编译所需的共享库列表 |
LOCAL_JAVA_LIBRARIES | 编译时所需的JAVA类库 |
LOCAL_LDLIBS | 编译时所需的链接选项 |
LOCAL_COPY_HEADERS | 安装应用程序时需要复制的头文件列表,需要和LOCAL_COPY_HEADERS_TO变量配合使用 |
LOCAL_COPY_HEADERS_TO | 上述头文件列表的复制目的地 |
BUILD_XX_XX | 各种形式的编译模板,如生成静态、动态库文件,可执行文件,文档等 |
总结
在Android编译系统的学习中,我们先从最基础的makefile语法规则入手,导出了依赖树的概念,然后按照依赖树的结构逐步梳理出一个完整的Android版本编译所设计的几个重要节点。Android编译系统是非常庞大的,不过经过这次的学习希望大家能够对它的结构和基本原理有一个初步的认识。那么接下来的各种编译细节也能通过代码的研读和分析变得明朗起来。