Add an option to input the boot variables for OTA package generation

The values of the ro.boot* variables are not part of the image files
and are provided (e.g. by bootloaders) at runtime. Meanwhile, their
values may affect some of the device build properties, as a different
build.prop file can be imported by init during runtime.

This cl adds an option to accepts a list of possible values for some
boot variables. The OTA generation script later use these values to
calculate the alternative runtime fingerprints of the device; and
list the device names and fingerprints in the OTA package's metadata.

The OTA metadata is verified by the OTA server or recovery to ensure
the correct OTA package is used for update. We haven't made any
restrictions on what ro.boot* variables can be used for fingerprint
override. One possible candidate can be the skus listed in
ODM_MANIFEST_SKUS.

Bug: 152167826
Test: unittests pass, generate an OTA file with the new option
Change-Id: I637dea3472354236d2fd1ef0a3306712b3283c29
This commit is contained in:
Tianjie 2020-05-10 14:30:13 -07:00
parent ed67178e4a
commit d6867167d1
2 changed files with 179 additions and 41 deletions

View file

@ -189,6 +189,13 @@ A/B OTA specific options
--payload_signer_key_size <key_size>
Deprecated. Use the '--payload_signer_maximum_signature_size' instead.
--boot_variable_file <path>
A file that contains the possible values of ro.boot.* properties. It's
used to calculate the possible runtime fingerprints when some
ro.product.* properties are overridden by the 'import' statement.
The file expects one property per line, and each line has the following
format: 'prop_name=value1,value2'. e.g. 'ro.boot.product.sku=std,pro'
--skip_postinstall
Skip the postinstall hooks when generating an A/B OTA package (default:
False). Note that this discards ALL the hooks, including non-optional
@ -257,8 +264,8 @@ OPTIONS.retrofit_dynamic_partitions = False
OPTIONS.skip_compatibility_check = False
OPTIONS.output_metadata_path = None
OPTIONS.disable_fec_computation = False
OPTIONS.boot_variable_values = None
OPTIONS.force_non_ab = False
OPTIONS.boot_variable_file = None
METADATA_NAME = 'META-INF/com/android/metadata'
@ -931,13 +938,23 @@ def GetPackageMetadata(target_info, source_info=None):
assert isinstance(target_info, common.BuildInfo)
assert source_info is None or isinstance(source_info, common.BuildInfo)
separator = '|'
boot_variable_values = {}
if OPTIONS.boot_variable_file:
d = common.LoadDictionaryFromFile(OPTIONS.boot_variable_file)
for key, values in d.items():
boot_variable_values[key] = [val.strip() for val in values.split(',')]
post_build_devices, post_build_fingerprints = \
CalculateRuntimeDevicesAndFingerprints(target_info, boot_variable_values)
metadata = {
'post-build' : target_info.fingerprint,
'post-build-incremental' : target_info.GetBuildProp(
'post-build': separator.join(sorted(post_build_fingerprints)),
'post-build-incremental': target_info.GetBuildProp(
'ro.build.version.incremental'),
'post-sdk-level' : target_info.GetBuildProp(
'post-sdk-level': target_info.GetBuildProp(
'ro.build.version.sdk'),
'post-security-patch-level' : target_info.GetBuildProp(
'post-security-patch-level': target_info.GetBuildProp(
'ro.build.version.security_patch'),
}
@ -955,12 +972,15 @@ def GetPackageMetadata(target_info, source_info=None):
is_incremental = source_info is not None
if is_incremental:
metadata['pre-build'] = source_info.fingerprint
pre_build_devices, pre_build_fingerprints = \
CalculateRuntimeDevicesAndFingerprints(source_info,
boot_variable_values)
metadata['pre-build'] = separator.join(sorted(pre_build_fingerprints))
metadata['pre-build-incremental'] = source_info.GetBuildProp(
'ro.build.version.incremental')
metadata['pre-device'] = source_info.device
metadata['pre-device'] = separator.join(sorted(pre_build_devices))
else:
metadata['pre-device'] = target_info.device
metadata['pre-device'] = separator.join(sorted(post_build_devices))
# Use the actual post-timestamp, even for a downgrade case.
metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
@ -1972,24 +1992,24 @@ def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
output_file)
def CalculateRuntimeFingerprints():
"""Returns a set of runtime fingerprints based on the boot variables."""
def CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values):
"""Returns a tuple of sets for runtime devices and fingerprints"""
build_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
device_names = {build_info.device}
fingerprints = {build_info.fingerprint}
if not OPTIONS.boot_variable_values:
return fingerprints
if not boot_variable_values:
return device_names, fingerprints
# Calculate all possible combinations of the values for the boot variables.
keys = OPTIONS.boot_variable_values.keys()
value_list = OPTIONS.boot_variable_values.values()
keys = boot_variable_values.keys()
value_list = boot_variable_values.values()
combinations = [dict(zip(keys, values))
for values in itertools.product(*value_list)]
for placeholder_values in combinations:
# Reload the info_dict as some build properties may change their values
# based on the value of ro.boot* properties.
info_dict = copy.deepcopy(OPTIONS.info_dict)
info_dict = copy.deepcopy(build_info.info_dict)
for partition in common.PARTITIONS_WITH_CARE_MAP:
partition_prop_key = "{}.build.prop".format(partition)
old_props = info_dict[partition_prop_key]
@ -1997,9 +2017,10 @@ def CalculateRuntimeFingerprints():
old_props.input_file, partition, placeholder_values)
info_dict["build.prop"] = info_dict["system.build.prop"]
build_info = common.BuildInfo(info_dict, OPTIONS.oem_dicts)
fingerprints.add(build_info.fingerprint)
return fingerprints
new_build_info = common.BuildInfo(info_dict, build_info.oem_dicts)
device_names.add(new_build_info.device)
fingerprints.add(new_build_info.fingerprint)
return device_names, fingerprints
def main(argv):
@ -2077,6 +2098,8 @@ def main(argv):
OPTIONS.disable_fec_computation = True
elif o == "--force_non_ab":
OPTIONS.force_non_ab = True
elif o == "--boot_variable_file":
OPTIONS.boot_variable_file = a
else:
return False
return True
@ -2114,6 +2137,7 @@ def main(argv):
"output_metadata_path=",
"disable_fec_computation",
"force_non_ab",
"boot_variable_file=",
], extra_option_handler=option_handler)
if len(args) != 2:

View file

@ -27,7 +27,7 @@ from ota_from_target_files import (
GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles,
Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
StreamingPropertyFiles, WriteFingerprintAssertion,
CalculateRuntimeFingerprints)
CalculateRuntimeDevicesAndFingerprints)
def construct_target_files(secondary=False):
@ -1334,6 +1334,9 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
'ro.build.version.incremental=version-incremental',
'ro.build.type=build-type',
'ro.build.tags=build-tags',
'ro.build.version.sdk=30',
'ro.build.version.security_patch=2020',
'ro.build.date.utc=12345678'
]
VENDOR_BUILD_PROP = [
@ -1345,11 +1348,12 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
def setUp(self):
common.OPTIONS.oem_dicts = None
self.test_dir = common.MakeTempDir()
self.writeFiles({'META/misc_info.txt': '\n'.join(self.MISC_INFO)})
self.writeFiles({'META/misc_info.txt': '\n'.join(self.MISC_INFO)},
self.test_dir)
def writeFiles(self, contents_dict):
def writeFiles(self, contents_dict, out_dir):
for path, content in contents_dict.items():
abs_path = os.path.join(self.test_dir, path)
abs_path = os.path.join(out_dir, path)
dir_name = os.path.dirname(abs_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
@ -1371,12 +1375,14 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
self.writeFiles({
'SYSTEM/build.prop': '\n'.join(build_prop),
'VENDOR/build.prop': '\n'.join(self.VENDOR_BUILD_PROP),
})
common.OPTIONS.info_dict = common.LoadInfoDict(self.test_dir)
}, self.test_dir)
self.assertEqual({
self.constructFingerprint('product-brand/product-name/product-device')
}, CalculateRuntimeFingerprints())
build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir))
expected = ({'product-device'},
{self.constructFingerprint(
'product-brand/product-name/product-device')})
self.assertEqual(expected,
CalculateRuntimeDevicesAndFingerprints(build_info, {}))
def test_CalculatePossibleFingerprints_single_override(self):
vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP)
@ -1390,20 +1396,22 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
'ro.product.vendor.name=vendor-product-std',
'VENDOR/etc/build_pro.prop':
'ro.product.vendor.name=vendor-product-pro',
})
common.OPTIONS.info_dict = common.LoadInfoDict(self.test_dir)
common.OPTIONS.boot_variable_values = {
'ro.boot.sku_name': ['std', 'pro']
}
}, self.test_dir)
self.assertEqual({
build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir))
boot_variable_values = {'ro.boot.sku_name': ['std', 'pro']}
expected = ({'vendor-product-device'}, {
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-product-device'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-std/vendor-product-device'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-pro/vendor-product-device'),
}, CalculateRuntimeFingerprints())
})
self.assertEqual(
expected, CalculateRuntimeDevicesAndFingerprints(
build_info, boot_variable_values))
def test_CalculatePossibleFingerprints_multiple_overrides(self):
vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP)
@ -1422,14 +1430,17 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
'ro.product.vendor.name=vendor-product-pro',
'VENDOR/etc/build_product2.prop':
'ro.product.vendor.device=vendor-device-product2',
})
common.OPTIONS.info_dict = common.LoadInfoDict(self.test_dir)
common.OPTIONS.boot_variable_values = {
}, self.test_dir)
build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir))
boot_variable_values = {
'ro.boot.sku_name': ['std', 'pro'],
'ro.boot.device_name': ['product1', 'product2'],
}
self.assertEqual({
expected_devices = {'vendor-product-device', 'vendor-device-product1',
'vendor-device-product2'}
expected_fingerprints = {
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-product-device'),
self.constructFingerprint(
@ -1439,5 +1450,108 @@ class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase):
self.constructFingerprint(
'vendor-product-brand/vendor-product-std/vendor-device-product2'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-pro/vendor-device-product2'),
}, CalculateRuntimeFingerprints())
'vendor-product-brand/vendor-product-pro/vendor-device-product2')
}
self.assertEqual((expected_devices, expected_fingerprints),
CalculateRuntimeDevicesAndFingerprints(
build_info, boot_variable_values))
def test_GetPackageMetadata_full_package(self):
vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP)
vendor_build_prop.extend([
'import /vendor/etc/build_${ro.boot.sku_name}.prop',
])
self.writeFiles({
'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP),
'VENDOR/build.prop': '\n'.join(vendor_build_prop),
'VENDOR/etc/build_std.prop':
'ro.product.vendor.name=vendor-product-std',
'VENDOR/etc/build_pro.prop':
'ro.product.vendor.name=vendor-product-pro',
}, self.test_dir)
common.OPTIONS.boot_variable_file = common.MakeTempFile()
with open(common.OPTIONS.boot_variable_file, 'w') as f:
f.write('ro.boot.sku_name=std,pro')
build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir))
metadata = GetPackageMetadata(build_info)
self.assertEqual('vendor-product-device', metadata['pre-device'])
fingerprints = [
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-product-device'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-pro/vendor-product-device'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-std/vendor-product-device'),
]
self.assertEqual('|'.join(fingerprints), metadata['post-build'])
def test_GetPackageMetadata_incremental_package(self):
vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP)
vendor_build_prop.extend([
'import /vendor/etc/build_${ro.boot.sku_name}.prop',
])
self.writeFiles({
'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP),
'VENDOR/build.prop': '\n'.join(vendor_build_prop),
'VENDOR/etc/build_std.prop':
'ro.product.vendor.device=vendor-device-std',
'VENDOR/etc/build_pro.prop':
'ro.product.vendor.device=vendor-device-pro',
}, self.test_dir)
common.OPTIONS.boot_variable_file = common.MakeTempFile()
with open(common.OPTIONS.boot_variable_file, 'w') as f:
f.write('ro.boot.sku_name=std,pro')
source_dir = common.MakeTempDir()
source_build_prop = [
'ro.build.version.release=source-version-release',
'ro.build.id=source-build-id',
'ro.build.version.incremental=source-version-incremental',
'ro.build.type=build-type',
'ro.build.tags=build-tags',
'ro.build.version.sdk=29',
'ro.build.version.security_patch=2020',
'ro.build.date.utc=12340000'
]
self.writeFiles({
'META/misc_info.txt': '\n'.join(self.MISC_INFO),
'SYSTEM/build.prop': '\n'.join(source_build_prop),
'VENDOR/build.prop': '\n'.join(vendor_build_prop),
'VENDOR/etc/build_std.prop':
'ro.product.vendor.device=vendor-device-std',
'VENDOR/etc/build_pro.prop':
'ro.product.vendor.device=vendor-device-pro',
}, source_dir)
common.OPTIONS.incremental_source = source_dir
target_info = common.BuildInfo(common.LoadInfoDict(self.test_dir))
source_info = common.BuildInfo(common.LoadInfoDict(source_dir))
metadata = GetPackageMetadata(target_info, source_info)
self.assertEqual(
'vendor-device-pro|vendor-device-std|vendor-product-device',
metadata['pre-device'])
suffix = ':source-version-release/source-build-id/' \
'source-version-incremental:build-type/build-tags'
pre_fingerprints = [
'vendor-product-brand/vendor-product-name/vendor-device-pro'
'{}'.format(suffix),
'vendor-product-brand/vendor-product-name/vendor-device-std'
'{}'.format(suffix),
'vendor-product-brand/vendor-product-name/vendor-product-device'
'{}'.format(suffix),
]
self.assertEqual('|'.join(pre_fingerprints), metadata['pre-build'])
post_fingerprints = [
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-device-pro'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-device-std'),
self.constructFingerprint(
'vendor-product-brand/vendor-product-name/vendor-product-device'),
]
self.assertEqual('|'.join(post_fingerprints), metadata['post-build'])