makefile文件控制整个工程的编译规则,比如指定需要生成哪些目标文件,指明生成这些目标文件依赖哪些源文件,指明生成的目标文件放在哪个文件夹下等等。而make就是一个命令工具,可以解析makefile文件中的指令的一个命令工具。
android.mk也是一样的功能,只不过它是android编译环境下的一种特殊的“makefile”文件, 它是经过了android编译系统处理的。所谓android编译系统,就是android顶层目录下的build目录里面的一系列编译控制文件,其实就是一系列makefile文件和 *.mk 文件,这些文件才是编译android系统完整的makefile文件.每个模块里的android.mk只不过是被包含进android编译系统的一小部分而已。经过android编译系统的一大堆处理,android.mk的格式就变得非常简单,且与普通的makefile文件书写格式不一样了,但这有利于为Android增加一个新的Component。
但归根结底,android.mk文件最终还是要被android编译系统层层解析,转化为make命令能够读懂的格式,从而调用gcc编译器进行编译。
先写一个最简单的普通makefile,工作平台:64位Ubuntu系统。
建立一个hello文件夹,假设路径是/home/username/Desktop/hello,在其中新建hello.c文件,内容如下:
#include <stdio.h>
void main ()
{
printf("hello world!\n");
}
再新建一个文本文件命名为Makefile,内容如下:
hello: hello.o
gcc -o hello hello.o
hello.o:hello.c
gcc -c hello.c
$(warning hello in the end!)
clean:
-rm hello.o hello
然后打开terminal进入hello目录运行make命令就可以看到,生成了hello.o和hello两个文件。
可以看到对于普通的makefile文件,格式为
target ... : prerequisites ...
command
这里的command就是 gcc -o hello hello.o gcc -c hello.c -rm hello.o hello 这三个。
可是在android.mk中 是看不到这种编译命令的 那么android模块是在哪里被编译的呢,其实编译命令是有的,只不过是被隐藏在了android编译系统中 。
先写一个简单的android.mk,进入事先下载好的Android源代码目录,在external目录下新建hello目录,新建hello.c文件,内容和刚才的一样。再新建一个文本文件命名为Android.mk。内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES:= hello.c
LOCAL_MODULE_TAGS:= optional
include $(BUILD_SHARED_LIBRARY)
当我们在android源代码目录中的external/hello/目录下运行mm命令时。就会执行mm函数,这个函数定义在android源代码根目录下的build目录中的envsetup.sh脚本文件中,是一段控制脚本。内容如下:
function mm()
{
# If we're sitting in the root of the build tree, just do a
# normal make.
if [ -f build/core/envsetup.mk -a -f Makefile ]; then
make $@
else
# Find the closest Android.mk file.
T=$(gettop)
local M=$(findmakefile)
# Remove the path to top as the makefilepath needs to be relative
local M=`echo $M|sed 's:'$T'/::'`
if [ ! "$T" ]; then
echo "Couldn't locate the top of the tree. Try setting TOP."
elif [ ! "$M" ]; then
echo "Couldn't locate a makefile from the current directory."
else
ONE_SHOT_MAKEFILE=$M make -C $T all_modules $@
fi
fi
}
(这个envsetup.sh脚本需要在运行mm之前手动运行一遍,否则将提示找不到mm命令)
红色部分就开始调用make拉,不过这时候还没有进入到我们hello模块的Android.mk,而是进入到了android顶层目录的makefile,这是由make后面的 -C $T 指定的,因为T的值打印出来就是android顶层目录的路径!
那么什么时候才开始进入模块自己的Android.mk呢?继续往下追踪发现,android顶层目录下的Makefile只有三行:
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
它只是将另一个main.mk文件的内容包含进来了,这个文件在android顶层目录下的/build/core/目录下,虽然文件后缀为mk,但被makefile文件包含进来后还是按照makefile文件的格式来解析。(这是我推测的,不过应该就是这样)
然后再进入main.mk查看,其中有一段如下:
include $(ONE_SHOT_MAKEFILE)
这个ONE_SHOT_MAKEFILE变量的值就是我们的模块的Android.mk文件的路径。。这个变量的赋值就是在envsetup.sh脚本的mm函数中:ONE_SHOT_MAKEFILE=$M 这个M变量的值就是Android.mk文件的路径。它是在
mm函数里的local M=$(findmakefile)里赋值的。
然后就进入到我们自己写的Android.mk文件,内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES:= hello.c
LOCAL_MODULE_TAGS:= optional
include $(BUILD_SHARED_LIBRARY)
前面几行主要就是给几个变量赋值,这些变量是由Android编译系统自己定义的,Android编译系统就是android顶层目录下的/build/目录下的一系列文件。我们的Android.mk文件在被include进来之前,这些变量就已经在别的文件中创建好了。最后一行又include进来一个$(BUILD_SHARED_LIBRARY),这又是一个.mk文件。。BUILD_SHARED_LIBRARY的值为/build/core/shared_library.mk因此 相当于include /build/core/shared_library.mk
这个shared_library.mk是在/build/core/目录下的,它是属于android编译系统的一个文件。
再跟踪这个shared_library.mk 其中有一行:include $(BUILD_SYSTEM)/dynamic_binary.mk,这也是android编译系统的一个文件,位于/build/core/目录下。
进入dynamic_binary.mk文件查看,有一行:include $(BUILD_SYSTEM)/binary.mk,它在同样的目录中,再查看这个文件,这个文件就开始调用gcc编译器准备编译我们的hello模块拉,它怎么知道要编译我们的hello模块呢?编译要依赖哪些源文件呢?前面不是已经在Android.mk中给出了嘛。
在binary.mk中如下一段指令就是调用编译器编译的指令:
cpp_arm_sources := $(patsubst %$(LOCAL_CPP_EXTENSION).arm,%$(LOCAL_CPP_EXTENSION),$(filter %$(LOCAL_CPP_EXTENSION).arm,$(LOCAL_SRC_FILES)))
cpp_arm_objects := $(addprefix $(intermediates)/,$(cpp_arm_sources:$(LOCAL_CPP_EXTENSION)=.o))
cpp_normal_sources := $(filter %$(LOCAL_CPP_EXTENSION),$(LOCAL_SRC_FILES))
cpp_normal_objects := $(addprefix $(intermediates)/,$(cpp_normal_sources:$(LOCAL_CPP_EXTENSION)=.o))
$(cpp_arm_objects): PRIVATE_ARM_MODE := $(arm_objects_mode)
$(cpp_arm_objects): PRIVATE_ARM_CFLAGS := $(arm_objects_cflags)
$(cpp_normal_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
$(cpp_normal_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
cpp_objects := $(cpp_arm_objects) $(cpp_normal_objects)
ifneq ($(strip $(cpp_objects)),)
$(cpp_objects): $(intermediates)/%.o: \
$(TOPDIR)$(LOCAL_PATH)/%$(LOCAL_CPP_EXTENSION) \
$(yacc_cpps) $(proto_generated_headers) $(my_compiler_dependencies) \
$(LOCAL_ADDITIONAL_DEPENDENCIES)
$(transform-$(PRIVATE_HOST)cpp-to-o)
-include $(cpp_objects:%.o=%.P)
endif
红色部分就是编译命令,和前面makefile里的
target ... : prerequisites ...
command
其实是一样的格式,只不过这里用了很多变量而已,将这些变量展开就行了。
$(transform-$(PRIVATE_HOST)cpp-to-o)这句其实是调用了/build/core/definitions.mk里的一段命令:查看如下
###########################################################
## Commands for running gcc to compile a C++ file
###########################################################
define transform-cpp-to-o
@mkdir -p $(dir $@)
@echo "target $(PRIVATE_ARM_MODE) C++: $(PRIVATE_MODULE) <= $<"
$(hide) $(PRIVATE_CXX) \
$(addprefix -I , $(PRIVATE_C_INCLUDES)) \
$(addprefix -isystem ,\
$(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
$(filter-out $(PRIVATE_C_INCLUDES), \
$(PRIVATE_TARGET_PROJECT_INCLUDES) \
$(PRIVATE_TARGET_C_INCLUDES)))) \
-c \
$(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
$(PRIVATE_TARGET_GLOBAL_CFLAGS) \
$(PRIVATE_TARGET_GLOBAL_CPPFLAGS) \
$(PRIVATE_ARM_CFLAGS) \
) \
$(PRIVATE_RTTI_FLAG) \
$(PRIVATE_CFLAGS) \
$(PRIVATE_CPPFLAGS) \
$(PRIVATE_DEBUG_CFLAGS) \
$(if $(filter n,$(MEMDECT_LEAK)),,-DMEMDECT_LEAK=1) \
-MD -DQCOM -DGAIA -MF $(patsubst %.o,%.d,$@) -o $@ $<
$(transform-d-to-p)
endef
变量展开后就是标准的调用gcc编译器编译的指令了,很多变量展开后是传递给编译器的参数。至此,在/out/target/product/generic/obj/SHARED_LIBRARIES/hello_intermediates/中,就可以看到刚刚编译生成的hello.o文件拉。
总结:Android.mk只不过是被android编译系统包含的一种文件,需要在android编译系统的支持下解析。本质上android模块在编译时最终还是使用make命令解析一大堆makefile 和*.mk文件(有些文件以.mk为后缀,前面说过应该也是按照makefile文件的规则来解析)。