diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py index 7db506c72..70ea967b3 100755 --- a/tools/releasetools/add_img_to_target_files.py +++ b/tools/releasetools/add_img_to_target_files.py @@ -548,17 +548,19 @@ def AddCareMapForAbOta(output_zip, ab_partitions, image_paths): care_map_list += care_map # adds fingerprint field to the care_map - build_props = OPTIONS.info_dict.get(partition + ".build.prop", {}) + # TODO(xunchang) revisit the fingerprint calculation for care_map. + partition_props = OPTIONS.info_dict.get(partition + ".build.prop") prop_name_list = ["ro.{}.build.fingerprint".format(partition), "ro.{}.build.thumbprint".format(partition)] - present_props = [x for x in prop_name_list if x in build_props] + present_props = [x for x in prop_name_list if + partition_props and partition_props.GetProp(x)] if not present_props: logger.warning("fingerprint is not present for partition %s", partition) property_id, fingerprint = "unknown", "unknown" else: property_id = present_props[0] - fingerprint = build_props[property_id] + fingerprint = partition_props.GetProp(property_id) care_map_list += [property_id, fingerprint] if not care_map_list: diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py index f30f7876a..756734649 100755 --- a/tools/releasetools/build_image.py +++ b/tools/releasetools/build_image.py @@ -509,9 +509,9 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): d = {} if "build.prop" in glob_dict: - bp = glob_dict["build.prop"] - if "ro.build.date.utc" in bp: - d["timestamp"] = bp["ro.build.date.utc"] + timestamp = glob_dict["build.prop"].GetProp("ro.build.date.utc") + if timestamp: + d["timestamp"] = timestamp def copy_prop(src_p, dest_p): """Copy a property from the global dictionary. diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py index b3d491fdf..95d09cc17 100755 --- a/tools/releasetools/check_target_files_vintf.py +++ b/tools/releasetools/check_target_files_vintf.py @@ -80,8 +80,9 @@ def GetArgsForSkus(info_dict): '--property', 'ro.boot.product.vendor.sku=' + vendor_sku] for odm_sku in odm_skus for vendor_sku in vendor_skus] + def GetArgsForShippingApiLevel(info_dict): - shipping_api_level = info_dict['vendor.build.prop'].get( + shipping_api_level = info_dict['vendor.build.prop'].GetProp( 'ro.product.first_api_level') if not shipping_api_level: logger.warning('Cannot determine ro.product.first_api_level') diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 1abf5a5cc..7805e30ce 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -423,6 +423,14 @@ class BuildInfo(object): def items(self): return self.info_dict.items() + def _GetRawBuildProp(self, prop, partition): + prop_file = '{}.build.prop'.format( + partition) if partition else 'build.prop' + partition_props = self.info_dict.get(prop_file) + if not partition_props: + return None + return partition_props.GetProp(prop) + def GetPartitionBuildProp(self, prop, partition): """Returns the inquired build property for the provided partition.""" # If provided a partition for this property, only look within that @@ -431,31 +439,33 @@ class BuildInfo(object): prop = prop.replace("ro.product", "ro.product.{}".format(partition)) else: prop = prop.replace("ro.", "ro.{}.".format(partition)) - try: - return self.info_dict.get("{}.build.prop".format(partition), {})[prop] - except KeyError: - raise ExternalError("couldn't find %s in %s.build.prop" % - (prop, partition)) + + prop_val = self._GetRawBuildProp(prop, partition) + if prop_val is not None: + return prop_val + raise ExternalError("couldn't find %s in %s.build.prop" % + (prop, partition)) def GetBuildProp(self, prop): """Returns the inquired build property from the standard build.prop file.""" if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS: return self._ResolveRoProductBuildProp(prop) - try: - return self.info_dict.get("build.prop", {})[prop] - except KeyError: - raise ExternalError("couldn't find %s in build.prop" % (prop,)) + prop_val = self._GetRawBuildProp(prop, None) + if prop_val is not None: + return prop_val + + raise ExternalError("couldn't find %s in build.prop" % (prop,)) def _ResolveRoProductBuildProp(self, prop): """Resolves the inquired ro.product.* build property""" - prop_val = self.info_dict.get("build.prop", {}).get(prop) + prop_val = self._GetRawBuildProp(prop, None) if prop_val: return prop_val default_source_order = self._GetRoProductPropsDefaultSourceOrder() - source_order_val = self.info_dict.get("build.prop", {}).get( - "ro.product.property_source_order") + source_order_val = self._GetRawBuildProp( + "ro.product.property_source_order", None) if source_order_val: source_order = source_order_val.split(",") else: @@ -466,11 +476,10 @@ class BuildInfo(object): raise ExternalError( "Invalid ro.product.property_source_order '{}'".format(source_order)) - for source in source_order: + for source_partition in source_order: source_prop = prop.replace( - "ro.product", "ro.product.{}".format(source), 1) - prop_val = self.info_dict.get( - "{}.build.prop".format(source), {}).get(source_prop) + "ro.product", "ro.product.{}".format(source_partition), 1) + prop_val = self._GetRawBuildProp(source_prop, source_partition) if prop_val: return prop_val @@ -479,11 +488,9 @@ class BuildInfo(object): def _GetRoProductPropsDefaultSourceOrder(self): # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and # values of these properties for each Android release. - android_codename = self.info_dict.get("build.prop", {}).get( - "ro.build.version.codename") + android_codename = self._GetRawBuildProp("ro.build.version.codename", None) if android_codename == "REL": - android_version = self.info_dict.get("build.prop", {}).get( - "ro.build.version.release") + android_version = self._GetRawBuildProp("ro.build.version.release", None) if android_version == "10": return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 # NOTE: float() conversion of android_version will have rounding error. @@ -566,6 +573,20 @@ class BuildInfo(object): script.AssertOemProperty(prop, values, oem_no_mount) +def ReadFromInputFile(input_file, fn): + """Reads the contents of fn from input zipfile or directory.""" + if isinstance(input_file, zipfile.ZipFile): + return input_file.read(fn).decode() + 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) + + def LoadInfoDict(input_file, repacking=False): """Loads the key/value pairs from the given input target_files. @@ -603,16 +624,7 @@ def LoadInfoDict(input_file, repacking=False): "input_file must be a path str when doing repacking" def read_helper(fn): - if isinstance(input_file, zipfile.ZipFile): - return input_file.read(fn).decode() - 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) + return ReadFromInputFile(input_file, fn) try: d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n")) @@ -675,13 +687,8 @@ def LoadInfoDict(input_file, repacking=False): # system and vendor. for partition in PARTITIONS_WITH_CARE_MAP: partition_prop = "{}.build.prop".format(partition) - d[partition_prop] = LoadBuildProp( - read_helper, "{}/build.prop".format(partition.upper())) - # Some partition might use //etc/build.prop as the new path. - # TODO: try new path first when majority of them switch to the new path. - if not d[partition_prop]: - d[partition_prop] = LoadBuildProp( - read_helper, "{}/etc/build.prop".format(partition.upper())) + d[partition_prop] = PartitionBuildProps.FromInputFile( + input_file, partition) d["build.prop"] = d["system.build.prop"] # Set up the salt (based on fingerprint) that will be used when adding AVB @@ -696,15 +703,6 @@ def LoadInfoDict(input_file, repacking=False): return d -def LoadBuildProp(read_helper, prop_file): - try: - data = read_helper(prop_file) - except KeyError: - logger.warning("Failed to read %s", prop_file) - data = "" - return LoadDictionaryFromLines(data.split("\n")) - - def LoadListFromFile(file_path): with open(file_path) as f: return f.read().splitlines() @@ -727,6 +725,116 @@ def LoadDictionaryFromLines(lines): return d +class PartitionBuildProps(object): + """The class holds the build prop of a particular partition. + + This class loads the build.prop and holds the build properties for a given + partition. It also partially recognizes the 'import' statement in the + build.prop; and calculates alternative values of some specific build + properties during runtime. + + Attributes: + input_file: a zipped target-file or an unzipped target-file directory. + partition: name of the partition. + props_allow_override: a list of build properties to search for the + alternative values during runtime. + build_props: a dict of build properties for the given partition. + prop_overrides: a set of props that are overridden by import. + placeholder_values: A dict of runtime variables' values to replace the + placeholders in the build.prop file. We expect exactly one value for + each of the variables. + """ + def __init__(self, input_file, name, placeholder_values=None): + self.input_file = input_file + self.partition = name + self.props_allow_override = [props.format(name) for props in [ + 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']] + self.build_props = {} + self.prop_overrides = set() + self.placeholder_values = {} + if placeholder_values: + self.placeholder_values = copy.deepcopy(placeholder_values) + + @staticmethod + def FromDictionary(name, build_props): + """Constructs an instance from a build prop dictionary.""" + + props = PartitionBuildProps("unknown", name) + props.build_props = build_props.copy() + return props + + @staticmethod + def FromInputFile(input_file, name, placeholder_values=None): + """Loads the build.prop file and builds the attributes.""" + data = '' + for prop_file in ['{}/etc/build.prop'.format(name.upper()), + '{}/build.prop'.format(name.upper())]: + try: + data = ReadFromInputFile(input_file, prop_file) + break + except KeyError: + logger.warning('Failed to read %s', prop_file) + + props = PartitionBuildProps(input_file, name, placeholder_values) + props._LoadBuildProp(data) + return props + + def _LoadBuildProp(self, data): + for line in data.split('\n'): + line = line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("import"): + overrides = self._ImportParser(line) + duplicates = self.prop_overrides.intersection(overrides.keys()) + if duplicates: + raise ValueError('prop {} is overridden multiple times'.format( + ','.join(duplicates))) + self.prop_overrides = self.prop_overrides.union(overrides.keys()) + self.build_props.update(overrides) + elif "=" in line: + name, value = line.split("=", 1) + if name in self.prop_overrides: + raise ValueError('prop {} is set again after overridden by import ' + 'statement'.format(name)) + self.build_props[name] = value + + def _ImportParser(self, line): + """Parses the build prop in a given import statement.""" + + tokens = line.split() + if len(tokens) != 2 or tokens[0] != 'import': + raise ValueError('Unrecognized import statement {}'.format(line)) + import_path = tokens[1] + if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path): + raise ValueError('Unrecognized import path {}'.format(line)) + + # We only recognize a subset of import statement that the init process + # supports. And we can loose the restriction based on how the dynamic + # fingerprint is used in practice. The placeholder format should be + # ${placeholder}, and its value should be provided by the caller through + # the placeholder_values. + for prop, value in self.placeholder_values.items(): + prop_place_holder = '${{{}}}'.format(prop) + if prop_place_holder in import_path: + import_path = import_path.replace(prop_place_holder, value) + if '$' in import_path: + logger.info('Unresolved place holder in import path %s', import_path) + return {} + + import_path = import_path.replace('/{}'.format(self.partition), + self.partition.upper()) + logger.info('Parsing build props override from %s', import_path) + + lines = ReadFromInputFile(self.input_file, import_path).split('\n') + d = LoadDictionaryFromLines(lines) + return {key: val for key, val in d.items() + if key in self.props_allow_override} + + def GetProp(self, prop): + return self.build_props.get(prop) + + def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path, system_root_image=False): class Partition(object): diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index 92a46a20c..47ad3d8a1 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -193,6 +193,8 @@ A/B OTA specific options from __future__ import print_function import collections +import copy +import itertools import logging import multiprocessing import os.path @@ -229,6 +231,7 @@ OPTIONS.include_secondary = False OPTIONS.no_signing = False OPTIONS.block_based = True OPTIONS.updater_binary = None +OPTIONS.oem_dicts = None OPTIONS.oem_source = None OPTIONS.oem_no_mount = False OPTIONS.full_radio = False @@ -247,6 +250,7 @@ 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 METADATA_NAME = 'META-INF/com/android/metadata' @@ -1959,6 +1963,36 @@ def GenerateNonAbOtaPackage(target_file, output_file, source_file=None): output_file) +def CalculateRuntimeFingerprints(): + """Returns a set of runtime fingerprints based on the boot variables.""" + + build_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) + fingerprints = {build_info.fingerprint} + + if not OPTIONS.boot_variable_values: + return 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() + 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) + for partition in common.PARTITIONS_WITH_CARE_MAP: + partition_prop_key = "{}.build.prop".format(partition) + old_props = info_dict[partition_prop_key] + info_dict[partition_prop_key] = common.PartitionBuildProps.FromInputFile( + 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 + + def main(argv): def option_handler(o, a): diff --git a/tools/releasetools/test_add_img_to_target_files.py b/tools/releasetools/test_add_img_to_target_files.py index 3d0766ffd..c82a40bee 100644 --- a/tools/releasetools/test_add_img_to_target_files.py +++ b/tools/releasetools/test_add_img_to_target_files.py @@ -128,13 +128,16 @@ class AddImagesToTargetFilesTest(test_utils.ReleaseToolsTestCase): 'vendor_image_size' : 40960, 'system_verity_block_device': '/dev/block/system', 'vendor_verity_block_device': '/dev/block/vendor', - 'system.build.prop': { - 'ro.system.build.fingerprint': - 'google/sailfish/12345:user/dev-keys', - }, - 'vendor.build.prop': { - 'ro.vendor.build.fingerprint': 'google/sailfish/678:user/dev-keys', - }, + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.system.build.fingerprint': + 'google/sailfish/12345:user/dev-keys'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.vendor.build.fingerprint': + 'google/sailfish/678:user/dev-keys'} + ), } # Prepare the META/ folder. @@ -206,18 +209,21 @@ class AddImagesToTargetFilesTest(test_utils.ReleaseToolsTestCase): """Tests the case for device using AVB.""" image_paths = self._test_AddCareMapForAbOta() OPTIONS.info_dict = { - 'extfs_sparse_flag' : '-s', - 'system_image_size' : 65536, - 'vendor_image_size' : 40960, - 'avb_system_hashtree_enable' : 'true', - 'avb_vendor_hashtree_enable' : 'true', - 'system.build.prop': { - 'ro.system.build.fingerprint': - 'google/sailfish/12345:user/dev-keys', - }, - 'vendor.build.prop': { - 'ro.vendor.build.fingerprint': 'google/sailfish/678:user/dev-keys', - } + 'extfs_sparse_flag': '-s', + 'system_image_size': 65536, + 'vendor_image_size': 40960, + 'avb_system_hashtree_enable': 'true', + 'avb_vendor_hashtree_enable': 'true', + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.system.build.fingerprint': + 'google/sailfish/12345:user/dev-keys'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.vendor.build.fingerprint': + 'google/sailfish/678:user/dev-keys'} + ), } AddCareMapForAbOta(None, ['system', 'vendor'], image_paths) @@ -258,17 +264,21 @@ class AddImagesToTargetFilesTest(test_utils.ReleaseToolsTestCase): """Tests the case for partitions with thumbprint.""" image_paths = self._test_AddCareMapForAbOta() OPTIONS.info_dict = { - 'extfs_sparse_flag' : '-s', - 'system_image_size' : 65536, - 'vendor_image_size' : 40960, + 'extfs_sparse_flag': '-s', + 'system_image_size': 65536, + 'vendor_image_size': 40960, 'system_verity_block_device': '/dev/block/system', 'vendor_verity_block_device': '/dev/block/vendor', - 'system.build.prop': { - 'ro.system.build.thumbprint': 'google/sailfish/123:user/dev-keys', - }, - 'vendor.build.prop' : { - 'ro.vendor.build.thumbprint': 'google/sailfish/456:user/dev-keys', - }, + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.system.build.thumbprint': + 'google/sailfish/123:user/dev-keys'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.vendor.build.thumbprint': + 'google/sailfish/456:user/dev-keys'} + ), } AddCareMapForAbOta(None, ['system', 'vendor'], image_paths) diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py index 665eb513d..787e6757e 100644 --- a/tools/releasetools/test_common.py +++ b/tools/releasetools/test_common.py @@ -48,109 +48,124 @@ def get_2gb_string(): class BuildInfoTest(test_utils.ReleaseToolsTestCase): TEST_INFO_DICT = { - 'build.prop' : { - 'ro.product.device' : 'product-device', - 'ro.product.name' : 'product-name', - 'ro.build.fingerprint' : 'build-fingerprint', - 'ro.build.foo' : 'build-foo', - }, - 'system.build.prop' : { - 'ro.product.system.brand' : 'product-brand', - 'ro.product.system.name' : 'product-name', - 'ro.product.system.device' : 'product-device', - 'ro.system.build.version.release' : 'version-release', - 'ro.system.build.id' : 'build-id', - 'ro.system.build.version.incremental' : 'version-incremental', - 'ro.system.build.type' : 'build-type', - 'ro.system.build.tags' : 'build-tags', - 'ro.system.build.foo' : 'build-foo', - }, - 'vendor.build.prop' : { - 'ro.product.vendor.brand' : 'vendor-product-brand', - 'ro.product.vendor.name' : 'vendor-product-name', - 'ro.product.vendor.device' : 'vendor-product-device', - 'ro.vendor.build.version.release' : 'vendor-version-release', - 'ro.vendor.build.id' : 'vendor-build-id', - 'ro.vendor.build.version.incremental' : 'vendor-version-incremental', - 'ro.vendor.build.type' : 'vendor-build-type', - 'ro.vendor.build.tags' : 'vendor-build-tags', - }, - 'property1' : 'value1', - 'property2' : 4096, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.device': 'product-device', + 'ro.product.name': 'product-name', + 'ro.build.fingerprint': 'build-fingerprint', + 'ro.build.foo': 'build-foo'} + ), + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.system.brand': 'product-brand', + 'ro.product.system.name': 'product-name', + 'ro.product.system.device': 'product-device', + 'ro.system.build.version.release': 'version-release', + 'ro.system.build.id': 'build-id', + 'ro.system.build.version.incremental': 'version-incremental', + 'ro.system.build.type': 'build-type', + 'ro.system.build.tags': 'build-tags', + 'ro.system.build.foo': 'build-foo'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.product.vendor.brand': 'vendor-product-brand', + 'ro.product.vendor.name': 'vendor-product-name', + 'ro.product.vendor.device': 'vendor-product-device', + 'ro.vendor.build.version.release': 'vendor-version-release', + 'ro.vendor.build.id': 'vendor-build-id', + 'ro.vendor.build.version.incremental': + 'vendor-version-incremental', + 'ro.vendor.build.type': 'vendor-build-type', + 'ro.vendor.build.tags': 'vendor-build-tags'} + ), + 'property1': 'value1', + 'property2': 4096, } TEST_INFO_DICT_USES_OEM_PROPS = { - 'build.prop' : { - 'ro.product.name' : 'product-name', - 'ro.build.thumbprint' : 'build-thumbprint', - 'ro.build.bar' : 'build-bar', - }, - 'vendor.build.prop' : { - 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', - }, - 'property1' : 'value1', - 'property2' : 4096, - 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand', + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.name': 'product-name', + 'ro.build.thumbprint': 'build-thumbprint', + 'ro.build.bar': 'build-bar'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.vendor.build.fingerprint': 'vendor-build-fingerprint'} + ), + 'property1': 'value1', + 'property2': 4096, + 'oem_fingerprint_properties': 'ro.product.device ro.product.brand', } TEST_OEM_DICTS = [ { - 'ro.product.brand' : 'brand1', - 'ro.product.device' : 'device1', + 'ro.product.brand': 'brand1', + 'ro.product.device': 'device1', }, { - 'ro.product.brand' : 'brand2', - 'ro.product.device' : 'device2', + 'ro.product.brand': 'brand2', + 'ro.product.device': 'device2', }, { - 'ro.product.brand' : 'brand3', - 'ro.product.device' : 'device3', + 'ro.product.brand': 'brand3', + 'ro.product.device': 'device3', }, ] TEST_INFO_DICT_PROPERTY_SOURCE_ORDER = { - 'build.prop' : { - 'ro.build.fingerprint' : 'build-fingerprint', - 'ro.product.property_source_order' : - 'product,odm,vendor,system_ext,system', - }, - 'system.build.prop' : { - 'ro.product.system.device' : 'system-product-device', - }, - 'vendor.build.prop' : { - 'ro.product.vendor.device' : 'vendor-product-device', - }, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.build.fingerprint': 'build-fingerprint', + 'ro.product.property_source_order': + 'product,odm,vendor,system_ext,system'} + ), + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.system.device': 'system-product-device'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.product.vendor.device': 'vendor-product-device'} + ), } TEST_INFO_DICT_PROPERTY_SOURCE_ORDER_ANDROID_10 = { - 'build.prop' : { - 'ro.build.fingerprint' : 'build-fingerprint', - 'ro.product.property_source_order' : - 'product,product_services,odm,vendor,system', - 'ro.build.version.release' : '10', - 'ro.build.version.codename' : 'REL', - }, - 'system.build.prop' : { - 'ro.product.system.device' : 'system-product-device', - }, - 'vendor.build.prop' : { - 'ro.product.vendor.device' : 'vendor-product-device', - }, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.build.fingerprint': 'build-fingerprint', + 'ro.product.property_source_order': + 'product,product_services,odm,vendor,system', + 'ro.build.version.release': '10', + 'ro.build.version.codename': 'REL'} + ), + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.system.device': 'system-product-device'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.product.vendor.device': 'vendor-product-device'} + ), } TEST_INFO_DICT_PROPERTY_SOURCE_ORDER_ANDROID_9 = { - 'build.prop' : { - 'ro.product.device' : 'product-device', - 'ro.build.fingerprint' : 'build-fingerprint', - 'ro.build.version.release' : '9', - 'ro.build.version.codename' : 'REL', - }, - 'system.build.prop' : { - 'ro.product.system.device' : 'system-product-device', - }, - 'vendor.build.prop' : { - 'ro.product.vendor.device' : 'vendor-product-device', - }, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.device': 'product-device', + 'ro.build.fingerprint': 'build-fingerprint', + 'ro.build.version.release': '9', + 'ro.build.version.codename': 'REL'} + ), + 'system.build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.system.device': 'system-product-device'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.product.vendor.device': 'vendor-product-device'} + ), } def test_init(self): @@ -178,25 +193,27 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): def test_init_badFingerprint(self): info_dict = copy.deepcopy(self.TEST_INFO_DICT) - info_dict['build.prop']['ro.build.fingerprint'] = 'bad fingerprint' + info_dict['build.prop'].build_props[ + 'ro.build.fingerprint'] = 'bad fingerprint' self.assertRaises(ValueError, common.BuildInfo, info_dict, None) - info_dict['build.prop']['ro.build.fingerprint'] = 'bad\x80fingerprint' + info_dict['build.prop'].build_props[ + 'ro.build.fingerprint'] = 'bad\x80fingerprint' self.assertRaises(ValueError, common.BuildInfo, info_dict, None) def test___getitem__(self): target_info = common.BuildInfo(self.TEST_INFO_DICT, None) self.assertEqual('value1', target_info['property1']) self.assertEqual(4096, target_info['property2']) - self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) + self.assertEqual('build-foo', + target_info['build.prop'].GetProp('ro.build.foo')) def test___getitem__with_oem_props(self): target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, self.TEST_OEM_DICTS) self.assertEqual('value1', target_info['property1']) self.assertEqual(4096, target_info['property2']) - self.assertRaises(KeyError, - lambda: target_info['build.prop']['ro.build.foo']) + self.assertIsNone(target_info['build.prop'].GetProp('ro.build.foo')) def test___setitem__(self): target_info = common.BuildInfo(copy.deepcopy(self.TEST_INFO_DICT), None) @@ -204,9 +221,11 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): target_info['property1'] = 'value2' self.assertEqual('value2', target_info['property1']) - self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) - target_info['build.prop']['ro.build.foo'] = 'build-bar' - self.assertEqual('build-bar', target_info['build.prop']['ro.build.foo']) + self.assertEqual('build-foo', + target_info['build.prop'].GetProp('ro.build.foo')) + target_info['build.prop'].build_props['ro.build.foo'] = 'build-bar' + self.assertEqual('build-bar', + target_info['build.prop'].GetProp('ro.build.foo')) def test_get(self): target_info = common.BuildInfo(self.TEST_INFO_DICT, None) @@ -214,7 +233,8 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): self.assertEqual(4096, target_info.get('property2')) self.assertEqual(4096, target_info.get('property2', 1024)) self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) - self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo']) + self.assertEqual('build-foo', + target_info.get('build.prop').GetProp('ro.build.foo')) def test_get_with_oem_props(self): target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, @@ -223,9 +243,7 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): self.assertEqual(4096, target_info.get('property2')) self.assertEqual(4096, target_info.get('property2', 1024)) self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) - self.assertIsNone(target_info.get('build.prop').get('ro.build.foo')) - self.assertRaises(KeyError, - lambda: target_info.get('build.prop')['ro.build.foo']) + self.assertIsNone(target_info.get('build.prop').GetProp('ro.build.foo')) def test_items(self): target_info = common.BuildInfo(self.TEST_INFO_DICT, None) @@ -262,7 +280,8 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): def test_GetPartitionFingerprint_uses_fingerprint_prop_if_available(self): info_dict = copy.deepcopy(self.TEST_INFO_DICT) - info_dict['vendor.build.prop']['ro.vendor.build.fingerprint'] = 'vendor:fingerprint' + info_dict['vendor.build.prop'].build_props[ + 'ro.vendor.build.fingerprint'] = 'vendor:fingerprint' target_info = common.BuildInfo(info_dict, None) self.assertEqual( target_info.GetPartitionFingerprint('vendor'), @@ -303,14 +322,15 @@ class BuildInfoTest(test_utils.ReleaseToolsTestCase): def test_ResolveRoProductProperty_FromSystem(self): info_dict = copy.deepcopy(self.TEST_INFO_DICT_PROPERTY_SOURCE_ORDER) - del info_dict['vendor.build.prop']['ro.product.vendor.device'] + del info_dict['vendor.build.prop'].build_props['ro.product.vendor.device'] info = common.BuildInfo(info_dict, None) self.assertEqual('system-product-device', info.GetBuildProp('ro.product.device')) def test_ResolveRoProductProperty_InvalidPropertySearchOrder(self): info_dict = copy.deepcopy(self.TEST_INFO_DICT_PROPERTY_SOURCE_ORDER) - info_dict['build.prop']['ro.product.property_source_order'] = 'bad-source' + info_dict['build.prop'].build_props[ + 'ro.product.property_source_order'] = 'bad-source' with self.assertRaisesRegexp(common.ExternalError, 'Invalid ro.product.property_source_order'): info = common.BuildInfo(info_dict, None) @@ -1513,12 +1533,14 @@ class CommonUtilsTest(test_utils.ReleaseToolsTestCase): common.OPTIONS.info_dict = { 'ab_update': 'true', 'avb_avbtool': 'avbtool', - 'build.prop': { - 'ro.build.version.incremental': '6285659', - 'ro.product.device': 'coral', - 'ro.build.fingerprint': 'google/coral/coral:R/RP1A.200311.002/' - '6285659:userdebug/dev-keys' - } + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.build.version.incremental': '6285659', + 'ro.product.device': 'coral', + 'ro.build.fingerprint': + 'google/coral/coral:R/RP1A.200311.002/' + '6285659:userdebug/dev-keys'} + ), } common.OPTIONS.aftl_tool_path = 'aftltool' common.OPTIONS.aftl_server = 'log.endpoints.aftl-dev.cloud.goog:9000' @@ -1551,12 +1573,14 @@ class CommonUtilsTest(test_utils.ReleaseToolsTestCase): common.OPTIONS.info_dict = { 'ab_update': 'true', 'avb_avbtool': 'avbtool', - 'build.prop': { - 'ro.build.version.incremental': '6285659', - 'ro.product.device': 'coral', - 'ro.build.fingerprint': 'google/coral/coral:R/RP1A.200311.002/' - '6285659:userdebug/dev-keys' - } + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.build.version.incremental': '6285659', + 'ro.product.device': 'coral', + 'ro.build.fingerprint': + 'google/coral/coral:R/RP1A.200311.002/' + '6285659:userdebug/dev-keys'} + ) } common.OPTIONS.aftl_tool_path = "aftltool" common.OPTIONS.aftl_server = "log.endpoints.aftl-dev.cloud.goog:9000" @@ -1871,3 +1895,241 @@ super_group_foo_group_size={group_foo_size} lines = self.get_op_list(self.output_path) self.assertEqual(lines, ["remove foo"]) + + +class PartitionBuildPropsTest(test_utils.ReleaseToolsTestCase): + def setUp(self): + self.odm_build_prop = [ + 'ro.odm.build.date.utc=1578430045', + 'ro.odm.build.fingerprint=' + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device=coral', + 'import /odm/etc/build_${ro.boot.product.device_name}.prop', + ] + + @staticmethod + def _BuildZipFile(entries): + input_file = common.MakeTempFile(prefix='target_files-', suffix='.zip') + with zipfile.ZipFile(input_file, 'w') as input_zip: + for name, content in entries.items(): + input_zip.writestr(name, content) + + return input_file + + def test_parseBuildProps_noImportStatement(self): + build_prop = [ + 'ro.odm.build.date.utc=1578430045', + 'ro.odm.build.fingerprint=' + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device=coral', + ] + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(build_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': ['std', 'pro'] + } + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm', placeholder_values) + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coral', + }, partition_props.build_props) + + self.assertEqual(set(), partition_props.prop_overrides) + + def test_parseBuildProps_singleImportStatement(self): + build_std_prop = [ + 'ro.product.odm.device=coral', + 'ro.product.odm.name=product1', + ] + build_pro_prop = [ + 'ro.product.odm.device=coralpro', + 'ro.product.odm.name=product2', + ] + + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(self.odm_build_prop), + 'ODM/etc/build_std.prop': '\n'.join(build_std_prop), + 'ODM/etc/build_pro.prop': '\n'.join(build_pro_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'std' + } + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm', placeholder_values) + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coral', + 'ro.product.odm.name': 'product1', + }, partition_props.build_props) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'pro' + } + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm', placeholder_values) + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coralpro', + 'ro.product.odm.name': 'product2', + }, partition_props.build_props) + + def test_parseBuildProps_noPlaceHolders(self): + build_prop = copy.copy(self.odm_build_prop) + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(build_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm') + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coral', + }, partition_props.build_props) + + self.assertEqual(set(), partition_props.prop_overrides) + + def test_parseBuildProps_multipleImportStatements(self): + build_prop = copy.deepcopy(self.odm_build_prop) + build_prop.append( + 'import /odm/etc/build_${ro.boot.product.product_name}.prop') + + build_std_prop = [ + 'ro.product.odm.device=coral', + ] + build_pro_prop = [ + 'ro.product.odm.device=coralpro', + ] + + product1_prop = [ + 'ro.product.odm.name=product1', + 'ro.product.not_care=not_care', + ] + + product2_prop = [ + 'ro.product.odm.name=product2', + 'ro.product.not_care=not_care', + ] + + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(build_prop), + 'ODM/etc/build_std.prop': '\n'.join(build_std_prop), + 'ODM/etc/build_pro.prop': '\n'.join(build_pro_prop), + 'ODM/etc/build_product1.prop': '\n'.join(product1_prop), + 'ODM/etc/build_product2.prop': '\n'.join(product2_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'std', + 'ro.boot.product.product_name': 'product1', + 'ro.boot.product.not_care': 'not_care', + } + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm', placeholder_values) + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coral', + 'ro.product.odm.name': 'product1' + }, partition_props.build_props) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'pro', + 'ro.boot.product.product_name': 'product2', + 'ro.boot.product.not_care': 'not_care', + } + partition_props = common.PartitionBuildProps.FromInputFile( + input_zip, 'odm', placeholder_values) + + self.assertEqual({ + 'ro.odm.build.date.utc': '1578430045', + 'ro.odm.build.fingerprint': + 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', + 'ro.product.odm.device': 'coralpro', + 'ro.product.odm.name': 'product2' + }, partition_props.build_props) + + def test_parseBuildProps_defineAfterOverride(self): + build_prop = copy.deepcopy(self.odm_build_prop) + build_prop.append('ro.product.odm.device=coral') + + build_std_prop = [ + 'ro.product.odm.device=coral', + ] + build_pro_prop = [ + 'ro.product.odm.device=coralpro', + ] + + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(build_prop), + 'ODM/etc/build_std.prop': '\n'.join(build_std_prop), + 'ODM/etc/build_pro.prop': '\n'.join(build_pro_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'std', + } + + self.assertRaises(ValueError, common.PartitionBuildProps.FromInputFile, + input_zip, 'odm', placeholder_values) + + def test_parseBuildProps_duplicateOverride(self): + build_prop = copy.deepcopy(self.odm_build_prop) + build_prop.append( + 'import /odm/etc/build_${ro.boot.product.product_name}.prop') + + build_std_prop = [ + 'ro.product.odm.device=coral', + 'ro.product.odm.name=product1', + ] + build_pro_prop = [ + 'ro.product.odm.device=coralpro', + ] + + product1_prop = [ + 'ro.product.odm.name=product1', + ] + + product2_prop = [ + 'ro.product.odm.name=product2', + ] + + input_file = self._BuildZipFile({ + 'ODM/etc/build.prop': '\n'.join(build_prop), + 'ODM/etc/build_std.prop': '\n'.join(build_std_prop), + 'ODM/etc/build_pro.prop': '\n'.join(build_pro_prop), + 'ODM/etc/build_product1.prop': '\n'.join(product1_prop), + 'ODM/etc/build_product2.prop': '\n'.join(product2_prop), + }) + + with zipfile.ZipFile(input_file, 'r') as input_zip: + placeholder_values = { + 'ro.boot.product.device_name': 'std', + 'ro.boot.product.product_name': 'product1', + } + self.assertRaises(ValueError, common.PartitionBuildProps.FromInputFile, + input_zip, 'odm', placeholder_values) diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py index 38faf64aa..4077d06e6 100644 --- a/tools/releasetools/test_ota_from_target_files.py +++ b/tools/releasetools/test_ota_from_target_files.py @@ -26,7 +26,8 @@ from ota_from_target_files import ( GetPackageMetadata, GetTargetFilesZipForSecondaryImages, GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles, Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles, - StreamingPropertyFiles, WriteFingerprintAssertion) + StreamingPropertyFiles, WriteFingerprintAssertion, + CalculateRuntimeFingerprints) def construct_target_files(secondary=False): @@ -108,55 +109,58 @@ class LoadOemDictsTest(test_utils.ReleaseToolsTestCase): class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): - TEST_TARGET_INFO_DICT = { - 'build.prop' : { - 'ro.product.device' : 'product-device', - 'ro.build.fingerprint' : 'build-fingerprint-target', - 'ro.build.version.incremental' : 'build-version-incremental-target', - 'ro.build.version.sdk' : '27', - 'ro.build.version.security_patch' : '2017-12-01', - 'ro.build.date.utc' : '1500000000', - }, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.device': 'product-device', + 'ro.build.fingerprint': 'build-fingerprint-target', + 'ro.build.version.incremental': 'build-version-incremental-target', + 'ro.build.version.sdk': '27', + 'ro.build.version.security_patch': '2017-12-01', + 'ro.build.date.utc': '1500000000'} + ) } TEST_SOURCE_INFO_DICT = { - 'build.prop' : { - 'ro.product.device' : 'product-device', - 'ro.build.fingerprint' : 'build-fingerprint-source', - 'ro.build.version.incremental' : 'build-version-incremental-source', - 'ro.build.version.sdk' : '25', - 'ro.build.version.security_patch' : '2016-12-01', - 'ro.build.date.utc' : '1400000000', - }, + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.device': 'product-device', + 'ro.build.fingerprint': 'build-fingerprint-source', + 'ro.build.version.incremental': 'build-version-incremental-source', + 'ro.build.version.sdk': '25', + 'ro.build.version.security_patch': '2016-12-01', + 'ro.build.date.utc': '1400000000'} + ) } TEST_INFO_DICT_USES_OEM_PROPS = { - 'build.prop' : { - 'ro.product.name' : 'product-name', - 'ro.build.thumbprint' : 'build-thumbprint', - 'ro.build.bar' : 'build-bar', - }, - 'vendor.build.prop' : { - 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', - }, - 'property1' : 'value1', - 'property2' : 4096, - 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand', + 'build.prop': common.PartitionBuildProps.FromDictionary( + 'system', { + 'ro.product.name': 'product-name', + 'ro.build.thumbprint': 'build-thumbprint', + 'ro.build.bar': 'build-bar'} + ), + 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( + 'vendor', { + 'ro.vendor.build.fingerprint': 'vendor-build-fingerprint'} + ), + 'property1': 'value1', + 'property2': 4096, + 'oem_fingerprint_properties': 'ro.product.device ro.product.brand', } TEST_OEM_DICTS = [ { - 'ro.product.brand' : 'brand1', - 'ro.product.device' : 'device1', + 'ro.product.brand': 'brand1', + 'ro.product.device': 'device1', }, { - 'ro.product.brand' : 'brand2', - 'ro.product.device' : 'device2', + 'ro.product.brand': 'brand2', + 'ro.product.device': 'device2', }, { - 'ro.product.brand' : 'brand3', - 'ro.product.device' : 'device3', + 'ro.product.brand': 'brand3', + 'ro.product.device': 'device3', }, ] @@ -288,10 +292,10 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): @staticmethod def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info): - (target_info['build.prop']['ro.build.date.utc'], - source_info['build.prop']['ro.build.date.utc']) = ( - source_info['build.prop']['ro.build.date.utc'], - target_info['build.prop']['ro.build.date.utc']) + (target_info['build.prop'].build_props['ro.build.date.utc'], + source_info['build.prop'].build_props['ro.build.date.utc']) = ( + source_info['build.prop'].build_props['ro.build.date.utc'], + target_info['build.prop'].build_props['ro.build.date.utc']) def test_GetPackageMetadata_unintentionalDowngradeDetected(self): target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) @@ -528,7 +532,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): def test_WriteFingerprintAssertion_without_oem_props(self): target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) source_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) - source_info_dict['build.prop']['ro.build.fingerprint'] = ( + source_info_dict['build.prop'].build_props['ro.build.fingerprint'] = ( 'source-build-fingerprint') source_info = common.BuildInfo(source_info_dict, None) @@ -567,7 +571,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, self.TEST_OEM_DICTS) source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) - source_info_dict['build.prop']['ro.build.thumbprint'] = ( + source_info_dict['build.prop'].build_props['ro.build.thumbprint'] = ( 'source-build-thumbprint') source_info = common.BuildInfo(source_info_dict, self.TEST_OEM_DICTS) @@ -1315,3 +1319,125 @@ class PayloadTest(test_utils.ReleaseToolsTestCase): Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT): continue self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type) + + +class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase): + MISC_INFO = [ + 'recovery_api_version=3', + 'fstab_version=2', + 'recovery_as_boot=true', + ] + + BUILD_PROP = [ + 'ro.build.version.release=version-release', + 'ro.build.id=build-id', + 'ro.build.version.incremental=version-incremental', + 'ro.build.type=build-type', + 'ro.build.tags=build-tags', + ] + + VENDOR_BUILD_PROP = [ + 'ro.product.vendor.brand=vendor-product-brand', + 'ro.product.vendor.name=vendor-product-name', + 'ro.product.vendor.device=vendor-product-device' + ] + + def setUp(self): + common.OPTIONS.oem_dicts = None + self.test_dir = common.MakeTempDir() + self.writeFiles({'META/misc_info.txt': '\n'.join(self.MISC_INFO)}) + + def writeFiles(self, contents_dict): + for path, content in contents_dict.items(): + abs_path = os.path.join(self.test_dir, path) + dir_name = os.path.dirname(abs_path) + if not os.path.exists(dir_name): + os.makedirs(dir_name) + with open(abs_path, 'w') as f: + f.write(content) + + @staticmethod + def constructFingerprint(prefix): + return '{}:version-release/build-id/version-incremental:' \ + 'build-type/build-tags'.format(prefix) + + def test_CalculatePossibleFingerprints_no_dynamic_fingerprint(self): + build_prop = copy.deepcopy(self.BUILD_PROP) + build_prop.extend([ + 'ro.product.brand=product-brand', + 'ro.product.name=product-name', + 'ro.product.device=product-device', + ]) + 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.assertEqual({ + self.constructFingerprint('product-brand/product-name/product-device') + }, CalculateRuntimeFingerprints()) + + def test_CalculatePossibleFingerprints_single_override(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', + }) + common.OPTIONS.info_dict = common.LoadInfoDict(self.test_dir) + common.OPTIONS.boot_variable_values = { + 'ro.boot.sku_name': ['std', 'pro'] + } + + self.assertEqual({ + 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()) + + def test_CalculatePossibleFingerprints_multiple_overrides(self): + vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP) + vendor_build_prop.extend([ + 'import /vendor/etc/build_${ro.boot.sku_name}.prop', + 'import /vendor/etc/build_${ro.boot.device_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_product1.prop': + 'ro.product.vendor.device=vendor-device-product1', + 'VENDOR/etc/build_pro.prop': + '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 = { + 'ro.boot.sku_name': ['std', 'pro'], + 'ro.boot.device_name': ['product1', 'product2'], + } + + self.assertEqual({ + self.constructFingerprint( + 'vendor-product-brand/vendor-product-name/vendor-product-device'), + self.constructFingerprint( + 'vendor-product-brand/vendor-product-std/vendor-device-product1'), + self.constructFingerprint( + 'vendor-product-brand/vendor-product-pro/vendor-device-product1'), + self.constructFingerprint( + 'vendor-product-brand/vendor-product-std/vendor-device-product2'), + self.constructFingerprint( + 'vendor-product-brand/vendor-product-pro/vendor-device-product2'), + }, CalculateRuntimeFingerprints())