因為Android的編譯系統不同于Linux Kernel的遞歸式的編譯系統,它的編譯系統是一種稱之為independent的模式,每個模塊基本獨立(它有可能依賴其他模塊),每個模塊都可以單獨編譯,這是Android independent編譯系統模式的好處。但這并不意味著它是完美的,普通電腦編譯android系統需要8個小時甚至更多(以本人的電腦為例),而編譯linux kernel只需要半個小時,代碼量是一回事,由independent模式造成的編譯時間長應該是可以肯定的。正因為每個模塊可以單獨編譯,所以android系統的編譯就是依次編譯每個模塊,然后把所有編譯好的模塊和其他一些文件一起打包成鏡像文件。因此,只要理解了每個模塊的編譯,理解android系統的編譯就輕松多了。(以上均是個人觀點,歡迎拍磚)
在我們source build/envsetup.sh 和 lunch 后,就可以執行mm命令編譯單個模塊了:
所以,編譯的其實位置從mm說起:
[java]?view plaincopy
function?mm()??{??????local?T=$(gettop)??????local?DRV=$(getdriver?$T)??????#?If?we're?sitting?in?the?root?of?the?build?tree,?just?do?a??????#?normal?make.??????if?[?-f?build/core/envsetup.mk?-a?-f?Makefile?];?then??????????$DRV?make?$@??????else??????????#?Find?the?closest?Android.mk?file.??????????local?M=$(findmakefile)??????????local?MODULES=??????????local?GET_INSTALL_PATH=??????????local?ARGS=??????????#?Remove?the?path?to?top?as?the?makefilepath?needs?to?be?relative??????????local?M=`echo?$M|sed?'s:'$T'/::'`??????????if?[?!?"$T"?];?then??????????????echo?"Couldn't?locate?the?top?of?the?tree.??Try?setting?TOP."??????????????return?1??????????elif?[?!?"$M"?];?then??????????????echo?"Couldn't?locate?a?makefile?from?the?current?directory."??????????????return?1??????????else??????????????for?ARG?in?$@;?do??????????????????case?$ARG?in????????????????????GET-INSTALL-PATH)?GET_INSTALL_PATH=$ARG;;??????????????????esac??????????????done??????????????if?[?-n?"$GET_INSTALL_PATH"?];?then????????????????MODULES=????????????????ARGS=GET-INSTALL-PATH??????????????else????????????????MODULES=all_modules????????????????ARGS=$@??????????????fi??????????????ONE_SHOT_MAKEFILE=$M?$DRV?make?-C?$T?-f?build/core/main.mk?$MODULES?$ARGS??????????fi??????fi??}??
這個函數做了三件事情:1.找到Android.mk文件,2.設置ONE_SHOT_MAKEFILE=$M,3.執行make all_modules進行編譯
1.findmakefile:
[java]?view plaincopy
function?findmakefile()??{??????TOPFILE=build/core/envsetup.mk??????local?HERE=$PWD??????T=??????while?[?!\(?f$TOPFILE!\(?f$TOPFILE?\)?-a?\(?$PWD?!=?"/"?\)?];?do??????????T=`PWD=?/bin/pwd`??????????if?[?-f?"$T/Android.mk"?];?then??????????????echo?$T/Android.mk??????????????\cd?$HERE??????????????return??????????fi??????????\cd?..??????done??????\cd?$HERE??}??
這個函數首先在當前目錄下查找Android.mk,如果沒有就向上查找。
2.ONE_SHOT_MAKEFILE=$M
$M = $(findmakefile),所以它就是用來編譯的那個模塊的Android.mk,一般情況下,如果你在當前目錄下執行mm,而且當前目錄下如果有個Android.mk的話,那她就是這個Android.mk的路勁+Android.mk了。
3.make -C $T -f build/core/main.mk $MODULES $ARGS
-C $T表明還是在源碼頂級目錄下執行make的,傳入的參數一個是$MODULES=all_modules,$ARGS為空
這個時候,代碼機會執行頂級的Makefile:
[java]?view plaincopy
###?DO?NOT?EDIT?THIS?FILE?###??include?build/core/main.mk??###?DO?NOT?EDIT?THIS?FILE?###??
加載main.mk
main.mk往下加載,不久我們就看到了我們在mm函數中設置的ONE_SHOT_MAKEFILE變量了:
[java]?view plaincopy
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??
這里判斷ONE_SHOT_MAKEFILE是否為空,當然不為空了。緊接著開始加載這個Android.mk,也就是我們要編譯的那個Android.mk。簡單起見,這里以frameworks/base/cmds/screencap模塊的編譯為例,它的內容如下:
[java]?view plaincopy
LOCAL_PATH:=?$(call?my-dir)??include?$(CLEAR_VARS)????LOCAL_SRC_FILES:=?\??????screencap.cpp????LOCAL_SHARED_LIBRARIES?:=?\??????libcutils?\??????libutils?\??????libbinder?\??????libskia?\??????libui?\??????libgui????LOCAL_MODULE:=?screencap????LOCAL_MODULE_TAGS?:=?optional????LOCAL_CFLAGS?+=?-Wall?-Werror?-Wunused?-Wunreachable-code????include?$(BUILD_EXECUTABLE)??
它的變量非常少,這很有利于我們搞清它編譯的過程。include 這個Android.mk后,又include $(CLEAR_VARS)
[java]?view plaincopy
core/config.mk:69:CLEAR_VARS:=?$(BUILD_SYSTEM)/clear_vars.mk??
CLEAR_VARS定義在config.mk文件中,它指向一個clear_vars.mk文件:
[java]?view plaincopy
LOCAL_MODULE:=??LOCAL_MODULE_PATH:=??LOCAL_MODULE_RELATIVE_PATH?:=??LOCAL_MODULE_STEM:=??LOCAL_DONT_CHECK_MODULE:=??LOCAL_CHECKED_MODULE:=??LOCAL_BUILT_MODULE:=??LOCAL_BUILT_MODULE_STEM:=??
[java]?view plaincopy
。。。??
這個文件就是把一大堆的文件置為空,除了LOCAL_PATH變量之外。
接著,它有include BUILD_EXTABLE指向的腳本。
[java]?view plaincopy
core/config.mk:74:BUILD_EXECUTABLE:=?$(BUILD_SYSTEM)/executable.mk??
BUILD_EXTABLE變量也定義在config.mk中,它指向的excutable.mk腳本內容如下:
關于閱讀Makefile,個人觀點就是緊追依賴鏈。我們執行的make的時候不是傳了一個目標叫all_mudules了嗎?所以make就會從它開始推導依賴關系,然后從依賴鏈的最葉子的位置生成目標,一次向上。所以那就看看all_modules:
[java]?view plaincopy
#?phony?target?that?include?any?targets?in?$(ALL_MODULES)??.PHONY:?all_modules??ifndef?BUILD_MODULES_IN_PATHS??all_modules:?$(ALL_MODULES)??else??#?BUILD_MODULES_IN_PATHS?is?a?list?of?paths?relative?to?the?top?of?the?tree??module_path_patterns?:=?$(foreach?p,?$(BUILD_MODULES_IN_PATHS),\??????$(if?$(filter?%/,$(p)),$(p)%,$(p)/%))??my_all_modules?:=?$(sort?$(foreach?m,?$(ALL_MODULES),$(if?$(filter\??????$(module_path_patterns),?$(addsuffix?/,$(ALL_MODULES.$(m).PATH))),$(m))))??all_modules:?$(my_all_modules)??endif??
all_modules的依賴取決于有沒有定義BUILD_MODULES_IN_PATHS,然而我們并有定義它,所以它就all_modules的依賴就是$(ALL_MODULES)。
至此,就需要我們一步步推導依賴關系了,為方便理解,現直接把依賴關系以圖的形式列出:
由于一張顯示不完,$(linked_module)的依賴如下:
圖中的變量未經推導,為了方便對比,推導出變量的值后的圖如下:
$(linked_module):
從圖中可以看到最終生成的文件有:
out/target/product/xxx/obj/excutable/screepcap__intermediates/screencap
out/target/product/xxx/symbols/system/bin/screencap
out/target/product/xxx/obj/excutable/screepcap__intermediates/PACKED/screencap
out/target/product/xxx/obj/excutable/screepcap__intermediates/LINKED/screencap
out/target/product/xxx/obj/excutable/screepcap__intermediates/screencap.o
out/target/product/xxx/obj/excutable/screepcap__intermediates/export_includes out/target/product/xxx/obj/excutable/screepcap__intermediates/import_includes
至于變量的推導過程,大家順著文件加載的順序慢慢推導就是了,這個過程可能比較花時間,但也是沒辦法的事。
以下是一些重要文件的加載順序(只有部分比較重要的):
畫圈的是我認為非常重要的文件。
在所有依賴生成以后,Android是怎么編譯某個模塊的呢?
以下是我認為的核心代碼,代碼在dynamic_binary.mk中:
[java]?view plaincopy
$(linked_module):?$(my_target_crtbegin_dynamic_o)?$(all_objects)?$(all_libraries)?$(my_target_crtend_o)??????$(transform-o-to-executable)??
還記得我們推導出來的linked_module的值嗎?它等于:
out/target/product/xxx/obj/excutable/screepcap__intermediates/LINKED/screencap
生成這個文件后,從依賴關系上也可以看出,其他文件在此基礎上生成,而這個文件使用transform-o-to-executable函數生成,該函數定義如下:
[java]?view plaincopy
define?transform-o-to-executable??@mkdir?-p?$(dir?$@)??@echo?"target?Executable:?$(PRIVATE_MODULE)?($@)"??$(transform-o-to-executable-inner)??endef??
調用transform-o-to-executable-inner函數進一步處理:
[java]?view plaincopy
define?transform-o-to-executable-inner??$(hide)?$(PRIVATE_CXX)?-pie?\??????-nostdlib?-Bdynamic?\??????-Wl,-dynamic-linker,$($(PRIVATE_2ND_ARCH_VAR_PREFIX)TARGET_LINKER)?\??????-Wl,--gc-sections?\??????-Wl,-z,nocopyreloc?\??????$(PRIVATE_TARGET_GLOBAL_LD_DIRS)?\??????-Wl,-rpath-link=$(PRIVATE_TARGET_OUT_INTERMEDIATE_LIBRARIES)?\??????$(if?$(filter?true,$(PRIVATE_NO_CRT)),,$(PRIVATE_TARGET_CRTBEGIN_DYNAMIC_O))?\??????$(PRIVATE_ALL_OBJECTS)?\??????-Wl,--whole-archive?\??????$(call?normalize-target-libraries,$(PRIVATE_ALL_WHOLE_STATIC_LIBRARIES))?\??????-Wl,--no-whole-archive?\??????$(if?$(PRIVATE_GROUP_STATIC_LIBRARIES),-Wl$(comma)--start-group)?\??????$(call?normalize-target-libraries,$(PRIVATE_ALL_STATIC_LIBRARIES))?\??????$(if?$(PRIVATE_GROUP_STATIC_LIBRARIES),-Wl$(comma)--end-group)?\??????$(if?$(filter?true,$(NATIVE_COVERAGE)),$(PRIVATE_TARGET_LIBGCOV))?\??????$(if?$(filter?true,$(NATIVE_COVERAGE)),$(PRIVATE_TARGET_LIBPROFILE_RT))?\??????$(PRIVATE_TARGET_LIBATOMIC)?\??????$(PRIVATE_TARGET_LIBGCC)?\??????$(call?normalize-target-libraries,$(PRIVATE_ALL_SHARED_LIBRARIES))?\??????-o?$@?\??????$(PRIVATE_TARGET_GLOBAL_LDFLAGS)?\??????$(PRIVATE_LDFLAGS)?\??????$(if?$(filter?true,$(PRIVATE_NO_CRT)),,$(PRIVATE_TARGET_CRTEND_O))?\??????$(PRIVATE_LDLIBS)??endef??
這個函數使用clang編譯器,最終生成了$(linked_module)目標。
而從$(linked_module)生成out/target/product/xxx/obj/excutable/screepcap__intermediates/PACKED/screencap則使用了如下方法:
[java]?view plaincopy
$(relocation_packer_output):?$(relocation_packer_input)?|?$(ACP)??????@echo?"target?Unpacked:?$(PRIVATE_MODULE)?($@)"??????$(copy-file-to-target)??endif??
copy-file-to-target定義如下:
[java]?view plaincopy
define?copy-file-to-target??@mkdir?-p?$(dir?$@)??$(hide)?$(ACP)?-fp?$<?$@??endef??
可以看就是一個簡單的拷貝,所以這兩個文件并沒有什么不同。
生成out/target/product/xxx/symbols/system/bin/screencap也是在$(linked_module)的基礎上做拷貝:
[java]?view plaincopy
$(symbolic_output)?:?$(symbolic_input)?|?$(ACP)??????@echo?"target?Symbolic:?$(PRIVATE_MODULE)?($@)"??????$(copy-file-to-target)??
瀏覽其他幾個screencap文件的生成方法發現,其他幾個screencap文件都是在$(linked_module)基礎上拷貝而來,而$(linked_module)文件則使用transform-o-to-executable編譯生成。因此,到這里一個完整的可執行文件的編譯就告一段落了。編譯apk、共享庫等其他模塊的思路都與之類似,正所謂觸類旁通,只要完整掌握了一種類型模塊的編譯,其他類型的模塊編譯都變得容易理解了。
總結
以上是生活随笔為你收集整理的Android编译系统分析二:mm编译单个模块的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。