1.概述
编译Android的第三步是使用mka命令进行编译,当然我们也可以使用make –j4,但是推荐使用mka命令。因为mka将自动计算-j选项的数字,让我们不用纠结这个数字到底是多少(这个数字其实就是所有cpu的核心数)。在编译时我们可以带上我们需要编译的目标,假设你想生成recovery,那么使用mka recoveryimage,如果想生成ota包,那么需要使用mka otapackage,后续会介绍所有可以使用的目标。另外注意有一些目标只是起到修饰的作用,也就是说需要和其它目标一起使用,共有4个用于修饰的伪目标:
- 1) showcommands 显示编译过程中使用的命令
- 2) incrementaljavac用于增量编译java代码
- 3) checkbuild用于检验那些需要检验的模块
- 4) all如果使用all修饰编译目标,会编译所有模块
研究Android编译系统时最头疼的可能是变量,成百个变量我们无法记住其含义,也不知道这些变量会是什么值,为此我专门做了一个编译变量的参考网站android.cloudchou.com,你可以在该网站查找变量,它能告诉你变量的含义,也会给出你该变量的示例值,另外也详细解释了编译系统里每个Makefile的作用,这样你在看编译系统的代码时不至于一头雾水。
编译的核心文件是build/core/main.mk和build/core/makefile,main.mk主要作用是检查编译环境是否符合要求,确定产品配置,决定产品需要使用的模块,并定义了许多目标供开发者使用,比如droid,sdk等目标,但是生成这些目标的规则主要在Makefile里定义,而内核的编译规则放在build/core/task/kernel.mk
我们将先整体介绍main.mk的执行流程,然后再针对在Linux上编译默认目标时使用的关键代码进行分析。Makefile主要定义了各个目标的生成规则,因此不再详细介绍它的执行流程,若有兴趣看每个目标的生成规则,可查看http://android.cloudchou.com/build/core/Makefile.php
2. main.mk执行流程
2.1 检验编译环境并建立产品配置
- 1) 设置Shell变量为bash,不能使用其它shell
- 2) 关闭make的suffix规则,rcs/sccs规则,并设置一个规则: 当某个规则失败了,就删除所有目标
- 3) 检验make的版本,cygwin可使用任意版本make,但是linux或者mac只能使用3.81版本或者3.82版本
- 4) 设置PWD,TOP,TOPDIR,BUILD_SYSTEM等变量,定义了默认目标变量,但是暂时并未定义默认目标的生成规则
- 5) 包含build/core/help.mk,该makefile定义了两个目标help和out, help用于显示帮助,out用于检验编译系统是否正确
- 6) 包含build/core/config.mk,config.mk作了很多配置,包括产品配置,包含该makefile后,会建立输出目录系列的变量,还会建立PRODUCT系列变量,后续介绍产品配置时,对此会有更多详细介绍
- 7) 包含build/core/cleanbuild.mk,该makefile会包含所有工程的CleanSpec.mk,写了CleanSpec.mk的工程会定义每次编译前的特殊清理步骤,cleanbuild.mk会执行这些清除步骤
- 8) 检验编译环境,先检测上次编译结果,如果上次检验的版本和此次检验的版本一致,则不再检测,然后进行检测并将此次编译结果写入
2.2 包含其它makefile及编译目标检测
- 1) 如果目标里含有incrementaljavac, 那么编译目标时将用incremental javac进行增量编译
- 2) 设置EMMA_INSTRUMENT变量的值,emma是用于测试代码覆盖率的库
- 3) 包含build/core/definistions.mk,该makefile定义了许多辅助函数
- 4) 包含build/core/qcom_utils.mk,该makefile定义了高通板子的一些辅助函数及宏
- 5) 包含build/core/dex_preopt.mk,该makefile定义了优化dex代码的一些宏
- 6) 检测编译目标里是否有user,userdebug,eng,如果有则告诉用户放置在buildspec.mk或者使用lunch设置,检测TARGET_BUILD_VARIANT变量,看是否有效
- 7) 包含build/core/pdk_config.mk, PDK主要是能提高现有设备升级能力,帮助设备制造商能更快的适配新版本的android
2.3 根据TARGET_BUILD_VARIANT建立配置
- 1) 如果编译目标里有sdk,win_sdk或者sdk_addon,那么设置is_sdk_build为true
- 2) 如果定义了HAVE_SELINUX,那么编译时为build prop添加属性ro.build.selinux=1
- 3) 如果TARGET_BUILD_VARIANT是user或者userdebug,那么tags_to_install += debug 如果用户未定义DISABLE_DEXPREOPT为true,并且是user模式,那么将设置WITH_DEXPREOPT := true,该选项将开启apk的预优化,即将apk分成odex代码文件和apk资源文件
- 4) 判断enable_target_debugging变量,默认是true,当build_variant是user时,则它是false。如果该变量值为true,则设置Rom的编译属性ro.debuggable为1,否则设置ro.debuggable为0
- 5) 如果TARGET_BUILD_VARIANT是eng,那么tags_to_install为debug,eng, 并设置Rom的编译属性ro.setupwizard.mode为OPTIONAL,因为eng模式并不要安装向导
- 6) 如果TARGET_BUILD_VARIANT是tests,那么tags_to_install := debug eng tests
- 7) 设置sdk相关变量
- 8) 添加一些额外的编译属性
- 9) 定义should-install-to-system宏函数
- 10) 若除了修饰目标,没定义任何目标,那么将使用默认目标编译
2.4 包含所有要编译的模块的Makefile
如果编译目标是clean clobber installclean dataclean,那么设置dont_bother为true,若dont_bother为false,则将所有要编译的模块包含进来
1) 如果主机操作系统及体系结构为darwin-ppc(Mac电脑),那么提示不支持编译Sdk,并将SDK_ONLY设置为true
2) 如果主机操作系统是windows,那么设置SDK_ONLY为true
3) 根据SDK_ONLY是否为true,编译主机操作系统类型,BUILD_TINY_ANDROID的值,设置sudbidrs变量
4) 将所有PRODUCT_*相关变量存储至stash_product_vars变量,稍后将验证它是否被修改
5) 根据ONE_SHOT_MAKEFILE的值是否为空,包含不同的makefile
6) 执行post_clean步骤,并确保产品相关变量没有变化
7) 检测是否有文件加入ALL_PREBUILT
8) 包含其它必须在所有Android.mk包含之后需要包含的makefile
9) 将known_custom_modules转化成安装路径得到变量CUSTOM_MODULES
10) 定义模块之间的依赖关系,$(ALL_MODULES.$(m).REQUIRED))变量指明了模块之间的依赖关系
11) 计算下述变量的值:product_MODULES,debug_MODULES,eng_MODULES,tests_MODULES,modules_to_install,overridden_packages,target_gnu_MODULES,ALL_DEFAULT_INSTALLED_MODULES
12) 包含build/core/Makefile
13) 定义变量modules_to_check
2.5 定义多个目标
这一节定义了众多目标,prebuilt,all_copied_headers,files,checkbuild,ramdisk,factory_ramdisk,factory_bundle,systemtarball,boottarball,userdataimage,userdatatarball,cacheimage,bootimage,droidcore,dist_files,apps_only,all_modules,docs,sdk,lintall,samplecode,findbugs,clean,modules,showcommands,nothing。
后续文章将列出所有可用的目标
3 编译默认目标时的执行流程
在介绍编译默认目标时的执行流程之前,先介绍一下ALL_系列的变量,否则看代码时很难搞懂这些变量的出处,这些变量在包含所有模块后被建立,每个模块都有对应的用于编译的makefile,这些makefile会包含一个编译类型对应的makefile,比如package.mk,而这些makefile最终都会包含base_rules.mk,在base_rules.mk里会为ALL系列变量添加值。所有这些变量及其来源均可在android.cloudchou.com查看详细解释:
- 1) ALL_DOCS所有文档的全路径,ALL_DOCS的赋值在droiddoc.mk里, ALL_DOCS += $(full_target)
- 2) ALL_MODULES系统的所有模块的简单名字集合,编译系统还为每一个模块还定义了其它两个变量,ALL_MODULES.$(LOCAL_MODULE).BUILT 所有模块的生成路径ALL_MODULES.$(LOCAL_MODULE).INSTALLED 所有模块的各自安装路径,详情请见http://android.cloudchou.com/build/core/definitions.php#ALL_MODULES
- 3) ALL_DEFAULT_INSTALLED_MODULES 所有默认要安装的模块,在build/core/main.mk和build/core/makfile里设置
- 4) ALL_MODULE_TAGS 使用LOCAL_MODULE_TAGS定义的所有tag集合,每一个tag对应一个ALL_MODULE_TAGS.变量,详情请见http://android.cloudchou.com/build/core/definitions.php#ALL_MODULE_TAGS
- 5) ALL_MODULE_NAME_TAGS类似于ALL_MODULE_TAGS,但是它的值是 某个tag的所有模块的名称 详情请见http://android.cloudchou.com/build/core/definitions.php#ALL_MODULE_NAME_TAGS
- 6) ALL_HOST_INSTALLED_FILES 安装在pc上的程序集合
- 7) ALL_PREBUILT 将会被拷贝的预编译文件的安装全路径的集合
- 8) ALL_GENERATED_SOURCES 某些工具生成的源代码文件的集合,比如aidl会生成java源代码文件
- 9) ALL_C_CPP_ETC_OBJECTS 所有asm,c,c++,以及lex和yacc生成的c代码文件的全路径
- 10) ALL_ORIGINAL_DYNAMIC_BINARIES 没有被优化,也没有被压缩的动态链接库
- 11) ALL_SDK_FILES 将会放在sdk的文件
- 12) ALL_FINDBUGS_FILES 所有findbugs程序用的xml文件
- 13) ALL_GPL_MODULE_LICENSE_FILES GPL 模块的 许可文件
- 14) ANDROID_RESOURCE_GENERATED_CLASSES Android 资源文件生成的java代码编译后的类的类型
3.1 关键代码
定义默认目标的代码位于main.mk:
1 2 3 |
.PHONY: droid DEFAULT_GOAL := droid $(DEFAULT_GOAL): |
droid目标依赖的目标有:
1 2 3 4 5 6 7 |
ifneq ($(TARGET_BUILD_APPS),) …… droid: apps_only #如果编译app,那么droid依赖apps_only目标 else …… droid: droidcore dist_files #默认依赖droidcore目标和dist_files目标 endif |
dist_files目标依赖的目标主要是一些用于打包的工具,它们都是用dist-for-goals宏添加依赖关系的:
1 2 3 4 5 6 7 8 9 10 11 |
$(call dist-for-goals, dist_files, $(EMMA_META_ZIP)) system/core/mkbootimg/Android.mk$(call dist-for-goals, dist_files, $( LOCAL_BUILT_MODULE )) system/core/cpio/Android.mk:13:$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE)) system/core/adb/Android.mk:88:$(call dist-for-goals,dist_files sdk,$(LOCAL_BUILT_MODULE)) system/core/fastboot/Android.mk:68:$(call dist-for-goals,dist_files sdk,$(LOCAL_BUILT_MODULE)) external/guava/Android.mk:26:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):guava.jar) external/yaffs2/Android.mk:28:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE)) external/mp4parser/Android.mk:26:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):mp4parser.jar) external/jsr305/Android.mk:25:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):jsr305.jar) frameworks/support/renderscript/v8/Android.mk:29:#$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):volley.jar) frameworks/support/volley/Android.mk:29:#$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):volley.jar) |
我们再看droidcore目标依赖的目标有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
droidcore: files \ systemimage \ #system.img $(INSTALLED_BOOTIMAGE_TARGET) \ #boot.img $(INSTALLED_RECOVERYIMAGE_TARGET) \#recovery.img $(INSTALLED_USERDATAIMAGE_TARGET) \#data.img $(INSTALLED_CACHEIMAGE_TARGET) \#cache.img $(INSTALLED_FILES_FILE)# installed-files.txt ifneq ($(TARGET_BUILD_APPS),) …. else $(call dist-for-goals, droidcore, \ $(INTERNAL_UPDATE_PACKAGE_TARGET) #cm_find5-img-eng.cloud.zip $(INTERNAL_OTA_PACKAGE_TARGET) \ # cm_find5-ota-eng.cloud.zip $(SYMBOLS_ZIP) \ # cm_find5-symbols-eng.cloud.zip $(INSTALLED_FILES_FILE) \# installed-files.txt $(INSTALLED_BUILD_PROP_TARGET) \# system/build.prop $(BUILT_TARGET_FILES_PACKAGE) \# cm_find5-target_files-eng.cloud.zip $(INSTALLED_ANDROID_INFO_TXT_TARGET) \# android-info.txt $(INSTALLED_RAMDISK_TARGET) \# ramdisk.img $(INSTALLED_FACTORY_RAMDISK_TARGET) \# factory_ramdisk.gz $(INSTALLED_FACTORY_BUNDLE_TARGET) \# cm_find5-factory_bundle- eng.cloud.zip ) endif |
system.img, boot.img, recovery.img, data.img,cache.img,installed_files.txt的生成规则在Makefile里定义, 在http://android.cloudchou.com/build/core/Makefile.php里可以看到详细的生成规则 再看一下files目标所依赖的目标:
1 2 3 |
files: prebuilt \ $(modules_to_install) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) |
prebuilt目标依赖$(ALL_PREBUILT),android-info.txt的生成规则在target/board/board.mk里定义,而$(modules_to_install)目标是所有要安装的模块的集合,计算比较复杂,现在以在linux下编译默认目标为例,将涉及到的代码组织如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
…… tags_to_install := ifneq (,$(user_variant)) ….. ifeq ($(user_variant),userdebug) tags_to_install += debug else … endif endif ifeq ($(TARGET_BUILD_VARIANT),eng) tags_to_install := debug eng … endif ifeq ($(TARGET_BUILD_VARIANT),tests) tags_to_install := debug eng tests endif ifdef is_sdk_build tags_to_install := debug eng else # !sdk endif …… # ------------------------------------------------------------ # Define a function that, given a list of module tags, returns # non-empty if that module should be installed in /system. # For most goals, anything not tagged with the "tests" tag should # be installed in /system. define should-install-to-system $(if $(filter tests,$(1)),,true) endef ifdef is_sdk_build # For the sdk goal, anything with the "samples" tag should be # installed in /data even if that module also has "eng"/"debug"/"user". define should-install-to-system $(if $(filter samples tests,$(1)),,true) endef endif … #接下来根据配置计算要查找的subdirs目录 ifneq ($(dont_bother),true) … ifeq ($(SDK_ONLY),true) include $(TOPDIR)sdk/build/sdk_only_whitelist.mk include $(TOPDIR)development/build/sdk_only_whitelist.mk # Exclude tools/acp when cross-compiling windows under linux ifeq ($(findstring Linux,$(UNAME)),) subdirs += build/tools/acp endif else # !SDK_ONLY ifeq ($(BUILD_TINY_ANDROID), true) subdirs := \ bionic \ system/core \ system/extras/ext4_utils \ system/extras/su \ build/libs \ build/target \ build/tools/acp \ external/gcc-demangle \ external/mksh \ external/openssl \ external/yaffs2 \ external/zlib else # !BUILD_TINY_ANDROID subdirs := $(TOP) FULL_BUILD := true endif # !BUILD_TINY_ANDROID endif # Before we go and include all of the module makefiles, stash away # the PRODUCT_* values so that later we can verify they are not modified. stash_product_vars:=true ifeq ($(stash_product_vars),true) $(call stash-product-vars, __STASHED) endif …. ifneq ($(ONE_SHOT_MAKEFILE),) include $(ONE_SHOT_MAKEFILE) CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS))) FULL_BUILD := … else # ONE_SHOT_MAKEFILE # # 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 --prune=.repo --prune=.git $(subdirs) Android.mk) include $(subdir_makefiles) endif # ONE_SHOT_MAKEFILE …… ifeq ($(stash_product_vars),true) $(call assert-product-vars, __STASHED) endif … # ------------------------------------------------------------------- # Fix up CUSTOM_MODULES to refer to installed files rather than # just bare module names. Leave unknown modules alone in case # they're actually full paths to a particular file. known_custom_modules := $(filter $(ALL_MODULES),$(CUSTOM_MODULES)) unknown_custom_modules := $(filter-out $(ALL_MODULES),$(CUSTOM_MODULES)) CUSTOM_MODULES := \ $(call module-installed-files,$(known_custom_modules)) \ $(unknown_custom_modules # ------------------------------------------------------------------- # Figure out our module sets. # # Of the modules defined by the component makefiles, # determine what we actually want to 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.make. product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES) # Filter out the overridden packages before doing expansion product_MODULES := $(filter-out $(foreach p, $(product_MODULES), \ $(PACKAGES.$(p).OVERRIDES)), $(product_MODULES)) $(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 # When modules are tagged with debug eng or tests, they are installed # for those variants regardless of what the product spec says. debug_MODULES := $(sort \ $(call get-tagged-modules,debug) \ $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_DEBUG)) \ ) eng_MODULES := $(sort \ $(call get-tagged-modules,eng) \ $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_ENG)) \ ) tests_MODULES := $(sort \ $(call get-tagged-modules,tests) \ $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_TESTS)) \ ) # TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES # and get rid of it from this list. # TODO: The shell is chosen by magic. Do we still need this? modules_to_install := $(sort \ $(ALL_DEFAULT_INSTALLED_MODULES) \ $(product_FILES) \ $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \ $(call get-tagged-modules, shell_$(TARGET_SHELL)) \ $(CUSTOM_MODULES) \ ) # Some packages may override others using LOCAL_OVERRIDES_PACKAGES. # Filter out (do not install) any overridden packages. overridden_packages := $(call get-package-overrides,$(modules_to_install)) ifdef overridden_packages # old_modules_to_install := $(modules_to_install) modules_to_install := \ $(filter-out $(foreach p,$(overridden_packages),$(p) %/$(p).apk), \ $(modules_to_install)) endif #$(error filtered out # $(filter-out $(modules_to_install),$(old_modules_to_install))) # Don't include any GNU targets in the SDK. It's ok (and necessary) # to build the host tools, but nothing that's going to be installed # on the target (including static libraries). ifdef is_sdk_build target_gnu_MODULES := \ $(filter \ $(TARGET_OUT_INTERMEDIATES)/% \ $(TARGET_OUT)/% \ $(TARGET_OUT_DATA)/%, \ $(sort $(call get-tagged-modules,gnu))) $(info Removing from sdk:)$(foreach d,$(target_gnu_MODULES),$(info : $(d))) modules_to_install := \ $(filter-out $(target_gnu_MODULES),$(modules_to_install)) # Ensure every module listed in PRODUCT_PACKAGES* gets something installed # TODO: Should we do this for all builds and not just the sdk? $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES), \ $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES has nothing to install!))) $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_DEBUG), \ $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_DEBUG has nothing to install!))) $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_ENG), \ $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_ENG has nothing to install!))) $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_TESTS), \ $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_TESTS has nothing to install!))) endif # Install all of the host modules modules_to_install += $(sort $(modules_to_install) $(ALL_HOST_INSTALLED_FILES)) # build/core/Makefile contains extra stuff that we don't want to pollute this # top-level makefile with. It expects that ALL_DEFAULT_INSTALLED_MODULES # contains everything that's built during the current make, but it also further # extends ALL_DEFAULT_INSTALLED_MODULES. ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install) include $(BUILD_SYSTEM)/Makefile modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES)) ALL_DEFAULT_INSTALLED_MODULES := endif # dont_bother # These are additional goals that we build, in order to make sure that there # is as little code as possible in the tree that doesn't build. modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED)) # If you would like to build all goals, and not skip any intermediate # steps, you can pass the "all" modifier goal on the commandline. ifneq ($(filter all,$(MAKECMDGOALS)),) modules_to_check += $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).BUILT)) endif # for easier debugging modules_to_check := $(sort $(modules_to_check)) #$(error modules_to_check $(modules_to_check)) |