Android编译系统——编译流程详解
2014-12-17 17:14
393 查看
1.概述
编译Android的第三步是使用mka命令进行编译,当然我们也可以使用make –j4,但是推荐使用mka命令。因为mka将自动计算-j选项的数字,让我们不用纠结这个数字到底是多少(这个数字其实就是所有cpu的核心数)。在编译时我们可以带上我们需要编译的目标,假设你想生成recovery,那么使用mka recoveryimage,如果想生成ota包,那么需要使用mkaotapackage,后续会介绍所有可以使用的目标。另外注意有一些目标只是起到修饰的作用,也就是说需要和其它目标一起使用,共有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,不能使用其它shell2) 关闭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为true2) 如果定义了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): |
1 2 34 | ifneq ($(TARGET_BUILD_APPS),) …… droid: apps_only #如果编译app,那么droid依赖apps_only目标 else …… droid: droidcore dist_files #默认依赖droidcore目标和dist_files目标 endif |
1 2 34 | $(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) |
1 2 34 | 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 |
再看一下files目标所依赖的目标:
1 2 3 | files: prebuilt \ $(modules_to_install) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) |
1 2 34 | …… 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)) |
相关文章推荐
- Android编译系统详解(二)——命令执行流程
- Android编译系统详解(三)——编译流程详解
- Android编译系统详解(三)——编译流程详解
- Android编译系统详解(二)——命令执行流程
- Android编译系统详解(三)——编译流程详解
- Android编译系统详解(二)——命令执行流程
- 【转】Android编译系统详解(三)——编译流程详解
- Android编译系统详解(一)
- Android编译系统详解
- Android编译系统详解 配置文件
- Android编译系统详解
- Android编译系统详解(三)
- Android编译系统详解(二)
- Android编译系统详解
- Android编译系统详解(三)
- Android编译系统详解(二)
- Android编译系统详解(二)
- Android编译系统详解(二)
- [置顶] Android编译系统详解(一)
- Android编译系统详解(一)