• android编译系统分析(二)mm编译单个模块



    http://blog.csdn.net/u011913612/article/details/52415948


    因为Android的编译系统不同于Linux Kernel的递归式的编译系统,它的编译系统是一种称之为independent的模式,每个模块基本独立(它有可能依赖其他模块),每个模块都可以单独编译,这是Android independent编译系统模式的好处。但这并不意味着它是完美的,普通电脑编译android系统需要8个小时甚至更多(以本人的电脑为例),而编译linux kernel只需要半个小时,代码量是一回事,由independent模式造成的编译时间长应该是可以肯定的。正因为每个模块可以单独编译,所以android系统的编译就是依次编译每个模块,然后把所有编译好的模块和其他一些文件一起打包成镜像文件。因此,只要理解了每个模块的编译,理解android系统的编译就轻松多了。(以上均是个人观点,欢迎拍砖)

    在我们source build/envsetup.sh 和 lunch 后,就可以执行mm命令编译单个模块了:

    所以,编译的其实位置从mm说起:

    1. function mm()  
    2. {  
    3.     local T=$(gettop)  
    4.     local DRV=$(getdriver $T)  
    5.     # If we're sitting in the root of the build tree, just do a  
    6.     # normal make.  
    7.     if [ -f build/core/envsetup.mk -a -f Makefile ]; then  
    8.         $DRV make $@  
    9.     else  
    10.         # Find the closest Android.mk file.  
    11.         local M=$(findmakefile)  
    12.         local MODULES=  
    13.         local GET_INSTALL_PATH=  
    14.         local ARGS=  
    15.         # Remove the path to top as the makefilepath needs to be relative  
    16.         local M=`echo $M|sed 's:'$T'/::'`  
    17.         if [ ! "$T" ]; then  
    18.             echo "Couldn't locate the top of the tree.  Try setting TOP."  
    19.             return 1  
    20.         elif [ ! "$M" ]; then  
    21.             echo "Couldn't locate a makefile from the current directory."  
    22.             return 1  
    23.         else  
    24.             for ARG in $@; do  
    25.                 case $ARG in  
    26.                   GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;  
    27.                 esac  
    28.             done  
    29.             if [ -n "$GET_INSTALL_PATH" ]; then  
    30.               MODULES=  
    31.               ARGS=GET-INSTALL-PATH  
    32.             else  
    33.               MODULES=all_modules  
    34.               ARGS=$@  
    35.             fi  
    36.             ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS  
    37.         fi  
    38.     fi  
    39. }  

    这个函数做了三件事情:1.找到Android.mk文件,2.设置ONE_SHOT_MAKEFILE=$M,3.执行make all_modules进行编译

    1.findmakefile:

    1. function findmakefile()  
    2. {  
    3.     TOPFILE=build/core/envsetup.mk  
    4.     local HERE=$PWD  
    5.     T=  
    6.     while [ !(f$TOPFILE ) -a ( $PWD != "/" ) ]; do  
    7.         T=`PWD= /bin/pwd`  
    8.         if [ -f "$T/Android.mk" ]; then  
    9.             echo $T/Android.mk  
    10.             cd $HERE  
    11.             return  
    12.         fi  
    13.         cd ..  
    14.     done  
    15.     cd $HERE  
    16. }  
    这个函数首先在当前目录下查找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:

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

    加载main.mk


    main.mk往下加载,不久我们就看到了我们在mm函数中设置的ONE_SHOT_MAKEFILE变量了:

    1. ifneq ($(ONE_SHOT_MAKEFILE),)  
    2. # We've probably been invoked by the "mm" shell function  
    3. # with a subdirectory's makefile.  
    4. include $(ONE_SHOT_MAKEFILE)  
    5. # Change CUSTOM_MODULES to include only modules that were  
    6. # defined by this makefile; this will install all of those  
    7. # modules as a side-effect.  Do this after including ONE_SHOT_MAKEFILE  
    8. # so that the modules will be installed in the same place they  
    9. # would have been with a normal make.  
    10. CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))  
    11. FULL_BUILD :=  
    12. # Stub out the notice targets, which probably aren't defined  
    13. # when using ONE_SHOT_MAKEFILE.  
    14. NOTICE-HOST-%: ;  
    15. NOTICE-TARGET-%: ;  
    16.   
    17. # A helper goal printing out install paths  
    18. .PHONY: GET-INSTALL-PATH  
    19. GET-INSTALL-PATH:  
    20.     @$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED),   
    21.         echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))  
    22.   
    23. else # ONE_SHOT_MAKEFILE  
    这里判断ONE_SHOT_MAKEFILE是否为空,当然不为空了。紧接着开始加载这个Android.mk,也就是我们要编译的那个Android.mk。简单起见,这里以frameworks/base/cmds/screencap模块的编译为例,它的内容如下:
    1. LOCAL_PATH:= $(call my-dir)  
    2. include $(CLEAR_VARS)  
    3.   
    4. LOCAL_SRC_FILES:=   
    5.     screencap.cpp  
    6.   
    7. LOCAL_SHARED_LIBRARIES :=   
    8.     libcutils   
    9.     libutils   
    10.     libbinder   
    11.     libskia   
    12.     libui   
    13.     libgui  
    14.   
    15. LOCAL_MODULE:= screencap  
    16.   
    17. LOCAL_MODULE_TAGS := optional  
    18.   
    19. LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code  
    20.   
    21. include $(BUILD_EXECUTABLE)  
    它的变量非常少,这很有利于我们搞清它编译的过程。include 这个Android.mk后,又include $(CLEAR_VARS)
    1. core/config.mk:69:CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk  
    CLEAR_VARS定义在config.mk文件中,它指向一个clear_vars.mk文件:
    1. LOCAL_MODULE:=  
    2. LOCAL_MODULE_PATH:=  
    3. LOCAL_MODULE_RELATIVE_PATH :=  
    4. LOCAL_MODULE_STEM:=  
    5. LOCAL_DONT_CHECK_MODULE:=  
    6. LOCAL_CHECKED_MODULE:=  
    7. LOCAL_BUILT_MODULE:=  
    8. LOCAL_BUILT_MODULE_STEM:=  
    1. 。。。  
    这个文件就是把一大堆的文件置为空,除了LOCAL_PATH变量之外。

    接着,它有include BUILD_EXTABLE指向的脚本。

    1. core/config.mk:74:BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk  
    BUILD_EXTABLE变量也定义在config.mk中,它指向的excutable.mk脚本内容如下:




    关于阅读Makefile,个人观点就是紧追依赖链。我们执行的make的时候不是传了一个目标叫all_mudules了吗?所以make就会从它开始推导依赖关系,然后从依赖链的最叶子的位置生成目标,一次向上。所以那就看看all_modules:

    1. # phony target that include any targets in $(ALL_MODULES)  
    2. .PHONY: all_modules  
    3. ifndef BUILD_MODULES_IN_PATHS  
    4. all_modules: $(ALL_MODULES)  
    5. else  
    6. # BUILD_MODULES_IN_PATHS is a list of paths relative to the top of the tree  
    7. module_path_patterns := $(foreach p, $(BUILD_MODULES_IN_PATHS),  
    8.     $(if $(filter %/,$(p)),$(p)%,$(p)/%))  
    9. my_all_modules := $(sort $(foreach m, $(ALL_MODULES),$(if $(filter  
    10.     $(module_path_patterns), $(addsuffix /,$(ALL_MODULES.$(m).PATH))),$(m))))  
    11. all_modules: $(my_all_modules)  
    12. 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中:

    1. $(linked_module): $(my_target_crtbegin_dynamic_o) $(all_objects) $(all_libraries) $(my_target_crtend_o)  
    2.     $(transform-o-to-executable)  
    还记得我们推导出来的linked_module的值吗?它等于:

    out/target/product/xxx/obj/excutable/screepcap__intermediates/LINKED/screencap

    生成这个文件后,从依赖关系上也可以看出,其他文件在此基础上生成,而这个文件使用transform-o-to-executable函数生成,该函数定义如下:

    1. define transform-o-to-executable  
    2. @mkdir -p $(dir $@)  
    3. @echo "target Executable: $(PRIVATE_MODULE) ($@)"  
    4. $(transform-o-to-executable-inner)  
    5. endef  
    调用transform-o-to-executable-inner函数进一步处理:

    1. define transform-o-to-executable-inner  
    2. $(hide) $(PRIVATE_CXX) -pie   
    3.     -nostdlib -Bdynamic   
    4.     -Wl,-dynamic-linker,$($(PRIVATE_2ND_ARCH_VAR_PREFIX)TARGET_LINKER)   
    5.     -Wl,--gc-sections   
    6.     -Wl,-z,nocopyreloc   
    7.     $(PRIVATE_TARGET_GLOBAL_LD_DIRS)   
    8.     -Wl,-rpath-link=$(PRIVATE_TARGET_OUT_INTERMEDIATE_LIBRARIES)   
    9.     $(if $(filter true,$(PRIVATE_NO_CRT)),,$(PRIVATE_TARGET_CRTBEGIN_DYNAMIC_O))   
    10.     $(PRIVATE_ALL_OBJECTS)   
    11.     -Wl,--whole-archive   
    12.     $(call normalize-target-libraries,$(PRIVATE_ALL_WHOLE_STATIC_LIBRARIES))   
    13.     -Wl,--no-whole-archive   
    14.     $(if $(PRIVATE_GROUP_STATIC_LIBRARIES),-Wl$(comma)--start-group)   
    15.     $(call normalize-target-libraries,$(PRIVATE_ALL_STATIC_LIBRARIES))   
    16.     $(if $(PRIVATE_GROUP_STATIC_LIBRARIES),-Wl$(comma)--end-group)   
    17.     $(if $(filter true,$(NATIVE_COVERAGE)),$(PRIVATE_TARGET_LIBGCOV))   
    18.     $(if $(filter true,$(NATIVE_COVERAGE)),$(PRIVATE_TARGET_LIBPROFILE_RT))   
    19.     $(PRIVATE_TARGET_LIBATOMIC)   
    20.     $(PRIVATE_TARGET_LIBGCC)   
    21.     $(call normalize-target-libraries,$(PRIVATE_ALL_SHARED_LIBRARIES))   
    22.     -o $@   
    23.     $(PRIVATE_TARGET_GLOBAL_LDFLAGS)   
    24.     $(PRIVATE_LDFLAGS)   
    25.     $(if $(filter true,$(PRIVATE_NO_CRT)),,$(PRIVATE_TARGET_CRTEND_O))   
    26.     $(PRIVATE_LDLIBS)  
    27. endef  

    这个函数使用clang编译器,最终生成了$(linked_module)目标。

    而从$(linked_module)生成out/target/product/xxx/obj/excutable/screepcap__intermediates/PACKED/screencap则使用了如下方法:

    1. $(relocation_packer_output): $(relocation_packer_input) | $(ACP)  
    2.     @echo "target Unpacked: $(PRIVATE_MODULE) ($@)"  
    3.     $(copy-file-to-target)  
    4. endif  
    copy-file-to-target定义如下:

    1. define copy-file-to-target  
    2. @mkdir -p $(dir $@)  
    3. $(hide) $(ACP) -fp $< $@  
    4. endef  

    可以看就是一个简单的拷贝,所以这两个文件并没有什么不同。

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

    1. $(symbolic_output) : $(symbolic_input) | $(ACP)  
    2.     @echo "target Symbolic: $(PRIVATE_MODULE) ($@)"  
    3.     $(copy-file-to-target)  
    浏览其他几个screencap文件的生成方法发现,其他几个screencap文件都是在$(linked_module)基础上拷贝而来,而$(linked_module)文件则使用transform-o-to-executable编译生成。因此,到这里一个完整的可执行文件的编译就告一段落了。编译apk、共享库等其他模块的思路都与之类似,正所谓触类旁通,只要完整掌握了一种类型模块的编译,其他类型的模块编译都变得容易理解了。

  • 相关阅读:
    asp.net FckEditor配置
    您请求的报表需要更多信息...
    水晶报表中如何动态增加字段
    使用JavaMail发送SMTP认证的邮件给多个收信人
    vim中删除每行行尾的空格
    转载:STUN在SIP中的工作原理及过程
    转载 URL和URI的区别
    转载 Android深入浅出Binder机制
    链接静态库的时候,命令行中库和源文件的位置问题
    使用dumpbin来查看程序的依赖
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645311.html
Copyright © 2020-2023  润新知