Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android編譯系統(二)-mm編譯單個模塊

Android編譯系統(二)-mm編譯單個模塊

編輯:關於Android編程

因為Android的編譯系統不同於Linux Kernel的遞歸式的編譯系統,它的編譯系統是一種稱之為independent的模式,每個模塊基本獨立(它有可能依賴其他模塊),每個模塊都可以單獨編譯,這是Android independent編譯系統模式的好處。

但這並不意味著它是完美的,普通電腦編譯android系統需要8個小時甚至更多(以本人的電腦為例),而編譯linux kernel只需要半個小時,代碼量是一回事,由independent模式造成的編譯時間長應該是可以肯定的。

正因為每個模塊可以單獨編譯,所以android系統的編譯就是依次編譯每個模塊,然後把所有編譯好的模塊和其他一些文件一起打包成鏡像文件。因此,只要理解了每個模塊的編譯,理解android系統的編譯就輕松多了。

在我們source build/envsetup.sh 和 lunch 後,就可以執行mm命令編譯單個模塊了:

所以,編譯的其實位置從mm說起:

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:

 

function findmakefile()
{
    TOPFILE=build/core/envsetup.mk
    local HERE=$PWD
    T=
    while [ \( ! \( -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:

 

### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###

加載main.mk

 

 

main.mk往下加載,不久我們就看到了我們在mm函數中設置的ONE_SHOT_MAKEFILE變量了:

 

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模塊的編譯為例,它的內容如下:

 

 

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)
core/config.mk:69:CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
CLEAR_VARS定義在config.mk文件中,它指向一個clear_vars.mk文件:

 

 

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:=
。。。
這個文件就是把一大堆的文件置為空,除了LOCAL_PATH變量之外。

 

接著,它有include BUILD_EXTABLE指向的腳本。

core/config.mk:74:BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_EXTABLE變量也定義在config.mk中,它指向的excutable.mk腳本內容如下:

 



 

關於閱讀Makefile,個人觀點就是緊追依賴鏈。我們執行的make的時候不是傳了一個目標叫all_mudules了嗎?所以make就會從它開始推導依賴關系,然後從依賴鏈的最葉子的位置生成目標,一次向上。所以那就看看all_modules:

 

# 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中:

 

$(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函數生成,該函數定義如下:

 

define transform-o-to-executable
@mkdir -p $(dir $@)
@echo "target Executable: $(PRIVATE_MODULE) ($@)"
$(transform-o-to-executable-inner)
endef
調用transform-o-to-executable-inner函數進一步處理:
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則使用了如下方法:

$(relocation_packer_output): $(relocation_packer_input) | $(ACP)
	@echo "target Unpacked: $(PRIVATE_MODULE) ($@)"
	$(copy-file-to-target)
endif
copy-file-to-target定義如下:
define copy-file-to-target
@mkdir -p $(dir $@)
$(hide) $(ACP) -fp $< $@
endef

可以看就是一個簡單的拷貝,所以這兩個文件並沒有什麼不同。

生成out/target/product/xxx/symbols/system/bin/screencap也是在$(linked_module)的基礎上做拷貝:

$(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、共享庫等其他模塊的思路都與之類似,正所謂觸類旁通,只要完整掌握了一種類型模塊的編譯,其他類型的模塊編譯都變得容易理解了。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved