本文基于AOSP的android-6.0.1_r9分支,介紹Android平臺編譯系統(tǒng)中的Makefile。
(本文轉(zhuǎn)載自本人的個人網(wǎng)站,謝絕二次轉(zhuǎn)載。原文鏈接:http://note.qidong.name/2017/08/android-6.0-makefile/)
簡介
之所以選android-6.0.1_r9這個分支,是因?yàn)檫@是最后一個純Makefile的大版本。后面隨著時間的發(fā)展,Android項目變得越來越龐大,純Makefile編譯系統(tǒng)已經(jīng)越來越不堪使用。使用Makefile,不僅擴(kuò)展不便,而且執(zhí)行效率也不太高。從7.0版本開始,Android已經(jīng)開始用Ninja來替代Makefile。
6.0版本,是Makefile最后的輝煌。Android平臺的編譯系統(tǒng),其實(shí)就是用Makefile寫出來的一個獨(dú)立項目。這個項目,不僅把分散在數(shù)百個Git庫中的代碼整合起來、統(tǒng)一編譯,而且還把產(chǎn)物分門別類地輸出到一個目錄,打包成手機(jī)ROM,更能產(chǎn)生應(yīng)用開發(fā)時所使用的SDK、NDK、網(wǎng)頁文檔等。以前,從來沒有這么大規(guī)模的一個手寫Makefile項目,以后應(yīng)該也不會再有了。
在大、中型項目都普遍使用高級工具來生成Makefile的時候,Android竟然還是手寫Makefile?,F(xiàn)在看來,這也是一件咄咄怪事。
主要文件
在Android項目根目錄,都有一個Makefile文件,其核心內(nèi)容只有一行。
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
所以,build/core/main.mk就是Android真正的Makefile入口。
在Android 6.0以前,主要的Makefile都在build/core目錄下。在Android 7.0以后,全部調(diào)整到了build/make/core中,因?yàn)閎uild目錄下,又新增了kati、soong、blueprint等項目的目錄。
Android的編譯系統(tǒng)的Makefile文件,主要分成三部分:
-
核心組件,全部在
build/core/目錄下。
這里的幾十個mk文件,是編譯系統(tǒng)的核心內(nèi)容,定義了編譯流程的框架。 -
產(chǎn)品組件,出現(xiàn)在
build/target/、device/或vendor/目錄中。
這部分的主要入口是AndroidProducts.mk文件,功能是指定一些產(chǎn)品獨(dú)特的內(nèi)容。這些額外的組件,會隨著編譯前lunch PRODUCT的PRODUCT參數(shù)而改變。 - 模塊組件,入口為各個Git庫的Android.mk。
這是普通平臺開發(fā)者最熟悉的文件,其中定義了一個模塊的必要參數(shù),使模塊跟隨平臺編譯。
本文只分析核心組件中的結(jié)構(gòu),以及build/core/下的一些重要文件。
核心組件
結(jié)構(gòu)
build/core/main.mk
├── help.mk
├── config.mk
│ ├── pathmap.mk
│ ├── envsetup.mk
│ │ ├── version_defaults.mk
│ │ └── product_config.mk
│ │ ├── node_fns.mk
│ │ ├── product.mk
│ │ └── device.mk
│ ├── combo/select.mk
│ ├── ccache.mk
│ ├── combo/javac.mk
│ ├── clang/config.mk
│ │ ├── clang/HOST_$(HOST_2ND_ARCH).mk
│ │ ├── clang/HOST_$(HOST_ARCH).mk
│ │ ├── clang/TARGET_$(TARGET_2ND_ARCH).mk
│ │ └── clang/TARGET_$(TARGET_ARCH).mk
│ └── dumpvar.mk
├── cleanbuild.mk
│ └── cleanspec.mk
├── definitions.mk
│ └── distdir.mk
├── dex_preopt.mk
│ └── dex_preopt_libart.mk
│ └── dex_preopt_libart_boot.mk
├── pdk_config.mk
├── post_clean.mk
├── legacy_prebuilts.mk
└── Makefile
├── sdk_font.mk
└── tasks/*.mk
以上結(jié)構(gòu)代表main.mk中對其它Makefile的include關(guān)系。排列順序,大致依照文件中include的順序,但要注意,Makefile的排列順序不一定代表執(zhí)行順序。
這個圖中僅列出了build/core目錄下的文件,不包括其它目錄下被包含的mk文件。并且,build/core目錄下總計近百個mk文件,這里也未列出沒被包含到main.mk中的那些。
部分文件說明
| 文件 | 作用 |
|---|---|
| main.mk | make命令入口,是整個Android編譯系統(tǒng)最核心的文件。 |
| help.mk | 在執(zhí)行make help時打印幾個主要的Target信息。 |
| config.mk | 定義了編譯過程中的環(huán)境變量,包括BUILD_*、TARGET_*等。它include的其它mk文件,也是類似作用。 |
| cleanbuild.mk | 定義了完整編譯前,清理產(chǎn)物的步驟和內(nèi)容。 |
| definitions.mk | 定義了大量編譯過程中會用到的函數(shù),如my-dir、all-subdir-makefile等。 |
| dex_preopt.mk | 利用dexopt(Dalvik)或dex2oat(ART)對dex進(jìn)行優(yōu)化。 |
| pdk_config.mk | 編譯PDK(Platform Developement Kit)的產(chǎn)物platform.zip。 |
| post_clean.mk | 針對應(yīng)用層模塊的產(chǎn)物清理。 |
| legacy_prebuilts.mk | 定義了GRANDFATHERED_ALL_PREBUILT變量,指定一些預(yù)編譯Target。 |
| Makefile | 這是一個功能復(fù)雜的文件,可以看做main.mk的延伸。 |
在上述Makefile文件中,還include了tasks/*.mk,其中內(nèi)容如下:
- apicheck.mk
- boot_jars_package_check.mk
- build_custom_images.mk
- collect_gpl_sources.mk
- cts.mk
- deps_licenses.mk
- ide.mk
- oem_image.mk
- product-graph.mk
- sdk-addon.mk
- vendor_module_check.mk
其作用可以通過文件名來推測,這里不再贅述。
產(chǎn)品組件的相關(guān)文件
產(chǎn)品組件的主要入口是AndroidProducts.mk文件,而product_config.mk就是提供相關(guān)支持的文件。文件頭中,有一段注釋:
# Generic functions
# TODO: Move these to definitions.make once we're able to include
# definitions.make before config.make.
可見,這個文件原本只是一個臨時的獨(dú)立文件,但因?yàn)闅v史原因,一直沿用至今。這也體現(xiàn)了這個Makefile編譯系統(tǒng)的一些固有缺陷。
其中的核心邏輯如下:
ifneq ($(strip $(TARGET_BUILD_APPS)),)
# An unbundled app build needs only the core product makefiles.
all_product_configs := $(call get-product-makefiles,\
$(SRC_TARGET_DIR)/product/AndroidProducts.mk)
else
# Read in all of the product definitions specified by the AndroidProducts.mk
# files in the tree.
all_product_configs := $(get-all-product-makefiles)
endif
$(SRC_TARGET_DIR)/product/AndroidProducts.mk,就是build/target/product/AndroidProducts.mk,是系統(tǒng)默認(rèn)自帶的文件,內(nèi)含aosp-arm、aosp-arm64等PRODUCT。而get-all-product-makefiles則是在項目的device/、vendor/兩個目錄下,查找6層以內(nèi)的所有AndroidProducts.mk文件。
它include的三個文件,與definitions.mk類似,分別定義一些函數(shù)。比如,product.mk中定義了get-product-makefiles、check-all-products等函數(shù),提供了查找AndroidProducts.mk文件、列出所有可以被lunch的PRODUCT參數(shù)等功能。
所以,如果要新增一個PRODUCT,只需在device/或vendor/下合適的位置,新增一個AndroidProducts.mk,指定PRODUCT_*的各項參數(shù)即可。
Android.mk的相關(guān)文件
在build/core/main.mk中,有以下代碼:
subdir_makefiles := \
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
這就是查找整個Android項目中所有的Android.mk文件,并且include進(jìn)來。Android.mk的組合方式,本質(zhì)上和AndroidProducts.mk并無不同。只是AndroidProducts.mk的范圍小一些,而Android.mk則是在整個項目中搜索。
除此之外,build/core/下還有很多mk文件,雖然沒有直接被main.mk所include,卻會被各種Android.mk所include。比如,clear_vars.mk、package.mk等。
新增一個LOCAL_MODULE,只需要在任意位置新增一個Android.mk,指定LOCAL_*參數(shù)即可。篇幅所限,不對此做詳細(xì)介紹。
其它文件
除了Makefile文件以外,在build/下還包含了其它的工具,主要集中在build/tools/目錄下。很多用Makefile做起來不方便的工作,都由它們?nèi)プ觥?/p>
這些工具,以Python、Bash腳本為主,也包含部分C語言寫的微型項目,比如build/tools/acp/。
總結(jié)
我在深入研究Android的編譯系統(tǒng)前,從未想過Makefile會有這樣壯麗的風(fēng)景。利用Makefile的依賴管理,構(gòu)建一個龐大而復(fù)雜的編譯系統(tǒng),這無疑是一個令人驚嘆的構(gòu)思。
不過,無論如何,它也快走到了盡頭。
如果說Bash的語法,令人難以把握,那么Makefile則更是令人每學(xué)每忘。假如早知道要以Makefile為主,Python、Bash、乃至C語言為輔,構(gòu)建這樣一個大型項目,說不定2003年組建之初的Android團(tuán)隊,就不會這樣選擇。若是以Python作為膠水,粘合各個模塊的Makefile,也許能經(jīng)久不衰。
Google從7.0開始,逐步用各種工具替換Makefile,利弊參半。在編譯前用kati來把Makefile轉(zhuǎn)換為Ninja,在實(shí)際編譯各模塊時用Ninja替代Makefile。歷時12年,Android中的Makefile終于在2016年,開始退場。