Android編譯系統入門(二)


Android.mk的使用方法

在上一篇Android編譯系統入門(一)中我們只要介紹了Android系統使用make命令默認編譯的依賴樹是droid,而droid是一個偽目標,它有兩個先決條件droidcore和dist_files,其中重點是droidcore,它主要用於編譯系統所需的system.img,boot.img等。有了上一篇的基礎,今天我們要分析一下Android.mk文件在整個編譯系統中的地位和作用。

一棵大樹的繁茂和枝葉的多少息息相關。一方面只有枝干足夠茁壯才能托起枝葉,另一方面枝葉的光合作用也能促進枝干的生長。那么在Android編譯系統中,droid就是這棵樹中強有里的枝干,而Android.mk則是一片片的葉子,縱觀整個Android平台Android.mk的數量在一千個以上。那么如此多的makefile文件又是在何時被整合進整個編譯系統的呢?其實答案還是在main.mk中。

ifneq ($(ONE_SHOT_MAKEFILE),)
# We've probably been invoked by the "mm" shell function
# with a subdirectory's makefile.
include $(ONE_SHOT_MAKEFILE)
# Change CUSTOM_MODULES to include only modules that were
# defined by this makefile; this will install all of those
# modules as a side-effect.  Do this after including ONE_SHOT_MAKEFILE
# so that the modules will be installed in the same place they
# would have been with a normal make.
CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))
FULL_BUILD :=
# Stub out the notice targets, which probably aren't defined
# when using ONE_SHOT_MAKEFILE.
NOTICE-HOST-%: ;
NOTICE-TARGET-%: ;

# A helper goal printing out install paths
.PHONY: GET-INSTALL-PATH
GET-INSTALL-PATH:
	@$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED), \
		echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))

else # ONE_SHOT_MAKEFILE

ifneq ($(dont_bother),true)
#
# Include all of the makefiles in the system
#

# Can't use first-makefiles-under here because
# --mindepth=2 makes the prunes not work.
subdir_makefiles := \
	$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)

$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))

endif # dont_bother

ONE_SHOT_MAKEFILE變量和編譯選項有關,當選擇默認make命令進行整編的時候ONE_SHOT_MAKEFILE值為空,這是就會走下面這個分支

subdir_makefiles := \
	$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)

$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))

其中就是通過findleaves.py這個腳本來查找所有的Android.mk文件但可能並不是所有的Android.mk都會被包好進來比如.repo .git下的就會被排除在外。這些排除選項由FIND_LEAVES_EXCLUDES決定。所有被包含進來的Android.mk的路徑都會被追加到subdir_makefiles變量,接着通過一個foreach函數將所有的Android.mk文件都include進來。其中$(info including $(mk) ...)負責打印這些文件信息,如下
這里寫圖片描述
再通過eval函數執行include操作將Android.mk文件整合進整棵大樹。

OK,到這里所有的Android.mk文件都被包含進來了,等整個大樹被構建完成后make會從依賴樹最外層的葉子開始往上執行所有的COMMANDS。

接下來我們選取Settings模塊作為例子,詳細的解釋一下Android.mk的編寫規則和一些注意事項。Settings模塊的Android.mk內容如下

#LOCAL_PATH表示當前目錄的地址,一般位於include $(CLEAR_VARS)之前
LOCAL_PATH:= $(call my-dir)

#CLEAR_VARS對應的是clean_vars.mk,用於清除除了LOCAL_PATH以外的所有LOCAL_打頭的變量
include $(CLEAR_VARS)

#重定向java庫文件
LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common

#重定向java靜態庫文件
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 jsr305

#模塊tag為optional,表示不管是選擇了什么模式都會編譯該模塊
LOCAL_MODULE_TAGS := optional

#重定向本地源碼
LOCAL_SRC_FILES := \
        $(call all-java-files-under, src) \
        src/com/android/settings/EventLogTags.logtags

#重定向本地資源文件
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res

#模塊名
LOCAL_PACKAGE_NAME := Settings

#模塊證書簽名
LOCAL_CERTIFICATE := platform

#是否是特權文件
LOCAL_PRIVILEGED_MODULE := true

#使用代碼混淆
LOCAL_PROGUARD_FLAG_FILES := proguard.flags

#判斷是否進行增量編譯
ifneq ($(INCREMENTAL_BUILDS),)
    LOCAL_PROGUARD_ENABLED := disabled
    LOCAL_JACK_ENABLED := incremental
endif

#include三個makefile文件,進項相關變量賦值
include frameworks/opt/setupwizard/navigationbar/common.mk
include frameworks/opt/setupwizard/library/common.mk
include frameworks/base/packages/SettingsLib/common.mk

#開始編譯Settings模塊,對應package.mk文件。感興趣的可以進一步研究apk是怎么被編譯出來的,里面還是很復雜的
include $(BUILD_PACKAGE)


# 如果使用的是mm或mmm命令單編Settings模塊的話會額外include test目錄下的Android.mk,用於編譯測試模塊。
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif

通過上述對Android.mk文件的分析,我們可以看到需要編譯一個模塊要做的工作還是很少的,只要指定幾個變量就可以了,這也得益與google的用心良苦,它把所有的公共操作都抽取了出來,做好了各種模板,如BUILD_PACKAGE等,我們要做的只是調用適當的模板就行了。
下面介紹一些Android.mk中常用的變量,以供讀者參考。

變量名 說明
LOCAL_PATH 用於確定源碼所在的目錄,一般把它放在CLEAR_VARS變量引用的前面,因為它不會背清除,每個Android.mk只需要定義一次就行
CLAER_VARS 清空很多LOCAL_開頭的變量(LOCAL_PATH除外)。因為所有的Makefile都是在一個編譯環境下執行,因此變量的定義理論上都是全局的,每個模塊開始編譯前進行清理工作是必不可少的
LOCAL_MODULE 模塊名,需要保證唯一存在且中間不能又空格
LOCAL_MODULE_PATH 模塊的輸出路徑
LOCAL_SRC_FILES 模塊編譯所涉及的源文件。如果是java程序,可以考慮調用all-java-files-under添加java代碼。因為有LOCAL_PATH,所以這里只需要給出文件名即可,如src
LOCAL_CC 用於指定C編譯器
LOCAL_CXX 用於指定C++編譯器
LOCAL_CPP_EXTENSION 用於指定特殊的C++文件后綴名
LOCAL_CFLAGS C語言編譯時的額外選項
LOCAL_CXXFLOAGS C++編譯時的額外選項
LOCAL_C_INCLUDES 編譯C和C++時需要的額外頭文件
LOCAL_STATIC_LIBRARIES 編譯所需的靜態庫列表
LOCAL_SHARED_LIBRARIES 編譯所需的共享庫列表
LOCAL_JAVA_LIBRARIES 編譯時所需的JAVA類庫
LOCAL_LDLIBS 編譯時所需的鏈接選項
LOCAL_COPY_HEADERS 安裝應用程序時需要復制的頭文件列表,需要和LOCAL_COPY_HEADERS_TO變量配合使用
LOCAL_COPY_HEADERS_TO 上述頭文件列表的復制目的地
BUILD_XX_XX 各種形式的編譯模板,如生成靜態、動態庫文件,可執行文件,文檔等

總結

在Android編譯系統的學習中,我們先從最基礎的makefile語法規則入手,導出了依賴樹的概念,然后按照依賴樹的結構逐步梳理出一個完整的Android版本編譯所設計的幾個重要節點。Android編譯系統是非常龐大的,不過經過這次的學習希望大家能夠對它的結構和基本原理有一個初步的認識。那么接下來的各種編譯細節也能通過代碼的研讀和分析變得明朗起來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM