Add APEX data to checkvintf for compatibility check.

As part of extending libvintf to support VINTF data inside of APEXes:
  Create apex-info-list.xml as part of build to pass into checkvintf.
  Include the /apex data to dirmap

  Extend check_target_files_vintf.py to include APEX data:
    Unzip APEX from partions
    Extract APEX data
    Create apex-info-list.xml

Bug: 239055387
Bug: 242314000
Test: m
Test: m dist
Change-Id: I4b4e159051bacb46dc43b83e006ca0f0eb58d772
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 67ee8ef..0ba356c 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -98,6 +98,8 @@
     ],
     required: [
         "checkvintf",
+        "deapexer",
+        "dump_apex_info",
     ],
 }
 
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index 4a2a905..ac3a5eb 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -22,6 +22,7 @@
 target_files can be a ZIP file or an extracted target files directory.
 """
 
+import json
 import logging
 import subprocess
 import sys
@@ -123,7 +124,12 @@
     logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
     return True
 
+
   dirmap = GetDirmap(input_tmp)
+
+  apex_root, apex_info_file = PrepareApexDirectory(input_tmp)
+  dirmap['/apex'] = apex_root
+
   args_for_skus = GetArgsForSkus(info_dict)
   shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
   kernel_args = GetArgsForKernel(input_tmp)
@@ -132,6 +138,8 @@
       'checkvintf',
       '--check-compat',
   ]
+  common_command += ['--apex-info-file', apex_info_file]
+
   for device_path, real_path in sorted(dirmap.items()):
     common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
   common_command += kernel_args
@@ -185,6 +193,98 @@
   paths = sum((PathToPatterns(path) for path in paths if path), [])
   return paths
 
+def GetVintfApexUnzipPatterns():
+  """ Build unzip pattern for APEXes. """
+  patterns = []
+  for target_files_rel_paths in DIR_SEARCH_PATHS.values():
+    for target_files_rel_path in target_files_rel_paths:
+      patterns.append(os.path.join(target_files_rel_path,"apex/*"))
+
+  return patterns
+
+def PrepareApexDirectory(inp):
+  """ Prepare the APEX data.
+
+  Apex binaries do not support dirmaps, in order to use these binaries we
+  need to move the APEXes from the extracted target file archives to the
+  expected device locations.
+
+  The APEXes will also be extracted under the APEX/ directory
+  matching what would be on the target.
+
+  Create the following structure under the input inp directory:
+       APEX/apex             # Extracted APEXes
+       APEX/system/apex/     # System APEXes
+       APEX/vendor/apex/     # Vendor APEXes
+       ...
+
+  Args:
+    inp: path to the directory that contains the extracted target files archive.
+
+  Returns:
+    extracted apex directory
+    apex-info-list.xml file
+  """
+
+  def ExtractApexes(path, outp):
+    # Extract all APEXes found in input path.
+    debugfs_path = 'debugfs'
+    deapexer = 'deapexer'
+    if OPTIONS.search_path:
+      debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
+      deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
+      if os.path.isfile(deapexer_path):
+        deapexer = deapexer_path
+
+    logger.info('Extracting APEXs in %s', path)
+    for f in os.listdir(path):
+      logger.info('  adding APEX %s', os.path.basename(f))
+      apex = os.path.join(path, f)
+      cmd = [deapexer,
+             '--debugfs_path', debugfs_path,
+             'info',
+             apex]
+      info = json.loads(common.RunAndCheckOutput(cmd))
+
+      cmd = [deapexer,
+             '--debugfs_path', debugfs_path,
+             'extract',
+             apex,
+             os.path.join(outp, info['name'])]
+      common.RunAndCheckOutput(cmd)
+
+  root_dir_name = 'APEX'
+  root_dir = os.path.join(inp, root_dir_name)
+  extracted_root = os.path.join(root_dir, 'apex')
+  apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
+
+  # Always create APEX directory for dirmap
+  os.makedirs(extracted_root)
+
+  create_info_file = False
+
+  # Loop through search path looking for and processing apex/ directories.
+  for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
+    for target_files_rel_path in target_files_rel_paths:
+      inp_partition = os.path.join(inp, target_files_rel_path,"apex")
+      if os.path.exists(inp_partition):
+        apex_dir = root_dir + os.path.join(device_path + "/apex");
+        os.makedirs(apex_dir)
+        os.rename(inp_partition, apex_dir)
+        ExtractApexes(apex_dir, extracted_root)
+        create_info_file = True
+
+  if create_info_file:
+    ### Create apex-info-list.xml
+    dump_cmd = ['dump_apex_info',
+                '--root_dir', root_dir,
+                '--out_file', apex_info_file]
+    common.RunAndCheckOutput(dump_cmd)
+    if not os.path.exists(apex_info_file):
+      raise RuntimeError('Failed to create apex info file %s', apex_info_file)
+    logger.info('Created %s', apex_info_file)
+
+  return extracted_root, apex_info_file
 
 def CheckVintfFromTargetFiles(inp, info_dict=None):
   """
@@ -198,7 +298,7 @@
     True if VINTF check is skipped or compatible, False if incompatible. Raise
     a RuntimeError if any error occurs.
   """
-  input_tmp = common.UnzipTemp(inp, GetVintfFileList() + UNZIP_PATTERN)
+  input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
   return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)