Android.mk的深入介绍
2017-08-09 16:11:24 +08
字数:3945
标签:
Android
Makefile
Android.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,参加编译。
参考 ¶