Merge "Reland "Calculate the runtime fingerprint prefixes from build prop""
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 71ae21f..7805e30 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -738,18 +738,22 @@
partition: name of the partition.
props_allow_override: a list of build properties to search for the
alternative values during runtime.
- build_props: a dictionary of build properties for the given partition.
- prop_overrides: a dict of list. And each list holds the overridden values
- for props_allow_override.
+ 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):
+ 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.{}.name', 'ro.product.{}.device']]
+ 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
self.build_props = {}
- self.prop_overrides = {}
+ self.prop_overrides = set()
+ self.placeholder_values = {}
+ if placeholder_values:
+ self.placeholder_values = copy.deepcopy(placeholder_values)
@staticmethod
def FromDictionary(name, build_props):
@@ -760,9 +764,8 @@
return props
@staticmethod
- def FromInputFile(input_file, name):
+ 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())]:
@@ -772,10 +775,62 @@
except KeyError:
logger.warning('Failed to read %s', prop_file)
- props = PartitionBuildProps(input_file, name)
- props.build_props = LoadDictionaryFromLines(data.split('\n'))
+ 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)
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 92a46a2..47ad3d8 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -193,6 +193,8 @@
from __future__ import print_function
import collections
+import copy
+import itertools
import logging
import multiprocessing
import os.path
@@ -229,6 +231,7 @@
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.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 @@
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_common.py b/tools/releasetools/test_common.py
index c0c2d3f..787e675 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1899,7 +1899,7 @@
class PartitionBuildPropsTest(test_utils.ReleaseToolsTestCase):
def setUp(self):
- self.build_prop = [
+ 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',
@@ -1918,13 +1918,81 @@
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',
+ '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),
+ '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:
@@ -1932,10 +2000,136 @@
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',
+ '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({}, partition_props.prop_overrides)
+ 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 e007863..4077d06 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -26,7 +26,8 @@
GetPackageMetadata, GetTargetFilesZipForSecondaryImages,
GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles,
Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
- StreamingPropertyFiles, WriteFingerprintAssertion)
+ StreamingPropertyFiles, WriteFingerprintAssertion,
+ CalculateRuntimeFingerprints)
def construct_target_files(secondary=False):
@@ -1318,3 +1319,125 @@
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())