Android.mk文件分析

来源:互联网 发布:硅片切片机编程 编辑:程序博客网 时间:2024/04/25 18:21

一个Android.mk file用来向编译系统描述你的源代码。具体来说:该文件是GNU Makefile的一小部分,会被编译系统解析一次或多次。你可以在每一个Android.mk file中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。编译系统为你处理许多细节问题。

一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。makefile 文件是许多编译器--包括 Windows NT 下的编译器--维护编译信息的常用方法,只是在集成开发环境中,用户通过友好的界面修改 makefile 文件而已。

下面直接看一个实例:

Jni 文件树 如下


Android.mk文件 如下:


一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。

2、include $( CLEAR_VARS)

CLEAR_VARS 由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。

3、LOCAL_MODULE :=  HcSyncml

LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包 含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'HcSyncml'的共享库模块,将会生成'libHcSyncml.so'文件。

4、LOCAL_C_INCLUDES := $(LOCAL_PATH)/extra_inc$(LOCAL_PATH)/main_inc

LOCAL_C_INCLUDES 中加入所需要包含的头文件路径

5、LOCAL_SRC_FILES

LOCAL_SRC_FILES中加入源文件路径(需要编译的文件),多个文件用 ‘\’ 隔开

6、LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib –llog

表示允许打印Log


对初学者来说,更需要明白的可能是,Android如何让使用脚本的人从Makefile语法当中解放出来,简单地按照上面的三大步就可以编译出任何模块。

 

拿AlarmClock来做例子的话:

//清除旧变量

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

//设置新变量

LOCAL_SRC_FILES := $(call all-subdir-java-files)

LOCAL_PACKAGE_NAME := AlarmClock

//调用编译函数

include $(BUILD_PACKAGE)

下面简单解释一下这三步:

1、清除旧变量,是因为Android.mk中所有的变量都是全局的,编译函数在编译时会调用这些变量。为了防止编译函数使用了编译其它模块时设置的变量,每次开始编译一个新的模块时清除所有的变量是个好习惯。

2、设置新变量就是把本次编译时用到的源码地址,包名等设置好。

3、调用编译函数其实就是include一个固定的mk文件,这个mk文件会根据设置的变量提取出编译模块需要的target,Command等信息并执行固定的编译命令。

 

看明白Android.mk后,最深的感受还是Android的这种封装思想,让我想起了很多以前没有思考过的东西。呵。。。

我会一些个人的理解记录Android一步步地演化出这种封装思想。真诚地欢迎批评指正。

 

Makefile这个东西,往最简单处说,就是这样的模式:

目标:信赖文件

执行命令

这里暂不考虑它的语法细节,举个不完整的例子,就是这样的:

AlarmClock.apk:AlarmClock.java

javac AlarmClock.java

java AlarmClock.class

要生成AlarmClock.apk,就需要AlarmClock.java这个信赖文件,然后执行后面两行的两个命令。假如我们简单粗暴地用这种写法去编译Android系统的话,会相当累。Android.mk就很巧妙地把它们进行了抽象。我们现在来模拟一下:

定义三个变量target,source,command,分别代表编译目标、信赖文件和编译命令,就成了这样:

$(target):$(source)

$(command)

这三个变量的值分别是:

target := AlarmClock.apk

source := AlarmClock.java

commnd := /

javac AlarmClock.java/

java AlarmClock.class

这样的话,在编译每个APK时,只要分别给target、source、command赋值,然后再这样写就可以了:

$(target):$(source)

$(command)

嗯,还是有点儿麻烦,那再抽象一次。定义一个变量name,让它存放要编译的模块的名字:

name := AlarmClock

然后再更改一下前面的三个变量:

target := $(name).apk

source := $(name).java

commnd := /

javac $(name).java/

java $(name).class

现在要编译一个模块Contacts需要做些什么呢?

name := Contacts

$(target):$(source)

$(command)

三行代码就可以了,很简单,对吧?但后面两行还是每次都要写,那再抽象。把后面两行放到一个build_apk.mk文件当中。然后定义一个变量:

BUILD_PACKAGES := build_apk.mk

全部完了,这回编译一个模块Contacts时只要这样做就可以了:

name := Contacts

include $(BUILD_PACKAGES)

一个不够复杂,那就多点儿:

name := Contacts

include $(BUILD_PACKAGES)

name := Phone

include $(BUILD_PACKAGES)

name := Email

include $(BUILD_PACKAGES)

//================强壮的分隔线=========

现在我们来完善一下上面的过程,把它系统化。

定义一个专门用来清除变量的clear_vars.mk里面的内容如下:

name :=

target :=

source :=

command :=

再定义一个变量CLEAR_VARS := clear_vars.mk。上面的编译脚本就变成了这样:

include $(CLEAR_VARS)

name := Contacts

include $(BUILD_PACKAGES)

include $(CLEAR_VARS)

name := Phone

include $(BUILD_PACKAGES)

include $(CLEAR_VARS)

name := Email

include $(BUILD_PACKAGES)

这回,清除旧变量、设置新变量、调用编译函数三大步全都有了。

 

好了,现在我们进入到Android源码的build/core/目录下。先看其中的main.mk,config.mk这两个文件。

main.mk里面是具体的脚本,在这里控制编译模块。

config.mk中定义了下面这样的文件调用:

CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk

BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk

编译模块时,每一个include其实都是一次功能调用,或者对mk文件的调用,或者对变量和函数的调用。做为单独的功能模块抽象出来的mk文件都在build/core/目录下,而函数的定义全部在definitions.mk中。


原创粉丝点击