Android.mk文件分析

原博客地址:http://blog.csdn.net/a345017062/article/details/6130264 (邏輯清楚,簡單易懂)


從對Makefile一無所知開始,折騰了一個多星期,終於對Android.mk有了一個全面些的瞭解。瞭解了標準的Makefile後,發現Android.mk其實是把真正的Makefile包裝起來,做成了一個對使用者來說很簡單的東西。使用它來編譯程序時,不管是動態庫、可執行的二進制文件,還是Jar庫、APK包,只要沿着一個簡單的思路來做三大步就可以了:清除舊變量,設置新變量,調用編譯函數。

 

明白了以後,發現Makefile語法不是問題,有很多教程和高手。編譯模塊時如何清除變量、調用編譯函數等也不是問題,源碼當中無處不在這樣的例子。而對初學者來說,更需要明白的可能是,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中。

 

萬事開頭難,有了上面的思路,再看這些腳本就很簡單了。一路順風,呵。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章