做過 Android 平台開發的朋友對make,mm或make clean指令應該很熟悉,但也許大家隻是熟知這些指令的作用卻不知道這些指令底下有些什麼原理?那麼今天我就帶着大家推開Android編譯系統的大門,探索一下這片未知的恐怖之森(問啥要用恐怖之森後面大家就知道了)。
Makefile入門
在講解Android編譯系統之前首先來了解一下什麼是Makefile:
簡單的說,Makefile提供了一種機制,讓使用者可以有效的組織工作。
注意這裡用的是“工作”而非“編譯”,這是因為Makefile并不是用來完成編譯工作的,它隻是一種規則的執行者,而使用者用它來執行什麼規則沒有任何限制。如既可以用它來編譯系統,也能用它備份文檔或隻是列印log。
既然它是用來執行規則的,那麼我們就來看看Makefile的規則。
TARGET:PREREQUISITES
COMMANDS
注意:COMMANDS前面必須有一個TAB制表符
在 Makefile 的規則中TARGET是需要生成的目标檔案,PREREQUISITES是目标的先決條件,也是另一個規則中的target。COMMANDS是生成目标檔案的指令,當 prerequisites 中的任何一個檔案比 target 檔案要新的時候就會觸發 commands。commands 的具體内容取決于使用者的需求,如調用 gcc 編譯器。
下面我們用一個簡單的例子說明一下Makefile規則的用法。
涉及到的檔案如下:
檔案名
描述
mian.c
主函數所在檔案
test.h
提供一個測試函數getNumber的聲明
test.c
提供getNumber的函數實作,用于傳回一個值
Makefile
我們的主角,用于編譯整個例子
檔案内容如下:
(1)test.h對getNumber進行聲明。
int getNumber();
(2)test.c實作getNumber函數
#include "test.h"
int getNumber()
{
return 2333;
}
(3)main.c列印getNumber的傳回值
#include
#include "test.h"
int main()
{
printf("Hello,getNumber=%d\n",getNumber());
return 0;
}
(4)Makefile檔案,用于組織這個小例子的編譯工作
MakefileTest : main.o test.o
gcc -o MakefileTest main.o test.o
main.o : main.c
gcc -c main.c
test.o : test.c
gcc -c test.c
對上面這段代碼簡單解釋一下,MakefileTest為TAEGET即目标産物,main.o和test.o是MakefileTest的先決條件。gcc -o MakefileTest main.o test.o則是MakefileTest對應的COMMANDS,這條指令使用gcc指令将main.o和test.o編譯成MakefileTest可執行檔案。
最後通過使用make指令就會在目前目錄生成一個MakefileTest可執行檔案,運作結果如下:

這裡寫圖檔描述
上面這個是一個非常簡單的Makefile的示例,但“麻雀雖小,五髒俱全”,它清晰的展示了一個Makefile的編寫過程。另外Make工具本身是非常強大的,它有很多隐含的規則來幫助開發者快速搭建複雜的編譯體系。如利用它的自動推導功能可以簡化對象間的依賴關系,還可以加入變量來減少重複輸入。
上面的Makefile還可以寫成這樣:
OBJECT = main.o test.o
MakefileTest : $(OBJECT)
gcc -o MakefileTest $(OBJECT)
關于Makefile的更多用法可以檢視how to write makefile。希望大家多了解一下makefile的用法,這有助于了解Android的編譯系統。
Android編譯系統入門
像我這樣從事過Android平台開發的開發人員對make,mm等指令應該很熟悉,同樣也知道Android.mk檔案,用它結合mm指令來編譯單個的Android子產品。如果這就是你對Android編譯系統的全部了解,那隻能說你是一個普通的Android平台開發人員。想要在Android平台開發界混的話,學習和掌握Android編譯系統的原理将是必不可少的。前面說道Android編譯系統是一個恐怖之森,應為它真好符合恐怖之森的兩個特點,讓人望而卻步和容易迷失方向。這麼說一點也不過分,在學習Android編譯系統的時候,如果缺少很明确的指引很容易迷失方向,或者讓你根本就不敢去試着闖一闖。
makefile依賴樹的概念
不難發現在makefile中的target的依賴關系實際上可以組成一棵樹,我将其稱為makefile依賴樹,仍然以上一個MakefileTest為例,給出它的makefile依賴樹:
這裡寫圖檔描述
隻要照個這個樹形結構順藤摸瓜,那麼分析Android編譯系統也就變得有目的性了。
恐怖之森裡的樹
有了上面的知識做鋪墊,我們也就可以大膽的往恐怖之森闖了。
這裡寫圖檔描述
根節點
既然是樹,肯定有它的根節點,我們就來找一找Android編譯系統的根節點。
如果make指令沒有指定檔案的話預設會在目前目錄尋找Makefile這個檔案,是以先看看Android源碼根目錄的那個Makefile檔案:
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
原來這裡隻是指路牌,真正的檔案是build/core/main.mk。
在 Makefile 使用 include 關鍵字可以把别的 Makefile 包含進來,這很像 C 語言的
\#include,被包含的檔案會原模原樣的放在目前檔案的包含位置。
打開main.mk發現它有上千行代碼,并且在其中又包含了很多其他makefile腳本,是以整個檔案的内部結構讓人感覺雜亂無章,無法入手。這時候我們就不能一行行的去讀這個檔案,這樣很可能會事倍功半。我們需要找出它其中依賴樹的根節點,以此為突破口。但往往大型的工程中不止一棵依賴樹,這時候如果沒有指定依賴樹的話make指令會以從上至下第一個target作為預設依賴樹的根節點。其實大家非常熟悉的make clean中的clean就是這些依賴樹中的一棵。
.PHONY: clean
clean:
@rm -rf $(OUT_DIR)/*
@echo "Entire build directory removed."
clean是一個僞目标,僞目标一般沒有目标檔案。且使用.PHONY顯示聲明。
從上面clean的COMMANDS知道它的作用是删除\$(OUT_DIR)下的所有目錄和檔案,而\$(OUT_DIR)就是out目錄。
OK,那麼我們就跟着上面的思路找尋那棵預設依賴樹,對照main.mk代碼很快就發現了它的預設依賴樹根節點:
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):
從注釋中可以看出來droid就是我們找的依賴樹的根節點,不過這裡隻是定義了一下,沒有給出真正的規則。根據關鍵字搜尋的話會發現main.mk中有幾處對droid規則的定義:
ifneq ($(TARGET_BUILD_APPS),)
# If this build is just for apps, only build apps and not the full system by default.
...
.PHONY: apps_only
apps_only: $(unbundled_build_modules)
droid: apps_only
...
else # TARGET_BUILD_APPS
...
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files
上面會根據TARGET_BUILD_APPS變量的值是否為空來走不同的分支,如果不為空,則droid的先決條件是apps_only,如果為空droid的先決條件是droidcore和dist_files,我們使用預設的make指令編譯android系統的話這裡的TARGET_BUILD_APPS的值為空,是以走下面這個分支。TARGET_BUILD_APPS何時不為空,感興趣的朋友可以自行分析一下。
main.mk總覽
在分析droidcore和dist_files兩個先決條件之前先來看一下main.mk的一個檔案結構,它除了建構droid等依賴樹外,有一大半内容是在做一下這些事情。
對編譯環境的檢測
比如java環境是否符合要求,目前是linux系統還是mac系統。如果這些檢測中有任何一項不符合要求,則會終止編譯。
進行一些必要的前期處理
比如整個項目工程是否要進行清理操作,部分工具的安裝等。
引用其他Makefile檔案
比如引用config.mk,cleanbuild.mk等。
設定全局變量
各種函數的實作
Android編譯系統中定義了很多實用的函數,它們提供了整個編譯系統的統一解決方案。比如my-dir這個在Android.mk中出鏡率最高的函數,就是用來獲得目前的路徑。
下表是對Android編譯系統中涉及的主要Makefile檔案的解釋,可供參考。
Name
Description
main.mk
整個編譯系統的主導檔案
config.mk
産品配置的主導檔案
base_rules.mk
編譯系統需要遵循的基礎規則定義
build_id.mk
版本id号的定義
cleanbuild.mk
clean操作的定義
clear_vars.mk
LOCAL開頭的相關系統變量
definitions.mk
提供了大量實用的函數定義
envsetup.mk
配置編譯時的環境變量
executable.mk
負責BUILD_EXECUTABLE的具體實作
host_executable.mk
負責BUILD_HOST_EXECUTABLE的具體實作
host_static_library.mk
負責BUILD_HOST_STATIC_LIBRARY的具體實作,其他類型的BUILD_XX這裡不再贅述
product_config.mk
産品級别的配置,屬于config的一部分
version_defaults.mk
負責生成版本資訊,如版本号BUILD_NUMBER := eng.$(USER).$(shell date +%Y%m%d.%H%M%S)
droidcore節點
droid規則的定義如下:
# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: files \
systemimage \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_FILES_FILE)
可以看出來droidcore有如下幾個先決條件,
Prerequisite
Description
files
代表其所依賴的先決條件的集合,沒有實際意義
systemimage
将生成system.img
INSTALLED_BOOTIMAGE_TARGET
将生成boot.img
INSTALLED_RECOVERYIMAGE_TARGET
将生成recovery.img
INSTALLED_USERDATAIMAGE_TARGET
将生成userdata.img
INSTALLED_CACHEIMAGE_TARGET
将生成cache.img
INSTALLED_VENDORIMAGE_TARGET
将生成vendor.img
INSTALLED_FILES_FILE
将生成install-files.txt,用于記錄目前系統中預裝的程式、庫等子產品
這幾個先決條件的生成原理類似,這裡挑幾個做重點分析。
1.files
定義如下:
```mk