from:http://blog.chinaunix.net/uid-29494667-id-4116547.html
1.概述
Android.mk编译文件是用来向Android NDK描述你的C,C++源代码文件的。具体来说:
该文件是GNU Makefile的一小部分,会被编译系统解析一次或更多次的build系统。因此,您应尽量减少您声明的变量,不要认为某些变量在解析过程中不会被定义。
这个文件的语法允许把你的源代码组织成模块,一个模块属下列类型之一:
静态库
共享库
只有共享库将被安装/复制到您的应用软件包。虽然静态库能被用于生成共享库。你可以在每一个Android.mk file中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。
2.样例简析
在描述语法细节之前,咱们来看一个简单的"hello world"的例子,比如,下面的文件:
sources/helloworld/helloworld.c
sources/helloworld/Android.mk
'helloworld.c'是一个JNI共享库,实现返回"hello world"字符串的原生方法。
相应的Android.mk文件会象下面这样:
---------- cut here ------------------
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= helloworld
LOCAL_SRC_FILES := helloworld.c
include $(BUILD_SHARED_LIBRARY)
---------- cut here ------------------
好,我们来解释一下这几行代码:
LOCAL_PATH:= $(call my-dir)
一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。
include $( CLEAR_VARS)
CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),
除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。
LOCAL_MODULE := helloworld
LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'foo'的共享库模块,将会生成'libfoo.so'文件。
重要注意事项
如果你把库命名为‘libhelloworld’,编译系统将不会添加任何的lib前缀,也会生成libhelloworld.so,这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。
LOCAL_SRC_FILES := helloworld.c
LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。【注意,默认的C++源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是定义为‘.cxx’,而不是‘cxx’)(当然这一步我们一般不会去改它)】
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY是编译系统提供的变量,指向一个GNU Makefile脚本(应该就是在build/core目录下的shared_library.mk),负责收集自从上次调用'include $(CLEAR_VARS)'以来,定义在LOCAL_XXX变量中的所有信息,并且决定编译什么,如何正确地去做。并根据其规则生成静态库。同理对于静态库。
3.深入分析
下面我们尝试从一个小模块逐步对android build system做一个深入剖析。选择的这个模块名字叫做acp ,源码位于build\tools\acp目录。
后续很多模块的编译都需要使用到acp,根据编译依赖一般会先编译本模块。当然它也需要依赖到其他文件,需要的时候我们再进行阐述。
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的基本组成。
- 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了
编译目标
编译目标 | 说明 |
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程序 |
有人就问了,在本Android.mk中又没有使用到LOCAL_PATH,为什么先 要定义这么一个变量呢?为什么规定必须放在所有的include $(CLEAR_VARS)之前呢?
Note that source files names are all relative to LOCAL_PATH and you can use path components .
- <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
- $(if $(filter $(CLEAR_VARS),$(md_file_))
- CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
- BUILD_SYSTEM := $(TOPDIR)build/core
- TOPDIR :=
讨论完LOCAL_PATH,我们紧接着来看看LOCAL_SRC_FILES。
LOCAL_SRC_FILES:
- LOCAL_SRC_FILES := acp.c
- ###########################################################
- ## 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
- 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)
- # 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_MODULE value acp
LOCAL_IS_HOST_MODULE value true
- ###########################################################
- ## 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
- $(eval _idfIntBase := $($(_idfPrefix)_OUT_INTERMEDIATES))
- HOST_OUT_INTERMEDIATES := $(HOST_OUT)/obj
- HOST_OUT := $(HOST_OUT_$(HOST_BUILD_TYPE))
- # 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 := $(OUT_DIR)/host
- 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