Android.mk的深入介绍
2017-08-09 16:11:24 +08 字数:3945 标签: Android MakefileAndroid.mk作为Android单模块编译的Makefile,有其独特的写法。 本文从原理、源码的角度触发,介绍Android.mk怎么写,以及如何查找更多信息。
Android的编译系统,总体来说是由Makefile写的一个项目。 而Android.mk,只是其中的一个子集。 详见此前的一篇《Android 6.0中的Makefile》。
新增Android.mk ¶
在Android平台项目任意一个合适的目录下,创建一个Android.mk文件,都可以新增一个模块(Module)。 所谓『合适的』,是一个比较含糊的要求。
在build/core/main.mk
中:
subdir_makefiles := \
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
其中的FIND_LEAVES_EXCLUDES
,定义在build/core/config.mk
中:
FIND_LEAVES_EXCLUDES := $(addprefix --prune=, $(OUT_DIR) $(SCAN_EXCLUDE_DIRS) .repo .git)
而build/tools/findleaves.py
中的主要逻辑如下:
def perform_find(mindepth, prune, dirlist, filename):
result = []
pruneleaves = set(map(lambda x: os.path.split(x)[1], prune))
for rootdir in dirlist:
rootdepth = rootdir.count("/")
for root, dirs, files in os.walk(rootdir, followlinks=True):
# prune
check_prune = False
for d in dirs:
if d in pruneleaves:
check_prune = True
break
if check_prune:
i = 0
while i < len(dirs):
if dirs[i] in prune:
del dirs[i]
else:
i += 1
# mindepth
if mindepth > 0:
depth = 1 + root.count("/") - rootdepth
if depth < mindepth:
continue
# match
if filename in files:
result.append(os.path.join(root, filename))
del dirs[:]
return result
其中,prune
就是FIND_LEAVES_EXCLUDES
传入的--prune=
列表,
而mindepth
,则是默认值-1
。
三段代码组合起来,可以看出查找Android.mk的逻辑:
- 排除
.repo
、.git
和产物输出目录(默认为out
)。SCAN_EXCLUDE_DIRS
默认为空,但可以通过参数传入、修改产品配置等方式设置。 - 查找目录深度不限。
- 如果父目录已经有Android.mk,不再查找子目录。
(不得不说一句题外话,代码写得真是不咋滴。)
使新增模块参与编译 ¶
满足上述逻辑的Android.mk,都会被include到Android的Makefile中。 但是,其中定义的模块是否参与编译,则未必。
相关代码,都在build/core/main.mk
中。
modules_to_install := $(sort \
$(ALL_DEFAULT_INSTALLED_MODULES) \
$(product_FILES) \
$(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
$(CUSTOM_MODULES) \
)
可以看到,可以被安装到手机ROM的模块,有四种来源。
ALL_DEFAULT_INSTALLED_MODULES
,从字面意思理解,系统默认的那些模块。product_FILES
,产品指定。 通过在产品配置中,指定PRODUCT_PACKAGES
,可以进入这个列表。tags_to_install
,指定Tag。 比如,Tag为debug的模块,只在eng或userdebug的编译模式下才编译,在user模式下不编译。CUSTOM_MODULES
,自定义模块。 从实现上来讲,其实也是通过Tag来添加,但主要是支持mm
、mmm
这种单模块编译。 在模块未被添加到完整编译中时,在该模块的目录下执行mm
,仍然可以强制编译,就是通过这里来指定的。
实际上,代码中的情况相当复杂,不像这里看上去这么简单。
不过,复杂的代码就不再进一步剖析了,上述理解与真实情况差别并不大。
四种来源的模块,是有重复的。
而Makefile的$(sort list)
,不仅可以排序,还可以去重。
修改ALL_DEFAULT_INSTALLED_MODULES
需要改核心Makefile,通常不考虑。
而CUSTOM_MODULES
是为单模块编译提供支持,也不考虑。
第三种方法是指定LOCAL_MODULE_TAGS
,利用Tag来参与编译。
比如,只在eng下编译,可以指定LOCAL_MODULE_TAGS := eng
。
原本可以通过指定Tag为user,参与全部类型的编译,但是已经被禁用了。
所以,平台开发者要想把已经准备好Android.mk的模块,添加到编译中,现在只剩指定PRODUCT_PACKAGES
一种办法。
每个产品都改一次,虽然略显麻烦,但与做产品的逻辑,恰好吻合。
如何写Android.mk ¶
Android.mk本质上就是用Makefile写的配置文件。 与普通Makefile不同的是,它相当于是Android编译系统的一个子系统。 配置简单,可读性高,但若要凭空手写,基本没可能。
以下以packages/apps/Settings/Android.mk
的内容为例,说明Android.mk的配置方式。
这是系统设置的源码,在系统应用中具有一定的代表性。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 jsr305
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 frameworks/opt/setupwizard/navigationbar/common.mk
include frameworks/opt/setupwizard/library/common.mk
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif
Android.mk中,可以放置任意多个模块,这里只有一个模块。 对一个模块来说,大致可以分为开头清理、指定模块变量与调用编译文件三部分。
开头清理 ¶
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
include $(CLEAR_VARS)
,其实就是调用build/core/clear_vars.mk
文件。
这个文件中,把单模块编译中,系统可能用到变量全部清理一遍。
如果想知道单模块编译时,可以指定哪些模块变量LOCAL_*
,可以查看该文件。
LOCAL_PATH
不是系统支持的模块变量,而是一个约定俗成的自定义变量,所以可以在调用CLEAR_VARS
前指定。
其它自定义变量,不推荐使用LOCAL_
作为前缀,防止和模块变量雷同。
my-dir
是在build/core/definitions.mk
定义的函数,通过Android.mk的位置来计算模块的路径。
如果想知道其它的常用函数,可以查看该文件。
由于Makefile天生的问题,不支持命名空间。
在需要反复使用一组变量时,要么给每一组变量不同的命名,要么必须反复初始化。
这里,Android使用的方法是后者。
所以在开始编译一个新模块前,必须清理模块变量LOCAL_*
,以免受上一模块的干扰。
接下来,就可以开始指定这些变量。
指定模块变量 ¶
对于一个Android模块来说,最重要的是名称、AndroidManifest.xml、源码、资源文件、编译依赖。
LOCAL_MODULE
就是模块名,不能和既有模块相同。
如果该变量未设置,则使用LOCAL_PACKAGE_NAME
。
如果再没有,就会编译失败。
LOCAL_MANIFEST_FILE
可以指定AndroidManifest.xml的位置。
如果未设置,则默认使用Android.mk的相同路径下的AndroidManifest.xml文件。
LOCAL_SRC_FILES
是指定源文件列表。
这里用$(call all-java-files-under, src)
来指定大部分文件。
all-java-files-under
也是定义在definitions.mk中的函数。
LOCAL_RESOURCE_DIR
是指定资源文件的目录。
LOCAL_JAVA_LIBRARIES
和LOCAL_STATIC_JAVA_LIBRARIES
,则指定了所依赖的共享与静态Java模块。
除了这些比较核心的变量以外,还有几十个其它变量可以按需指定。
调用编译文件 ¶
include $(BUILD_PACKAGE)
调用build/core/package.mk
文件,执行APK的编译。
前面指定的模块变量LOCAL_*
,都是为这一句而准备。
这些用来给Android.mk调用的文件及其变量,定义在build/core/config.mk
中。
BUILD_COMBOS:= $(BUILD_SYSTEM)/combo
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_NATIVE_BENCHMARK := $(BUILD_SYSTEM)/native_benchmark.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk
BUILD_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/shared_test_lib.mk
BUILD_HOST_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/host_shared_test_lib.mk
BUILD_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/static_test_lib.mk
BUILD_HOST_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/host_static_test_lib.mk
BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
根据以上代码,整理相关信息为以下表格。 其中,加粗的几个是自定义模块里比较常用的变量。 说明中的主机是指编译Android的机器,而设备是指安装Android的机器。
include 变量 | Makefile | 说明 |
---|---|---|
BUILD_HOST_STATIC_LIBRARY | host_static_library.mk | 编译主机上的静态库。 |
BUILD_HOST_SHARED_LIBRARY | host_shared_library.mk | 编译主机上的共享库。 |
BUILD_STATIC_LIBRARY | static_library.mk | 编译设备上的静态库。 |
BUILD_SHARED_LIBRARY | shared_library.mk | 编译设备上的共享库。 |
BUILD_EXECUTABLE | executable.mk | 编译设备上的可执行文件。 |
BUILD_HOST_EXECUTABLE | host_executable.mk | 编译主机上的可执行文件。 |
BUILD_PACKAGE | package.mk | 编译APK文件。 |
BUILD_PHONY_PACKAGE | phony_package.mk | 定义一个不能实际编译的虚假模块。可以指定依赖。 |
BUILD_HOST_PREBUILT | host_prebuilt.mk | 处理一个或多个主机上使用的已编译文件,该文件的实现依赖 multi_prebuilt.mk。 |
BUILD_PREBUILT | prebuilt.mk | 处理一个已经编译好的文件( 例如Jar包)。 |
BUILD_MULTI_PREBUILT | multi_prebuilt.mk | 处理一个或多个已编译文件,该文件的实现依赖prebuilt.mk。 |
BUILD_JAVA_LIBRARY | java_library.mk | 编译设备上的共享Java库。 |
BUILD_STATIC_JAVA_LIBRARY | static_java_library.mk | 编译设备上的静态Java库。 |
BUILD_HOST_JAVA_LIBRARY | host_java_library.mk | 编译主机上的共享Java库。 |
BUILD_DROIDDOC | droiddoc.mk | 编译生成droiddoc或javadoc文件。 |
BUILD_COPY_HEADERS | copy_headers.mk | 复制相关的头文件,被static_library.mk等文件使用,通常不直接include。 |
BUILD_NATIVE_TEST | native_test.mk | 编译设备上的可执行文件测试。 |
BUILD_NATIVE_BENCHMARK | native_benchmark.mk | 添加libbenchmark,编译设备上的可执行文件。 |
BUILD_HOST_NATIVE_TEST | host_native_test.mk | 主机上的可执行文件测试。 |
BUILD_SHARED_TEST_LIBRARY | shared_test_lib.mk | 设备上共享库的测试。 |
BUILD_HOST_SHARED_TEST_LIBRARY | host_shared_test_lib.mk | 主机上共享库的测试。 |
BUILD_STATIC_TEST_LIBRARY | static_test_lib.mk | 设备上静态库的测试。 |
BUILD_HOST_STATIC_TEST_LIBRARY | host_static_test_lib.mk | 主机上静态库的测试。 |
BUILD_NOTICE_FILE | notice_files.mk | 追踪、生成版权相关的NOTICE文件。 |
BUILD_HOST_DALVIK_JAVA_LIBRARY | host_dalvik_java_library.mk | 编译主机上的Dalvik共享Java库。 |
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY | host_dalvik_static_java_library.mk | 编译主机上的Dalvik静态Java库。 |
其它 ¶
在Settings这个模块的Android.mk中,还有几个有趣的地方。
编译额外模块 ¶
# Use the following include to make our test apk.
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif
在mm
这类单模块编译的情况下,再额外include当前目录下的所有其它Android.mk,比如测试APK。
增量编译 ¶
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_JACK_ENABLED := incremental
endif
在增量编译的情况下,禁用混淆、启用Jack工具链的增量编译功能。
引用其它mk文件 ¶
include frameworks/opt/setupwizard/navigationbar/common.mk
include frameworks/opt/setupwizard/library/common.mk
include frameworks/base/packages/SettingsLib/common.mk
引用这三个文件,目的是添加额外的资源文件、编译依赖与aapt
参数。
这是一种比较高明的方式来使用自定义的模块。
总结 ¶
Android.mk是Android的编译系统中,独立模块的编译配置文件。 优点突出,兼容Makefile语法、可读性也还不错,代码量也不太高。 缺点也很明显,好读不好写,不过好在有很多既有文件可以借鉴。
在7.0以后的项目,开始逐渐使用更简洁的Android.bp,利用blueprint转换成Ninja,参加编译。