gsi_util: adding check_compat subcommand

'check_compat' command can check the compatibility between
system and vendor image, which can be any source supported by
mounters, ex. image file, folder or adb.

Uses following command for the detail:

    $ ./gsu_util.py check_compat --help

The patch also includes a 'checker' framework. There is only
one checker 'VintfChecker' at this time. VintfChecker uses a
host tool, 'checkvintf', to check the compatibility.

Bug: 70253825
Test: check_compat with different mounters
Change-Id: I459b4cbd38465c0058087b4c68bca66e491c940e
diff --git a/gsi/gsi_util/Android.bp b/gsi/gsi_util/Android.bp
index cf72dd9..c1b2828 100644
--- a/gsi/gsi_util/Android.bp
+++ b/gsi/gsi_util/Android.bp
@@ -17,6 +17,7 @@
   srcs: [
     "gsi_util.py",
     "gsi_util/*.py",
+    "gsi_util/checkers/*.py",
     "gsi_util/commands/*.py",
     "gsi_util/dumpers/*.py",
     "gsi_util/mounters/*.py",
@@ -25,6 +26,7 @@
   required: [
     "adb",
     "avbtool",
+    "checkvintf",
     "simg2img",
   ],
   version: {
diff --git a/gsi/gsi_util/gsi_util.py b/gsi/gsi_util/gsi_util.py
index 6dc0931..81c8ffc 100755
--- a/gsi/gsi_util/gsi_util.py
+++ b/gsi/gsi_util/gsi_util.py
@@ -28,7 +28,7 @@
 
   # Adds gsi_util COMMAND here.
   # TODO(bowgotsai): auto collect from gsi_util/commands/*.py
-  _COMMANDS = ['flash_gsi', 'pull', 'dump', 'hello']
+  _COMMANDS = ['flash_gsi', 'pull', 'dump', 'check_compat', 'hello']
 
   _LOGGING_FORMAT = '%(message)s'
   _LOGGING_LEVEL = logging.WARNING
diff --git a/gsi/gsi_util/gsi_util/checkers/__init__.py b/gsi/gsi_util/gsi_util/checkers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/checkers/__init__.py
diff --git a/gsi/gsi_util/gsi_util/checkers/check_result.py b/gsi/gsi_util/gsi_util/checkers/check_result.py
new file mode 100644
index 0000000..d3dbede
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/checkers/check_result.py
@@ -0,0 +1,19 @@
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Provide namedtuple CheckResultItem."""
+
+from collections import namedtuple
+
+CheckResultItem = namedtuple('CheckResultItem', 'name result message')
diff --git a/gsi/gsi_util/gsi_util/checkers/checker.py b/gsi/gsi_util/gsi_util/checkers/checker.py
new file mode 100644
index 0000000..cb79cb1
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/checkers/checker.py
@@ -0,0 +1,57 @@
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Provide class Checker and maintain the checking list."""
+
+from collections import namedtuple
+
+from gsi_util.checkers.vintf_checker import VintfChecker
+
+CheckListItem = namedtuple('CheckListItem', 'id checker_class')
+
+_CHECK_LIST = [
+    CheckListItem('checkvintf', VintfChecker),
+]
+
+
+class Checker(object):
+  """Implement methods and utils to checking compatibility a FileAccessor."""
+
+  def __init__(self, file_accessor):
+    self._file_accessor = file_accessor
+
+  def check(self, check_list):
+    check_result_items = []
+
+    for x in check_list:
+      checker = x.checker_class(self._file_accessor)
+      check_result_items += checker.check()
+
+    return check_result_items
+
+  @staticmethod
+  def make_check_list_with_ids(ids):
+    check_list = []
+    for check_id in ids:
+      # Find the first item matched check_id
+      matched_check_item = next((x for x in _CHECK_LIST if x.id == check_id),
+                                None)
+      if not matched_check_item:
+        raise RuntimeError('Unknown check ID: "{}"'.format(check_id))
+      check_list.append(matched_check_item)
+    return check_list
+
+  @staticmethod
+  def get_all_check_list():
+    return _CHECK_LIST
diff --git a/gsi/gsi_util/gsi_util/checkers/vintf_checker.py b/gsi/gsi_util/gsi_util/checkers/vintf_checker.py
new file mode 100644
index 0000000..1150e64
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/checkers/vintf_checker.py
@@ -0,0 +1,42 @@
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Provides class VintfChecker."""
+
+from gsi_util.checkers.check_result import CheckResultItem
+import gsi_util.utils.vintf_utils as vintf_utils
+
+
+class VintfChecker(object):
+
+  _SYSTEM_MANIFEST_XML = '/system/manifest.xml'
+  _VENDOR_MATRIX_XML = '/vendor/compatibility_matrix.xml'
+  _REQUIRED_FILES = [_SYSTEM_MANIFEST_XML, _VENDOR_MATRIX_XML]
+
+  def __init__(self, file_accessor):
+    self._file_accessor = file_accessor
+
+  def check(self):
+    fa = self._file_accessor
+
+    with fa.prepare_multi_files(self._REQUIRED_FILES) as [manifest, matrix]:
+      if not manifest:
+        raise RuntimeError('Cannot open manifest file: {}'.format(
+            self._SYSTEM_MANIFEST_XML))
+      if not matrix:
+        raise RuntimeError('Cannot open matrix file: {}'.format(
+            self._VENDOR_MATRIX_XML))
+
+      result, error_message = vintf_utils.checkvintf(manifest, matrix)
+      return [CheckResultItem('checkvintf', result, error_message)]
diff --git a/gsi/gsi_util/gsi_util/commands/check_compat.py b/gsi/gsi_util/gsi_util/commands/check_compat.py
new file mode 100644
index 0000000..d05533f
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/commands/check_compat.py
@@ -0,0 +1,145 @@
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Provide command 'check_compat'."""
+
+import argparse
+import logging
+
+from gsi_util.checkers.checker import Checker
+from gsi_util.mounters.composite_mounter import CompositeMounter
+
+
+class CheckReporter(object):
+  """Output the checker result with formating."""
+
+  _OUTPUT_FORMAT = '{:30}: {}'
+  _ERR_MSE_FORMAT = '    {}'
+  _SUMMARY_NAME = 'summary'
+
+  @staticmethod
+  def _get_pass_str(is_pass):
+    return 'pass' if is_pass else 'fail'
+
+  def _output_result_item(self, result_item):
+    name, result, message = result_item
+    if not self._only_summary:
+      result_str = self._get_pass_str(result)
+      print self._OUTPUT_FORMAT.format(name, result_str)
+      if message:
+        print self._ERR_MSE_FORMAT.format(message)
+    return result
+
+  def _output_summary(self, summary_result):
+    summary_result_str = self._get_pass_str(summary_result)
+    print self._OUTPUT_FORMAT.format(self._SUMMARY_NAME, summary_result_str)
+
+  def __init__(self):
+    self._only_summary = False
+
+  def set_only_summary(self):
+    self._only_summary = True
+
+  def output(self, check_results):
+    all_pass = True
+    for result_item in check_results:
+      item_pass = self._output_result_item(result_item)
+      all_pass = all_pass and item_pass
+    self._output_summary(all_pass)
+
+
+def do_list_check(_):
+  for info in Checker.get_all_check_list():
+    print info.id
+
+
+def do_check_compat(args):
+  logging.info('==== CHECK_COMPAT ====')
+  logging.info('  system=%s vendor=%s', args.system, args.vendor)
+
+  # args.system and args.vendor are required
+  mounter = CompositeMounter()
+  mounter.add_by_mount_target('system', args.system)
+  mounter.add_by_mount_target('vendor', args.vendor)
+
+  logging.debug('Checking ID list: %s', args.ID)
+  check_list = Checker.make_check_list_with_ids(args.ID) if len(
+      args.ID) else Checker.get_all_check_list()
+
+  with mounter as file_accessor:
+    checker = Checker(file_accessor)
+    check_result = checker.check(check_list)
+
+  reporter = CheckReporter()
+  if args.only_summary:
+    reporter.set_only_summary()
+  reporter.output(check_result)
+
+  logging.info('==== DONE ====')
+
+
+DUMP_DESCRIPTION = """'check_compat' command checks compatibility images
+
+You must assign at least one image source by SYSTEM and/or VENDOR.
+Image source could be:
+
+ adb[:SERIAL_NUM]: form the device which be connected with adb
+  image file name: from the given image file, e.g. the file name of a GSI.
+                   If a image file is assigned to be the source of system
+                   image, gsu_util will detect system-as-root automatically.
+      folder name: from the given folder, e.g. the system/vendor folder in an
+                   Android build out folder.
+
+You could use command 'list_check' to query all IDs:
+
+    $ ./gsi_util.py list_check
+
+Here is an examples to check a system.img and a device are compatible:
+
+    $ ./gsi_util.py check_compat --system system.img --vendor adb"""
+
+
+def setup_command_args(parser):
+  """Setup command 'list_check' and 'check_compat'."""
+
+  # command 'list_check'
+  list_check_parser = parser.add_parser(
+      'list_check', help='list all possible checking IDs')
+  list_check_parser.set_defaults(func=do_list_check)
+
+  # command 'check_compat'
+  check_compat_parser = parser.add_parser(
+      'check_compat',
+      help='checks compatibility between a system and a vendor',
+      description=DUMP_DESCRIPTION,
+      formatter_class=argparse.RawTextHelpFormatter)
+  check_compat_parser.add_argument(
+      '--system',
+      type=str,
+      required=True,
+      help='system image file name, folder name or "adb"')
+  check_compat_parser.add_argument(
+      '--vendor',
+      type=str,
+      required=True,
+      help='vendor image file name, folder name or "adb"')
+  check_compat_parser.add_argument(
+      '--only-summary',
+      action='store_true',
+      help='only output the summary result')
+  check_compat_parser.add_argument(
+      'ID',
+      type=str,
+      nargs='*',
+      help='the checking ID to be dumped. Check all if not given')
+  check_compat_parser.set_defaults(func=do_check_compat)
diff --git a/gsi/gsi_util/gsi_util/utils/vintf_utils.py b/gsi/gsi_util/gsi_util/utils/vintf_utils.py
new file mode 100644
index 0000000..d51807e
--- /dev/null
+++ b/gsi/gsi_util/gsi_util/utils/vintf_utils.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""VINTF-related utilities."""
+
+import logging
+
+from gsi_util.utils.cmd_utils import run_command
+
+
+def checkvintf(manifest, matrix):
+  """call checkvintf.
+
+  Args:
+    manifest: manifest file
+    matrix: matrix file
+
+  Returns:
+    A tuple with (check_result, error_message)
+  """
+  logging.debug('checkvintf %s %s...', manifest, matrix)
+
+  # 'read_stdout=True' to disable output
+  (returncode, _, stderrdata) = run_command(
+      ['checkvintf', manifest, matrix],
+      raise_on_error=False,
+      read_stdout=True,
+      read_stderr=True)
+  return (returncode == 0, stderrdata)