ANDROID系统编译过程详解
第⼀部分:概述
在研究Android编译系统之前,我们⾸先需要了解Linux系统的make命令。在Linux系统中,我们可以通过make命令来编译代码。Make命令在执⾏的时候,默认会在当前⽬录到⼀个Makefile⽂件,然后根据Makefile⽂件中的指令来对代码进⾏编译。也就是说,make命令执⾏的是Makefile⽂件中的指令。Makefile⽂件中的指令可以是编译命令,例如gcc,也可以是其它命令,例如Linux系统中的shell命令cp、rm等等。理解这⼀点⾮常重要,因为虽然通常我们说make命令是可以编译代码的,但是它实际上可以做任何事情。
看到这⾥,有的⼩伙伴可能会说,在Linux系统中,直接通过shell命令也可以做很多事情啊,它和make命令有什么区别呢?通过前⾯的介绍可以知道,make命令事实也是通过shell命令来完成任务的,但是它的神奇之处是可以帮我们处理好⽂件之间的依赖关系。我们通常都有会这样的⼀个需求,假设有⼀个⽂件T,它依赖于另外⼀个⽂件D,要求只有当⽂件D的内容发⽣变化,才重新⽣成⽂件T。这种需求在编译系统中表现得尤其典型,当⼀个*.c⽂件include的*.h⽂件发⽣变化时,需要重新编译该*.c⽂件,或者当⼀个模块A所引⽤的模块B发⽣变化时,重新编译模块B。正是由于编译系统中存在这种典型的⽂件依赖需求,⽽make命令⼜是专门⽤来解决这种⽂件依赖问题的,因此我们通常认为make命令是⽤来编译代码的。
Make命令是怎么知道两个⽂件之间存在依赖关系,以及当被依赖⽂件发⽣变化时如何处理⽬标⽂件的呢?答案就在前⾯提到的Makefile⽂件。Makefile⽂件实际上是⼀个脚本⽂件,就像普通的shell脚本⽂件⼀样,只不过它遵循的是Makefile语法。Makefile⽂件最基础的功能就是描述⽂件之间的依赖关系,以及怎么处理这些依赖关系。例如,假设有⼀个⽬录⽂件target,它依赖于⽂件dependency,并且当⽂件dependency发⽣变化时,需要通过command命令来重新⽣成⽂件T,这时候我们就可以在Makefile编写以下语句:
[plain]
1. target: dependency
2. <tab>command -o target -i dependency
我们假设命令command的-o选项指定的是输出⽂件,⽽-i选项指定的是输⼊⽂件。此外,命令command必须是另起⼀⾏,并且以tab键开头。
这就是最基础也是最主要的Makefile⽂件语法。当然,Makefile⽂件还有很多其它的语法,这⾥不可能⼀⼀描述。推荐⼀本书《GNU make中⽂⼿册》,⾥⾯⾮常详细地介绍了make以及Makefile⽂件语法。
整个⼯程只有⼀个Makefile,听起来似乎是⼀件很疯狂的事情,因为这个Makefile可能会变得⽆⽐庞⼤和复杂。其实不⽤担⼼,我们可以按照模块来将这个Makefile划分成⼀个个Makefile⽚段(fragement),然后通过Makefile的include指令来将这些Makefile⽚段组装在⼀个Makefile中。与递归Makefile相⽐,每⼀个模块现在拥有的是⼀个Makefile⽚段,⽽不是⼀个Makefile⽂件。这正是Android编译系统的设计思想和原则,也就是说,我们平时所编写的Android.mk编译脚本都只不过是整个Android编译系统的⼀个Makefile⽚段。
明⽩了Android编译系统的设计思想和原则之后,我们就可以通过图5来观察⼀下Android编译系统的整体架构了:
图5 Android编译系统架构
在使⽤Android编译系统之前,我们需要打开⼀个shell进⼊到Android源码根⽬录中,并且在该shell中将build/envsetup.sh脚本⽂件source进来。脚本⽂件build/envsetup.sh被source到当前shell的过程中,会在vendor和device两个⽬录将⼚商指定的envsetup.sh 也source到当前shell当中,这样就可以获得⼚商提供的产品配置信息。此外,脚本⽂件build/envsetup.sh还提供了以下⼏个重要的命令来帮助我们编译Android源码:
1. lunch
⽤来初始化编译环境,例如设置环境变量和指定⽬标产品型号。Lunch命令在执⾏的时候,主要做两件事情。第⼀件事情是设置TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BUILD_TYPE和TARGET_BUILD_APPS等环境变量,⽤来指定⽬标产品类型和编译类型。第⼆件事情是通过make命令执⾏build/core/config.mk脚本,并且通过加载另外⼀个脚本build/core/dumpvar.mk 打印出当前的编译环境配置信息。注意,build/core/config.mk和build/core/dumpvar.mk均为Makefile脚本,因此它们可以通过make 命令来执⾏。另外,build/core/config.mk脚本还会加载⼀个名称为BoradConfig.mk的脚本以及build/core/envsetup.mk脚本来配置⽬标产品型号的相关信息。
2. m
相当于是在执⾏make命令。对整个Android源码进⾏编译。
3. mm
如果是在Android源码根⽬录下执⾏,那么就相当于是执⾏make命令对整个源码进⾏编译。如果是在Android源码根⽬录下的某⼀个⼦⽬录执⾏,那么就在会在从该⼦⽬录开始,⼀直往上⼀个⽬录直⾄到根⽬录,寻是否存在⼀个Android.mk⽂件。如果存在的话,那么就通过make命令对该Android.mk⽂件描述的模块进⾏编译。
4. mmm
后⾯可以跟⼀个或者若⼲个⽬录。如果指定了多个⽬录,那么⽬录之间以空格分隔,并且每⼀个⽬录下都必须存在⼀个Android,mk⽂件。如果没有在⽬录后⾯通过冒号指定模块名称,那么在Android.mk⽂件中描述的所有模块都会被编译,否则只有指定的模块会被编译。如果需要同时指定多个模块,那么这些模块名称必须以逗号分隔。它的语法如下所⽰:
[html]
1. mmm <dir-1> <dir-2> ... <dir-N>[:module-1,module-2,...,module-M]
该命令会通过make命令来执⾏Android源码根⽬录下的Makefile⽂件,该Makefile⽂件⼜会将build/core/main.mk加载进来。⽂件build/core/main.mk在加载的过程中,还会加载以下⼏个主要的⽂件:
(1). build/core/config.mk
该⽂件根据lunch命令所配置的产品信息在build/target/board、vendor或者device⽬录中到对应的BoradConfig.mk⽂件,以及通过加载build/core/product_config.mk⽂件在build/target/product、vendor或者device⽬录中到对应的AndroidProducts.mk⽂件,来进⼀步对编译环境进⾏配置,以便接下来编译指定模块时可以获得必要的信息。
(2). build/core/definitions.mk
该⽂件定义了在编译过程需要调⽤到的各种⾃定义函数。
(3). 指定的Android.mk
这些指定的Android.mk环境是由mmm命令通过环境变量ONE_SHOT_MAKEFILE传递给build/core/main.mk⽂件使⽤的。这些Android.mk⽂件⼀般还会通过环境变量BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_STATIC_JAVA_LIBRARY、
BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_EXECUTABLE和BUILD_PREBUILT将build/core/package.mk、build/core/java_library.mk、build/core/static_java_library.mk、build/core/shared_library.mk、build/core/static_library.mk、build/core/executable.mk和build/core/prebuilt.mk等编译⽚段模板⽂件加载进来,来表⽰要编译是APK、Java库、Linux动态库/静态库/可执⾏⽂件或者预先编译好的⽂件等等。
(4). build/core/Makefile
该⽂件包含了⽤来制作system.img、ramdisk.img、boot.img和recovery.img等镜像⽂件的脚本。
第⼆部分:Android编译环境初始化
对编译环境进⾏初始化,其中最主要就是指定编译的类型和⽬标设备的型号。Android的编译类型主要有eng、userdebug和user三种,⽽⽀持的⽬标设备型号则是不确定的,它们由当前的源码配置情况所决定。为了确定源码⽀持的所有⽬标设备型号,Android编译系统在初始化的过程中,需要在特定的⽬录中加载特定的配置⽂件。
Android的优势就在于其开源,
我们在对Android的源码进⾏定制的时候,很有必要了解下,Android的编译过程。
但是,这⼉只是告诉你了如何去编译⼀个通⽤的系统,并没有详细告诉你细节,我们跟着编译过程来了解下。
按照google给出的编译步骤如下:
1> source build/envsetup.sh:加载命令
2> lunch:选择平台编译选项
3> make:执⾏编译
我们按照编译步骤来分析编译过程的细节,最终添加⾃⼰的平台编译选项。
1. source build/envsetup.sh
这个命令是⽤来将envsetup.sh⾥的所有⽤到的命令加载到环境变量⾥去,我们来分析下它。envsetup.sh⾥的主要命令如下:
[html]
1. function help()                  # 显⽰帮助信息
2. function get_abs_build_var()           # 获取绝对变量
3. function get_build_var()             # 获取绝对变量
4. function check_product()             # 检查product
5. function check_variant()             # 检查变量
6. function setpaths()                # 设置⽂件路径
7. function printconfig()              # 打印配置
8. function set_stuff_for_environment()        # 设置环境变量
9. function set_sequence_number()            # 设置序号
10. function settitle()                # 设置标题
11. function choosetype()              # 设置type
12. function chooseproduct()              # 设置product
13. function choosevariant()              # 设置variant
14. function tapas()                  # 功能同choosecombo
15. function choosecombo()              # 设置编译参数
16. function add_lunch_combo()            # 添加lunch项⽬
17. function print_lunch_menu()            # 打印lunch列表
18. function lunch()                 # 配置lunch
19. function m()                   # make from top
20. function findmakefile()              # 查makefile
21. function mm()                  # make from current directory
22. function mmm()                  # make the supplied directories
23. function croot()                 # 回到根⽬录
24. function cproj()
25. function pid()
26. function systemstack()
27. function gdbclient()
28. function jgrep()                 # 查java⽂件
29. function cgrep()                  # 查c/cpp⽂件
30. function resgrep()
31. function tracedmdump()
32. function runhat()
33. function getbugreports()
34. function startviewserver()
35. function stopviewserver()
36. function isviewserverstarted()
37. function smoketest()
38. function runtest()
39. function godir ()                 # 跳到指定⽬录 405
40.
41.  # add_lunch_combo函数被多次调⽤,就是它来添加Android编译选项
42.  # Clear this variable.  It will be built up again when the vendorsetup.sh
43.  # files are included at the end of this file.
44.  # 清空LUNCH_MENU_CHOICES变量,⽤来存在编译选项
45.  unset LUNCH_MENU_CHOICES
46. function add_lunch_combo()
47. {
48.      local new_combo=$1        # 获得add_lunch_combo被调⽤时的参数
49.      local c
50.      # 依次遍历LUNCH_MENU_CHOICES⾥的值,其实该函数第⼀次调⽤时,该值为空
51.      for c in ${LUNCH_MENU_CHOICES[@]} ; do
52.          if [ "$new_combo" = "$c" ] ; then    # 如果参数⾥的值已经存在于LUNCH_MENU_CHOICES变量⾥,则返回
53.              return
54.          fi
55.      done
56.      # 如果参数的值不存在,则添加到LUNCH_MENU_CHOICES变量⾥
57.      LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
58. }
59.
60.
61. # 这是系统⾃动增加了⼀个默认的编译项 generic-eng
62. # add the default one here
63. add_lunch_combo generic-eng    # 调⽤上⾯的add_lunch_combo函数,将generic-eng作为参数传递过去
64.
65. # if we're on linux, add the simulator.  There is a special case
66. # in lunch to deal with the simulator
67. if [ "$(uname)" = "Linux" ] ; then
制作android软件流程
68.      add_lunch_combo simulator
69. fi
70.
71. # 下⾯的代码很重要,它要从vendor⽬录下查vendorsetup.sh⽂件,如果查到了,就加载它
72. # Execute the contents of any vendorsetup.sh files we can find.
73. for f in `/bin/ls vendor/*/vendorsetup.sh vendor/*/build/vendorsetup.sh 2> /dev/null`
74.    do
75.      echo "including $f"

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。