本文轉載自:http://blog.csdn.net/kitty_landon/article/details/60764232
Android是一個龐大的系統,包含太多的模塊,各種模塊的類型也有10多種。為了管理整套源碼的編譯,Android專門開發了自己的Build系統。從大的方面講,Android的Build系統可分為3大塊:第一塊是位於build/core目錄下的文件,這是Android Build系統的框架和核心;第二塊是位於device目錄下的文件,存放的是具體產品的配置文件;第三塊是各模塊的編譯文件:Android.mk,位於模塊的源文件目錄下。
Android Build系統核心
核心位於build/core,這個目錄中有幾十個mk文件,以及一些shell腳本和perl腳本,構成了Android Build系統的基礎和框架。
通常使用下面的命令來編譯Android系統:
- # . build/envsetup.sh
- # lunch
- # make
那么我們以此作為入口,進行解析。
編譯環境的建立
1、envsetup.sh文件的作用
執行Android系統的編譯,必須先運行envsetup.sh腳本,這個腳本會建立Android的編譯環境。envsetup.sh文件定義了很多shell命令,這些命令在envsetup.sh腳本之后就可以從shell環境中調用了。envsetup.sh 腳本還定義了一些其他有用的shell 命令,這些命令可以單獨使用。例如:
- <span style="font-size:14px;">lunch 當前編譯的產品
- croot 跳轉到根目錄
- m 編譯整個源碼
- mm 編譯當前目錄所有模塊,不編譯依賴模塊
- mmm 編譯指定模塊,不編譯依賴模塊
- mma 編譯當前目錄所有模塊,編譯依賴模塊
- mmma 編譯指定模塊,編譯依賴模塊
- cgrep 對所有的c/c++文件執行grep命令
- ggrep 對所有的Gradle文件執行grep命令
- jgrep 對所有的java文件執行grep命令
- resgrep 對所有的資源文件執行grep命令
- sgrep 對所有的文件執行grep命令
- godir 查找目錄,並切換。</span>
2、lunch命令的功能
lunch 命令沒有參數,打印產品列表,以供選擇;如果有名稱,格式為“<product_name>--< build_variant >”,其中前半部分是產品名稱,后面是“build_variant”必須是eng /user/userdebug 三者之一。
lunch主要作用就是根據用戶輸入或選擇的產品的名來設置與產品相關的環境變量。這些環境變量與產品編譯相關的主要有:
- TARGET_PRODUCT:所編譯的產品名稱。
- TARGET_BUILD_VARIANT:表示編譯產品的類型。可能值有 eng ,user .userdebug。
- TARGET_BUILD_TYPE:表示編譯的類型,可選值為release和debug當選擇debug版本時,系統會加入調式信息,方便追蹤。
Build相關環境變量
PLATFORM_VERSION_CODENAME:平台版本名稱,通常是AOSP(Android OpenSource Project的縮寫)
PLATFORM_ VERSION:android平台的版本號。
TARGET_PRODUCT:所編譯的產品名稱。
TARGET_BUILD_VARIANT:表示編譯產品的類型,可能值有 eng、user、userdebug。
TARGET_BUILD_TYPE:表示編譯的類型,可選值為release和debug。當選擇debug版本時,系統會加入調式信息,方便追蹤。
TARGET_BUILD_APPS:編譯系統時,這個變量值為空,編譯單個模塊時,這個模塊的變量值為模塊的路徑。
TARGET_ARCH:編譯目標的CPU架構。
TARGET_ARCH_VARIANT:編譯目標的CPU架構版本。
TARGET_CPU_VARIANT:編譯目標的CPU代號。
TARGET_2ND_ARCH: 編譯目標的第二CPU架構。
TARGET_2ND_ARCH _VARIANT: 編譯目標的第二CPU架構版本。
TARGET_2ND_CPU_VARIANT:編譯目標的第二CPU代號
HOST_ARCH:編譯平台架構。
HOST_OS:編譯平台使用的操作系統。
HOST_OS_EXTRA:編譯平台操作系統的一些額外信息,包括版本號、產品名稱、代號等。
BUILD_ID:編譯版本信息,可以定義公司特有標識。
OUT_DIR:編譯結果輸出目錄。
對這些環境變量的修改,可以放到產品的定義文件中。如果只是希望臨時改變這些環境變量的值,可以通過在make命令中加入參數的方式完成。如:
- make BUILD_ID='Android L'
Build系統的層次關系
設置好環境變量之后,就是用make命令開始執行編譯的過程。編譯產品的目的是生成用於“刷機”的各種img文件,因此,生成這些特殊格式的文件將是Build系統的主要功能。
Build系統主要編譯腳本簡介,如下:
main.mk:Android Build系統的主控文件。主要作用是包含進其他mk文件,以及定義幾個最重要的編譯目標,如droid、sdk、ndk等。同時檢查編譯工具的版本,如make、gcc、javac等。
help.mk:Android Build系統的幫助。文件中定義了一個名為help的編譯目標,因此,輸入“make help”會打印出Build系統的使用說明。
config.mk:Android Build系統的配置文件。主要定義了許多常量來負責不同類型模塊的編譯,定義編譯器參數並引入產品的BoardConfig.mk文件來配置產品參數,同時也定義了一些編譯工具的路徑,如aapt、javajar等。
pathmap.mk:將許多頭文件的路徑通過名值對的方式定義為映射表,並通過include-path-for函數來獲取。例如,通過 $(callinclude-path-for, frameworks-native)便可以獲取到 framework 本地代碼需要的頭文件路徑。
envsetup.sh:包含進product_config.mk文件並根據其內容設置編譯產品所需要的環境變量,如TARGET_PRODUCT等,並檢查這些變量值的合法性,同時還指定了各種編譯結果的輸出路徑。
product_config.mk:包含進了系統中所有AndroidProduct.mk文件,並根據當前產品的配置文件來設置產品編譯相關的變量。
product.mk:定義product_config.mk文件中使用的各種函數。
version_default.mk:定義系統版本相關的變量。
build_id.mk:定義環境變量BUILD_ID。
Makefile:定義了系統最終編譯完成所需要的各種目標和規則。
分析main.mk文件
main.mk是Android Build系統的主控文件。分段看下源碼:
(1)、檢查gnu make的版本號是否大於等於3.81,否則報錯並停止編譯。
- # Check for broken versions of make.
- ifneq (1,$(strip $(shell expr $(MAKE_VERSION) \>= 3.81)))
- $(warning ********************************************************************************)
- $(warning * You are using version $(MAKE_VERSION) of make.)
- $(warning * Android can only be built by versions 3.81 and higher.)
- $(warning * see https://source.android.com/source/download.html)
- $(warning ********************************************************************************)
- $(error stopping)
- endif
(2)、定義缺省的編譯目標為“droid”。因此,命令“make”相當於“make droid”:
- # This is the default target. It must be the first declared target.
- .PHONY: droid
- DEFAULT_GOAL := droid
- $(DEFAULT_GOAL): droid_targets
(3)、引入幾個make文件。這里注意“-include”和“include”的區別:前者包含的文件如果不存在不會報錯,后者則會報錯並停止編譯。
- # Targets that provide quick help on the build system.
- include $(BUILD_SYSTEM)/help.mk
- # Set up various standard variables based on configuration
- # and host information.基於配置和主機信息設置各種標准變量。
- include $(BUILD_SYSTEM)/config.mk
- # This allows us to force a clean build - included after the config.mk
- # environment setup is done, but before we generate any dependencies. This
- # file does the rm -rf inline so the deps which are all done below will
- # be generated correctly
- include $(BUILD_SYSTEM)/cleanbuild.mk
- # Include the google-specific config
- -include vendor/google/build/config.mk
- VERSION_CHECK_SEQUENCE_NUMBER := 6
- -include $(OUT_DIR)/versions_checked.mk
(4)、檢查java的版本是否是1.8或1.7,不是則會報錯退出。1.8或1.7版本的java,在Linux下還要求是openJDK的版本,否則要求是Oracle的JDK版本。
- # Check for the correct version of java, should be 1.8 by
- # default and only 1.7 if LEGACY_USE_JAVA7 is set.
- ifeq ($(LEGACY_USE_JAVA7),) # if LEGACY_USE_JAVA7 == ''
- required_version := "1.8.x"
- required_javac_version := "1.8"
- java_version := $(shell echo '$(java_version_str)' | grep '[ "]1\.8[\. "$$]')
- javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.8[\. "$$]')
- else
- required_version := "1.7.x"
- required_javac_version := "1.7"
- java_version := $(shell echo '$(java_version_str)' | grep '^java .*[ "]1\.7[\. "$$]')
- javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.7[\. "$$]')
- endif # if LEGACY_USE_JAVA7 == ''
- ifeq ($(strip $(java_version)),)
- $(info ************************************************************)
- $(info You are attempting to build with the incorrect version)
- $(info of java.)
- $(info $(space))
- $(info Your version is: $(java_version_str).)
- $(info The required version is: $(required_version))
- $(info $(space))
- $(info Please follow the machine setup instructions at)
- $(info $(space)$(space)$(space)$(space)https://source.android.com/source/initializing.html)
- $(info ************************************************************)
- $(error stop)
- endif
- # Check for the current JDK.
- #
- # For Java 1.7/1.8, we require OpenJDK on linux and Oracle JDK on Mac OS.
- requires_openjdk := false
- ifeq ($(BUILD_OS),linux)
- requires_openjdk := true
- endif
(5)、將變量VERSIONS_CHECKED和BUILD_EMULATOR寫入文件out/version_checked.mk,下次build時會重新包含這個文件。
- ifndef BUILD_EMULATOR
- # Emulator binaries are now provided under prebuilts/android-emulator/
- BUILD_EMULATOR := false
- endif
- $(shell echo 'VERSIONS_CHECKED := $(VERSION_CHECK_SEQUENCE_NUMBER)' \
- > $(OUT_DIR)/versions_checked.mk)
- $(shell echo 'BUILD_EMULATOR ?= $(BUILD_EMULATOR)' \
- >> $(OUT_DIR)/versions_checked.mk)
- endif
(6)、再包含進3個mk文件:
- # Bring in standard build system definitions.
- include $(BUILD_SYSTEM)/definitions.mk
- # Bring in dex_preopt.mk
- include $(BUILD_SYSTEM)/dex_preopt.mk
- # The pdk (Platform Development Kit) build
- include build/core/pdk_config.mk
(7)、如果變量ONE_SHOT_MAKEFILE的值不為空,將它定義的文件包含進來。當編譯一個單獨的模塊時,ONE_SHOT_MAKEFILE的值會設為模塊的make文件路徑。如果值ONE_SHOT_MAKEFILE的值為空,說明正在編譯整個系統,因此,調用findleayes.py腳本搜索系統里所有Android.mk文件並將它們包含進來。
(8)、根據編譯類型來設置屬性ro.secure的值。
(9)、包含進post_clean.mk和legacy_prebuilts.mk腳本。根據legacy_prebuilds.mk中定義的變量GRANDFATHERED_ALL_PREBUILT檢查是由有不在這個列表中的prebuilt模塊。如果有則報錯退出。
- # Now with all Android.mks loaded we can do post cleaning steps.
- include $(BUILD_SYSTEM)/post_clean.mk
- ifeq ($(stash_product_vars),true)
- $(call assert-product-vars, __STASHED)
- endif
- include $(BUILD_SYSTEM)/legacy_prebuilts.mk
- ifneq ($(filter-out $(GRANDFATHERED_ALL_PREBUILT),$(strip $(notdir $(ALL_PREBUILT)))),)
- $(warning *** Some files have been added to ALL_PREBUILT.)
- $(warning *)
- $(warning * ALL_PREBUILT is a deprecated mechanism that)
- $(warning * should not be used for new files.)
- $(warning * As an alternative, use PRODUCT_COPY_FILES in)
- $(warning * the appropriate product definition.)
- $(warning * build/target/product/core.mk is the product)
- $(warning * definition used in all products.)
- $(warning *)
- $(foreach bad_prebuilt,$(filter-out $(GRANDFATHERED_ALL_PREBUILT),$(strip $(notdir $(ALL_PREBUILT)))),$(warning * unexpected $(bad_prebuilt) in ALL_PREBUILT))
- $(warning *)
- $(error ALL_PREBUILT contains unexpected files)
- endif
(10)、計算哪些模塊應該在本次編譯之后引入。
(11)、包含Makefile文件。至此,所有的編譯文件都包含進來了。
- # build/core/Makefile contains extra stuff that we don't want to pollute this
- # top-level makefile with. It expects that ALL_DEFAULT_INSTALLED_MODULES
- # contains everything that's built during the current make, but it also further
- # extends ALL_DEFAULT_INSTALLED_MODULES.
- ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
- include $(BUILD_SYSTEM)/Makefile
- modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
(12)、定義系統的編譯目標。下面詳細介紹。
Build系統的編譯目標介紹
Android Build系統的缺省編譯目標是droid。droid目標會依賴其他目標,所有這些目標共同組成了最終的產品。下面是droid目標定義。
- # Building a full system-- the default is to build droidcore
- droid_targets: droidcore dist_files
- # 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) \
- # All the droid stuff, in directories
- .PHONY: files
- files: prebuilt \
- $(modules_to_install) \
- $(INSTALLED_ANDROID_INFO_TXT_TARGET)
- .PHONY: prebuilt
- prebuilt: $(ALL_PREBUILT)
在droid的依賴目標中,droidcore、files和prebuilt是中間目標,其余目標的作用介紹如下:
| 目標 | 說明 |
| dist_file | 用來復制文件到out/dist目錄 |
| 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) | 用來產生名為installed-files.txt文件,該文件將會存放在out/target/product/<product_name>下。文件的內容是當前產品配置將要安裝的所有文件列表。 |
| $(modules_to_install) | modules_to_install變量是當前產品配置下所有將要安裝的模塊的列表。因此,該目標將導致所有這些模塊的編譯。 |
| $(INSTALLED_ANDROID_INFO_TXT_TARGET) | 用來產生名為android_info.txt文件,該文件將會存放在out/target/product/<product_name>下。文件內容是當前產品的設備信息。 |
| $(ALL_PREBUILT) | 用來產生所有在變量GRANDFATHERED_ALL_PREBUILT中的文件。 |
除了droid及其相關目標,Build系統里還有很多可以獨立使用的目標。
| 目標 | 說明 |
| make clean | 清除所有編譯結果,相當於“rm -rf out” |
| make snod | 快速打包system.img。 |
| make help | 打印build系統簡單的幫助信息。 |
| make sdk | 生成Android SDK。 |
| make framework | 編譯出所有framework的jar包 |
| make services | 編譯出系統服務及相關的模塊。 |
Android的產品配置文件
產品的配置文件的作用是按照Build系統的要求,將生成產品的各種image文件所需要的配置信息(如版本號、各種參數等)、資源(圖片、字體、鈴聲等)、二進制文件(apk、jar包、so庫等)有機地組織起來,同時進行剪裁,加入或去掉一些模塊。
Android的產品配置文件位於源碼的device目錄下,但是產品配置文件也可以放在vendor目錄下。這兩個目錄從Build系統的角度看沒有太大的區別,Build系統中搜尋產品配置的關聯文件時會同時在這兩個目錄下進行,但是在實際使用中,往往會讓這兩個目錄配合使用,通常產品配置文件放在device目錄下,而vendor目錄下則存放一些硬件的HAL庫。編譯某一款手機的“刷機包”之前,需要將手機上一些不開源的HAL庫(主要是so文件)、驅動等抽取出來,放在vendor目錄下。
分析配置文件
通常device目錄下有以下幾個子目錄。
(1)、common:用來存放各個產品通用的配置腳本、文件等。
(2)、sample:一個產品配置的例子,寫一個新的產品配置時可以使用sample目錄下的文件做為模板。
(3)、google:幾個簡單的模塊。
(4)、generic:存放的是用於模擬器的產品,包括x86、arm、mips等。
(5)、zeusis:產品則放在對應目錄下,各家公司的命名不同。如需添加新產品,可在device目錄下新建一個目錄。
下面介紹Build系統會包含的產品配置中的幾個文件,這幾個文件和Build系統關系最密切,也是關鍵文件。
1、vendorsetup.sh
vendorsetup.sh文件會在初始化編譯環境被包含進去,主要作用就是調用add_lunch_combo命令來添加產品名稱串。例如,pollux目錄下的vendorsetup.sh文件的內容如下:
- add_lunch_combo pollux-user
- add_lunch_combo pollux-userdebug
- add_lunch_combo pollux-eng
產品名稱串的格式是<produce_name>-<goal>,前半部分是產品名稱,后半部分是編譯類型。產品的編譯類型有三種:eng、user、userdebug。
2、AndroidProducts.mk
AndroidProducts.mk會在Build系統的product.mk文件中被包含進去,這個文件最重要的作用是定義了一個變量PRODUCT_MAKEFILES,它定義了本配置目錄中所有編譯入口文件,但是,每種產品編譯時只會使用其中之一。例如,pollux目錄下的AndroidProducts.mk文件內容如下:
- PRODUCT_MAKEFILES := \
- $(LOCAL_DIR)/pollux.mk
vendorsetup.sh文件中加入“選擇列表”的是pollux,因此,實際能選用的文件只用pollux.mk。
3、BoardConfig.mk
BoardConfig.mk文件會被Build系統的envsetup.mk文件包含進來。主要定義了和設備硬件(CPU、WIFI、GPS等)相關的一些參數。看懂這個文件的關鍵是理解文件中使用的編譯變量。下面簡單介紹幾種。
一些重要的編譯變量總結下:
PRODUCT_COPY_FILE:格式為“原文件路徑:目標文件路徑”字串的集合。該變量能方便地將編譯目錄下的一個文件復制到目標文件系統中。
PRODUCT_PACKAGES:定義產品的模塊列表,多有在模塊列表中定義的模塊都會被執行。
PRODUCT_AAPT_CONFIG:指定了系統中能夠支持的屏幕密度類型(dip)。所謂支持,是指系統編譯時,會將相應的資源文件添加到framework-res.apk文件中。
PRODUCT_AAPT_PREF_CONFIG:指定系統實際的屏幕密度類型。
DEVICE_PACKAGE_OVERLAYS:指定了系統的overlay目錄。系統編譯時會使用overlay目錄下存放的資源文件替換系統或模塊原有的資源文件。
PRODUCT_PROPERTY_OVERRIDES:定義系統的屬性值。如果屬性名稱以“ro.”開頭,就是只讀屬性。如果屬性名稱以“persist.”開頭,當設置這個屬性時,它的值將寫入文件/data/property中。
編譯類型eng、user、userdebug
產品的image文件
Android編譯完成后會生成幾個image文件,包括:boot.img、system.img、recovery.img和userdata.img。
boot.img
| Boot header(文件頭) |
1 page |
| Kernel(gzip壓縮的映像) |
n pages |
| Ramdisk(映像) |
m pages |
| Second stage(載入器程序) |
o pages |
boot.img是一種Android自定義的文件格式。該格式包括了一個2*1024大小的文件頭,后面是用gzip壓縮過的kernel鏡像,在后面是ramdisk映像,最后是一個加載器程序。
recovery.img
相當於一個小型文本界面的Linux系統,有自己的內核和文件系統,作用是恢復或升級系統。
system.img
是設備中system目錄的鏡像,包含了Android系統主要的目錄和文件。
- app目錄:一般的apk文件。
- bin目錄:一些Linux工具。
- etc目錄:系統的配置文件。
- framework目錄:系統平台所有jar包和資源文件包
- lib目錄:系統共享庫
- media目錄:系統的多媒體資源,主要是鈴聲
- priv-app目錄:系統核心的apk文件
- usr目錄:鍵盤布局、時間區域文件
- build.prop目錄:系統屬性的定義文件
- tts目錄:系統的語音合成文件
userdata.img
是設備中data目錄的鏡像,初始時一般不包含任何文件。Android系統初始化時會在/data目錄創建一些子目錄和文件。
