android编译系统分析

1:source build/envsetup.sh与lunch

虽然已经有很多人分析过android的编译系统的代码了,我也看过他们的博客,也学到了不少知识,但单纯的看别人分析,终究还是理解的不深入,所以,我还是要自己再认真的分析一遍。

想想我们编译android系统的过程:

首先:source build/envsetup.sh

其次:lunch    ---选择一个特定的类型

最后:make

按着这个顺序,追踪这看似简单的几步,到底有哪些背后的秘密?

1. source build/envsetup.sh

这个文件虽然很大,但暂且不需要统统看一遍。它里面定义了很多函数,这些函数在使用的时候再具体详细学习,现在主要看看这个脚本做的事情。即便如此,打开这个脚本后第一个函数还是非常吸引人的,因为它里面介绍了这个脚本主要要做的事情:

  1. function hmm() {
  2. cat <<EOF
  3. Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
  4. - lunch: lunch <product_name>-<build_variant>
  5. - tapas: tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
  6. - croot: Changes directory to the top of the tree.
  7. - m: Makes from the top of the tree.
  8. - mm: Builds all of the modules in the current directory, but not their dependencies.
  9. - mmm: Builds all of the modules in the supplied directories, but not their dependencies.
  10. To limit the modules being built use the syntax: mmm dir/:target1,target2.
  11. - mma: Builds all of the modules in the current directory, and their dependencies.
  12. - mmma: Builds all of the modules in the supplied directories, and their dependencies.
  13. - cgrep: Greps on all local C/C++ files.
  14. - ggrep: Greps on all local Gradle files.
  15. - jgrep: Greps on all local Java files.
  16. - resgrep: Greps on all local res/*.xml files.
  17. - mangrep: Greps on all local AndroidManifest.xml files.
  18. - sepgrep: Greps on all local sepolicy files.
  19. - sgrep: Greps on all local source files.
  20. - godir: Go to the directory containing a file.
  21. Environemnt options:
  22. - SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that
  23. ASAN_OPTIONS=detect_leaks=0 will be set by default until the
  24. build is leak-check clean.
  25. Look at the source to view more functions. The complete list is:
  26. EOF
  27. T=$(gettop)
  28. local A
  29. A=""
  30. for i in `cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
  31. A="$A $i"
  32. done
  33. echo $A
  34. }

这个函数列出来主要是它介绍了这个脚本的一些功能,第一行cat <<EOP是一个HERE文档,意思就是把EOF后面到下一个EOF前面的内容当做一个文件,然后cat 会接收这个文件的内容,而cat默认的输出是标准输出,也就是这个文件的内容会被打印到屏幕上来。这些内容介绍了这个脚本的用法和功能,用法就是“. build/envsetup.sh”注意.后面有个空格,这个.命令就是source命令,也就是说你也可以执行“source build/envsetup.sh”。此外,这个函数介绍了很多函数的功能,比如lunch,m,mm,mmm,cgrep等。

函数只有在调用到它的时候才会执行,所以暂时统统不看,现在只看函数外面的内容:

# Clear this variable.  It will be built up again when the vendorsetup.sh
# files are included at the end of this file.
unset LUNCH_MENU_CHOICES

这里调用了unset命令,unset是一个bash命令,它会删除给定的变量。也就是说它会删除LUNCH_MENU_CHOICES变量。既然这里刻意把它删除了,那么它肯定要立刻重新构造这个变量了,果然:

# add the default one here
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng

这几行调用了add_lunch_combo函数,并且传入了一些参数,这使得我们执行lunch函数的时候,多了这几条可以选择的选项。

而这个函数,就是就是重新构造LUNCH_MENU_CHOICES变量:

  1. function add_lunch_combo()
  2. {
  3. local new_combo=$1
  4. local c
  5. for c in ${LUNCH_MENU_CHOICES[@]} ; do
  6. if [ "$new_combo" = "$c" ] ; then
  7. return
  8. fi
  9. done
  10. LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
  11. }

了解过shell编程就知道,$1表示传入函数的第一个参数,然后又定义了一个变量c。

从for循环中可以看出,LUNCH_MENU_CHOICES是一个数组,在shell中,可以通过 "变量名[@]"或者“变量名[*]”的方式过得数组的所有项。然后注意比较数组中的每一项,如果数组中已经有传入的参数项,就继续返回,否则,就把新的传入的参数加入到LUNCH_MENU_CHOIES数组中。

这个脚本虽然很长,但是正真执行的代码没有多少,就是说当执行source build/envsetup.sh的时候执行的代码没有多少,它里面大多数内容都是函数的定义。在该文件的最后,又执行了一点代码:

if [ "x$SHELL" != "x/bin/bash" ]; thencase `ps -o command -p $$` in*bash*);;*)echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results";;esac
fi# Execute the contents of any vendorsetup.sh files we can find.
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \`test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
doecho "including $f". $f
done
unset f

前面的if语句块就是检查支持的shell解析器。后面的for语句块比较关键。

这里使用了shell的反引号的用法,它的作用就是把反引号的内容当做shell命令来执行。然后把执行结果使用echo输出到屏幕。反引号中,首先使用test命令测试 device目录是否存在,存在的话就查找vendorsetup.sh脚本,并且设定了查找深度为4层目录。2> /dev/null 是shell中的重定向语法,这里把标准错误重定向到/dev/null中,也就是,把错误统统删掉,左后把找到的vendorsetup.sh脚本使用sort命令进行排序。

. $f相当于source $f,也就是source /device和/vendor下找到的所有vendorsetup.sh脚本。然后,这个脚本就分析结束了,接下来,自然是要去分析/device和/vendor下的vendorsetup.sh脚本了。

2./device/vendor下的vendorsetup.sh

执行一下source build/envsetup.sh,会打印一下内容:

  1. including device/generic/mini-emulator-arm64/vendorsetup.sh
  2. including device/generic/mini-emulator-armv7-a-neon/vendorsetup.sh
  3. including device/generic/mini-emulator-mips/vendorsetup.sh
  4. including device/generic/mini-emulator-x86_64/vendorsetup.sh
  5. including device/generic/mini-emulator-x86/vendorsetup.sh

这里只列出了一部分,那以第一条为例,看看它的内容:

add_lunch_combo mini_emulator_arm64-userdebug

这个函数我们已经分析过了,而且这个文件也只有这么一行,这里列出的这几个vendorsetup.sh脚本都是只有这么一行,他就是在lunch的菜单中添加一项。当然也不都是如此,在有些厂家的vendorsetup.sh也会做一些其他的工作,但是,不管做多少其他的事情,第一件事情似乎都是一定的,就是调用add_lunch_combo 添加一项。

因此,总结来说,envsetup.sh脚本做了这样的事情:

3.lunch

编译android的时候,执行完souce build/envsetup.sh,我们还需要执行lunch,选择一个特定的单板。

  1. function lunch()
  2. {
  3. local answer
  4. if [ "$1" ] ; then
  5. answer=$1
  6. else
  7. print_lunch_menu
  8. echo -n "Which would you like? [aosp_arm-eng] "
  9. read answer
  10. fi
  11. local selection=
  12. if [ -z "$answer" ]
  13. then
  14. selection=aosp_arm-eng
  15. elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
  16. then
  17. if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
  18. then
  19. selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
  20. fi
  21. elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
  22. then
  23. selection=$answer
  24. fi
  25. if [ -z "$selection" ]
  26. then
  27. echo
  28. echo "Invalid lunch combo: $answer"
  29. return 1
  30. fi
  31. export TARGET_BUILD_APPS=
  32. local product=$(echo -n $selection | sed -e "s/-.*$//")
  33. check_product $product
  34. if [ $? -ne 0 ]
  35. then
  36. echo
  37. echo "** Don't have a product spec for: '$product'"
  38. echo "** Do you have the right repo manifest?"
  39. product=
  40. fi
  41. local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
  42. check_variant $variant
  43. if [ $? -ne 0 ]
  44. then
  45. echo
  46. echo "** Invalid variant: '$variant'"
  47. echo "** Must be one of ${VARIANT_CHOICES[@]}"
  48. variant=
  49. fi
  50. if [ -z "$product" -o -z "$variant" ]
  51. then
  52. echo
  53. return 1
  54. fi
  55. export TARGET_PRODUCT=$product
  56. export TARGET_BUILD_VARIANT=$variant
  57. export TARGET_BUILD_TYPE=release
  58. echo
  59. set_stuff_for_environment
  60. printconfig
  61. }

这个函数首先判断有没有传入参数,如果传入了就把值赋给answer这个变量,如果没有传参数,也就是说知识执行了lunch,那么就会调用fprint_lunch_menu函数显示一份菜单出来,显示出来后,接受用户的输入。

print_lunch_menu函数如下:

  1. function print_lunch_menu()
  2. {
  3. local uname=$(uname)
  4. echo
  5. echo "You're building on" $uname
  6. echo
  7. echo "Lunch menu... pick a combo:"
  8. local i=1
  9. local choice
  10. for choice in ${LUNCH_MENU_CHOICES[@]}
  11. do
  12. echo " $i. $choice"
  13. i=$(($i+1))
  14. done
  15. echo
  16. }

可以看到,这个函数输出了一些头信息以后,就输出LUNCH_MENU_CHOICES数组,这里通过for遍历整个这个数组,打印每一项。这个数组在之前就已经分析过,它的每一项是通过调用add_lunch_combo函数添加进来的。

1.不管执行lunch的时候有没有传入参数,最终都会将选择的结果存入到answer变量中,那么接下来,当然是检查输入的参数的合法性了。这里首先判断answer变量是不是为0,如果为零的话,selection变量就会赋值为aosp_arm-eng,但是如果不为零的话,首先会输出answer的值,使用echo -n $answer,-n选项是的输出的时候不输出换行符,answer变量的值并没有输出到屏幕上,而是通过管道传给了后面一条命令:grep -q -e "^[0-9][0-9]*$",这条命令从answer变量中搜寻以两位数字开头的字符串,如果找到,就认为是输入的是数字。然后进一步对这个数字做有没有越界的检查。如果这个数字小于LUNCH_MENU_CHOICES的大小,就会把LUNCH_MENU_CHOICES的地$answer-1项复制给selection变量。

2.如果传入的不是数字,就会尝试匹配这样的字符串grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$",-q标示quiet模式,也就是不打印信息,-e表示后面跟的是一个用于匹配的模式,这里,^标示匹配开始,[]中的^则标示排除,\是转译字符,因为-可能是表示范围的符号。所以,这里匹配的是不以连续两个--开始,后面跟任意字符,然后必须有个-,最后又不以--结束的字符串。其实,这里就是期望得到product-varient的形式。也就是我们之前使用add_lunch_combo添加的那些字符串的格式。比如:aosp_arm-eng,product就是aosp_arm,varient就是eng.中间的-是必须的。如果发现符合要求的格式的话selection变量就会被$answer赋值,也就是说,selection其实就是一个product-varient模式的字符串。

3.如果既不是数字,又不是合法的字符串,或者是数字,这个时候,selection应该就没有被赋值过,这个时候-z就成立了,那么就会提示你输入的东西不对。并且直接返回。

如果输入合法,通过检测后,  export TARGET_BUILD_APPS=   这行导出了一个变量,但是它的值是空的。而之后的 local product=$(echo -n $selection | sed -e "s/-.*$//")这句则是得到了product部分,也就是把varient部分砍掉了。这里使用了sed编辑器,-e 表示执行多条命令,但这里只有一条,双引号的s表示替换,这里就是把-后面接任意字符,然后以任意字符结尾的部分替换为空,也就是砍掉-后面的了。得到produce后就开始检查product的合法性。 check_product $produc  ,这里使用了check_product函数:

  1. # check to see if the supplied product is one we can build
  2. function check_product()
  3. {
  4. T=$(gettop)
  5. if [ ! "$T" ]; then
  6. echo "Couldn't locate the top of the tree. Try setting TOP." >&2
  7. return
  8. fi
  9. TARGET_PRODUCT=$1 \
  10. TARGET_BUILD_VARIANT= \
  11. TARGET_BUILD_TYPE= \
  12. TARGET_BUILD_APPS= \
  13. get_build_var TARGET_DEVICE > /dev/null
  14. # hide successful answers, but allow the errors to show
  15. }

函数的一开始就调用了gettop函数,所以,我们得先弄明白这个函数的功能:

  1. function gettop
  2. {
  3. local TOPFILE=build/core/envsetup.mk
  4. if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
  5. # The following circumlocution ensures we remove symlinks from TOP.
  6. (cd $TOP; PWD= /bin/pwd)
  7. else
  8. if [ -f $TOPFILE ] ; then
  9. # The following circumlocution (repeated below as well) ensures
  10. # that we record the true directory name and not one that is
  11. # faked up with symlink names.
  12. PWD= /bin/pwd
  13. else
  14. local HERE=$PWD
  15. T=
  16. while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
  17. \cd ..
  18. T=`PWD= /bin/pwd -P`
  19. done
  20. \cd $HERE
  21. if [ -f "$T/$TOPFILE" ]; then
  22. echo $T
  23. fi
  24. fi
  25. fi
  26. }

gettop函数一开始定义了一个局部变量TOPFILE,并且给他赋了值,然后是一个测试语句:if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then,这里-n 是判断 $TOP是否不为空, -a 就是and的意思,和C语言中的&&相同, -f是判断给定的变量是不是文件,那么,这个测试语句就是如果 $TOP不为空 切同时 $TOP/$TOPFILE文件存在,就执行下面的代码:

(cd $TOP; PWD= /bin/pwd)

也就是进入到$TOP目录下,并且给PWD变量赋一个pwd命令的返回值,也就是当前目录的路劲。我试着在这个脚本中搜索TOP变量,发现它并没有出现并且赋值,所以,这里应该执行else部分。else中,首先判断build/core/envsetup.mk这个文件是否存在,当在源码顶层目录下的时候,这个文件是存在的,那么这里为真,然后PWD变量就是android代码的根目录。所以如果souce build/envsetup.sh的时候,如果处于android源码的顶级目录,那么这个函数就返回了。关于shell函数的返回值问题,还需要留意一下,当一个函数没有返回任何内容的时候,默认返回的是最后一条命令的执行结果,也就是这里的/bin/pwd的结果。那当然就是android源码的顶级目录了。这个时候如果不在顶级目录,build/core/envsetup.mk应该不存在,这个时候就会while循环不断的进入道上层目录,然后判断$TOPFILE是否存在,并且判断是否到达根目录了,如果这个文件不存在且没有到达根目录,那么就会一个往上一级目录查找。最终如果找到了这个文件,就意味着找到了android源码的顶层目录,并把这个路劲返回。前面的两次判断如果都成立的话也没有返回任何东西,是因为,当前目录肯定就是源码的顶级目录了。也就是说,这个函数就是找到源码的顶级目录,如果当前目录就是顶级目录,就什么也不返回,如果当前目录不是顶级目录,就返回顶级目录的路劲。
再回过头来看check_product函数,可以看到在获取到android源码的顶级目录以后,就会判断这个T是不是空值,空的的话就说明没有获取到顶级目录,这个时候这个函数就直接返回了。如果一切正常,那么就会定义几个变量。

TARGET_PRODUCT=$1 \
        TARGET_BUILD_VARIANT= \
        TARGET_BUILD_TYPE= \
        TARGET_BUILD_APPS= \

这几个变量只有一个变量都是全局变量,因为没有加local关键字修饰,它们中,只有第一个变量赋值为$1,也就是这个函数的第一个参数。目前,这几个变量的作用还不得而知,所以,我们继续向下分析。

这个函数还有最后一件事情要做:

get_build_var TARGET_DEVICE > /dev/null

这里调用了get_build_var这个函数,这个函数如下:

# Get the exact value of a build variable.
function get_build_var()
{T=$(gettop)if [ ! "$T" ]; thenecho "Couldn't locate the top of the tree.  Try setting TOP." >&2returnfi(\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \command make --no-print-directory -f build/core/config.mk dumpvar-$1)
}

乍看之下,这个函数非常简单,一开始,就是获得android源码的顶层目录,然后检查是否获得成功,这在之前已经分析过了。做完这些以后,迎来了一个看着很奇怪的语句。这个语句先是进入到android源码的顶层目录,然后然后定义了两个变量,之后使用command执行了一条命令。command是一个shell中的命令,它的功能是执行指定的命令,
这里就是执行make命令,-f给它指定了makefile,同时还传入了一个目标。然后,config.mk就会执行。

这个函数,我们不妨感性认识一下先:在命令行中执行get_build_var TARGET_DEVICE

可以看到打印了generic这个值。这个函数虽然分析起来比较复杂,但是它做的非常简单。config.mk会include dumpvar.mk,这个文件中会提取我们传入的dumpvar-TARGET_DEVICE变量中的TARGET_DEVICE,然后打印$(TARGET_DEVICE)。所以它做的事情很简单。

这个函数执行完以后,有返回到lunch函数继续执行:

    if [ $? -ne 0 ]thenechoecho "** Don't have a product spec for: '$product'"echo "** Do you have the right repo manifest?"product=fi

这里就是判断这个函数的返回值,-ne是不等于的意思,如果返回值不等于0,那么就出问题了。

假定一切正常,继续执行代码:

    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")check_variant $variantif [ $? -ne 0 ]thenechoecho "** Invalid variant: '$variant'"echo "** Must be one of ${VARIANT_CHOICES[@]}"variant=fi

这段代码时提取variant并且检查是否合法,不要忘了前面说过,add_lunch_combo添加的字符串就是product-variant格式的字符串,这两部分代码非常相似,就不具体分析这段代码了。

下面的代码是:

    if [ -z "$product" -o -z "$variant" ]thenechoreturn 1fi

检查得到的product和variant是不是为空,空就不可以 了。

    export TARGET_PRODUCT=$productexport TARGET_BUILD_VARIANT=$variantexport TARGET_BUILD_TYPE=release

然后把辛辛苦苦得到的product和variant变量复制给全局变量并且导出为环境变量。以后看到这几个变量,知道它们的值就可以了。

然后调用了set_stuff_for_environment函数,这个函数内容如下:

function set_stuff_for_environment()
{settitleset_java_homesetpathsset_sequence_numberexport ANDROID_BUILD_TOP=$(gettop)# With this environment variable new GCC can apply colors to warnings/errorsexport GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'export ASAN_OPTIONS=detect_leaks=0
}

这个函数会继续补充一些变量。其中set_java_home会检查导出JAVA_HOME这个环境变量,这个环境变量就是JDK坐在的路劲,setpaths函数会给PATH环境变量补充编译Android需要的一些路径。

最后lunch调用了 printconfig函数,这个函数打印出了配置信息。

至此,source build/envsetup.sh 和 lunch就分析完了。接下来将分析make 命令所做的事情。

小结:source build/envsetup.sh会调用add_lunch_combo函数添加很多单板信息进来,同时还会查找/device和/vendor下的vendorsetup.sh文件,查找深度为4级目录,找到后就执行它,它里面至少会有这么一行:add_lunch_combo xxxx,继续添加单板信息。lunch函数则会打印出所有的单板信息供你选择,你输入选择后,luch命令会对你的选择做一系列检测,并从中提取出product和varient,并最终导出这些信息,供正式编译的时候使用。

2 mm编译单个模块

因为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. # A helper goal printing out install paths
  17. .PHONY: GET-INSTALL-PATH
  18. GET-INSTALL-PATH:
  19. @$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED), \
  20. echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))
  21. 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. LOCAL_SRC_FILES:= \
  4. screencap.cpp
  5. LOCAL_SHARED_LIBRARIES := \
  6. libcutils \
  7. libutils \
  8. libbinder \
  9. libskia \
  10. libui \
  11. libgui
  12. LOCAL_MODULE:= screencap
  13. LOCAL_MODULE_TAGS := optional
  14. LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
  15. 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文件:

  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:=
。。。

这个文件就是把一大堆的文件置为空,除了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:

  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、共享库等其他模块的思路都与之类似,正所谓触类旁通,只要完整掌握了一种类型模块的编译,其他类型的模块编译都变得容易理解了。

3 make完整编译android系统

当我们执行make的时候,会查找当前的Makefie文件或者makefile文件并且执行,在android顶级源码目录下面,确实有个Makefile,它之后一行内容:

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

因此,正真执行的是build/core/main.mk

一.依赖浅析

当我们执行make命令的时候,如果没有传入一个目标,那么就会执行默认的目标。注意,我们在编译android系统的时候,只需要执行make就可以了,那么很显然它会执行默认的目标了,那么默认的目标是什么呢?

在build/core/main.mk中:

# This is the default target.  It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):

在main.mk开始不久,就出现了一个伪目标,即便你看不懂Makefile也没有关系,注释上说的很清楚了,他就是默认的目标了。而且这个默认的目标是一个伪目标。make工具遇到伪目标以后,会检查解析伪目标的依赖,如果伪目标存在依赖,就会检查这些依赖,如果这些依赖是伪目标,继续检查这个伪目标的依赖,如果不是伪目标,就会生成这个目标。

阅读一个Makefile,理清目标的依赖关系很重,下图列出了部分重要的以来关系:

在对依赖关系有个了解之后,我们开始顺着make的加载流程,看看它到底做了什么。

首先,我觉得很重要的就是加载特定产品的配置信息。

二.配置产品信息

首先,大致的流程如下图所示:

在product_config.mk中:

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

1.AndoridProducts.mk 使用get-all-product-makefiles获取所有的AndoridProducts.mk文件:

  1. define get-all-product-makefiles
  2. $(call get-product-makefiles,$(_find-android-products-files))
  3. endef

调用_find-android-products-files获取所有的AndroidProducts.mk,然后交由get-product-makefiles函数处理。

  1. define _find-android-products-files
  2. $(shell test -d device && find device -maxdepth 6 -name AndroidProducts.mk) \
  3. $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \
  4. $(SRC_TARGET_DIR)/product/AndroidProducts.mk
  5. endef
  1. define get-product-makefiles
  2. $(sort \
  3. $(foreach f,$(1), \
  4. $(eval PRODUCT_MAKEFILES :=) \
  5. $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
  6. $(eval include $(f)) \
  7. $(PRODUCT_MAKEFILES) \
  8. ) \
  9. $(eval PRODUCT_MAKEFILES :=) \
  10. $(eval LOCAL_DIR :=) \
  11. )
  12. endef

可以看到最终处理的结果是加载了AndroidProducts.mk, 返回了一个排好顺序的PRODUCT_MAKEFILES。

这里把所有的AndroidProducts.mk都加载进来了,但是我们只需要我们产品的配置信息呀,所以接着做一个查找,找到属于我们产品的AndroidProducts.mk:

# Find the product config makefile for the current product.
# 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))

2.current_product_makefile

最终找到的结果存储在current_product_makefile中。关于它的值,这里举例说明:

加入我们在lunch的时候选择了 5:

     1. aosp_arm-eng2. aosp_arm64-eng3. aosp_mips-eng4. aosp_mips64-eng5. aosp_x86-eng6. aosp_x86_64-eng

那么经过以上查找current_product_makefile就等于device/generic/x86/mini_x86.mk3.加载产品配置文件

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 Can not 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# Sanity check
$(check-all-products)

在import-products中导入产品的配置信息,这里就是device/generic/x86/mini_x86.mk。

4然后获取TARGET_DEVICE的值:

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

此时,TARGET_DEVICE = mini_x86.mk;

5获取要拷贝的文件

# A list of words like <source path>:<destination path>[:<owner>].
# The file at the source path should be copied to the destination path
# when building  this product.  <destination path> is relative to
# $(PRODUCT_OUT), so it should look like, e.g., "system/etc/file.xml".
# The rules for these copy steps are defined in build/core/Makefile.
# The optional :<owner> is used to indicate the owner of a vendor file.
PRODUCT_COPY_FILES := \$(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_COPY_FILES))

这个变量也很重要,它存储了需要拷贝的文件。格式为 <source path>:<destination path>,在build/core/Makefile一开始就会先拷贝这个变量指定的文件。

6.加载BoardConfig.mk

又回到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 \$(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \$(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(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)
ifeq ($(TARGET_ARCH),)$(error TARGET_ARCH not defined by board config: $(board_config_mk))
endif

BoardConfig.mk中配置了重要的板级信息,比如cpu架构等。

至此,配置一个产品所需的AndroidProducts.mk,具体产品的配置文件,比如这里的mini_x86.mk以及BoardConfig.mk都加载进来了。

三.加载所有模块

加载完单板信息,make又回到main.mk中,不就就发现了ONE_SHOT_MAKEFILE变量的判断:

1ONE_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_MAKEFILEifneq ($(dont_bother),true)
#
# Include all of the makefiles in the system
## Can't use first-makefiles-under here because
# --mindepth=2 makes the prunes not work.
subdir_makefiles := \$(shell build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git $(subdirs) Android.mk)$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))endif # dont_botherendif # ONE_SHOT_MAKEFILE

如果这个变量定义了,那么,就是编译一个模块,在上一篇博客中已将分析过了,如果没有定义,就说明是编译整个系统。

MAKECMDGOALS是make的一个环境变量,当我们执行make的时候并没有设置它,因此它为空。所以dont_bother不等于true,因此,就会加载所有的Android.mk.这里使用
一个python脚本查找系统中所有的Android.mk,然后Include进来。
四 收集所有要安装的模块
在main.mk中继续往下看:

3.1FULL_BUILD

ifdef FULL_BUILD# The base list of modules to build for this product is specified# by the appropriate product definition file, which was included# by product_config.mk.product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES)# Filter out the overridden packages before doing expansionproduct_MODULES := $(filter-out $(foreach p, $(product_MODULES), \$(PACKAGES.$(p).OVERRIDES)), $(product_MODULES))# Resolve the :32 :64 module namemodules_32 := $(patsubst %:32,%,$(filter %:32, $(product_MODULES)))modules_64 := $(patsubst %:64,%,$(filter %:64, $(product_MODULES)))modules_rest := $(filter-out %:32 %:64,$(product_MODULES))# Note for 32-bit product, $(modules_32) and $(modules_64) will be# added as their original module names.product_MODULES := $(call get-32-bit-modules-if-we-can, $(modules_32))product_MODULES += $(modules_64)# For the rest we add bothproduct_MODULES += $(call get-32-bit-modules, $(modules_rest))product_MODULES += $(modules_rest)$(call expand-required-modules,product_MODULES,$(product_MODULES))product_FILES := $(call module-installed-files, $(product_MODULES))ifeq (0,1)$(info product_FILES for $(TARGET_DEVICE) ($(INTERNAL_PRODUCT)):)$(foreach p,$(product_FILES),$(info :   $(p)))$(error done)endif
else# We're not doing a full build, and are probably only including# a subset of the module makefiles.  Don't try to build any modules# requested by the product, because we probably won't have rules# to build them.product_FILES :=
endif

在执行make的时候,FULL_BUILD:=true

product_MODULES是所有产品配置文件中添加的要打包进系统镜像中的模块,它只是一个名字,比如上篇博客分析过的screencap。
product_FILES获取对应模块的.INSTALLED的值。
define module-installed-files
$(foreach module,$(1),$(ALL_MODULES.$(module).INSTALLED))
endef

在加载单个模块的时候,会给每一个模块生成另外两个值:

$(ALL_MODULES.$(target)).BUILT
$(ALL_MODULES.$(target)).INSTALLED
它们在base_rule.mk中生成:
ALL_MODULES.$(my_register_name).BUILT := \$(ALL_MODULES.$(my_register_name).BUILT) $(LOCAL_BUILT_MODULE)
ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
ALL_MODULES.$(my_register_name).INSTALLED := \$(strip $(ALL_MODULES.$(my_register_name).INSTALLED) $(LOCAL_INSTALLED_MODULE))
ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \$(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) $(LOCAL_BUILT_MODULE):$(LOCAL_INSTALLED_MODULE))
endif
$(ALL_MODULES.$(target)).BUILT代表的一般是out/target/product/xxx/obj下编译生成的模块。
$(ALL_MODULES.$(target)).INSTALLED代表的是out/target/product/xxx/system下生成的模块。

3.2 全部安装模块

modules_to_install := $(sort \$(ALL_DEFAULT_INSTALLED_MODULES) \$(product_FILES) \$(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \$(CUSTOM_MODULES) \)
ALL_DEFAULT_INSTALLED_MODULES是系统默认要安装的模块,product_FILES是特定产品附加的要安装的模块,foreach找到的是特定
TAG的模块,以及加上CUSTOM_MODULES,这样modules_to_install就是全部的要安装的模块了。
ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
include $(BUILD_SYSTEM)/Makefile

然后把modules_to_install的值全部赋给ALL_DEFAULT_INSTALLED_MODULES,接着加载build/core/Makefile。这个Makefile会使用

ALL_DEFAULT_INSTALLED_MODULES变量最终生成所有的镜像文件。生成镜像文件的过程放在下一节讨论。

四.编译所有模块

依赖关系我们在一开始就做了简单的梳理,现在开始分析编译所有模块的依赖关系。

从droid目标定义的地方来看,没有看到它的依赖,但我们向下搜索,就会发现:

.PHONY: apps_only
apps_only: $(unbundled_build_modules)droid: apps_only
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files

我们会发现它有出现了两个依赖,那它到底依赖哪一个呢?

droid依赖哪一个取决于ifneq ($(TARGET_BUILD_APPS),)是否成立,也就是有没有给TARGET_BUILD_APPS赋值过,源码如下:

ifneq ($(TARGET_BUILD_APPS),)# If this build is just for apps, only build apps and not the full system by default.unbundled_build_modules :=ifneq ($(filter all,$(TARGET_BUILD_APPS)),)# If they used the magic goal "all" then build all apps in the source tree.unbundled_build_modules := $(foreach m,$(sort $(ALL_MODULES)),$(if $(filter APPS,$(ALL_MODULES.$(m).CLASS)),$(m)))elseunbundled_build_modules := $(TARGET_BUILD_APPS)endif....PHONY: apps_only
apps_only: $(unbundled_build_modules)droid: apps_onlyelse # TARGET_BUILD_APPS$(call dist-for-goals, droidcore, \$(INTERNAL_UPDATE_PACKAGE_TARGET) \$(INTERNAL_OTA_PACKAGE_TARGET) \$(BUILT_OTATOOLS_PACKAGE) \$(SYMBOLS_ZIP) \$(INSTALLED_FILES_FILE) \$(INSTALLED_BUILD_PROP_TARGET) \$(BUILT_TARGET_FILES_PACKAGE) \$(INSTALLED_ANDROID_INFO_TXT_TARGET) \$(INSTALLED_RAMDISK_TARGET) \)
# Building a full system-- the default is to build droidcore
droid: droidcore dist_filesendif # TARGET_BUILD_APPS

我们期望的是整个系统的编译,所以,droid依赖的是droidcore 和 dist_files

4.1droidcore的定义:

# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: files \systemimage \$(INSTALLED_BOOTIMAGE_TARGET) \$(INSTALLED_RECOVERYIMAGE_TARGET) \$(INSTALLED_USERDATAIMAGE_TARGET) \$(INSTALLED_CACHEIMAGE_TARGET) \$(INSTALLED_VENDORIMAGE_TARGET) \$(INSTALLED_FILES_FILE)

可以droidcore又是一个伪目标,它又依赖于files 等一系列目标,从名字来看,这些目标应该是systemimage,userdataimage,recoryimage等,也就是说,droidcore的最终目的就是生成system.img,userdata.img等系统镜像文件。

看到变量的定义就明白了:

1.boot.img:

INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

2.recovery.img:

INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

3.userdata.img:

INSTALLED_USERDATAIMAGE_TARGET := $(BUILT_USERDATAIMAGE_TARGET)

--->BUILT_USERDATAIMAGE_TARGET := $(PRODUCT_OUT)/userdata.img

4.cache.img

INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET)

--->BUILT_CACHEIMAGE_TARGET := $(PRODUCT_OUT)/cache.img

5.vendor.img

INSTALLED_VENDORIMAGE_TARGET := $(BUILT_VENDORIMAGE_TARGET)

BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img

因此,droidcore的最终目的就是生成这些.Img文件。

dist_files的定义:

# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files

从定义来看,dist_files也是个伪目标,并且它没有任何依赖,作用是完整编译系统的时候拷贝库文件。

4.2.files

它的第一个目标是files:

# All the droid stuff, in directories
.PHONY: files
files: prebuilt \$(modules_to_install) \$(INSTALLED_ANDROID_INFO_TXT_TARGET)、

1.1files又依赖了三个目标,第一个是prebuilt:

# -------------------------------------------------------------------
# This is used to to get the ordering right, you can also use these,
# but they're considered undocumented, so don't complain if their
# behavior changes.
.PHONY: prebuilt
prebuilt: $(ALL_PREBUILT)

prebuilt又是一个伪目标,它又依赖于ALL_PREBUILT变量指向的目标,ALL_PREBUILT是一些预编译模块:

Android.mk (makefile\frameworks\base\cmds\bmgr):ALL_PREBUILT += $(TARGET_OUT)/bin/bmgr
Android.mk (makefile\frameworks\base\cmds\ime):ALL_PREBUILT += $(TARGET_OUT)/bin/ime
Android.mk (makefile\frameworks\base\cmds\input):ALL_PREBUILT += $(TARGET_OUT)/bin/input
Android.mk (makefile\frameworks\base\cmds\pm):ALL_PREBUILT += $(TARGET_OUT)/bin/pm
Android.mk (makefile\frameworks\base\cmds\svc):ALL_PREBUILT += $(TARGET_OUT)/bin/svc

4.3modules_to_install

modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_FILES) \
    $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
    $(CUSTOM_MODULES) \
  )

这个变量之前已经分析过,它包含所有的要安装的模块,make会为这个目标生成依赖关系链,也就是会给其中的每一个模块生成依赖关系链,然后编译每一个模块,这个过程在上一节中已经说过了。
至此,所有应该编译的模块都已经被编译,剩下的就是打包镜像文件了。这将在下一节讨论。

Android系统(138)--- Android编译系统相关推荐

  1. Android系统架构-[Android取经之路]

    摘要:本节主要来讲解Android的系统架构 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢 ...

  2. 【android系统】android系统升级流程分析(二)---update升级包分析

    接下来我们将通过几篇文章来分析update.zip包在具体Android系统升级的过程,来理解Android系统中Recovery模式服务的工作原理.今天让我先来分析下升级包update.zip. 一 ...

  3. android 服务端技术,移动应用服务器端开发(基于JSP技术)-2017 Android系统构架 Android系统构架.docx...

    Android系统构架 PAGE 1 目 录 TOC \o "1-3" \h \z \u 一.Android系统构架 1 二.Linux内核层 2 三.系统运行库层 3 (一)系统 ...

  4. 【android系统】android系统升级流程分析(一)---recovery模式中进行update包升级流程分析

    今天我们直接来看下android中具体的升级过程是如何的. 升级流程概述 升级的流程图: 升级流程分析 第一步:升级包获取 升级获取可以通过远程下载,也可直接拷贝到指定目录即可. 第二步:准备升级 然 ...

  5. android log抓取方法,Android系统之Android抓取各种log的方法

    Android系统之Android抓取各种log的方法 2018年11月25日 | 萬仟网移动技术 | 我要评论 android之android抓取各种log的方法 1.logcat (四类log b ...

  6. Android 系统(11)---android 系统权限大全

    收集到的android权限都很实用的(permission)大全 1.android.permission.WRITE_USER_DICTIONARY 允许应用程序向用户词典中写入新词 2.andro ...

  7. android系统语音合成,android 语音合成报错

    发现了2个问题 第一个貌似是复制离线的资源出错了(已经核对过读写等权限): 12-19 19:54:49.739 32006-32159/com.zhanglf.youxuanz I/NonBlock ...

  8. Android 系统(71)---Android系统build.prop文件生成过程

    Android系统build.prop文件生成过程 Android系统build.prop生成过程 这个文件类似于windows的注册表文件,定义了系统初始的一些参数属性,功能的开放等,通过调整或增加 ...

  9. Android系统(62)-----Android 7.1 新特性之 Shortcuts 介绍

    Android 7.1 新特性之 Shortcuts 介绍 Android 7.1 允许 App 自定义 Shortcuts,类似 iOS 的 3D touch.通过在桌面长按 App 弹出 Shor ...

  10. android系统怎么刷机教程,如何刷新Android系统? Android手机通用刷机教程

    方法一,刷卡机 1. 在恢复模式下,从sdcard中选择第四项Flash zip. 2. 然后,您将在SD卡中看到所有zip文件. 名称update.zip只是为了找到rom软件包,找到您放入SD卡中 ...

最新文章

  1. commons-pool2-2.3 jar包_Java Jar包压缩、解压使用指南
  2. 浏览器自动化操作标准--WebDriver
  3. 题解【luogu P2421 bzoj P1407 [NOI2002]荒岛野人】
  4. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-10项目各种全局帮助类
  5. Mapx 具体使用方法
  6. 局域网不同网段远程桌面_自动化已非原来的自动化:看虚拟局域网技术应用到罗克韦尔的DCS...
  7. 邮箱地址是什么?企业邮箱的密码安全
  8. Sloth组件之NetRisc.Configuration
  9. 农行网银登录无法显示该网页_登录网银无法显示网页
  10. Teams Bot App 代码解析
  11. linux系统分区支持ntfs吗,如何使Linux支持NTFS分区
  12. 山东大学软件学院项目实训-创新实训-山大软院网络攻防靶场实验平台(二)-docker安装与学习
  13. 聊天室小程序服务端源码(客户端接下一条)
  14. 紧急!!!请尽快将Flash Player升级到9.0.124
  15. opencv--GrabCut
  16. 2W字!详解20道Redis经典面试题!(珍藏版)
  17. R DT包 datatable()的使用
  18. 斐波那契查找算法解析
  19. ominipeek 发包_使用OmniPeek抓无线数据包
  20. wine常见问题集合

热门文章

  1. 库ppt演示 python_python操作Power Point:PPT幻灯片布局基础
  2. STM32 软硬件调试
  3. 哈希表、冲突处理方法、查找长度
  4. 机器人局部避障的动态窗口法(dynamic window approach)
  5. ai怎么渐变颜色_你根本想不到AI的混合工具有多神奇!
  6. c# msi中加入驱动_MSI微星:给你的CPU装上热交换气缸活塞,不用电也能驱动风扇降温...
  7. 并发容器——ConcurrentHashMap
  8. 精神独立,才是一个人最大的底气
  9. mongoose使用简记
  10. Java枚举(用Java普通类模拟枚举的实现原理及JDK枚举API使用示例)