Merge "OTA: Support A/B devices custom images update."
This commit is contained in:
@@ -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:
|
||||
|
@@ -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',
|
||||
|
Reference in New Issue
Block a user