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()