本文基于AOSP的android-6.0.1_r9
分支,介紹Android平臺編譯系統中的Makefile。
(本文轉載自本人的個人網站,謝絕二次轉載。原文鏈接:http://note.qidong.name/2017/08/android-6.0-makefile/)
簡介
之所以選android-6.0.1_r9
這個分支,是因為這是最后一個純Makefile的大版本。后面隨著時間的發展,Android項目變得越來越龐大,純Makefile編譯系統已經越來越不堪使用。使用Makefile,不僅擴展不便,而且執行效率也不太高。從7.0版本開始,Android已經開始用Ninja來替代Makefile。
6.0版本,是Makefile最后的輝煌。Android平臺的編譯系統,其實就是用Makefile寫出來的一個獨立項目。這個項目,不僅把分散在數百個Git庫中的代碼整合起來、統一編譯,而且還把產物分門別類地輸出到一個目錄,打包成手機ROM,更能產生應用開發時所使用的SDK、NDK、網頁文檔等。以前,從來沒有這么大規模的一個手寫Makefile項目,以后應該也不會再有了。
在大、中型項目都普遍使用高級工具來生成Makefile的時候,Android竟然還是手寫Makefile。現在看來,這也是一件咄咄怪事。
主要文件
在Android項目根目錄,都有一個Makefile文件,其核心內容只有一行。
### 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以后,全部調整到了build/make/core
中,因為build目錄下,又新增了kati、soong、blueprint等項目的目錄。
Android的編譯系統的Makefile文件,主要分成三部分:
-
核心組件,全部在
build/core/
目錄下。
這里的幾十個mk文件,是編譯系統的核心內容,定義了編譯流程的框架。 -
產品組件,出現在
build/target/
、device/
或vendor/
目錄中。
這部分的主要入口是AndroidProducts.mk文件,功能是指定一些產品獨特的內容。這些額外的組件,會隨著編譯前lunch PRODUCT
的PRODUCT參數而改變。 - 模塊組件,入口為各個Git庫的Android.mk。
這是普通平臺開發者最熟悉的文件,其中定義了一個模塊的必要參數,使模塊跟隨平臺編譯。
本文只分析核心組件中的結構,以及build/core/
下的一些重要文件。
核心組件
結構
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
以上結構代表main.mk
中對其它Makefile的include關系。排列順序,大致依照文件中include的順序,但要注意,Makefile的排列順序不一定代表執行順序。
這個圖中僅列出了build/core
目錄下的文件,不包括其它目錄下被包含的mk文件。并且,build/core
目錄下總計近百個mk文件,這里也未列出沒被包含到main.mk
中的那些。
部分文件說明
文件 | 作用 |
---|---|
main.mk | make命令入口,是整個Android編譯系統最核心的文件。 |
help.mk | 在執行make help 時打印幾個主要的Target信息。 |
config.mk | 定義了編譯過程中的環境變量,包括BUILD_* 、TARGET_* 等。它include的其它mk文件,也是類似作用。 |
cleanbuild.mk | 定義了完整編譯前,清理產物的步驟和內容。 |
definitions.mk | 定義了大量編譯過程中會用到的函數,如my-dir 、all-subdir-makefile 等。 |
dex_preopt.mk | 利用dexopt (Dalvik)或dex2oat (ART)對dex進行優化。 |
pdk_config.mk | 編譯PDK(Platform Developement Kit)的產物platform.zip 。 |
post_clean.mk | 針對應用層模塊的產物清理。 |
legacy_prebuilts.mk | 定義了GRANDFATHERED_ALL_PREBUILT 變量,指定一些預編譯Target。 |
Makefile | 這是一個功能復雜的文件,可以看做main.mk的延伸。 |
在上述Makefile文件中,還include了tasks/*.mk
,其中內容如下:
- 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
其作用可以通過文件名來推測,這里不再贅述。
產品組件的相關文件
產品組件的主要入口是AndroidProducts.mk文件,而product_config.mk就是提供相關支持的文件。文件頭中,有一段注釋:
# Generic functions
# TODO: Move these to definitions.make once we're able to include
# definitions.make before config.make.
可見,這個文件原本只是一個臨時的獨立文件,但因為歷史原因,一直沿用至今。這也體現了這個Makefile編譯系統的一些固有缺陷。
其中的核心邏輯如下:
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
,是系統默認自帶的文件,內含aosp-arm
、aosp-arm64
等PRODUCT。而get-all-product-makefiles
則是在項目的device/
、vendor/
兩個目錄下,查找6層以內的所有AndroidProducts.mk文件。
它include的三個文件,與definitions.mk類似,分別定義一些函數。比如,product.mk中定義了get-product-makefiles
、check-all-products
等函數,提供了查找AndroidProducts.mk文件、列出所有可以被lunch
的PRODUCT參數等功能。
所以,如果要新增一個PRODUCT,只需在device/
或vendor/
下合適的位置,新增一個AndroidProducts.mk,指定PRODUCT_*
的各項參數即可。
Android.mk的相關文件
在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進來。Android.mk的組合方式,本質上和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_*
參數即可。篇幅所限,不對此做詳細介紹。
其它文件
除了Makefile文件以外,在build/
下還包含了其它的工具,主要集中在build/tools/
目錄下。很多用Makefile做起來不方便的工作,都由它們去做。
這些工具,以Python、Bash腳本為主,也包含部分C語言寫的微型項目,比如build/tools/acp/
。
總結
我在深入研究Android的編譯系統前,從未想過Makefile會有這樣壯麗的風景。利用Makefile的依賴管理,構建一個龐大而復雜的編譯系統,這無疑是一個令人驚嘆的構思。
不過,無論如何,它也快走到了盡頭。
如果說Bash的語法,令人難以把握,那么Makefile則更是令人每學每忘。假如早知道要以Makefile為主,Python、Bash、乃至C語言為輔,構建這樣一個大型項目,說不定2003年組建之初的Android團隊,就不會這樣選擇。若是以Python作為膠水,粘合各個模塊的Makefile,也許能經久不衰。
Google從7.0開始,逐步用各種工具替換Makefile,利弊參半。在編譯前用kati來把Makefile轉換為Ninja,在實際編譯各模塊時用Ninja替代Makefile。歷時12年,Android中的Makefile終于在2016年,開始退場。