Android recovery分析(一)---如何编译全量升级包
扫描二维码
随时随地手机看文章
一、前言
recovery的最主要功能就是升级,而升级文件就是升级包了,那么升级包时如何编译出来的呢?文就这个问题做个简要的分析。
注:本文中的叙述纯属个人理解,欢迎批评指正。
二、升级包编译命令
1.source build/envsetup.sh
2.lunch (选择合适的配置)
3.make otapackage
注:有些平台可能没有将“recoveryimage”、“bootimage”等目标添加为“otapackage”目标的依赖,而otapackage目标必定会依赖“boot.img”和“recovery.img”这些文件,这时就需要先执行编译对应image文件的命令之后才能使“make otapackage”命令顺利执行,最终生成升级包。
三、从makefile分析升级包编译流程
寻找otapackage目标(我以正在进行的一个项目为例)
首先我们在makefile中找到otapackage目标,然后顺藤摸瓜来分析整个编译的过程。下面我贴出相关的makefile代码,代码路径:rootdir/build/core/Makefile
$(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BOOTIMAGE_TARGET) -------------------------------(1) $(INSTALLED_RADIOIMAGE_TARGET) $(INSTALLED_RECOVERYIMAGE_TARGET) $(INSTALLED_SYSTEMIMAGE) $(INSTALLED_USERDATAIMAGE_TARGET) $(INSTALLED_CACHEIMAGE_TARGET) $(INSTALLED_VENDORIMAGE_TARGET) $(INSTALLED_CUSTOMIMAGE_TARGET) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(SELINUX_FC) $(built_ota_tools) $(APKCERTS_FILE) $(HOST_OUT_EXECUTABLES)/fs_config | $(ACP) -----------------(2) @echo "Package target files: $@" $(hide) rm -rf $@ $(zip_root) $(hide) mkdir -p $(dir $@) $(zip_root) --------------(3) @# Components of the recovery image $(hide) mkdir -p $(zip_root)/RECOVERY $(hide) $(call package_files-copy-root, -------------(4) $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK) $(hide) mkdir -p $(zip_root)/OTHER $(hide) $(ACP) $(INSTALLED_UBOOT_TARGET) $(zip_root)/OTHER/bootloader ---(5) $(hide) $(ACP) $(INSTALLED_DTB_TARGET) $(zip_root)/OTHER/dtb $(hide) $(ACP) $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/OTHER/boot.img $(hide) $(ACP) $(INSTALLED_LDFW_TARGET) $(zip_root)/OTHER/ldfw $(hide) $(ACP) $(INSTALLED_RECOVERY_TARGET) $(zip_root)/OTHER/recovery.img ifdef BOARD_KERNEL_BASE $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base endif ifdef BOARD_KERNEL_PAGESIZE $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/pagesize endif @# Components of the boot image $(hide) mkdir -p $(zip_root)/BOOT $(hide) $(call package_files-copy-root, $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK) ifdef INSTALLED_KERNEL_TARGET $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel endif ifdef INSTALLED_2NDBOOTLOADER_TARGET $(hide) $(ACP) $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second endif ifdef BOARD_KERNEL_CMDLINE $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline endif ifdef BOARD_KERNEL_BASE $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base endif ifdef BOARD_KERNEL_PAGESIZE $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/BOOT/pagesize endif $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET), mkdir -p $(zip_root)/RADIO; $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));) @# Contents of the system image $(hide) $(call package_files-copy-root, $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM) @# Contents of the data image $(hide) $(call package_files-copy-root, $(TARGET_OUT_DATA),$(zip_root)/DATA) ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE @# Contents of the vendor image $(hide) $(call package_files-copy-root, $(TARGET_OUT_VENDOR),$(zip_root)/VENDOR) endif @# Extra contents of the OTA package $(hide) mkdir -p $(zip_root)/OTA/bin $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/ $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/ @# Files that do not end up in any images, but are necessary to @# build them. $(hide) mkdir -p $(zip_root)/META @# imei.dat information of the OTA package @echo "[imei.dat] Adding imei.dat to OTA package" @echo "[imei.dat] path : bootable/recovery/etc/META-INF/imei.dat" $(hide) $(ACP) bootable/recovery/etc/META-INF/imei.dat $(zip_root)/META/ @# machine_match information of the OTA package @echo "[machine_match] Adding machine_match to OTA package" $(hide) $(ACP) $(machine_match_binary) $(PRODUCT_OUT)/ $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt $(hide) if test -e $(tool_extensions)/releasetools.py; then $(ACP) $(tool_extensions)/releasetools.py $(zip_root)/META/; fi $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt $(hide) echo "recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/misc_info.txt -------------------------------------------------------------------------------------(6) $(hide) echo "fstab_version=$(PRIVATE_RECOVERY_FSTAB_VERSION)" >> $(zip_root)/META/misc_info.txt $(hide) -$(ACP) $(PRODUCT_OUT)/firmware_type $(zip_root)/META/ ifdef BOARD_FLASH_BLOCK_SIZE $(hide) echo "blocksize=$(BOARD_FLASH_BLOCK_SIZE)" >> $(zip_root)/META/misc_info.txt endif ifdef BOARD_BOOTIMAGE_PARTITION_SIZE $(hide) echo "boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt endif ifdef BOARD_RECOVERYIMAGE_PARTITION_SIZE $(hide) echo "recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt endif ifdef BOARD_HAS_EXT4_RESERVED_BLOCKS $(hide) echo "has_ext4_reserved_blocks=$(BOARD_HAS_EXT4_RESERVED_BLOCKS)" >> $(zip_root)/META/misc_info.txt endif ifdef TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS @# TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS can be empty to indicate that nothing but defaults should be used. $(hide) echo "recovery_mount_options=$(TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS)" >> $(zip_root)/META/misc_info.txt else $(hide) echo "recovery_mount_options=$(DEFAULT_TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS)" >> $(zip_root)/META/misc_info.txt endif $(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt $(hide) echo "default_system_dev_certificate=$(DEFAULT_SYSTEM_DEV_CERTIFICATE)" >> $(zip_root)/META/misc_info.txt ifdef PRODUCT_EXTRA_RECOVERY_KEYS $(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $(zip_root)/META/misc_info.txt endif $(hide) echo 'mkbootimg_args=$(BOARD_MKBOOTIMG_ARGS)' >> $(zip_root)/META/misc_info.txt $(hide) echo "use_set_metadata=1" >> $(zip_root)/META/misc_info.txt $(hide) echo "multistage_support=1" >> $(zip_root)/META/misc_info.txt $(hide) echo "update_rename_support=1" >> $(zip_root)/META/misc_info.txt $(hide) echo "blockimgdiff_versions=1,2,3" >> $(zip_root)/META/misc_info.txt ifneq ($(OEM_THUMBPRINT_PROPERTIES),) # OTA scripts are only interested in fingerprint related properties $(hide) echo "oem_fingerprint_properties=$(OEM_THUMBPRINT_PROPERTIES)" >> $(zip_root)/META/misc_info.txt endif $(call generate-userimage-prop-dictionary, $(zip_root)/META/misc_info.txt) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) ./build/tools/releasetools/make_recovery_patch $(zip_root) $(zip_root) @# Zip everything up, preserving symlinks $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .) @# Run fs_config on all the system, vendor, boot ramdisk, @# and recovery ramdisk files in the zip, and save the output $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt ---------(7) $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="VENDOR/" } /^VENDOR// {print "vendor/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/vendor_filesystem_config.txt $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT/RAMDISK// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/boot_filesystem_config.txt $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY/RAMDISK// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt) --------------------------------------------------------------------------(8) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) ./build/tools/releasetools/add_img_to_target_files -p $(HOST_OUT) $@ --------------------------------------------------------------------------------(9) $(hide) ./build/tools/releasetools/replace_img_from_target_files.py $@ $(PRODUCT_OUT) -----------------------------------------------------------------------------------------------(10) .PHONY: target-files-package target-files-package: $(BUILT_TARGET_FILES_PACKAGE) ifneq ($(filter $(MAKECMDGOALS),target-files-package),) $(call dist-for-goals, target-files-package, $(BUILT_TARGET_FILES_PACKAGE)) endif ifneq ($(TARGET_PRODUCT),sdk) ifeq ($(filter generic%,$(TARGET_DEVICE)),) ifneq ($(TARGET_NO_KERNEL),true) ifneq ($(recovery_fstab),) # ----------------------------------------------------------------- # OTA update package name := $(TARGET_PRODUCT) ifeq ($(TARGET_BUILD_TYPE),debug) name := $(name)_debug endif name := $(name)-ota-$(FILE_NAME_TAG) INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR) $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS) @echo "Package OTA: $@" $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) ./build/tools/releasetools/ota_from_target_files -v ------------------(11) --block -p $(HOST_OUT) -k $(KEY_CERT_PAIR) $(if $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)) $(BUILT_TARGET_FILES_PACKAGE) $@ .PHONY: otapackage otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
下面来分析一下makefile代码:
首先otapackage是一个伪目标,它有个INTERNAL_OTA_PACKAGE_TARGET的依赖,INTERNAL_OTA_PACKAGE_TARGET :=
$(PRODUCT_OUT)/$(name).zip,就是最终的那个升级包。我们继续看下面:
$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE)
$(DISTTOOLS)
$(INTERNAL_OTA_PACKAGE_TARGET)又有一些依赖,这里我只关心 $(BUILT_TARGET_FILES_PACKAGE) ,
$(BUILT_TARGET_FILES_PACKAGE) 目标在代码片段的最开始定义,现在重点分析一下 $(BUILT_TARGET_FILES_PACKAGE) 目标。
我们可以通过阅读makefile可以知道,$(BUILT_TARGET_FILES_PACKAGE) =out/target/product//obj/PACKAGING/target_files_intermediates/*target_files-.zip,target_files-.zip(后续我们简称为target_file.zip)中保存着生成升级包需要的文件。下面分析代码
(1)一些依赖目标
(2)在依赖前加“|”的意思是”|”后面的依赖要比其他依赖优先编译
(3)生成目录out/target/product//obj/PACKAGING/target_files_intermediates/*target_files-(后续我们简称为target_file),后面会把一些中间文件拷贝到这个目录,最终打包成out/target/product/*/obj/PACKAGING/target_files_intermediates/target_files-.zip
(4)把生成recovery的中间文件也拷贝到target_file目录,因为升级包中的recovery.img不是跟out/target/product/*/recovery.img相同的,在执行ota_from_target_files 脚本的过程中会重新生成recovery.img。当然,现在的Android都是通过打patch(recovery.img基于boot.img的patch)的方式升级recovery的。
(5)拷贝一些文件到target_file的OTHER目录,最终的升级包一般直接把这些文件拷贝过去就ok了。
(6)把一些编译信息放在文件中,方便后面编译升级包时使用,比如分区信息(编译升级包时需要检测升级文件的大小是否大于对应的分区大小,如果大于的话当然无法升级了,只能终止编译)。
(7)先看下面命令的执行结果
这是在提取分区中文件的信息,包括文件权限,selinux相关的信息,这些信息将会在制作文件系统的时候使用。
(8)打包target_file.zip
(9)生成img文件,并将它们拷贝到target_file/IMAGE目录,后面可能会使用。
(10)调用replace_img_from_target_files.py脚本把target_file/IMAGE目录下面的img文件拷贝到out/target/product/目录。调用这个脚本的目的是使out/target/product/和升级包中的升级img文件保持一致,这跟Android的一个设计缺陷有关。下面就来了解一下
我们知道out/target/product//system.img文件是由out/target/product//system编译来的,按理说升级包中的system分区的文件来源也应该是out/target/product//system,但实际上升级包中的system分区的文件来源是target_file/SYSTEM。如果说out/target/product//system与target_file/SYSTEM两个目录的文件内容一样那也没什么问题,问题是这两个目录是不一样的,因为在执行ota_from_target_file脚本的时候会生成一个install_recovery.sh脚本(这个脚本就是在Android启动的时候执行,用来升级recovery的),然后把install_recovery.sh拷贝到target_file/SYSTEM,所以造成了最终out/target/product/*/system.img和升级包中的system(升级包中没有system.img,recovery用一种新的方法来升级system)的差异,这会引发下面的问题:
(a)如果你是用fastboot烧录system,那么你就会用到out/target/product/*/system.img,同时你升级recovery的方式是用install_recovery.sh来升级的,那你会发现你的recovery其实是没法升级的,因为在你的系统中压根就没有install_recovery.sh这个文件。
(b)如果你说,没关系,我是在recovery中直接把recovery.img写到recovery中升级的,(a)问题对我没影响。那你可能会发现你无法进行增量升级。因为增量升级包是通过比较两个版本的target_file.zip生成的,现在你的板子里面的system和增量包的基准版本不一致。
(11)执行 ./build/tools/releasetools/ota_from_target_files脚本,最终生成升级包。下面来分析一下 ./build/tools/releasetools/ota_from_target_files。
四、ota_from_target_files脚本分析
1.使用说明(常用)
-k:指定key的路径,如build/target/product/security/testkey
-i:编译增量升级包, ./build/tools/releasetools/ota_from_target_files -i
def main(argv): def option_handler(o, a): ----------------------------------(1) if o == "--board_config": pass # deprecated elif o in ("-k", "--package_key"): OPTIONS.package_key = a elif o in ("-i", "--incremental_from"): OPTIONS.incremental_source = a elif o == "--full_radio": OPTIONS.full_radio = True elif o == "--full_bootloader": OPTIONS.full_bootloader = True elif o in ("-w", "--wipe_user_data"): OPTIONS.wipe_user_data = True elif o in ("-n", "--no_prereq"): OPTIONS.omit_prereq = True elif o in ("-o", "--oem_settings"): OPTIONS.oem_source = a elif o in ("-e", "--extra_script"): OPTIONS.extra_script = a elif o in ("-a", "--aslr_mode"): if a in ("on", "On", "true", "True", "yes", "Yes"): OPTIONS.aslr_mode = True else: OPTIONS.aslr_mode = False elif o in ("-t", "--worker_threads"): if a.isdigit(): OPTIONS.worker_threads = int(a) else: raise ValueError("Cannot parse value %r for option %r - only " "integers are allowed." % (a, o)) elif o in ("-2", "--two_step"): OPTIONS.two_step = True elif o == "--no_signing": OPTIONS.no_signing = True elif o == "--verify": OPTIONS.verify = True elif o == "--block": OPTIONS.block_based = True elif o in ("-b", "--binary"): OPTIONS.updater_binary = a elif o in ("--no_fallback_to_full",): OPTIONS.fallback_to_full = False elif o == "--stash_threshold": try: OPTIONS.stash_threshold = float(a) except ValueError: raise ValueError("Cannot parse value %r for option %r - expecting " "a float" % (a, o)) else: return False return True args = common.ParseOptions(argv, __doc__, ------------------------------------(2) extra_opts="b:k:i:d:wne:t:a:2o:", extra_long_opts=[ "board_config=", "package_key=", "incremental_from=", "full_radio", "full_bootloader", "wipe_user_data", "no_prereq", "extra_script=", "worker_threads=", "aslr_mode=", "two_step", "no_signing", "block", "binary=", "oem_settings=", "verify", "no_fallback_to_full", "stash_threshold=", ], extra_option_handler=option_handler) if len(args) != 2: common.Usage(__doc__) sys.exit(1) if OPTIONS.extra_script is not None: --------------------(3) OPTIONS.extra_script = open(OPTIONS.extra_script).read() print "unzipping target target-files..." OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) ---------------------(4) OPTIONS.target_tmp = OPTIONS.input_tmp OPTIONS.info_dict = common.LoadInfoDict(input_zip) ----------------------(5) # If this image was originally labelled with SELinux contexts, make sure we # also apply the labels in our new image. During building, the "file_contexts" # is in the out/ directory tree, but for repacking from target-files.zip it's # in the root directory of the ramdisk. if "selinux_fc" in OPTIONS.info_dict: OPTIONS.info_dict["selinux_fc"] = os.path.join( OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts") if OPTIONS.verbose: print "--- target info ---" common.DumpInfoDict(OPTIONS.info_dict) # If the caller explicitly specified the device-specific extensions # path via -s/--device_specific, use that. Otherwise, use # META/releasetools.py if it is present in the target target_files. # Otherwise, take the path of the file from 'tool_extensions' in the # info dict and look for that in the local filesystem, relative to # the current directory. if OPTIONS.device_specific is None: -------------------------(6) from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") if os.path.exists(from_input): print "(using device-specific extensions from target_files)" OPTIONS.device_specific = from_input else: OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) if OPTIONS.device_specific is not None: OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) while True: if OPTIONS.no_signing: ---------------------------(7) if os.path.exists(args[1]): os.unlink(args[1]) output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED) else: temp_zip_file = tempfile.NamedTemporaryFile() output_zip = zipfile.ZipFile(temp_zip_file, "w", compression=zipfile.ZIP_DEFLATED) cache_size = OPTIONS.info_dict.get("cache_size", None) if cache_size is None: -------------------------------(8) raise RuntimeError("can't determine the cache partition size") OPTIONS.cache_size = cache_size if OPTIONS.incremental_source is None: -------------------------------(9) WriteFullOTAPackage(input_zip, output_zip) -------------------------------(10) if OPTIONS.package_key is None: OPTIONS.package_key = OPTIONS.info_dict.get( "default_system_dev_certificate", "build/target/product/security/testkey") common.ZipClose(output_zip) break else: print "unzipping source target-files..." OPTIONS.source_tmp, source_zip = common.UnzipTemp( OPTIONS.incremental_source) OPTIONS.target_info_dict = OPTIONS.info_dict OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) if "selinux_fc" in OPTIONS.source_info_dict: OPTIONS.source_info_dict["selinux_fc"] = os.path.join( OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts") if OPTIONS.package_key is None: OPTIONS.package_key = OPTIONS.source_info_dict.get( "default_system_dev_certificate", "build/target/product/security/testkey") if OPTIONS.verbose: print "--- source info ---" common.DumpInfoDict(OPTIONS.source_info_dict) try: WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) common.ZipClose(output_zip) break except ValueError: if not OPTIONS.fallback_to_full: raise print "--- failed to build incremental; falling back to full ---" OPTIONS.incremental_source = None common.ZipClose(output_zip) if not OPTIONS.no_signing: SignOutput(temp_zip_file.name, args[1]) ---------------------(11) temp_zip_file.close() print "done."
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
(1)首先定义一个命令参数处理的回调函数option_handler,这个回调方法把参数对应的值存储在一个全局的OPTIONS变量中,后续的编译通过判断OPTIONS变量执行不同的流程。
(2)调用common.ParseOptions方法()位于commom.py文件中,该方法的功能就是处理一些自己能处理的参数(如-h),自己不能处理的参数就交给option_handler来处理。
(3)当用户使用-e参数指定一个文件时,最终会在升级包脚本的适当位置添加指定文件的内容。这为一些私有的命令插入updater-script提供了方便。
(4)OPTIONS.input_tmp是target_file.zip解压后的临时目录;input_zip是target_file的句柄,后面可以通过这个句柄来操作target_file.zip。可以参看common.py的UnzipTemp方法。
(5)根据target_file.zip中的META/misc_info.txt文件初始化字典,这个要重点分析一下,因为这个字典里面存放着一些重要的信息。
首先我们看看misc_info.txt的内容:
再来看看具体处理misc_info.txt的流程:
def LoadInfoDict(input_file): ---这里的input_file就是target_file.zip """Read and parse the META/misc_info.txt key/value pairs from the input target files and return a dict.""" ---由上面的注释可以知道,LoadInfoDict方法的功能是解析target_file.zip中的META/misc_info.txt文件,并以key:value的形式存放在OPTIONS.info_dict字典中 def read_helper(fn): if isinstance(input_file, zipfile.ZipFile): return input_file.read(fn) ---input_file就是zipfile.ZipFile对象,因此走if分支 else: path = os.path.join(input_file, *fn.split("/")) try: with open(path) as f: return f.read() except IOError as e: if e.errno == errno.ENOENT: raise KeyError(fn) d = {} try: d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("n")) ---处理misc_info.txt文件,下面会分析LoadDictionaryFromLines方法 except KeyError: # ok if misc_info.txt doesn't exist pass # backwards compatibility: These values used to be in their own # files. Look for them, in case we're processing an old # target_files zip. if "mkyaffs2_extra_flags" not in d: try: d["mkyaffs2_extra_flags"] = read_helper( "META/mkyaffs2-extra-flags.txt").strip() except KeyError: # ok if flags don't exist pass ----下面几个if语句就是判断misc_info.txt文件的正确性了 if "recovery_api_version" not in d: try: d["recovery_api_version"] = read_helper( "META/recovery-api-version.txt").strip() except KeyError: raise ValueError("can't find recovery API version in input target-files") if "tool_extensions" not in d: try: d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip() except KeyError: # ok if extensions don't exist pass if "fstab_version" not in d: d["fstab_version"] = "1" try: ---我的项目中没有这个文件,不分析了 data = read_helper("META/imagesizes.txt") for line in data.split("n"): if not line: continue name, value = line.split(" ", 1) if not value: continue if name == "blocksize": d[name] = value else: d[name + "_size"] = value except KeyError: pass
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364651234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
最终OPTONS.info_dict={recovery_api_version:3, fstab_version:2, blocksize:4096, recovery_size:33554432, custom_size:536870912
recovery_mount_options:ext4=max_batch_time=0,commit=1,data=ordered,barrier=1,errors=panic,nodelalloc
tool_extensions:device/*/m96/../common,
default_system_dev_certificate:build/target/product/security/testkey,
…
}
至于OPTONS.info_dict的用处,后面用到了再来分析,总之,很多时候我们编译升级包出错都是因为OPTONS.info_dict的内容不合法导致的。
(6)这里应该是指定一个OEM厂商自己定制的一个Python脚本(可以通过-s参数传入),完成一些厂商自己定制的一些功能。我当前的项目没有指定-s参数,根据代码分析会使用OPTONS.info_dict中的tool_extensions:device/*/m96/../common,这显然不是一个Python脚本,如果后面使用到的话会报错的,然而我当前没有使用,这里就不去深究了。
(7)可以通过–no_signing参数指定不对升级包签名,当然一般厂商是不会这么做的。
(8)cache_size就是misc_info.txt中的“cache_size=536870912”这个字段,如果misc_info.txt文件中没有这个字段的话就会终止编译。顺便说一下cache_size的值是在BoardConfig.mk中设置的:
BOARD_CACHEIMAGE_PARTITION_SIZE := 536870912
(9)通过-i参数可以使OPTIONS.incremental_source=True,由于本文讲述的是全量升级包的编译,所有这里只讲述if分支的流程。
(10)全量升级包的生成由这个方法完成,前面的代码都是浮云。下面就来仔细看看WriteFullOTAPackage方法
def WriteFullOTAPackage(input_zip, output_zip): # TODO: how to determine this? We don't know what version it will # be installed on top of. For now, we expect the API just won't # change very often. Similarly for fstab, it might have changed # in the target build. script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) --------------------(a) oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")--------------------(b) oem_dict = None if oem_props is not None and len(oem_props) > 0: if OPTIONS.oem_source is None: raise common.ExternalError("OEM source required for this build") script.Mount("/oem", recovery_mount_options) oem_dict = common.LoadDictionaryFromLines( open(OPTIONS.oem_source).readlines()) metadata = { "post-build": CalculateFingerprint(oem_props, oem_dict, OPTIONS.info_dict), "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, OPTIONS.info_dict), "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), } device_specific = common.DeviceSpecificParams( input_zip=input_zip, input_version=OPTIONS.info_dict["recovery_api_version"], output_zip=output_zip, script=script, input_tmp=OPTIONS.input_tmp, metadata=metadata, info_dict=OPTIONS.info_dict) has_recovery_patch = HasRecoveryPatch(input_zip) ---------------------------(c) block_based = OPTIONS.block_based and has_recovery_patch ---------------------------(d) if not OPTIONS.omit_prereq: ---------------------------(e) ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) script.AssertOlderBuild(ts, ts_text) AppendAssertions(script, OPTIONS.info_dict, oem_dict) device_specific.FullOTA_Assertions() # delete /cache/backup script.AppendExtra('delete_recursive("/cache/backup");') kernel_img = common.GetOtherImage("boot.img", "boot.img", ------------------------------(f) OPTIONS.input_tmp, "OTHER") ldfw_img = common.GetOtherImage("ldfw", "ldfw", OPTIONS.input_tmp, "OTHER") if OPTIONS.two_step: -----------------------------(g) if not OPTIONS.info_dict.get("multistage_support", None): assert False, "two-step packages not supported by this build" fs = OPTIONS.info_dict["fstab"]["/misc"] assert fs.fs_type.upper() == "EMMC", "two-step packages only supported on devices with EMMC /misc partitions" bcb_dev = {"bcb_dev": fs.device} common.CheckSize(recovery_img.data, "recovery", OPTIONS.info_dict) common.ZipWriteStr(output_zip, "recovery", recovery_img.data) script.AppendExtra(""" if get_stage("%(bcb_dev)s") == "2/3" then """ % bcb_dev) script.WriteRawImage("/recovery", "recovery") script.WriteRawImage("/ldfw", "ldfw") script.AppendExtra(""" set_stage("%(bcb_dev)s", "3/3"); reboot_now("%(bcb_dev)s", "recovery"); else if get_stage("%(bcb_dev)s") == "3/3" then """ % bcb_dev) # Dump fingerprints script.Print("Target: %s" % CalculateFingerprint( oem_props, oem_dict, OPTIONS.info_dict)) device_specific.FullOTA_InstallBegin() system_progress = 0.95 if OPTIONS.wipe_user_data: system_progress -= 0.1 if HasVendorPartition(input_zip): system_progress -= 0.1 if "selinux_fc" in OPTIONS.info_dict: WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options") system_items = ItemSet("system", "META/filesystem_config.txt") script.ShowProgress(system_progress, 100) if block_based: ----------------------(h) # Full OTA is done as an "incremental" against an empty source # image. This has the effect of writing new data from the package # to the entire partition, but lets us reuse the updater code that # writes incrementals to do it. system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict) system_tgt.ResetFileMap() system_diff = common.BlockDifference("system", system_tgt, src=None) system_diff.WriteScript(script, output_zip) else: script.FormatPartition("/system") script.Mount("/system", recovery_mount_options) if not has_recovery_patch: script.UnpackPackageDir("recovery", "/system") script.UnpackPackageDir("system", "/system") symlinks = CopyPartitionFiles(system_items, input_zip, output_zip) script.MakeSymlinks(symlinks) boot_img = common.GetOtherImage("boot.img", "boot.img", OPTIONS.input_tmp, "OTHER") if not block_based: def output_sink(fn, data): common.ZipWriteStr(output_zip, "recovery/" + fn, data) system_items.Get("system/" + fn) common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, boot_img) system_items.GetMetadata(input_zip) system_items.Get("system").SetPermissions(script) if HasVendorPartition(input_zip): vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") script.ShowProgress(0.1, 0) if block_based: vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict) vendor_tgt.ResetFileMap() vendor_diff = common.BlockDifference("vendor", vendor_tgt) vendor_diff.WriteScript(script, output_zip) else: script.FormatPartition("/vendor") script.Mount("/vendor", recovery_mount_options) script.UnpackPackageDir("vendor", "/vendor") symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip) script.MakeSymlin