Performs basic APEX validation in the merged target files package.

Uses apex_utils.GetApexInfoFromTargetFiles to find and parse APEX files
in the target files partition dirs. Raises an error on failure to parse
or duplicate package names.

Bug: 177225446
Test: releasetools_test
Test: Create a merged build that provides the VNDK APEX on both vendor
      and system. Observe failure.
Change-Id: I1356e263b7b32d6063129e079f3ba7ab4ff132a7
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index a56c305..00bbb21 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -62,7 +62,7 @@
 import verity_utils
 import ota_metadata_pb2
 
-from apex_utils import GetSystemApexInfoFromTargetFiles
+from apex_utils import GetApexInfoFromTargetFiles
 from common import AddCareMapForAbOta
 
 if sys.hexversion < 0x02070000:
@@ -686,7 +686,7 @@
                            "{}.img".format(partition_name))))
 
 def AddApexInfo(output_zip):
-  apex_infos = GetSystemApexInfoFromTargetFiles(OPTIONS.input_tmp)
+  apex_infos = GetApexInfoFromTargetFiles(OPTIONS.input_tmp, 'system')
   apex_metadata_proto = ota_metadata_pb2.ApexMetadata()
   apex_metadata_proto.apex_info.extend(apex_infos)
   apex_info_bytes = apex_metadata_proto.SerializeToString()
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index 1c88053..893266f 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -516,7 +516,7 @@
     raise ApexInfoError(
         'Failed to get type for {}:\n{}'.format(apex_file, e))
 
-def GetSystemApexInfoFromTargetFiles(input_file):
+def GetApexInfoFromTargetFiles(input_file, partition, compressed_only=True):
   """
   Get information about system APEX stored in the input_file zip
 
@@ -532,15 +532,17 @@
   if not isinstance(input_file, str):
     raise RuntimeError("must pass filepath to target-files zip or directory")
 
+  apex_subdir = os.path.join(partition.upper(), 'apex')
   if os.path.isdir(input_file):
     tmp_dir = input_file
   else:
-    tmp_dir = UnzipTemp(input_file, ["SYSTEM/apex/*"])
-  target_dir = os.path.join(tmp_dir, "SYSTEM/apex/")
+    tmp_dir = UnzipTemp(input_file, [os.path.join(apex_subdir, '*')])
+  target_dir = os.path.join(tmp_dir, apex_subdir)
 
   # Partial target-files packages for vendor-only builds may not contain
   # a system apex directory.
   if not os.path.exists(target_dir):
+    logger.info('No APEX directory at path: %s', target_dir)
     return []
 
   apex_infos = []
@@ -585,6 +587,7 @@
                          '--output', decompressed_file_path])
       apex_info.decompressed_size = os.path.getsize(decompressed_file_path)
 
+    if not compressed_only or apex_info.is_compressed:
       apex_infos.append(apex_info)
 
   return apex_infos
diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py
index 17d3030..5e6c42d 100755
--- a/tools/releasetools/merge_target_files.py
+++ b/tools/releasetools/merge_target_files.py
@@ -96,6 +96,7 @@
 from xml.etree import ElementTree
 
 import add_img_to_target_files
+import apex_utils
 import build_image
 import build_super_image
 import check_target_files_vintf
@@ -739,6 +740,35 @@
   return cmd
 
 
+def validate_merged_apex_info(output_target_files_dir, partitions):
+  """Validates the APEX files in the merged target files directory.
+
+  Checks the APEX files in all possible preinstalled APEX directories.
+  Depends on the <partition>/apex/* APEX files within partitions.
+
+  Args:
+    output_target_files_dir: Output directory containing merged partition directories.
+    partitions: A list of all the partitions in the output directory.
+
+  Raises:
+    RuntimeError: if apex_utils fails to parse any APEX file.
+    ExternalError: if the same APEX package is provided by multiple partitions.
+  """
+  apex_packages = set()
+
+  apex_partitions = ('system', 'system_ext', 'product', 'vendor')
+  for partition in filter(lambda p: p in apex_partitions, partitions):
+    apex_info = apex_utils.GetApexInfoFromTargetFiles(
+        output_target_files_dir, partition, compressed_only=False)
+    partition_apex_packages = set([info.package_name for info in apex_info])
+    duplicates = apex_packages.intersection(partition_apex_packages)
+    if duplicates:
+      raise ExternalError(
+          'Duplicate APEX packages found in multiple partitions: %s' %
+          ' '.join(duplicates))
+    apex_packages.update(partition_apex_packages)
+
+
 def generate_care_map(partitions, output_target_files_dir):
   """Generates a merged META/care_map.pb file in the output target files dir.
 
@@ -1116,6 +1146,9 @@
   common.RunAndCheckOutput(split_sepolicy_cmd)
   # TODO(b/178864050): Run tests on the combined.policy file.
 
+  # Run validation checks on the pre-installed APEX files.
+  validate_merged_apex_info(output_target_files_temp_dir, partition_map.keys())
+
   generate_images(output_target_files_temp_dir, rebuild_recovery)
 
   generate_super_empty_image(output_target_files_temp_dir, output_super_empty)
diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py
index 072bb01..4f61472 100644
--- a/tools/releasetools/test_merge_target_files.py
+++ b/tools/releasetools/test_merge_target_files.py
@@ -15,6 +15,7 @@
 #
 
 import os.path
+import shutil
 
 import common
 import test_utils
@@ -22,7 +23,7 @@
     validate_config_lists, DEFAULT_FRAMEWORK_ITEM_LIST,
     DEFAULT_VENDOR_ITEM_LIST, DEFAULT_FRAMEWORK_MISC_INFO_KEYS, copy_items,
     item_list_to_partition_set, process_apex_keys_apk_certs_common,
-    compile_split_sepolicy)
+    compile_split_sepolicy, validate_merged_apex_info)
 
 
 class MergeTargetFilesTest(test_utils.ReleaseToolsTestCase):
@@ -274,3 +275,36 @@
                       '{OTP}/vendor/etc/selinux/plat_pub_versioned.cil '
                       '{OTP}/product/etc/selinux/mapping/30.0.cil').format(
                           OTP=product_out_dir))
+
+  def _copy_apex(self, source, output_dir, partition):
+    shutil.copy(
+        source,
+        os.path.join(output_dir, partition, 'apex', os.path.basename(source)))
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_validate_merged_apex_info(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    self._copy_apex(
+        os.path.join(self.testdata_dir, 'has_apk.apex'), output_dir, 'SYSTEM')
+    self._copy_apex(
+        os.path.join(test_utils.get_current_dir(),
+                     'com.android.apex.compressed.v1.capex'), output_dir,
+        'VENDOR')
+    validate_merged_apex_info(output_dir, ('system', 'vendor'))
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_validate_merged_apex_info_RaisesOnPackageInMultiplePartitions(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    same_apex_package = os.path.join(self.testdata_dir, 'has_apk.apex')
+    self._copy_apex(same_apex_package, output_dir, 'SYSTEM')
+    self._copy_apex(same_apex_package, output_dir, 'VENDOR')
+    self.assertRaisesRegexp(
+        common.ExternalError,
+        'Duplicate APEX packages found in multiple partitions: com.android.wifi',
+        validate_merged_apex_info, output_dir, ('system', 'vendor'))
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 9f64849..661712a 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -33,7 +33,7 @@
     GetTargetFilesZipWithoutPostinstallConfig,
     Payload, PayloadSigner, POSTINSTALL_CONFIG,
     StreamingPropertyFiles, AB_PARTITIONS)
-from apex_utils import GetSystemApexInfoFromTargetFiles
+from apex_utils import GetApexInfoFromTargetFiles
 from test_utils import PropertyFilesTestCase
 
 
@@ -281,9 +281,9 @@
         metadata)
 
   @test_utils.SkipIfExternalToolsUnavailable()
-  def test_GetSystemApexInfoFromTargetFiles(self):
+  def test_GetApexInfoFromTargetFiles(self):
     target_files = construct_target_files(compressedApex=True)
-    apex_infos = GetSystemApexInfoFromTargetFiles(target_files)
+    apex_infos = GetApexInfoFromTargetFiles(target_files, 'system')
     self.assertEqual(len(apex_infos), 1)
     self.assertEqual(apex_infos[0].package_name, "com.android.apex.compressed")
     self.assertEqual(apex_infos[0].version, 1)