AOSP的的编译过程复杂就在于要编译很多种类型的目标文件,不像kernel那样,只需要一种编译工具就能完成,在AOSP中主要包括以下几种目标文件:


1. APK程序,一般的Android程序,编译打包生成apk文件


2. Java库,java类库,编译打包生成jar文件


3. C\C++应用程序,可执行的C\C++应用程序


4. C\C++静态库,编译生成C\C++静态库,并打包成.a文件


5. C\C++共享库, 编译生成共享库(动态链接库),并打包成.so, 有且只有共享库才能被安装/复制到您的应用软件(APK)包中。


大量的源码按照功能通过目录来分类,同一功能的代码通常被编译成一个目标文件,目标文件不仅仅包含可执行C/C++应用程序,还包含动态库、静态库、 Java类库、Android应用程序等,在Android编译系统中,每个被编译的目标文件被称为一个模块(module),在每个模块的源码目录中必须创建一个Android.mk文件作为编译规则,这些Android.mk文件在编译时被编译系统中的findleaves.py脚本包含进去。


下面是 build/core/main.mk 中的内容:


#
# 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)))


注:findleaves.py由 Python语言编译的脚本,Python是一种执行效率比较高的面向对象的脚本,上述脚本意思是返回subdirs目录下的Android.mk文件,但是会跳过out、.reop、.git目录。


通常编译一个模块时编译器需要知道以下内容:


1. 编译什么文件?(指定源码目录和源码文件)


2.编译器需要哪些编译参数?


3.编译时需要哪些库或头文件?


4.如何编译?(编译成动态库、静态库、二进制程序、Android应用还是Java库?)


5. 编译目标


Android.mk的语法不同于Makefile,Android.mk语法更简洁,用户只需在Android.mk中定义出一些编译变量,Android的编译系统会根据Android.mk文件中变量的值进行编译。


比如Zygote进程app_process模块中的Android.mk如下面代码所示:


@ frameworks/base/cmds/app_process/Android.mk
 LOCAL_PATH:= $(call my-dir)              #指定源码目录
 include $(CLEAR_VARS)                     #包含清除编译变量的mk文件,防止影响本次编译
 
 LOCAL_SRC_FILES:= \                      #指定被编译源码
      app_main.cpp
 
 LOCAL_SHARED_LIBRARIES := \               #指定编译Zygote时用到的其它动态库
      libcutils \
      libutils \
      libbinder \
      libandroid_runtime
 
 LOCAL_MODULE:= app_process                #指定被编译模块的名字
 
 include $(BUILD_EXECUTABLE)                  #指定编译方式,编译成可执行程序


再比如Camera应用程序中的Android.mk:


@ packages/apps/Camera/Android.mk
 LOCAL_PATH:= $(call my-dir)                        #指定源码目录
 include $(CLEAR_VARS)                              #包含清除编译变量的mk文件,防止影响本次编译
 
 LOCAL_MODULE_TAGS := optional                      #指定应用程序标签
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)      #指定被编译源码
 
 LOCAL_PACKAGE_NAME := Camera                   #指定Android应用程序名
 LOCAL_SDK_VERSION := current                        #指定该应用程序依赖的SDK版本
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags    #指定混淆编译配置文件

include $(BUILD_PACKAGE)                        #指定模块编译方式,这儿编译成Android应用程序

 # Usethe following include to make our test apk.
 include $(call all-makefiles-under,$(LOCAL_PATH))        # 包含当前目录下子目录中的Android.mk文件,向下编译


通过上面两个例子可以看出来,Android.mk文件结构很简单,每个模块的Android.mk文件必须完成以下操作:


1. 指定当前模块的目录


通过调用$(call my-dir)命令包(一些Makefile的集合),来获得当前模块目录。


2.清除所有的LOCAL_XX变量


通过include命令包含clear_vars.mk文件来清除所有的LOCAL_XX变量,防止影响本次编译结果,clear_vars.mk文件由变量CLEAR_VARS来定义


3. 指定源码文件


通过LOCAL_SRC_FILES变量指定源码文件,对于C/C++文件,要将它们全部列出来赋值给LOCAL_SRC_FILES(见上面程序代码),对于Java源码,可以通过调用命令包$(callall-java-files-under, src)来实现,它会在src目录下查找所有的Java文件,将其罗列出来。


4. 指定编译细节


在编译时可能需要修改编译器参数、需要链接其它的库、需要其它路径下的头文件等编译细节。


5. 指定目标模块名


如果是C/C++库、可执行程序或Java类库,通过LOCAL_MODULE指定最终编译出来的模块名,如果是Android应用程序,通过LOCAL_PACKAGE_NAME变量来指定。


6. 指定目标模块类型


模块最终都要进行编译,通过include 命令包含一些预定义好的变量来指定模块最终的类型,这些变量分别对应一个makefile文件,包含了模块类型的编译过程。主要的预定义编译变量如下:


编译变量

功能

BUILD_SHARED_LIBRARY

将模块编译成共享库

BUILD_STATIC_LIBRARY

将模块编译成静态库

BUILD_EXECUTABLE

将模块编译成可执行文件

BUILD_JAVA_LIBRARY

将模块编译成Java类库

BUILD_PACKAGE

将模块编译成Android应用程序包


注:上述编译变量的定义在build/core/definitions.mk中。


在Android.mk中,主要编译变量如下表所示:


编译变量

功能

LOCAL_PATH

指定编译路径

LOCAL_MODULE

指定编译模块名

LOCAL_SRC_FILES

指定编译源码列表

LOCAL_SHARED_LIBRARIES

指定使用的C/C++共享库列表

LOCAL_STATIC_LIBRARIES

指定使用的C/C++静态库列表

LOCAL_STATIC_JAVA_LIBRARIES

指定使用的Java库列表

LOCAL_CFLAGS

指定编译器参数

LOCAL_C_INCLUDES

指定C/C++头文件路径

LOCAL_PACKAGE_NAME

指定Android应用程序名

LOCAL_CERTIFICATE

指定签名认证

LOCAL_JAVA_LIBRARIES

指定使用的Java库列表

LOCAL_SDK_VERSION

指定编译Android应用程序时的SDK版本


=========================分割线==========================


搞清楚Android.mk文件后,接下来需要研究我们编译时使用m,mm,mmm命令的执行原理了,AOSP中并没有像Make系统中通过 -f 参数指定Makefile文件,而是采用了自有的一套机制来完成,具体实现也比较复杂,下面这篇文章讲得非常详细: