Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49ab1b90 authored by Hongguang Chen's avatar Hongguang Chen
Browse files

OTA: Support A/B devices custom images update.

Add a new custom_image option to configure which custom images to use to
update custom partitions in A/B update.

This change also moves oem_settings to common option as A/B update will
use it to set oem properties too.

BUG: 171225290
Test: unittest pass, generate OTAs, flash to devices and check results
Change-Id: I279477d6b2954fb3705d7efede0a8bcd330c108b
parent f6923467
Loading
Loading
Loading
Loading
+59 −8
Original line number Diff line number Diff line
@@ -85,6 +85,13 @@ Common options that apply to both of non-A/B and A/B OTAs
      If not set, generates A/B package for A/B device and non-A/B package for
      non-A/B device.

  -o  (--oem_settings) <main_file[,additional_files...]>
      Comma separated list of files used to specify the expected OEM-specific
      properties on the OEM partition of the intended device. Multiple expected
      values can be used by providing multiple files. Only the first dict will
      be used to compute fingerprint, while the rest will be used to assert
      OEM-specific properties.

Non-A/B OTA specific options

  -b  (--binary) <file>
@@ -114,13 +121,6 @@ Non-A/B OTA specific options
      builds for an incremental package. This option is only meaningful when -i
      is specified.

  -o  (--oem_settings) <main_file[,additional_files...]>
      Comma seperated list of files used to specify the expected OEM-specific
      properties on the OEM partition of the intended device. Multiple expected
      values can be used by providing multiple files. Only the first dict will
      be used to compute fingerprint, while the rest will be used to assert
      OEM-specific properties.

  --oem_no_mount
      For devices with OEM-specific properties but without an OEM partition, do
      not mount the OEM partition in the updater-script. This should be very
@@ -206,6 +206,11 @@ A/B OTA specific options
  --partial "<PARTITION> [<PARTITION>[...]]"
      Generate partial updates, overriding ab_partitions list with the given
      list.

  --custom_image <custom_partition=custom_image>
      Use the specified custom_image to update custom_partition when generating
      an A/B OTA package. e.g. "--custom_image oem=oem.img --custom_image
      cus=cus_test.img"
"""

from __future__ import print_function
@@ -262,7 +267,7 @@ OPTIONS.skip_postinstall = False
OPTIONS.skip_compatibility_check = False
OPTIONS.disable_fec_computation = False
OPTIONS.partial = None

OPTIONS.custom_images = {}

POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
@@ -901,6 +906,43 @@ def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,

  return target_file

def GetTargetFilesZipForCustomImagesUpdates(input_file, custom_images):
  """Returns a target-files.zip for custom partitions update.

  This function modifies ab_partitions list with the desired custom partitions
  and puts the custom images into the target target-files.zip.

  Args:
    input_file: The input target-files.zip filename.
    custom_images: A map of custom partitions and custom images.

  Returns:
    The filename of a target-files.zip which has renamed the custom images in
    the IMAGS/ to their partition names.
  """
  # Use zip2zip to avoid extracting the zipfile.
  target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
  cmd = ['zip2zip', '-i', input_file, '-o', target_file]

  with zipfile.ZipFile(input_file, allowZip64=True) as input_zip:
    namelist = input_zip.namelist()

  # Write {custom_image}.img as {custom_partition}.img.
  for custom_partition, custom_image in custom_images.items():
    default_custom_image = '{}.img'.format(custom_partition)
    if default_custom_image != custom_image:
      logger.info("Update custom partition '%s' with '%s'",
                  custom_partition, custom_image)
      # Default custom image need to be deleted first.
      namelist.remove('IMAGES/{}'.format(default_custom_image))
      # IMAGES/{custom_image}.img:IMAGES/{custom_partition}.img.
      cmd.extend(['IMAGES/{}:IMAGES/{}'.format(custom_image,
                                               default_custom_image)])

  cmd.extend(['{}:{}'.format(name, name) for name in namelist])
  common.RunAndCheckOutput(cmd)

  return target_file

def GenerateAbOtaPackage(target_file, output_file, source_file=None):
  """Generates an Android OTA package that has A/B update payload."""
@@ -927,6 +969,11 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None):

  additional_args = []

  # Prepare custom images.
  if OPTIONS.custom_images:
    target_file = GetTargetFilesZipForCustomImagesUpdates(
        target_file, OPTIONS.custom_images)

  if OPTIONS.retrofit_dynamic_partitions:
    target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
        target_file, target_info.get("super_block_devices").strip().split(),
@@ -1105,6 +1152,9 @@ def main(argv):
      if not partitions:
        raise ValueError("Cannot parse partitions in {}".format(a))
      OPTIONS.partial = partitions
    elif o == "--custom_image":
      custom_partition, custom_image = a.split("=")
      OPTIONS.custom_images[custom_partition] = custom_image
    else:
      return False
    return True
@@ -1144,6 +1194,7 @@ def main(argv):
                                 "force_non_ab",
                                 "boot_variable_file=",
                                 "partial=",
                                 "custom_image=",
                             ], extra_option_handler=option_handler)

  if len(args) != 2:
+41 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ from ota_utils import (
    FinalizeMetadata, GetPackageMetadata, PropertyFiles)
from ota_from_target_files import (
    _LoadOemDicts, AbOtaPropertyFiles,
    GetTargetFilesZipForCustomImagesUpdates,
    GetTargetFilesZipForPartialUpdates,
    GetTargetFilesZipForSecondaryImages,
    GetTargetFilesZipWithoutPostinstallConfig,
@@ -545,6 +546,46 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase):
    with zipfile.ZipFile(target_file) as verify_zip:
      self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())

  @test_utils.SkipIfExternalToolsUnavailable()
  def test_GetTargetFilesZipForCustomImagesUpdates_oemDefaultImage(self):
    input_file = construct_target_files()
    with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip:
      common.ZipWriteStr(append_zip, 'IMAGES/oem.img', 'oem')
      common.ZipWriteStr(append_zip, 'IMAGES/oem_test.img', 'oem_test')

    target_file = GetTargetFilesZipForCustomImagesUpdates(
        input_file, {'oem': 'oem.img'})

    with zipfile.ZipFile(target_file) as verify_zip:
      namelist = verify_zip.namelist()
      ab_partitions = verify_zip.read('META/ab_partitions.txt').decode()
      oem_image = verify_zip.read('IMAGES/oem.img').decode()

    self.assertIn('META/ab_partitions.txt', namelist)
    self.assertEqual('boot\nsystem\nvendor\nbootloader\nmodem', ab_partitions)
    self.assertIn('IMAGES/oem.img', namelist)
    self.assertEqual('oem', oem_image)

  @test_utils.SkipIfExternalToolsUnavailable()
  def test_GetTargetFilesZipForCustomImagesUpdates_oemTestImage(self):
    input_file = construct_target_files()
    with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip:
      common.ZipWriteStr(append_zip, 'IMAGES/oem.img', 'oem')
      common.ZipWriteStr(append_zip, 'IMAGES/oem_test.img', 'oem_test')

    target_file = GetTargetFilesZipForCustomImagesUpdates(
        input_file, {'oem': 'oem_test.img'})

    with zipfile.ZipFile(target_file) as verify_zip:
      namelist = verify_zip.namelist()
      ab_partitions = verify_zip.read('META/ab_partitions.txt').decode()
      oem_image = verify_zip.read('IMAGES/oem.img').decode()

    self.assertIn('META/ab_partitions.txt', namelist)
    self.assertEqual('boot\nsystem\nvendor\nbootloader\nmodem', ab_partitions)
    self.assertIn('IMAGES/oem.img', namelist)
    self.assertEqual('oem_test', oem_image)

  def _test_FinalizeMetadata(self, large_entry=False):
    entries = [
        'required-entry1',