android随笔之整包ota命令执行过程分析

提示:本文对android整包ota命令执行过程进行分析



前言

本文基于Android9.0从make命令开始一步步跟踪分析


1. Makefile文件

build/core/Makefile

ifeq ($(build_ota_package),true)
# -----------------------------------------------------------------
# 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)

ifeq ($(AB_OTA_UPDATER),true)
$(INTERNAL_OTA_PACKAGE_TARGET): $(BRILLO_UPDATE_PAYLOAD)
else
$(INTERNAL_OTA_PACKAGE_TARGET): $(BROTLI)
endif

$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) \
        build/make/tools/releasetools/ota_from_target_files
    @echo "Package OTA: $@"
    $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
       build/make/tools/releasetools/ota_from_target_files -v \
       --block \
       --extracted_input_target_files $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE)) \
       -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)

endif    # build_ota_package

这里可以看到目标是otapackage,关键的脚本是ota_from_target_files,这里可以自定义带很多参数。
很多厂家可能会有自己打包的命令,而不用原生的otapackage。但万变不离其宗,都离不开ota_from_target_files

2. ota_from_target_files文件

build/tools/releasetools/ota_from_target_files

if __name__ == '__main__':
  try:
    common.CloseInheritedPipes()
    main(sys.argv[1:])
  except common.ExternalError as e:
    print("\n   ERROR: %s\n" % (e,))
    sys.exit(1)
  finally:
    common.Cleanup()

sys.argv[1:]把ota_from_target_files后面带的所有参数全部传给了main,
那么这里我们就可以配置一些自定义参数,比如–wipe_user_data,ota后会去清除用户数据

def main(argv):

  def option_handler(o, a):
    if 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 == "--wipe_user_data":
      OPTIONS.wipe_user_data = True
    elif o == "--downgrade":
............
    elif o == "--skip_postinstall":
      OPTIONS.skip_postinstall = True
    else:
      return False
    return True

  args = common.ParseOptions(argv, __doc__,
                             extra_opts="b:k:i:d:e:t:2o:",
                             extra_long_opts=[
                                 "package_key=",
                                 "incremental_from=",
                                 "full_radio",
                                 "full_bootloader",
                                 "wipe_user_data",
                                 "downgrade",
                                 "override_timestamp",
                                 "extra_script=",
                                 "worker_threads=",
                                 "two_step",
                                 "include_secondary",
                                 "no_signing",
                                 "block",
                                 "binary=",
                                 "oem_settings=",
                                 "oem_no_mount",
                                 "verify",
                                 "stash_threshold=",
                                 "log_diff=",
                                 "payload_signer=",
                                 "payload_signer_args=",
                                 "extracted_input_target_files=",
                                 "skip_postinstall",
                             ], extra_option_handler=option_handler)

  if len(args) != 2:
    common.Usage(__doc__)
    sys.exit(1)

  if OPTIONS.downgrade:
    # We should only allow downgrading incrementals (as opposed to full).
    # Otherwise the device may go back from arbitrary build with this full
    # OTA package.
............
  # 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:
    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")

  if OPTIONS.device_specific is not None:
    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)

  # Generate a full OTA.
  if OPTIONS.incremental_source is None:
    with zipfile.ZipFile(args[0], 'r') as input_zip:
      WriteFullOTAPackage(
          input_zip,
          output_file=args[1])
......

main里面会根据传进来的不同参数,设置不同的状态
另外这里还调用了build/tools/releasetools/common.py里的ParseOptions去补充设置额外的状态。

def ParseOptions(argv,
                 docstring,
                 extra_opts="", extra_long_opts=(),
                 extra_option_handler=None):
  """Parse the options in argv and return any arguments that aren't
  flags.  docstring is the calling module's docstring, to be displayed
  for errors and -h.  extra_opts and extra_long_opts are for flags
  defined by the caller, which are processed by passing them to
  extra_option_handler."""

  try:
    opts, args = getopt.getopt(
        argv, "hvp:s:x:" + extra_opts,
        ["help", "verbose", "path=", "signapk_path=",
         "signapk_shared_library_path=", "extra_signapk_args=",
         "java_path=", "java_args=", "public_key_suffix=",
         "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
         "verity_signer_path=", "verity_signer_args=", "device_specific=",
         "extra="] +
        list(extra_long_opts))
  except getopt.GetoptError as err:
    Usage(docstring)
    print("**", str(err), "**")
    sys.exit(2)

  for o, a in opts:
    if o in ("-h", "--help"):
      Usage(docstring)
      sys.exit()
    elif o in ("-v", "--verbose"):
      OPTIONS.verbose = True
    elif o in ("-p", "--path"):
      OPTIONS.search_path = a
    elif o in ("--signapk_path",):
      OPTIONS.signapk_path = a
    elif o in ("--signapk_shared_library_path",):
      OPTIONS.signapk_shared_library_path = a
    elif o in ("--extra_signapk_args",):
.........

回到ota_from_target_files再仔细分析main里的内容,发现这里有个OPTIONS.device_specific,会去读取一个文件的绝对路径。

另外还有个WriteFullOTAPackage调用,这个就是生成OTA整包的地方。

分析WriteFullOTAPackage内容,不难发现device_specific很关键。

Created with Raphaël 2.3.0 device_specific = common.DeviceSpecificParams device_specific.FullOTA_Assertions device_specific.FullOTA_InstallBegin device_specific.FullOTA_InstallEnd

那么这里又是怎么调用的呢?

3. common.py文件

我们接着分析上面的流程图,首先是DeviceSpecificParams,
它是在build/tools/releasetools/common.py

class DeviceSpecificParams(object):
  module = None
  def __init__(self, **kwargs):
    """Keyword arguments to the constructor become attributes of this
    object, which is passed to all functions in the device-specific
    module."""
    for k, v in kwargs.iteritems():
      setattr(self, k, v)
    self.extras = OPTIONS.extras

    if self.module is None:
      path = OPTIONS.device_specific
      if not path:
        return
      try:
        if os.path.isdir(path):
          info = imp.find_module("releasetools", [path])
        else:
          d, f = os.path.split(path)
          b, x = os.path.splitext(f)
          if x == ".py":
            f = b
          info = imp.find_module(f, [d])
        print("loaded device-specific extensions from", path)
        self.module = imp.load_module("device_specific", *info)
      except ImportError:
        print("unable to load device-specific module; assuming none")
  def _DoCall(self, function_name, *args, **kwargs):
    """Call the named function in the device-specific module, passing
    the given args and kwargs.  The first argument to the call will be
    the DeviceSpecific object itself.  If there is no module, or the
    module does not define the function, return the value of the
    'default' kwarg (which itself defaults to None)."""
    if self.module is None or not hasattr(self.module, function_name):
      return kwargs.get("default", None)
    return getattr(self.module, function_name)(*((self,) + args), **kwargs)

这里会把OPTIONS.device_specific里的module全部记录在self.module里
然后device_specific.xxxxx调用common.py对应的函数
比如

  def FullOTA_InstallBegin(self):
    """Called at the start of full OTA installation."""
    return self._DoCall("FullOTA_InstallBegin")

实际是调用自身_DoCall

  def _DoCall(self, function_name, *args, **kwargs):
    """Call the named function in the device-specific module, passing
    the given args and kwargs.  The first argument to the call will be
    the DeviceSpecific object itself.  If there is no module, or the
    module does not define the function, return the value of the
    'default' kwarg (which itself defaults to None)."""
    if self.module is None or not hasattr(self.module, function_name):
      return kwargs.get("default", None)
    return getattr(self.module, function_name)(*((self,) + args), **kwargs)

先判断self.module是否有对应的函数,如果有,则是调用releasetools里的同名函数;如果没有,则不调用。

4. releasetools.py文件

这个文件基本是预留给各大厂商客制化的文件,这里就不详细说明了。
由TARGET_RELEASETOOLS_EXTENSIONS决定使用哪个
里面的东西也比较简单

......
def FullOTA_InstallEnd(info):
    if os.path.exists(OUTPUT_PATH + '/uboot.bin'):
      WriteToOTAPackage(info, "/uboot", OUTPUT_PATH,  "uboot.bin")
    if os.path.exists(OUTPUT_PATH + '/uenv.bin'):
      WriteToOTAPackage(info, "/uboot_env", OUTPUT_PATH,  "uenv.bin")
    if os.path.exists(OUTPUT_PATH + '/logo.jpg'):
      WriteToOTAPackage(info, "/logo", OUTPUT_PATH, "logo.jpg")
    if os.path.exists(OUTPUT_PATH + '/tz.bin.lzhs'):
      WriteToOTAPackage(info, "/tzbp", OUTPUT_PATH, "tz.bin.lzhs")
    if os.path.exists(OUTPUT_PATH + '/AQ.bin'):
      WriteToOTAPackage(info, "/aq", OUTPUT_PATH, "AQ.bin")
    if os.path.exists(OUTPUT_PATH + '/pq.bin'):
      WriteToOTAPackage(info, "/pq", OUTPUT_PATH, "pq.bin")
    if os.path.exists(OUTPUT_PATH + '/rootfs.bin'):
      WriteToOTAPackage(info, "/linux_rootfs", OUTPUT_PATH, "rootfs.bin")
    if os.path.exists(OUTPUT_PATH + '/adsp.bin'):
      WriteToOTAPackage(info, "/adsp", OUTPUT_PATH, "adsp.bin")
def WriteToOTAPackage(info, dev_name, bin_path, bin_name):
  try:
    common.ZipWriteStr(info.output_zip, bin_name,
                       open(bin_path + '/' + bin_name).read())
  except KeyError:
    print ("warning: no "+ bin_name +" in input target_files; ")

  try:
    info.script.WriteRawImage(dev_name, bin_name)
  except KeyError:
    print ("warning: "+ bin_name +" write script failed;")
......

总结

整个流程到这也就差不多结束了。

猜你喜欢

转载自blog.csdn.net/hmz0303hf/article/details/128317773