Allow merging target files without framework ab_partitions.txt

Introduce a new option `--allow-partial-ab` in merge_target_files,
which allows merging a non-AB framework with an AB vendor.

The reason for adding this option is to support merging a real
device framework with a cuttlefish vendor. Cuttlefish enables AB
partition by default; however, some real devices do not.

Bug: 318326532
Test: merge_target_files
Test: atest --host releasetools_test
Change-Id: Iaebd06796153fe82fbf56e86fcc8c500b6d60771
diff --git a/tools/releasetools/merge/merge_meta.py b/tools/releasetools/merge/merge_meta.py
index 198c973..76582c0 100644
--- a/tools/releasetools/merge/merge_meta.py
+++ b/tools/releasetools/merge/merge_meta.py
@@ -53,23 +53,31 @@
 MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
 
 
-def MergeUpdateEngineConfig(input_metadir1, input_metadir2, merged_meta_dir):
-  UPDATE_ENGINE_CONFIG_NAME = "update_engine_config.txt"
-  config1_path = os.path.join(
-      input_metadir1, UPDATE_ENGINE_CONFIG_NAME)
-  config2_path = os.path.join(
-      input_metadir2, UPDATE_ENGINE_CONFIG_NAME)
-  config1 = ParseUpdateEngineConfig(config1_path)
-  config2 = ParseUpdateEngineConfig(config2_path)
-  # Copy older config to merged target files for maximum compatibility
-  # update_engine in system partition is from system side, but
-  # update_engine_sideload in recovery is from vendor side.
-  if config1 < config2:
-    shutil.copy(config1_path, os.path.join(
-        merged_meta_dir, UPDATE_ENGINE_CONFIG_NAME))
+def MergeUpdateEngineConfig(framework_meta_dir, vendor_meta_dir,
+                            merged_meta_dir):
+  """Merges META/update_engine_config.txt.
+
+  The output is the configuration for maximum compatibility.
+  """
+  _CONFIG_NAME = 'update_engine_config.txt'
+  framework_config_path = os.path.join(framework_meta_dir, _CONFIG_NAME)
+  vendor_config_path = os.path.join(vendor_meta_dir, _CONFIG_NAME)
+  merged_config_path = os.path.join(merged_meta_dir, _CONFIG_NAME)
+
+  if os.path.exists(framework_config_path):
+    framework_config = ParseUpdateEngineConfig(framework_config_path)
+    vendor_config = ParseUpdateEngineConfig(vendor_config_path)
+    # Copy older config to merged target files for maximum compatibility
+    # update_engine in system partition is from system side, but
+    # update_engine_sideload in recovery is from vendor side.
+    if framework_config < vendor_config:
+      shutil.copy(framework_config_path, merged_config_path)
+    else:
+      shutil.copy(vendor_config_path, merged_config_path)
   else:
-    shutil.copy(config2_path, os.path.join(
-        merged_meta_dir, UPDATE_ENGINE_CONFIG_NAME))
+    if not OPTIONS.allow_partial_ab:
+      raise FileNotFoundError(framework_config_path)
+    shutil.copy(vendor_config_path, merged_config_path)
 
 
 def MergeMetaFiles(temp_dir, merged_dir, framework_partitions):
@@ -125,8 +133,7 @@
 
   if OPTIONS.merged_misc_info.get('ab_update') == 'true':
     MergeUpdateEngineConfig(
-        framework_meta_dir,
-        vendor_meta_dir, merged_meta_dir)
+        framework_meta_dir, vendor_meta_dir, merged_meta_dir)
 
   # Write the now-finalized OPTIONS.merged_misc_info.
   merge_utils.WriteSortedData(
@@ -140,16 +147,24 @@
 
   The output contains the union of the partition names.
   """
-  with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
-    # Filter out some partitions here to support the case that the
-    # ab_partitions.txt of framework-target-files has non-framework partitions.
-    # This case happens when we use a complete merged target files package as
-    # the framework-target-files.
-    framework_ab_partitions = [
-        partition
-        for partition in f.read().splitlines()
-        if partition in framework_partitions
-    ]
+  framework_ab_partitions = []
+  framework_ab_config = os.path.join(framework_meta_dir, 'ab_partitions.txt')
+  if os.path.exists(framework_ab_config):
+    with open(framework_ab_config) as f:
+      # Filter out some partitions here to support the case that the
+      # ab_partitions.txt of framework-target-files has non-framework
+      # partitions. This case happens when we use a complete merged target
+      # files package as the framework-target-files.
+      framework_ab_partitions.extend([
+          partition
+          for partition in f.read().splitlines()
+          if partition in framework_partitions
+      ])
+  else:
+    if not OPTIONS.allow_partial_ab:
+      raise FileNotFoundError(framework_ab_config)
+    logger.info('Use partial AB because framework ab_partitions.txt does not '
+                'exist.')
 
   with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
     vendor_ab_partitions = f.read().splitlines()
diff --git a/tools/releasetools/merge/merge_target_files.py b/tools/releasetools/merge/merge_target_files.py
index 4619246..fdba927 100755
--- a/tools/releasetools/merge/merge_target_files.py
+++ b/tools/releasetools/merge/merge_target_files.py
@@ -98,6 +98,10 @@
       If provided, resolve the conflict AVB rollback index location when
       necessary.
 
+  --allow-partial-ab
+      If provided, allow merging non-AB framework target files with AB vendor
+      target files, which means that only the vendor has AB partitions.
+
   The following only apply when using the VSDK to perform dexopt on vendor apps:
 
   --framework-dexpreopt-config
@@ -154,6 +158,7 @@
 OPTIONS.rebuild_sepolicy = False
 OPTIONS.keep_tmp = False
 OPTIONS.avb_resolve_rollback_index_location_conflict = False
+OPTIONS.allow_partial_ab = False
 OPTIONS.framework_dexpreopt_config = None
 OPTIONS.framework_dexpreopt_tools = None
 OPTIONS.vendor_dexpreopt_config = None
@@ -576,6 +581,8 @@
       OPTIONS.keep_tmp = True
     elif o == '--avb-resolve-rollback-index-location-conflict':
       OPTIONS.avb_resolve_rollback_index_location_conflict = True
+    elif o == '--allow-partial-ab':
+      OPTIONS.allow_partial_ab = True
     elif o == '--framework-dexpreopt-config':
       OPTIONS.framework_dexpreopt_config = a
     elif o == '--framework-dexpreopt-tools':
@@ -617,6 +624,7 @@
           'rebuild-sepolicy',
           'keep-tmp',
           'avb-resolve-rollback-index-location-conflict',
+          'allow-partial-ab',
       ],
       extra_option_handler=option_handler)