本文轉載自:http://blog.csdn.net/luoshengyang/article/details/18928789
?Android源代碼在編譯之前,要先對編譯環境進行初始化,其中最主要就是指定編譯的類型和目標設備的型號。Android的編譯類型主要有eng、userdebug和user三種,而支持的目標設備型號則是不確定的,它們由當前的源碼配置情況所決定。為了確定源碼支持的所有目標設備型號,Android編譯系統在初始化的過程中,需要在特定的目錄中加載特定的配置文件。接下來本文就對上述的初始化過程進行詳細分析。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!
《Android系統源代碼情景分析》一書正在進擊的程序員網(http://0xcc0xcd.com)中連載,點擊進入!
? ? ? ?對Android編譯環境進行初始化很簡單,分為兩步。第一步是打開一個終端,并且將build/envsetup.sh加載到該終端中:
?
[html]?view plaincopy
$?.?./build/envsetup.sh???including?device/asus/grouper/vendorsetup.sh??including?device/asus/tilapia/vendorsetup.sh??including?device/generic/armv7-a-neon/vendorsetup.sh??including?device/generic/armv7-a/vendorsetup.sh??including?device/generic/mips/vendorsetup.sh??including?device/generic/x86/vendorsetup.sh??including?device/lge/mako/vendorsetup.sh??including?device/samsung/maguro/vendorsetup.sh??including?device/samsung/manta/vendorsetup.sh??including?device/samsung/toroplus/vendorsetup.sh??including?device/samsung/toro/vendorsetup.sh??including?device/ti/panda/vendorsetup.sh??including?sdk/bash_completion/adb.bash?? ????? 從命令的輸出可以知道,文件build/envsetup.sh在加載的過程中,又會在device目錄中尋找那些名稱為vendorsetup.sh的文件,并且也將它們加載到當前終端來。另外,在sdk/bash_completion目錄下的adb.bash文件也會加載到當前終端來,它是用來實現adb命令的bash completion功能的。也就是說,加載了該文件之后,我們在運行adb相關的命令的時候,通過按tab鍵就可以幫助我們自動完成命令的輸入。關于bash completion的知識,可以參考官方文檔:?http://www.gnu.org/s/bash/manual/bash.html#Programmable-Completion。
??? ? 第二步是執行命令lunch,如下所示:
?
?
[html]?view plaincopy
$?lunch????You're?building?on?Linux????Lunch?menu...?pick?a?combo:???????1.?full-eng???????2.?full_x86-eng???????3.?vbox_x86-eng???????4.?full_mips-eng???????5.?full_grouper-userdebug???????6.?full_tilapia-userdebug???????7.?mini_armv7a_neon-userdebug???????8.?mini_armv7a-userdebug???????9.?mini_mips-userdebug???????10.?mini_x86-userdebug???????11.?full_mako-userdebug???????12.?full_maguro-userdebug???????13.?full_manta-userdebug???????14.?full_toroplus-userdebug???????15.?full_toro-userdebug???????16.?full_panda-userdebug????Which?would?you?like??[full-eng]??? ?????? 我們看到lunch命令輸出了一個Lunch菜單,該菜單列出了當前Android源碼支持的所有設備型號及其編譯類型。例如,第一項“full-eng”表示的設備“full”即為模擬器,并且編譯類型為“eng”即為工程機。
?
?????? 當我們選定了一個Lunch菜單項序號(1-16)之后,按回車鍵,就可以完成Android編譯環境的初始化過程。例如,我們選擇1,可以看到以下輸出:
?
[html]?view plaincopy
Which?would?you?like??[full-eng]?1????============================================??PLATFORM_VERSION_CODENAME=REL??PLATFORM_VERSION=4.2??TARGET_PRODUCT=full??TARGET_BUILD_VARIANT=eng??TARGET_BUILD_TYPE=release??TARGET_BUILD_APPS=??TARGET_ARCH=arm??TARGET_ARCH_VARIANT=armv7-a??HOST_ARCH=x86??HOST_OS=linux??HOST_OS_EXTRA=Linux-3.8.0-31-generic-x86_64-with-Ubuntu-13.04-raring??HOST_BUILD_TYPE=release??BUILD_ID=JOP40C??OUT_DIR=out??============================================?? ?????? 我們可以看到,lunch命令幫我們設置好了很多環境變量。通過設置這些環境變量,就配置好了Android編譯環境。
?
?????? 通過圖1我們就可以直觀地看到Android編譯環境初始化完成后,我們所獲得的東西:
圖1 Android編譯環境初始化完成之后
? ? ? ?總體來說,Android編譯環境初始化完成之后,獲得了以下三樣東西:
? ? ? ?1. 將vendor和device目錄下的vendorsetup.sh文件加載到了當前終端;
? ? ? ?2. 新增了lunch、m、mm和mmm等命令;
? ? ? ?3. 通過執行lunch命令設置好了TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BUILD_TYPE和TARGET_BUILD_APPS等環境變量。 ?
? ? ? ?接下來我們就主要分析build/envsetup.sh文件的加載過程以及lunch命令的執行過程。
? ? ? ?一. 文件build/envsetup.sh的加載過程
? ? ? ?文件build/envsetup.sh是一個bash shell腳本,從它里面定義的函數hmm可以知道,它提供了lunch、m、mm和mmm等命令供我們初始化編譯環境或者編譯Android源碼。
?????? 函數hmm的實現如下所示:
?
[plain]?view plaincopy
function?hmm()?{??cat?<<EOF??Invoke?".?build/envsetup.sh"?from?your?shell?to?add?the?following?functions?to?your?environment:??-?lunch:???lunch?<product_name>-<build_variant>??-?tapas:???tapas?[<App1>?<App2>?...]?[arm|x86|mips]?[eng|userdebug|user]??-?croot:???Changes?directory?to?the?top?of?the?tree.??-?m:???????Makes?from?the?top?of?the?tree.??-?mm:??????Builds?all?of?the?modules?in?the?current?directory.??-?mmm:?????Builds?all?of?the?modules?in?the?supplied?directories.??-?cgrep:???Greps?on?all?local?C/C++?files.??-?jgrep:???Greps?on?all?local?Java?files.??-?resgrep:?Greps?on?all?local?res/*.xml?files.??-?godir:???Go?to?the?directory?containing?a?file.????Look?at?the?source?to?view?more?functions.?The?complete?list?is:??EOF??????T=$(gettop)??????local?A??????A=""??????for?i?in?`cat?$T/build/envsetup.sh?|?sed?-n?"/^function?/s/function?[a?z]?.*/\1/p"?|?sort`;?do????????A="$A?$i"??????done??????echo?$A??}?? ?????? 我們在當前終端中執行hmm命令即可以看到函數hmm的完整輸出。
?
?????? 函數hmm主要完成三個工作:
?????? 1. 調用另外一個函數gettop獲得Android源碼的根目錄T。?
?????? 2. 通過cat命令顯示一個Here Document,說明$T/build/envsetup.sh文件加載到當前終端后所提供的主要命令。
?????? 3. 通過sed命令解析$T/build/envsetup.sh文件,并且獲得在里面定義的所有函數的名稱,這些函數名稱就是$T/build/envsetup.sh文件加載到當前終端后提供的所有命令。
?????? 注意,sed命令是一個強大的文本分析工具,它以行為單位為執行文本替換、刪除、新增和選取等操作。函數hmm通過執行以下的sed命令來獲得在$T/build/envsetup.sh文件定義的函數的名稱:
?
[plain]?view plaincopy
sed?-n?"/^function?/s/function?[a?z]?.*/\1/p"?? ?????? 它表示對所有以“function ”開頭的行,如果緊接在“function ”后面的字符串僅由字母a-z和下橫線(_)組成,那么就將這個字符串提取出來。這正好就對應于shell腳本里面函數的定義。
?
?????? 文件build/envsetup.sh除了定義一堆函數之外,還有一個重要的代碼段,如下所示:
?
[plain]?view plaincopy
#?Execute?the?contents?of?any?vendorsetup.sh?files?we?can?find.??for?f?in?`/bin/ls?vendor/*/vendorsetup.sh?vendor/*/*/vendorsetup.sh?device/*/*/vendorsetup.sh?2>?/dev/null`??do??????echo?"including?$f"??????.?$f??done??unset?f?? ??????? 這個for循環遍歷vendor目錄下的一級子目錄和二級子目錄以及device目錄下的二級子目錄中的vendorsetup.sh文件,并且通過source命令(.)將它們加載當前終端來。vendor和device相應子目錄下的vendorsetup.sh文件的實現很簡單,它們主要就是添加相應的設備型號及其編譯類型支持到Lunch菜單中去。
??????? 例如,device/samsung/maguro目錄下的vendorsetup.sh文件的實現如下所示:
?
[plain]?view plaincopy
add_lunch_combo?full_maguro-userdebug?? ??????? 它調用函數add_lunch_combo添加一個名稱為“full_maguro-userdebug”的菜單項到Lunch菜單去。
?
??????? 函數add_lunch_combo定義在build/envsetup.sh文件中,它的實現如下所示:
?
[plain]?view plaincopy
function?add_lunch_combo()??{??????local?new_combo=$1??????local?c??????for?c?in?${LUNCH_MENU_CHOICES[@]}?;?do??????????if?[?"$new_combo"?=?"$c"?]?;?then??????????????return??????????fi??????done??????LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]}?$new_combo)??}?? ??????? 傳遞給函數add_lunch_combo的參數保存在位置參數$1中,接著又保存在一個本地變量new_combo中,用來表示一個要即將要添加的Lunch菜單項。函數首先是在數組LUNCH_MENU_CHOICES中檢查要添加的菜單項是否已經存在。只有在不存在的情況下,才會將它添加到數組LUNCH_MENU_CHOICES中去。注意,${LUNCH_MENU_CHOICES[@]}表示數組LUNCH_MENU_CHOICES的所有元素。
?
??????? 數組LUNCH_MENU_CHOICES是定義在文件build/envsetup.sh的一個全局變量,當文件build/envsetup.sh被加載的時候,這個數組會被初始化為化full-eng、full_x86-eng、vbox_x86-eng和full_mips-eng,如下所示:
?
[plain]?view plaincopy
#?add?the?default?one?here??add_lunch_combo?full-eng??add_lunch_combo?full_x86-eng??add_lunch_combo?vbox_x86-eng??add_lunch_combo?full_mips-eng?? ?????? 這樣當文件build/envsetup.sh加載完成之后,數組LUNCH_MENU_CHOICES就包含了當前源碼支持的所有設備型號及其編譯類型,于是當接下來我們執行lunch命令的時候,就可以通過數組LUNCH_MENU_CHOICES看到一個完整的Lunch藤蔓。
?
?????? 二. lunch命令的執行過程
?????? lunch命令實際上是定義在文件build/envsetup.sh的一個函數,它的實現如下所示:
?
[plain]?view plaincopy
function?lunch()??{??????local?answer????????if?[?"$1"?]?;?then??????????answer=$1??????else??????????print_lunch_menu??????????echo?-n?"Which?would?you?like??[full-eng]?"??????????read?answer??????fi????????local?selection=????????if?[?-z?"$answer"?]??????then??????????selection=full-eng??????elif?(echo?-n?$answer?|?grep?-q?-e?"^[0-9][0-9]*$")??????then??????????if?[?$answer?-le?${#LUNCH_MENU_CHOICES[@]}?]??????????then??????????????selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}??????????fi??????elif?(echo?-n?$answer?|?grep?-q?-e?"^[^\-][^\-]*-[^\-][^\-]*$")??????then??????????selection=$answer??????fi????????if?[?-z?"$selection"?]??????then??????????echo??????????echo?"Invalid?lunch?combo:?$answer"??????????return?1??????fi????????export?TARGET_BUILD_APPS=????????local?product=$(echo?-n?$selection?|?sed?-e?"s/-.*$//")??????check_product?$product??????if?[?$??-ne?0?]??????then??????????echo??????????echo?"**?Don't?have?a?product?spec?for:?'$product'"??????????echo?"**?Do?you?have?the?right?repo?manifest?"??????????product=??????fi????????local?variant=$(echo?-n?$selection?|?sed?-e?"s/^[^\-]*-//")??????check_variant?$variant??????if?[?$??-ne?0?]??????then??????????echo??????????echo?"**?Invalid?variant:?'$variant'"??????????echo?"**?Must?be?one?of?${VARIANT_CHOICES[@]}"??????????variant=??????fi????????if?[?-z?"$product"?-o?-z?"$variant"?]??????then??????????echo??????????return?1??????fi????????export?TARGET_PRODUCT=$product??????export?TARGET_BUILD_VARIANT=$variant??????export?TARGET_BUILD_TYPE=release????????echo????????set_stuff_for_environment??????printconfig??}?? ??????? 函數lunch的執行邏輯如下所示:
??????? 1. 檢查是否帶有參數,即位置參數$1是否等于空。如果不等于空的話,就表明帶有參數,并且該參數是用來指定要編譯的設備型號及其編譯類型的。如果等于空的話,那么就調用另外一個函數print_lunch_menu來顯示Lunch菜單項,并且通過調用read函數來等待用戶輸入。無論通過何種方式,最終變量answer的值就保存了用戶所指定的備型號及其編譯類型。
??????? 2. 對變量answer的值的合法性進行檢查。如果等于空的話,就將它設置為默認值“full-eng”。如果不等于空的話,就分為三種情況考慮。第一種情況是值為數字,那么就需要確保該數字的大小不能超過Lunch菜單項的個數。在這種情況下,會將輸入的數字索引到數組LUNCH_MENU_CHOICES中去,以便獲得一個用來表示設備型號及其編譯類型的文本。第二種情況是非數字文本,那么就需要確保該文本符合<product>-<variant>的形式,其中<product>表示設備型號,而<variant>表示編譯類型 。第三種情況是除了前面兩種情況之外的所有情況,這是非法的。經過合法性檢查后,變量selection代表了用戶所指定的備型號及其編譯類型,如果它的值是非法的,即它的值等于空,那么函數lunch就不往下執行了。
??????? 3. 接下來是解析變量selection的值,也就是通過sed命令將它的<product>和<variant>值提取出來,并且分別保存在變量product和variant中。提取出來的product和variant值有可能是不合法的,因此需要進一步通過調用函數check_product和check_variant來檢查。一旦檢查失敗,也就是函數check_product和check_variant的返回值$?等于非0,那么函數lunch就不往下執行了。
??????? 4. 通過以上合法性檢查之后,就將變量product和variant的值保存在環境變量TARGET_PRODUCT和TARGET_BUILD_VARIANT中。此外,另外一個環境變量TARGET_BUILD_TYPE的值會被設置為"release",表示此次編譯是一個release版本的編譯。另外,前面還有一個環境變量TARGET_BUILD_APPS,它的值被函數lunch設置為空,用來表示此次編譯是對整個系統進行編譯。如果環境變量TARGET_BUILD_APPS的值不等于空,那么就表示此次編譯是只對某些APP模塊進行編譯,而這些APP模塊就是由環境變量TARGET_BUILD_APPS來指定的。
??????? 5. 調用函數set_stuff_for_environment來配置環境,例如設置Java?SDK路徑和交叉編譯工具路徑等。
??????? 6. 調用函數printfconfig來顯示已經配置好的編譯環境參數。
??????? 在上述執行過程中,函數check_product、check_variant和printconfig是比較關鍵的,因此接下來我們就繼續分析它們的實現。
??????? 函數check_product定義在文件build/envsetup.sh中,它的實現如下所示:
?
[plain]?view plaincopy
#?check?to?see?if?the?supplied?product?is?one?we?can?build??function?check_product()??{??????T=$(gettop)??????if?[?!?"$T"?];?then??????????echo?"Couldn't?locate?the?top?of?the?tree.??Try?setting?TOP."?>&2??????????return??????fi??????CALLED_FROM_SETUP=true?BUILD_SYSTEM=build/core?\??????????TARGET_PRODUCT=$1?\??????????TARGET_BUILD_VARIANT=?\??????????TARGET_BUILD_TYPE=?\??????????TARGET_BUILD_APPS=?\??????????get_build_var?TARGET_DEVICE?>?/dev/null??????#?hide?successful?answers,?but?allow?the?errors?to?show??}?? ??????? 函數gettop用來返回Android源代碼工程的根目錄。函數check_product需要在Android源代碼工程根目錄或者子目錄下調用。否則的話,函數check_product就出錯返回。
?
??????? 接下來函數check_product設置幾個環境變量,其中最重要的是前面三個CALLED_FROM_SETUP、BUILD_SYSTEM和TARGET_PRODUCT。環境變量CALLED_FROM_SETUP的值等于true表示接下來執行的make命令是用來初始化Android編譯環境的。環境變量BUILD_SYSTEM用來指定Android編譯系統的核心目錄,它的值被設置為build/core。環境變量TARGET_PRODUCT用來表示要檢查的產品名稱(也就是我們前面說的設備型號),它的值被設置為$1,即函數check_product的調用參數。
??????? 最后函數check_product調用函數get_build_var來檢查由環境變量TARGET_PRODUCT指定的產品名稱是否合法,注意,它的調用參數為TARGET_DEVICE。
??????? 函數get_build_var定義在文件build/envsetup.sh中,它的實現如下所示:
?
[plain]?view plaincopy
#?Get?the?exact?value?of?a?build?variable.??function?get_build_var()??{??????T=$(gettop)??????if?[?!?"$T"?];?then??????????echo?"Couldn't?locate?the?top?of?the?tree.??Try?setting?TOP."?>&2??????????return??????fi??????CALLED_FROM_SETUP=true?BUILD_SYSTEM=build/core?\????????make?--no-print-directory?-C?"$T"?-f?build/core/config.mk?dumpvar-$1??}?? ??????? 這里就可以看到,函數get_build_var實際上就是通過make命令在Android源代碼工程根目錄中執行build/core/config.mk文件,并且將make目標設置為dumpvar-$1,也就是dumpvar-TARGET_DEVICE。
?
??????? 文件build/core/config.mk的內容比較多,這里我們只關注與產品名稱合法性檢查相關的邏輯,這些邏輯也基本上涵蓋了Android編譯系統初始化的邏輯,如下所示:
?
[plain]?view plaincopy
......????#?---------------------------------------------------------------??#?Define?most?of?the?global?variables.??These?are?the?ones?that??#?are?specific?to?the?user's?build?configuration.??include?$(BUILD_SYSTEM)/envsetup.mk????#?Boards?may?be?defined?under?$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)??#?or?under?vendor/*/$(TARGET_DEVICE).??Search?in?both?places,?but??#?make?sure?only?one?exists.??#?Real?boards?should?always?be?associated?with?an?OEM?vendor.??board_config_mk?:=?\??????$(strip?$(wildcard?\??????????$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk?\??????????device/*/$(TARGET_DEVICE)/BoardConfig.mk?\??????????vendor/*/$(TARGET_DEVICE)/BoardConfig.mk?\??????))??ifeq?($(board_config_mk),)????$(error?No?config?file?found?for?TARGET_DEVICE?$(TARGET_DEVICE))??endif??ifneq?($(words?$(board_config_mk)),1)????$(error?Multiple?board?config?files?for?TARGET_DEVICE?$(TARGET_DEVICE):?$(board_config_mk))??endif??include?$(board_config_mk)????......????include?$(BUILD_SYSTEM)/dumpvar.mk?? ?????? 上述代碼主要就是將envsetup.mk、BoardConfig,mk和dumpvar.mk三個Makefile片段文件加載進來。其中,envsetup.mk文件位于$(BUILD_SYSTEM)目錄中,也就是build/core目錄中,BoardConfig.mk文件的位置主要就是由環境變量TARGET_DEVICE來確定,它是用來描述目標產品的硬件模塊信息的,例如CPU體系結構。環境變量TARGET_DEVICE用來描述目標設備,它的值是在envsetup.mk文件加載的過程中確定的。一旦目標設備確定后,就可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)、device/*/$(TARGET_DEVICE)和vendor/*/$(TARGET_DEVICE)目錄中找到對應的BoradConfig.mk文件。注意,變量SRC_TARGET_DIR的值等于build/target。最后,dumpvar.mk文件也是位于build/core目錄中,它用來打印已經配置好的編譯環境信息。
?
??????? 接下來我們就通過進入到build/core/envsetup.mk文件來分析變量TARGET_DEVICE的值是如何確定的:
?
[plain]?view plaincopy
#?Read?the?product?specs?so?we?an?get?TARGET_DEVICE?and?other??#?variables?that?we?need?in?order?to?locate?the?output?files.??include?$(BUILD_SYSTEM)/product_config.mk?? ?????? 它通過加載另外一個文件build/core/product_config.mk文件來確定變量TARGET_DEVICE以及其它與目標產品相關的變量的值。
?
?????? 文件build/core/product_config.mk的內容很多,這里我們只關注變量TARGET_DEVICE設置相關的邏輯,如下所示:
?
[plain]?view plaincopy
......????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????#?all_product_configs?consists?items?like:??#?<product_name>:<path_to_the_product_makefile>??#?or?just?<path_to_the_product_makefile>?in?case?the?product?name?is?the??#?same?as?the?base?filename?of?the?product?config?makefile.??current_product_makefile?:=??all_product_makefiles?:=??$(foreach?f,?$(all_product_configs),\??????$(eval?_cpm_words?:=?$(subst?:,$(space),$(f)))\??????$(eval?_cpm_word1?:=?$(word?1,$(_cpm_words)))\??????$(eval?_cpm_word2?:=?$(word?2,$(_cpm_words)))\??????$(if?$(_cpm_word2),\??????????$(eval?all_product_makefiles?+=?$(_cpm_word2))\??????????$(if?$(filter?$(TARGET_PRODUCT),$(_cpm_word1)),\??????????????$(eval?current_product_makefile?+=?$(_cpm_word2)),),\??????????$(eval?all_product_makefiles?+=?$(f))\??????????$(if?$(filter?$(TARGET_PRODUCT),$(basename?$(notdir?$(f)))),\??????????????$(eval?current_product_makefile?+=?$(f)),)))??_cpm_words?:=??_cpm_word1?:=??_cpm_word2?:=??current_product_makefile?:=?$(strip?$(current_product_makefile))??all_product_makefiles?:=?$(strip?$(all_product_makefiles))????ifneq?(,$(filter?product-graph?dump-products,?$(MAKECMDGOALS)))??#?Import?all?product?makefiles.??$(call?import-products,?$(all_product_makefiles))??else??#?Import?just?the?current?product.??ifndef?current_product_makefile??$(error?Cannot?locate?config?makefile?for?product?"$(TARGET_PRODUCT)")??endif??ifneq?(1,$(words?$(current_product_makefile)))??$(error?Product?"$(TARGET_PRODUCT)"?ambiguous:?matches?$(current_product_makefile))??endif??$(call?import-products,?$(current_product_makefile))??endif??#?Import?all?or?just?the?current?product?makefile????......????#?Convert?a?short?name?like?"sooner"?into?the?path?to?the?product??#?file?defining?that?product.??#??INTERNAL_PRODUCT?:=?$(call?resolve-short-product-name,?$(TARGET_PRODUCT))??ifneq?($(current_product_makefile),$(INTERNAL_PRODUCT))??$(error?PRODUCT_NAME?inconsistent?in?$(current_product_makefile)?and?$(INTERNAL_PRODUCT))??endif??current_product_makefile?:=??all_product_makefiles?:=??all_product_configs?:=????#?Find?the?device?that?this?product?maps?to.??TARGET_DEVICE?:=?$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)????......?? ?????? 上述代碼的執行邏輯如下所示:
?
?????? 1. 檢查環境變量TARGET_BUILD_APPS的值是否等于空。如果不等于空,那么就說明此次編譯不是針對整個系統,因此只要將核心的產品相關的Makefile文件加載進來就行了,否則的話,就要將所有與產品相關的Makefile文件加載進來的。核心產品Makefile文件在$(SRC_TARGET_DIR)/product/AndroidProducts.mk文件中指定,也就是在build/target/product/AndroidProducts.mk文件,通過調用函數get-product-makefiles可以獲得。所有與產品相關的Makefile文件可以通過另外一個函數get-all-product-makefiles獲得。無論如何,最終獲得的產品Makefie文件列表保存在變量all_product_configs中。
?????? 2. 遍歷變量all_product_configs所描述的產品Makefile列表,并且在這些Makefile文件中,找到名稱與環境變量TARGET_PRODUCT的值相同的文件,保存在另外一個變量current_product_makefile中,作為需要為當前指定的產品所加載的Makefile文件列表。在這個過程當中,上一步找到的所有的產品Makefile文件也會保存在變量all_product_makefiles中。注意,環境變量TARGET_PRODUCT的值是在我們執行lunch命令的時候設置并且傳遞進來的。
?????? 3.? 如果指定的make目標等于product-graph或者dump-products,那么就將所有的產品相關的Makefile文件加載進來,否則的話,只加載與目標產品相關的Makefile文件。從前面的分析可以知道,此時的make目標為dumpvar-TARGET_DEVICE,因此接下來只會加載與目標產品,即$(TARGET_PRODUCT),相關的Makefile文件,這是通過調用另外一個函數import-products實現的。
?????? 4. 調用函數resolve-short-product-name解析環境變量TARGET_PRODUCT的值,將它變成一個Makefile文件路徑。并且保存在變量INTERNAL_PRODUCT中。這里要求變量INTERNAL_PRODUCT和current_product_makefile的值相等,否則的話,就說明用戶指定了一個非法的產品名稱。
?????? 5. 找到一個名稱為PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE的變量,并且將它的值保存另外一個變量TARGET_DEVICE中。變量PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE是在加載產品Makefile文件的過程中定義的,用來描述當前指定的產品的名稱。
?????? 上述過程主要涉及到了get-all-product-makefiles、import-products和resolve-short-product-name三個關鍵函數,理解它們的執行過程對理解Android編譯系統的初始化過程很有幫助,接下來我們分別分析它們的實現。
??????? 函數get-all-product-makefiles定義在文件build/core/product.mk中,如下所示:
?
[plain]?view plaincopy
#??#?Returns?the?sorted?concatenation?of?all?PRODUCT_MAKEFILES??#?variables?set?in?all?AndroidProducts.mk?files.??#?$(call?)?isn't?necessary.??#??define?get-all-product-makefiles??$(call?get-product-makefiles,$(_find-android-products-files))??endef?? ?????? 它首先是調用函數_find-android-products-files來找到Android源代碼目錄中定義的所有AndroidProducts.mk文件,然后再調用函數get-product-makefiles獲得在這里AndroidProducts.mk文件里面定義的產品Makefile文件。
?
?????? 函數_find-android-products-files也是定義在文件build/core/product.mk中,如下所示:
?
[plain]?view plaincopy
#??#?Returns?the?list?of?all?AndroidProducts.mk?files.??#?$(call?)?isn't?necessary.??#??define?_find-android-products-files??$(shell?test?-d?device?&&?find?device?-maxdepth?6?-name?AndroidProducts.mk)?\????$(shell?test?-d?vendor?&&?find?vendor?-maxdepth?6?-name?AndroidProducts.mk)?\????$(SRC_TARGET_DIR)/product/AndroidProducts.mk??endef?? ????? 從這里就可以看出,Android源代碼目錄中定義的所有AndroidProducts.mk文件位于device、vendor或者build/target/product目錄或者相應的子目錄(最深是6層)中。
?
????? 函數get-product-makefiles也是定義在文件build/core/product.mk中,如下所示:
?
[plain]?view plaincopy
#??#?Returns?the?sorted?concatenation?of?PRODUCT_MAKEFILES??#?variables?set?in?the?given?AndroidProducts.mk?files.??#?$(1):?the?list?of?AndroidProducts.mk?files.??#??define?get-product-makefiles??$(sort?\????$(foreach?f,$(1),?\??????$(eval?PRODUCT_MAKEFILES?:=)?\??????$(eval?LOCAL_DIR?:=?$(patsubst?%/,%,$(dir?$(f))))?\??????$(eval?include?$(f))?\??????$(PRODUCT_MAKEFILES)?\?????)?\????$(eval?PRODUCT_MAKEFILES?:=)?\????$(eval?LOCAL_DIR?:=)?\???)??endef?? ?????? 這個函數實際上就是遍歷參數$1所描述的AndroidProucts.mk文件列表,并且將定義在這些AndroidProucts.mk文件中的變量PRODUCT_MAKEFILES的值提取出來,形成一個列表返回給調用者。
?
?????? 例如,在build/target/product/AndroidProducts.mk文件中,變量PRODUCT_MAKEFILES的值如下所示:
?
[plain]?view plaincopy
#?Unbundled?apps?will?be?built?with?the?most?generic?product?config.??ifneq?($(TARGET_BUILD_APPS),)??PRODUCT_MAKEFILES?:=?\??????$(LOCAL_DIR)/full.mk?\??????$(LOCAL_DIR)/full_x86.mk?\??????$(LOCAL_DIR)/full_mips.mk??else??PRODUCT_MAKEFILES?:=?\??????$(LOCAL_DIR)/core.mk?\??????$(LOCAL_DIR)/generic.mk?\??????$(LOCAL_DIR)/generic_x86.mk?\??????$(LOCAL_DIR)/generic_mips.mk?\??????$(LOCAL_DIR)/full.mk?\??????$(LOCAL_DIR)/full_x86.mk?\??????$(LOCAL_DIR)/full_mips.mk?\??????$(LOCAL_DIR)/vbox_x86.mk?\??????$(LOCAL_DIR)/sdk.mk?\??????$(LOCAL_DIR)/sdk_x86.mk?\??????$(LOCAL_DIR)/sdk_mips.mk?\??????$(LOCAL_DIR)/large_emu_hw.mk??endif?? ?????? 這里列出的每一個文件都對應于一個產品。
?
?????? 我們再來看函數import-products的實現,它定義在文件build/core/product.mk中,如下所示:
?
[plain]?view plaincopy
#??#?$(1):?product?makefile?list??#??#TODO:?check?to?make?sure?that?products?have?all?the?necessary?vars?defined??define?import-products??$(call?import-nodes,PRODUCTS,$(1),$(_product_var_list))??endef?? ?????? 它調用另外一個函數import-nodes來加載由參數$1所指定的產品Makefile文件,并且指定了另外兩個參數PRODUCTS和$(_product_var_list)。其中,變量_product_var_list也是定義在文件build/core/product.mk中,它的值如下所示:
?
?
[plain]?view plaincopy
_product_var_list?:=?\??????PRODUCT_NAME?\??????PRODUCT_MODEL?\??????PRODUCT_LOCALES?\??????PRODUCT_AAPT_CONFIG?\??????PRODUCT_AAPT_PREF_CONFIG?\??????PRODUCT_PACKAGES?\??????PRODUCT_PACKAGES_DEBUG?\??????PRODUCT_PACKAGES_ENG?\??????PRODUCT_PACKAGES_TESTS?\??????PRODUCT_DEVICE?\??????PRODUCT_MANUFACTURER?\??????PRODUCT_BRAND?\??????PRODUCT_PROPERTY_OVERRIDES?\??????PRODUCT_DEFAULT_PROPERTY_OVERRIDES?\??????PRODUCT_CHARACTERISTICS?\??????PRODUCT_COPY_FILES?\??????PRODUCT_OTA_PUBLIC_KEYS?\??????PRODUCT_EXTRA_RECOVERY_KEYS?\??????PRODUCT_PACKAGE_OVERLAYS?\??????DEVICE_PACKAGE_OVERLAYS?\??????PRODUCT_TAGS?\??????PRODUCT_SDK_ADDON_NAME?\??????PRODUCT_SDK_ADDON_COPY_FILES?\??????PRODUCT_SDK_ADDON_COPY_MODULES?\??????PRODUCT_SDK_ADDON_DOC_MODULES?\??????PRODUCT_DEFAULT_WIFI_CHANNELS?\??????PRODUCT_DEFAULT_DEV_CERTIFICATE?\??????PRODUCT_RESTRICT_VENDOR_FILES?\??????PRODUCT_VENDOR_KERNEL_HEADERS?\??????PRODUCT_FACTORY_RAMDISK_MODULES?\??????PRODUCT_FACTORY_BUNDLE_MODULES?? ?????? 它描述的是在產品Makefile文件中定義在各種變量。
?
?????? 函數import-nodes定義在文件build/core/node_fns.mk中,如下所示:
?
[plain]?view plaincopy
#??#?$(1):?output?list?variable?name,?like?"PRODUCTS"?or?"DEVICES"??#?$(2):?list?of?makefiles?representing?nodes?to?import??#?$(3):?list?of?node?variable?names??#??define?import-nodes??$(if?\????$(foreach?_in,$(2),?\??????$(eval?_node_import_context?:=?_nic.$(1).[[$(_in)]])?\??????$(if?$(_include_stack),$(eval?$(error?ASSERTION?FAILED:?_include_stack?\??????????????????should?be?empty?here:?$(_include_stack))),)?\??????$(eval?_include_stack?:=?)?\??????$(call?_import-nodes-inner,$(_node_import_context),$(_in),$(3))?\??????$(call?move-var-list,$(_node_import_context).$(_in),$(1).$(_in),$(3))?\??????$(eval?_node_import_context?:=)?\??????$(eval?$(1)?:=?$($(1))?$(_in))?\??????$(if?$(_include_stack),$(eval?$(error?ASSERTION?FAILED:?_include_stack?\??????????????????should?be?empty?here:?$(_include_stack))),)?\?????)?\??,)??endef?? ?????? 這個函數主要是做了三件事情:
?
?????? 1. 調用函數_import-nodes-inner將參數$2描述的每一個產品Makefile文件加載進來。
?????? 2. 調用函數move-var-list將定義在前面所加載的產品Makefile文件里面的由參數$3指定的變量的值分別拷貝到另外一組獨立的變量中。
?????? 3. 將參數$2描述的每一個產品Makefile文件路徑以空格分隔保存在參數$1所描述的變量中,也就是保存在變量PRODUCTS中。
?????? 上述第二件事情需要進一步解釋一下。由于當前加載的每一個文件都會定義相同的變量,為了區分這些變量,我們需要在這些變量前面加一些前綴。例如,假設加載了build/target/product/full.mk這個產品Makefile文件,它里面定義了以下幾個變量:
?
[plain]?view plaincopy
#?Overrides??PRODUCT_NAME?:=?full??PRODUCT_DEVICE?:=?generic??PRODUCT_BRAND?:=?Android??PRODUCT_MODEL?:=?Full?Android?on?Emulator?? ?????? 當調用了函數move-var-list對它進行解析后,就會得到以下的新變量:
?
?
[plain]?view plaincopy
PRODUCTS.build/target/product/full.mk.PRODUCT_NAME?:=?full??PRODUCTS.build/target/product/full.mk.PRODUCT_DEVICE?:=?generic??PRODUCTS.build/target/product/full.mk.PRODUCT_BRAND?:=?Android??PRODUCTS.build/target/product/full.mk.PRODUCT_MODEL?:=?Full?Android?on?Emulator?? ?????? 正是由于調用了函數move-var-list,我們在build/core/product_config.mk文件中可以通過PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE來設置變量TARGET_DEVICE的值。
?
?????? 回到build/core/config.mk文件中,接下來我們再看BoardConfig.mk文件的加載過程。前面提到,當前要加載的BoardConfig.mk文件由變量TARGET_DEVICE來確定。例如,假設我們在運行lunch命令時,輸入的文本為full-eng,那么build/target/product/full.mk就會被加載,并且我們得到TARGET_DEVICE的值就為generic,接下來加載的BoradConfig.mk文件就會在build/target/board/generic目錄中找到。
?????? BoardConfig.mk文件定義的信息可以參考build/target/board/generic/BoardConfig.mk文件的內容,如下所示:
?
[plain]?view plaincopy
#?config.mk??#??#?Product-specific?compile-time?definitions.??#????#?The?generic?product?target?doesn't?have?any?hardware-specific?pieces.??TARGET_NO_BOOTLOADER?:=?true??TARGET_NO_KERNEL?:=?true??TARGET_ARCH?:=?arm????#?Note:?we?build?the?platform?images?for?ARMv7-A?_without_?NEON.??#??#?Technically,?the?emulator?supports?ARMv7-A?_and_?NEON?instructions,?but??#?emulated?NEON?code?paths?typically?ends?up?2x?slower?than?the?normal?C?code??#?it?is?supposed?to?replace?(unlike?on?real?devices?where?it?is?2x?to?3x??#?faster).??#??#?What?this?means?is?that?the?platform?image?will?not?use?NEON?code?paths??#?that?are?slower?to?emulate.?On?the?other?hand,?it?is?possible?to?emulate??#?application?code?generated?with?the?NDK?that?uses?NEON?in?the?emulator.??#??TARGET_ARCH_VARIANT?:=?armv7-a??TARGET_CPU_ABI?:=?armeabi-v7a??TARGET_CPU_ABI2?:=?armeabi??ARCH_ARM_HAVE_TLS_REGISTER?:=?true????HAVE_HTC_AUDIO_DRIVER?:=?true??BOARD_USES_GENERIC_AUDIO?:=?true????#?no?hardware?camera??USE_CAMERA_STUB?:=?true????#?Enable?dex-preoptimization?to?speed?up?the?first?boot?sequence??#?of?an?SDK?AVD.?Note?that?this?operation?only?works?on?Linux?for?now??ifeq?($(HOST_OS),linux)????ifeq?($(WITH_DEXPREOPT),)??????WITH_DEXPREOPT?:=?true????endif??endif????#?Build?OpenGLES?emulation?guest?and?host?libraries??BUILD_EMULATOR_OPENGL?:=?true????#?Build?and?enable?the?OpenGL?ES?View?renderer.?When?running?on?the?emulator,??#?the?GLES?renderer?disables?itself?if?host?GL?acceleration?isn't?available.??USE_OPENGL_RENDERER?:=?true?? ?????? 它描述了產品的Boot Loader、Kernel、CPU體系結構、CPU ABI和Opengl加速等信息。
?
?????? 再回到build/core/config.mk文件中,它最后加載build/core/dumpvar.mk文件。加載build/core/dumpvar.mk文件是為了生成make目標,以便可以對這些目標進行操作。例如,在我們這個情景中,我們要執行的make目標是dumpvar-TARGET_DEVICE,因此在加載build/core/dumpvar.mk文件的過程中,就會生成dumpvar-TARGET_DEVICE目標。
?????? 文件build/core/dumpvar.mk的內容也比較多,這里我們只關注生成make目標相關的邏輯:
?
[plain]?view plaincopy
......????#?The?"dumpvar"?stuff?lets?you?say?something?like??#??#?????CALLED_FROM_SETUP=true?\??#???????make?-f?config/envsetup.make?dumpvar-TARGET_OUT??#?or??#?????CALLED_FROM_SETUP=true?\??#???????make?-f?config/envsetup.make?dumpvar-abs-HOST_OUT_EXECUTABLES??#??#?The?plain?(non-abs)?version?just?dumps?the?value?of?the?named?variable.??#?The?"abs"?version?will?treat?the?variable?as?a?path,?and?dumps?an??#?absolute?path?to?it.??#??dumpvar_goals?:=?\??????$(strip?$(patsubst?dumpvar-%,%,$(filter?dumpvar-%,$(MAKECMDGOALS))))??ifdef?dumpvar_goals??????ifneq?($(words?$(dumpvar_goals)),1)??????$(error?Only?one?"dumpvar-"?goal?allowed.?Saw?"$(MAKECMDGOALS)")????endif??????#?If?the?goal?is?of?the?form?"dumpvar-abs-VARNAME",?then????#?treat?VARNAME?as?a?path?and?return?the?absolute?path?to?it.????absolute_dumpvar?:=?$(strip?$(filter?abs-%,$(dumpvar_goals)))????ifdef?absolute_dumpvar??????dumpvar_goals?:=?$(patsubst?abs-%,%,$(dumpvar_goals))??????ifneq?($(filter?/%,$($(dumpvar_goals))),)????????DUMPVAR_VALUE?:=?$($(dumpvar_goals))??????else????????DUMPVAR_VALUE?:=?$(PWD)/$($(dumpvar_goals))??????endif??????dumpvar_target?:=?dumpvar-abs-$(dumpvar_goals)????else??????DUMPVAR_VALUE?:=?$($(dumpvar_goals))??????dumpvar_target?:=?dumpvar-$(dumpvar_goals)????endif????.PHONY:?$(dumpvar_target)??$(dumpvar_target):??????@echo?$(DUMPVAR_VALUE)????endif?#?dumpvar_goals????......?? ????? 我們在執行make命令時,指定的目示會經由MAKECMDGOALS變量傳遞到Makefile中,因此通過變量MAKECMDGOALS可以獲得make目標。
?
????? 上述代碼的邏輯很簡單,例如,在我們這個情景中,指定的make目標為dumpvar-TARGET_DEVICE,那么就會得到變量DUMPVAR_VALUE的值為$(TARGET_DEVICE)。TARGET_DEVICE的值在前面已經被設置為“generic”,因此變量DUMPVAR_VALUE的值就等于“generic”。此外,變量dumpvar_target的被設置為“dumpvar-TARGET_DEVICE”。最后我們就可以得到以下的make規則:
[plain]?view plaincopy
.PHONY?dumpvar-TARGET_DEVICE??dumpvar-TARGET_DEVICE:??????@echo?generic?? ?????? 至此,在build/envsetup.sh文件中定義的函數check_product就分析完成了。看完了之后,小伙伴們可能會問,前面不是說這個函數是用來檢查用戶輸入的產品名稱是否合法的嗎?但是這里沒看出哪一段代碼給出了true或者false的答案啊。實際上,在前面分析的build/core/config.mk和build/core/product_config.mk等文件的加載過程中,如果發現輸入的產品名稱是非法的,也就是找不到相應的產品Makefile文件,那么就會通過調用error函數來產生一個錯誤,這時候函數check_product的返回值$?就會等于非0值。
?
?????? 接下來我們還要繼續分析在build/envsetup.sh文件中定義的函數check_variant的實現,如下所示:
?
[plain]?view plaincopy
VARIANT_CHOICES=(user?userdebug?eng)????#?check?to?see?if?the?supplied?variant?is?valid??function?check_variant()??{??????for?v?in?${VARIANT_CHOICES[@]}??????do??????????if?[?"$v"?=?"$1"?]??????????then??????????????return?0??????????fi??????done??????return?1??}?? ?????? 這個函數的實現就簡單多了。合法的編譯類型定義在數組VARIANT_CHOICES中,并且它只有三個值user、userdebug和eng。其中,user表示發布版本,userdebug表示帶調試信息的發布版本,而eng表標工程機版本。
?
?????? 最后,我們再來分析在build/envsetup.sh文件中定義的函數printconfig的實現,如下所示:
?
[plain]?view plaincopy
function?printconfig()??{??????T=$(gettop)??????if?[?!?"$T"?];?then??????????echo?"Couldn't?locate?the?top?of?the?tree.??Try?setting?TOP."?>&2??????????return??????fi??????get_build_var?report_config??}?? ?????? 對比我們前面對函數check_product的分析,就會發現函數printconfig的實現與這很相似,都是通過調用get_build_var來獲得相關的信息,但是這里傳遞給函數get_build_var的參數為report_config。
?
?????? 我們跳過前面build/core/config.mk和build/core/envsetup.mk等文件對目標產品Makefile文件的加載,直接跳到build/core/dumpvar.mk文件來查看與report_config這個make目標相關的邏輯:
?
[plain]?view plaincopy
......????dumpvar_goals?:=?\??????$(strip?$(patsubst?dumpvar-%,%,$(filter?dumpvar-%,$(MAKECMDGOALS))))??.....????ifneq?($(dumpvar_goals),report_config)??PRINT_BUILD_CONFIG:=??endif????......????ifneq?($(PRINT_BUILD_CONFIG),)??HOST_OS_EXTRA:=$(shell?python?-c?"import?platform;?print(platform.platform())")??$(info?============================================)??$(info???PLATFORM_VERSION_CODENAME=$(PLATFORM_VERSION_CODENAME))??$(info???PLATFORM_VERSION=$(PLATFORM_VERSION))??$(info???TARGET_PRODUCT=$(TARGET_PRODUCT))??$(info???TARGET_BUILD_VARIANT=$(TARGET_BUILD_VARIANT))??$(info???TARGET_BUILD_TYPE=$(TARGET_BUILD_TYPE))??$(info???TARGET_BUILD_APPS=$(TARGET_BUILD_APPS))??$(info???TARGET_ARCH=$(TARGET_ARCH))??$(info???TARGET_ARCH_VARIANT=$(TARGET_ARCH_VARIANT))??$(info???HOST_ARCH=$(HOST_ARCH))??$(info???HOST_OS=$(HOST_OS))??$(info???HOST_OS_EXTRA=$(HOST_OS_EXTRA))??$(info???HOST_BUILD_TYPE=$(HOST_BUILD_TYPE))??$(info???BUILD_ID=$(BUILD_ID))??$(info???OUT_DIR=$(OUT_DIR))??$(info?============================================)??endif?? ?????? 變量PRINT_BUILD_CONFIG定義在文件build/core/envsetup.mk中,默認值設置為true。當make目標為report-config的時候,變量PRINT_BUILD_CONFIG的值就會被設置為空。因此,接下來就會打印一系列用來描述編譯環境配置的變量的值,也就是我們執行lunch命令后看到的輸出。注意,這些環境配置相關的變量量都是在加載build/core/config.mk和build/core/envsetup.mk文件的過程中設置的,就類似于前面我們分析的TARGET_DEVICE變量的值的設置過程。
?
?????? 至此,我們就分析完成Android編譯系統環境的初始化過程了。從分析的過程可以知道,Android編譯系統環境是由build/core/config.mk、build/core/envsetup.mk、build/core/product_config.mk、AndroidProducts.mk和BoardConfig.mk等文件來完成的。這些mk文件涉及到非常多的細節,而我們這里只提供了一個大體的骨架和脈絡,希望能夠起到拋磚引玉的作用。
?????? 有了Android編譯系統環境的初始化過程知識之后,在接下來的一篇文章中,老羅將繼續分析Android編譯系統提供的m/mm/mmm編譯命令,敬請關注!更多信息也可以關注老羅的新浪微博:http://weibo.com/shengyangluo
總結
以上是生活随笔為你收集整理的Android编译系统环境过程初始化分析【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。