Merge "Add an option to input the boot variables for OTA package generation"
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 16b278a..b753414 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -189,6 +189,13 @@
   --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.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 @@
   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 @@
 
   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 @@
           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 @@
           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 @@
       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 @@
                                  "output_metadata_path=",
                                  "disable_fec_computation",
                                  "force_non_ab",
+                                 "boot_variable_file=",
                              ], extra_option_handler=option_handler)
 
   if len(args) != 2:
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 4077d06..7783f96 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -27,7 +27,7 @@
     GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles,
     Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
     StreamingPropertyFiles, WriteFingerprintAssertion,
-    CalculateRuntimeFingerprints)
+    CalculateRuntimeDevicesAndFingerprints)
 
 
 def construct_target_files(secondary=False):
@@ -1334,6 +1334,9 @@
       '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 @@
   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 @@
     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 @@
         '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 @@
         '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 @@
         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'])