Android7.0 编译系统流程分析

本文按照Android编译三部曲(source,lunch和make)的步骤来分析查看每个环节的主要流程,由于编译系统太过庞大,这里只是从关键的主干流程上做一个分析,不可能做到每个细节都剖析清楚,由于水平有限,如果有描述不够正确的地方,欢迎大家毫无保留的指正错误,在此先谢过。


1,source流程

当我们在终端执行命令source build/envsetup.sh时,其实是完整的加载了脚本envsetup.sh中的变量和方法,其中最重要的函数比如lunch就是在这一步加载到shell环境变量中去的,比较常用的函数有:

croot

切换到源码树的根目录

lunch

选择编译板型

m

在源码树的根目录执行 make

mm

build 当前目录下的模块

mmm

build 指定目录下的模块

cgrep

在所有 C/C++ 文件上执行 grep

jgrep

在所有 Java 文件上执行 grep

resgrep

在所有 res/*.xml 文件上执行 grep

godir

转到包含某个文件的目录路径

printconfig

显示当前 Build 的配置信息

add_lunch_combo

在 lunch 函数的菜单中添加一个条目

除了加载上述函数,还执行了以下初始化和动作:

1>    提前定义3种编译模式,供后面使用:

  1. 139 VARIANT_CHOICES=(user userdebug eng)  

说明:数字139是这行代码在文件中的行号,下面的也是如此。


2>    使用变量LUNCH_MENU_CHOICES之前,先将它清空:

  1. 503 unset LUNCH_MENU_CHOICES  

3>    默认会加载如下6个板型选项,这样后续的lunch菜单中就可以看到这几个板型: 

  1. 517 add_lunch_combo aosp_arm-eng  
  2. 518 add_lunch_combo aosp_arm64-eng  
  3. 519 add_lunch_combo aosp_mips-eng  
  4. 520 add_lunch_combo aosp_mips64-eng  
  5. 521 add_lunch_combo aosp_x86-eng  
  6. 522 add_lunch_combo aosp_x86_64-eng  

下面就看看函数add_lunch_combo的实现:

  1. 504 function add_lunch_combo()  
  2. 505 {  
  3. 506     local new_combo=$1  
  4. 507     local c  
  5. 508     for c in ${LUNCH_MENU_CHOICES[@]} ; do    
  6. 509         if [ "$new_combo" = "$c" ] ; then  
  7. 510             return  
  8. 511         fi  
  9. 512     done  
  10. 513     LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)  //这里把板型添加到数组中去了  
  11. 514 }  

处理逻辑就是先从LUNCH_MENU_CHOICES中循环查找,看存不存在要添加的板型,如果存在就直接返回,如果不存在就添加到LUNCH_MENU_CHOICES中;除了上述添加的aosp的6个板型外,在device/actions/目录中还有大量的add_lunch_combo的调用,部分摘录如下:

  1. ./device/actions/s900_evb/vendorsetup.sh:add_lunch_combo s900_evb-eng  
  2. ./device/actions/s900_evb/vendorsetup.sh:add_lunch_combo s900_evb-userdebug  
  3. ./device/actions/s900_evb/vendorsetup.sh:add_lunch_combo s900_evb-user  
  4. ./device/actions/v700_gb7_nibiru/vendorsetup.sh:add_lunch_combo v700_gb7_nibiru-eng  
  5. ./device/actions/v700_gb7_nibiru/vendorsetup.sh:add_lunch_combo v700_gb7_nibiru-userdebug  
  6. ./device/actions/v700_gb7_nibiru/vendorsetup.sh:add_lunch_combo v700_gb7_nibiru-user  
  7. ./device/actions/v700_cxvr/vendorsetup.sh:add_lunch_combo v700_cxvr-eng  
  8. ./device/actions/v700_cxvr/vendorsetup.sh:add_lunch_combo v700_cxvr-userdebug  
  9. ./device/actions/v700_cxvr/vendorsetup.sh:add_lunch_combo v700_cxvr-user  
  10. ./device/actions/s900_96board/vendorsetup.sh:add_lunch_combo s900_96board-eng  
  11. ./device/actions/s900_96board/vendorsetup.sh:add_lunch_combo s900_96board-userdebug  
  12. ./device/actions/s900_96board/vendorsetup.sh:add_lunch_combo s900_96board-user  

这些全部都是在板型目录中的vendorsetup.sh脚本中写明的,脚本何时被调用的呢?【留个问题在此】


4>    下面这行的意思是,当敲入lunch命令后用Tab键进行补全时,会执行_lunch()函数:

  1. 630 complete -F _lunch lunch  

马上实践一下,发现敲完lunch后按Tab键补全果然会有东西打印出来:

  1. aosp_angler-userdebug            gt9_ebox-user                    s900_96board_sd0_boot-eng        s900_evb_sd0_boot-eng            s900vr_ys_2k-eng  
  2. aosp_arm-eng                     gt9_ebox-userdebug               s900_96board_sd0_boot-user       s900_evb_sd0_boot-user           s900vr_ys_2k-user  
  3. aosp_arm64-eng                   hikey-userdebug                  s900_96board_sd0_boot-userdebug  s900_evb_sd0_boot-userdebug      s900vr_ys_2k-userdebug  
  4. aosp_bullhead-userdebug          m_e_arm-userdebug                s900_RY_VR-eng                   s900_qcb-eng                     v700_cxvr-eng  
  5. aosp_dragon-eng                  m_e_mips-userdebug               s900_RY_VR-user                  s900_qcb-user                    v700_cxvr-user  
  6. aosp_dragon-userdebug            m_e_mips64-eng                   s900_RY_VR-userdebug             s900_qcb-userdebug               v700_cxvr-userdebug  
  7. aosp_flounder-userdebug          mini_emulator_arm64-userdebug    s900_evb-eng                     s900_tpe-eng                     v700_gb7_nibiru-eng  

那就看看函数_lunch的实现:

  1. 620 function _lunch()  
  2. 621 {  
  3. 622     local cur prev opts  
  4. 623     COMPREPLY=()  
  5. 624     cur="${COMP_WORDS[COMP_CWORD]}"  
  6. 625     prev="${COMP_WORDS[COMP_CWORD-1]}"  
  7. 627     COMPREPLY=( $(compgen -W "${LUNCH_MENU_CHOICES[*]}" -- ${cur}) )  
  8. 628     return 0  
  9. 629 }  

这个函数的代码几乎是固定的,除了627行中的${LUNCH_MENU_CHOICES[*]}是可变的,其他部分都必须这么写,否则无法实现补全。所以数组LUNCH_MENU_CHOICES的内容就是补全时打印出来的内容,而数组的内容其实就是上面提到的调用函数add_lunch_combo增加进来的。


5>    shell检查和警告,这里只支持bash,如果是其他的shell会发出这个WARNING:

  1. 1612 if [ "x$SHELL" != "x/bin/bash" ]; then  
  2. 1613     case `ps -o command -p $$` in  
  3. 1614         *bash*)  
  4. 1615             ;;  
  5. 1616         *)  
  6. 1617             echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"  
  7. 1618             ;;  
  8. 1619     esac  
  9. 1620 fi  

 

6>    这一步是整个source过程中最重要的步骤:

  1. 1622 # Execute the contents of any vendorsetup.sh files we can find.  
  2. 1623 for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \  
  3. 1624          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \  
  4. 1625          `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`  
  5. 1626 do  
  6. 1627     echo "including $f"  
  7. 1628     . $f  
  8. 1629 done  
  9. 1630 unset f  

这里可以分解为3个动作:

第一,  搜索所有vendorsetup.sh脚本;

第二,  打印所有找到的vendorsetup.sh文件路径;

第三,  加载所有找到的vendorsetup.sh脚本。【这里回答了上面留的问题】

那就看看vendorsetup.sh的内容,以板型v700_cxvr为例:

  1. 17 add_lunch_combo v700_cxvr-eng  
  2. 18 add_lunch_combo v700_cxvr-userdebug  
  3. 19 add_lunch_combo v700_cxvr-user  

其实就是调用函数add_lunch_combo把板型v700_cxvr增加到LUNCH_MENU_CHOICES数组中去。

注意:查找的目录层级为4层,如果添加的vendorsetup.sh脚本的目录层级太深,会发生找不到的情况。


7>    看看source的最后一步:

  1. 1632 addcompletions  

直接看函数addcompletions的实现:

  1. 311 function addcompletions()  
  2. 312 {  
  3. 313     local T dir f  
  4. 314  
  5. 315     # Keep us from trying to run in something that isn't bash.  
  6. 316     if [ -z "${BASH_VERSION}" ]; then  
  7. 317         return  
  8. 318     fi  
  9. 319  
  10. 320     # Keep us from trying to run in bash that's too old.  
  11. 321     if [ ${BASH_VERSINFO[0]} -lt 3 ]; then  
  12. 322         return  
  13. 323     fi  
  14. 324  
  15. 325     dir="sdk/bash_completion"  
  16. 326     if [ -d ${dir} ]; then  
  17. 327         for f in `/bin/ls ${dir}/[a-z]*.bash 2> /dev/null`; do  
  18. 328             echo "including $f"  
  19. 329             . $f  
  20. 330         done  
  21. 331     fi  
  22. 332 }  

如果BASH的版本为空或者小于3都直接返回,否则打印android/sdk/bash_completion/目录下的以.bash结尾的所有文件,目前看来只有这一个:

including sdk/bash_completion/adb.bash

 

至此, source build/envsetup.sh的过程就分析完了。

 

2,lunch流程

在source流程之后,紧接着就是执行lunch操作,lunch操作执行的其实就是build/envsetup.sh脚本中的lunch函数,下面看看lunch函数都做了哪些事情。

 

1>    获取用户编译目标到answer变量:

  1. 545     local answer  
  2. 546  
  3. 547     if [ "$1" ] ; then        
  4. 548         answer=$1  
  5. 549     else  
  6. 550         print_lunch_menu  
  7. 551         echo -n "Which would you like? [aosp_arm-eng] "  
  8. 552         read answer  
  9. 553     fi  

如果lunch命令后跟有参数,则直接赋给answer变量;

如果lunch命令后没有参数,则调用函数print_lunch_menu打印出板型列表供用户选择,并将用户的选择存储在answer变量中。

看看print_lunch_menu函数的实现:

  1. 524 function print_lunch_menu()  
  2. 525 {  
  3. 526     local uname=$(uname)  
  4. 527     echo  
  5. 528     echo "You're building on" $uname  
  6. 529     echo  
  7. 530     echo "Lunch menu... pick a combo:"  
  8. 531  
  9. 532     local i=1  
  10. 533     local choice  
  11. 534     for choice in ${LUNCH_MENU_CHOICES[@]}  
  12. 535     do  
  13. 536         echo "     $i. $choice"  
  14. 537         i=$(($i+1))  
  15. 538     done  
  16. 539  
  17. 540     echo  
  18. 541 }  

函数的作用就是打印数组LUNCH_MENU_CHOICES的内容。

 

2>    从answer变量到selection变量:

  1. 555     local selection=  
  2. 556  
  3. 557     if [ -z "$answer" ]  
  4. 558     then  
  5. 559         selection=aosp_arm-eng  
  6. 560     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")  
  7. 561     then  
  8. 562         if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]  
  9. 563         then  
  10. 564             selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}  
  11. 565         fi  
  12. 566     elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")  
  13. 567     then  
  14. 568         selection=$answer  
  15. 569     fi  
  16. 570  
  17. 571     if [ -z "$selection" ]  
  18. 572     then  
  19. 573         echo  
  20. 574         echo "Invalid lunch combo: $answer"  
  21. 575         return 1  
  22. 576     fi  

如果answer为空,则selection默认赋值为aosp_arm-eng;

如果answer是纯数字,则将answer作为数组下标从LUNCH_MENU_CHOICES数组中取出板型名称;

如果anwser是字符串,并且字符串使用”-”连接,而且”-”连接的前后两个子串中都没有”-”,则认为是板型名称字符串,直接赋给selection。

经过上述3步,如果发现selection仍然为空,则直接报错并退出。

 

3>    从selection变量到variant变量

  1. 580     local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")  
  2. 581     check_variant $variant  
  3. 582     if [ $? -ne 0 ]  
  4. 583     then  
  5. 584         echo  
  6. 585         echo "** Invalid variant: '$variant'"  
  7. 586         echo "** Must be one of ${VARIANT_CHOICES[@]}"  
  8. 587         variant=  
  9. 588     fi  

从selection中取出“-”后面的字符串到variant,类似从字符串"aosp_arm-eng"中取出"eng",然后调用函数check_variant判断variant是否符合要求。check_variant函数很简单,主要就是用到前文source流程中初始化为"user userdebug eng"的VARIANT_CHOICES数组,判断variant是否是数组成员之一,是则返回0,不是则返回1。

 

4>    从selection变量到product变量

  1. 590     local product=$(echo -n $selection | sed -e "s/-.*$//")  
  2. 591     TARGET_PRODUCT=$product \  
  3. 592     TARGET_BUILD_VARIANT=$variant \  
  4. 593     build_build_var_cache  
  5. 594     if [ $? -ne 0 ]  
  6. 595     then  
  7. 596         echo  
  8. 597         echo "** Don't have a product spec for: '$product'"  
  9. 598         echo "** Do you have the right repo manifest?"  
  10. 599         product=  
  11. 600     fi  

再从selection中取出“-”前面的字符串到product,类似从字符串"aosp_arm-eng"中取出"aosp_arm",然后将product赋值给TARGET_PRODUCT,将variant赋值给TARGET_BUILD_VARIANT,然后再调用函数build_build_var_cache对编译时所必需的环境变量进行赋值和处理,并根据函数返回结果做对应处理。

 

5>    对关键变量做最终处理:

  1. 602     if [ -z "$product" -o -z "$variant" ]  
  2. 603     then  
  3. 604         echo  
  4. 605         return 1  
  5. 606     fi  
  6. 607  
  7. 608     export TARGET_PRODUCT=$product  
  8. 609     export TARGET_BUILD_VARIANT=$variant  
  9. 610     export TARGET_BUILD_TYPE=release  

如果到这里仍然发现product或者variant为空,那肯定是出错了,直接退出;否则导出以下准备好的宏变量供整个shell环境使用:

TARGET_PRODUCT

TARGET_BUILD_VARIANT

TARGET_BUILD_TYPE

6>    最后三个函数调用:

  1. 614     set_stuff_for_environment  
  2. 615     printconfig   
  3. 616     destroy_build_var_cache  

函数set_stuff_for_environment主要就是设置PROMPT_COMMAND,ANDROID_BUILD_PATHS,JAVA_HOME和BUILD_ENV_SEQUENCE_NUMBER等等环境变量;

函数printconfig用来打印最终准备好的环境变量,通常如下:

  1. ============================================  
  2. PLATFORM_VERSION_CODENAME=REL  
  3. PLATFORM_VERSION=7.0  
  4. TARGET_PRODUCT=s900_RY_VR  
  5. TARGET_BUILD_VARIANT=userdebug  
  6. TARGET_BUILD_TYPE=release  
  7. TARGET_BUILD_APPS=  
  8. TARGET_ARCH=arm  
  9. TARGET_ARCH_VARIANT=armv7-a-neon  
  10. TARGET_CPU_VARIANT=cortex-a53  
  11. TARGET_2ND_ARCH=  
  12. TARGET_2ND_ARCH_VARIANT=  
  13. TARGET_2ND_CPU_VARIANT=  
  14. HOST_ARCH=x86_64  
  15. HOST_2ND_ARCH=x86  
  16. HOST_OS=linux  
  17. HOST_OS_EXTRA=Linux-3.2.0-29-generic-x86_64-with-Ubuntu-12.04-precise  
  18. HOST_CROSS_OS=windows  
  19. HOST_CROSS_ARCH=x86  
  20. HOST_CROSS_2ND_ARCH=x86_64  
  21. HOST_BUILD_TYPE=release  
  22. BUILD_ID=NRD90M  
  23. OUT_DIR=out  
  24. ============================================  

函数destroy_build_var_cache用来清除不再需要的中间环节产生的变量的值。

 

至此,lunch流程就分析完了。

 

3,make流程

3.1 编译入口 

当我们在Android源码根目录下执行make的时候,会查找当前目录下的Makefie文件或者makefile文件并且执行,在android/Makefile文件中,它只有一行有用的内容:

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

因此,执行make时真正的入口是android/build/core/main.mk文件。

 

3.2 整体依赖

我们在Android源码根目录下执行make命令的时候,并没有传入目标,那么就会执行默认的目标。那默认的目标是什么呢?在android/build/core/main.mk中有这样几行:

  1. 63 # This is the default target.  It must be the first declared target.  
  2. 64 .PHONY: droid  
  3. 65 DEFAULT_GOAL := droid  
  4. 66 $(DEFAULT_GOAL): droid_targets  

从63行注释可以看出,默认编译的就是droid这个伪目标,make工具遇到伪目标以后,会检查解析伪目标的依赖,如果伪目标存在依赖,就会检查这些依赖,如果这些依赖是伪目标,就继续检查这个伪目标的依赖,如果不是伪目标,就会生成这个目标,如此一层一层递归下去。


另外,在android/build/core/main.mk中还有这样几行:

  1. 1046 # Building a full system-- the default is to build droidcore  
  2. 1047 droid_targets: droidcore dist_files  

这就说明droid这个伪目标依赖droidcore和dist_files两大部分(整体编译时TARGET_BUILD_APPS为空),然后再将这两个依赖逐步解析下去,可以得到编译droid的整体依赖关系如下图:


有必要说明两点:

1)有些依赖(比如INSTALLED_BOOTIMAGE_TARGET)在android/build/core/main.mk中没有定义,而是在android/build/core/Makefile中定义的;

2)上面dist_files也是个伪目标,并且它没有任何依赖,利用dist-for-goals方法来拷贝库文件,可忽略。


3.3 编译主流程

3.3.1 加载板型配置 

首先各mk文件调用关系如下:

下面就来看看product_config.mk文件,首先调用方法get-all-product-makefiles找出所有的AndoridProducts.mk文件:

  1. 182 ifneq ($(strip $(TARGET_BUILD_APPS)),)  
  2. 183 # An unbundled app build needs only the core product makefiles.  
  3. 184 all_product_configs := $(call get-product-makefiles,\  
  4. 185     $(SRC_TARGET_DIR)/product/AndroidProducts.mk)  
  5. 186 else  
  6. 187 # Read in all of the product definitions specified by the AndroidProducts.mk  
  7. 188 # files in the tree.  
  8. 189 all_product_configs := $(get-all-product-makefiles)  
  9. 190 endif  

(方法get-all-product-makefiles的定义在文件product.mk中,实现细节可自行研究)

 

然后从all_product_configs中找出我们当前产品的AndoridProducts.mk文件:

  1. 192 # Find the product config makefile for the current product.  
  2. 193 # all_product_configs consists items like:  
  3. 194 # <product_name>:<path_to_the_product_makefile>  
  4. 195 # or just <path_to_the_product_makefile> in case the product name is the  
  5. 196 # same as the base filename of the product config makefile.  
  6. 197 current_product_makefile :=  
  7. 198 all_product_makefiles :=  
  8. 199 $(foreach f, $(all_product_configs),\  
  9. 200     $(eval _cpm_words := $(subst :,$(space),$(f)))\  
  10. 201     $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\  
  11. 202     $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\  
  12. 203     $(if $(_cpm_word2),\  
  13. 204         $(eval all_product_makefiles += $(_cpm_word2))\  
  14. 205         $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\  
  15. 206             $(eval current_product_makefile += $(_cpm_word2)),),\  
  16. 207         $(eval all_product_makefiles += $(f))\  
  17. 208         $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\  
  18. 209             $(eval current_product_makefile += $(f)),)))  
  19. 210 _cpm_words :=  
  20. 211 _cpm_word1 :=  
  21. 212 _cpm_word2 :=  
  22. 213 current_product_makefile := $(strip $(current_product_makefile))  
比如我们在执行lunch时选择的板型是v700_cxvr,则current_product_makefile的值就是:android/device/actions/v700_cxvr/AndroidProducts.mk,而这个AndroidProducts.mk的内容其实就是指定板型配置信息文件android/device/actions/v700_cxvr/v700_cxvr.mk

 

接着调用import-products导入产品配置信息:

  1. 239 $(call import-products, $(current_product_makefile))  

(方法import-products的定义也在文件android/build/core/product.mk中,实现细节可自行研究)

 

接着设置TARGET_DEVICE的值,其实就是v700_cxvr:

  1. 268 # Find the device that this product maps to.  
  2. 269 TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)  

 

接着设置PRODUCT_COPY_FILES,这个变量指定了需要拷贝的文件:

  1. 346 PRODUCT_COPY_FILES := \  
  2. 347     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_COPY_FILES))  

接着设置PRODUCT_PROPERTY_OVERRIDES属性:

  1. 351 PRODUCT_PROPERTY_OVERRIDES := \  
  2. 352     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PROPERTY_OVERRIDES))  

 

接着回到android/build/core/envsetup.mk中,include了板型配置文件BoardConfig.mk

  1. 144 board_config_mk := \  
  2. 145     $(strip $(sort $(wildcard \  
  3. 146         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \  
  4. 147         $(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \  
  5. 148         $(shell test -d vendor && find -L vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \  
  6. 149     )))  
  7. 150 ifeq ($(board_config_mk),)  
  8. 151   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))  
  9. 152 endif  
  10. 153 ifneq ($(words $(board_config_mk)),1)  
  11. 154   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))  
  12. 155 endif  
  13. 156 include $(board_config_mk)  

至此,板型配置基本加载完毕。


3.3.2 加载所有模块

加载完板型配置信息后,回到main.mk文件中,很快发现了ONE_SHOT_MAKEFILE的使用。如果这个变量被定义了,那么就是编译一个模块,如果没有被定义,就说明是编译整个系统。MAKECMDGOALS是make的一个环境变量,当我们执行make的时候并没有设置它,因此它为空。所以dont_bother不等于true,因此会加载所有的Android.mk,这里是调用一个Python脚本android/build/tools/findleaves.py来查找系统中所有的Android.mk,然后循环include进来:

  1. 542 # Include all of the makefiles in the system  
  2. 543 #  
  3. 544  
  4. 545 # Can't use first-makefiles-under here because  
  5. 546 # --mindepth=2 makes the prunes not work.  
  6. 547 subdir_makefiles := \  
  7. 548     $(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)  
  8. 549  
  9. 550 ifeq ($(USE_SOONG),true)  
  10. 551 subdir_makefiles := $(SOONG_ANDROID_MK) $(call filter-soong-makefiles,$(subdir_makefiles))  
  11. 552 endif  
  12. 553  
  13. 554 $(foreach mk, $(subdir_makefiles),$(info including $(mk) ...)$(eval include $(mk)))  //这里循环include进来  

 

3.4 JACK调用

JACK是Android7.0默认使用的编译器,在config.mk中,有JACK的定义,其实JACK的值为:out/host/linux-x86/bin/jack

  1. 499 # Generic tools.  
  2. 500 JACK := $(HOST_OUT_EXECUTABLES)/jack  


在definitions.mk中,有call-jack的定义:

  1. 2015 # Call jack  
  2. 2016 #  
  3. 2017 define call-jack  
  4. 2018  JACK_VERSION=$(PRIVATE_JACK_VERSION) $(JACK) $(DEFAULT_JACK_EXTRA_ARGS)  
  5. 2019 endef  


在definitions.mk中,也有jack-java-to-dex的定义,在jack-java-to-dex中会调用call-jack去编译java文件:

  1. 2092 define jack-java-to-dex  
  2. 2093 $(hide) rm -f $@  
  3. 2094 $(hide) rm -f $(PRIVATE_CLASSES_JACK)  
  4. 2095 $(hide) rm -rf $(PRIVATE_JACK_INTERMEDIATES_DIR)  
  5. 2096 $(hide) mkdir -p $(dir $@)  
  6. 2097 $(hide) mkdir -p $(dir $(PRIVATE_CLASSES_JACK))  
  7. 2098 $(hide) mkdir -p $(PRIVATE_JACK_INTERMEDIATES_DIR)  
  8. 2099 $(if $(PRIVATE_JACK_INCREMENTAL_DIR),$(hide) mkdir -p $(PRIVATE_JACK_INCREMENTAL_DIR))  
  9. 2100 $(call dump-words-to-file,$(PRIVATE_JAVA_SOURCES),$(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list)  
  10. 2101 $(hide) if [ -d "$(PRIVATE_SOURCE_INTERMEDIATES_DIR)" ]; then \  
  11. 2102           find $(PRIVATE_SOURCE_INTERMEDIATES_DIR) -name '*.java' >> $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list; \  
  12. 2103 fi  
  13. 2104 $(hide) tr ' ' '\n' < $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list \  
  14. 2105     | $(NORMALIZE_PATH) | sort -u > $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq  
  15. 2106 $(if $(PRIVATE_JACK_PROGUARD_FLAGS), \  
  16. 2107     $(hide) echo -basedirectory $(CURDIR) > $@.flags; \  
  17. 2108     echo $(PRIVATE_JACK_PROGUARD_FLAGS) >> $@.flags; \  
  18. 2109 )  
  19. 2110 $(if $(PRIVATE_EXTRA_JAR_ARGS),  
  20. 2111     $(hide) mkdir -p $@.res.tmp  
  21. 2112     $(hide) $(call create-empty-package-at,$@.res.tmp.zip)  
  22. 2113     $(hide) $(call add-java-resources-to,$@.res.tmp.zip)  
  23. 2114     $(hide) unzip -qo $@.res.tmp.zip -d $@.res.tmp  
  24. 2115     $(hide) rm $@.res.tmp.zip)  
  25. 2116 $(hide) if [ -s $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq ] ; then \  
  26. 2117     export tmpEcjArg="@$(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq"; \  
  27. 2118 else \  
  28. 2119     export tmpEcjArg=""; \  
  29. 2120 fi; \  
  30. 2121 $(call call-jack) \       //这里调用的call-jack  
  31. 2122     $(strip $(PRIVATE_JACK_FLAGS)) \  
  32. 2123     $(strip $(PRIVATE_JACK_COVERAGE_OPTIONS)) \  
  33. 2124     $(if $(NO_OPTIMIZE_DX), \  
  34. 2125         -D jack.dex.optimize="false") \  
  35. 2126     $(if $(PRIVATE_RMTYPEDEFS), \  
  36. 2127         -D jack.android.remove-typedef="true") \  
  37. 2128     $(addprefix --classpath ,$(strip \  
  38. 2129         $(call normalize-path-list,$(PRIVATE_BOOTCLASSPATH_JAVA_LIBRARIES) $(PRIVATE_ALL_JACK_LIBRARIES)))) \  
  39. 2130     $(addprefix --import ,$(call reverse-list,$(PRIVATE_STATIC_JACK_LIBRARIES))) \  
  40. 2131     $(if $(PRIVATE_EXTRA_JAR_ARGS),--import-resource $@.res.tmp) \  
  41. 2132     -D jack.android.min-api-level=$(PRIVATE_JACK_MIN_SDK_VERSION) \  
  42. 2133     -D jack.import.resource.policy=keep-first \  
  43. 2134     -D jack.import.type.policy=keep-first \  
  44. 2135     --output-jack $(PRIVATE_CLASSES_JACK) \  
  45. 2136     $(if $(PRIVATE_JACK_INCREMENTAL_DIR),--incremental-folder $(PRIVATE_JACK_INCREMENTAL_DIR)) \  
  46. 2137     --output-dex $(PRIVATE_JACK_INTERMEDIATES_DIR) \  
  47. 2138     $(addprefix --config-jarjar ,$(strip $(PRIVATE_JARJAR_RULES))) \  
  48. 2139     $(if $(PRIVATE_JACK_PROGUARD_FLAGS),--config-proguard $@.flags) \  
  49. 2140     $$tmpEcjArg \  
  50. 2141     || ( rm -rf $(PRIVATE_CLASSES_JACK); exit 41 )  
  51. 2142 $(hide) mv $(PRIVATE_JACK_INTERMEDIATES_DIR)/classes*.dex $(dir $@)  
  52. 2143 $(hide) rm -f $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list  
  53. 2144 $(if $(PRIVATE_EXTRA_JAR_ARGS),$(hide) rm -rf $@.res.tmp)  
  54. 2145 $(hide) mv $(PRIVATE_JACK_INTERMEDIATES_DIR)/java-source-list-uniq $(PRIVATE_JACK_INTERMEDIATES_DIR).java-source-list  
  55. 2146 $(if $(PRIVATE_JAR_PACKAGES), $(hide) echo unsupported options PRIVATE_JAR_PACKAGES in $@; exit 53)  
  56. 2147 $(if $(PRIVATE_JAR_EXCLUDE_PACKAGES), $(hide) echo unsupported options JAR_EXCLUDE_PACKAGES in $@; exit 53)  
  57. 2148 $(if $(PRIVATE_JAR_MANIFEST), $(hide) echo unsupported options JAR_MANIFEST in $@; exit 53)  
  58. 2149 endef  

 

在java.mk和host_dalvik_java_library.mk中,都有调用jack-java-to-dex来编译java:

 

 

上图中,左边浅红色部分这些宏变量在 config.mk文件中有定义,定义如下:

BUILD_STATIC_JAVA_LIBRARY=static_java_library.mk

BUILD_JAVA_LIBRARY=java_library.mk

BUILD_PACKAGE=package.mk

BUILD_HOST_DALVIK_JAVA_LIBRARY=host_dalvik_java_library.mk

这些宏变量将会大量出现在各个模块的Android.mk文件中,而Android.mk文件又被编译系统全部找出并include进来(上面3.3.2有提到),这样编译系统就等于间接调用了jack来编译java文件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值