diff --git a/gsi/gsi_util/Android.bp b/gsi/gsi_util/Android.bp index cf72dd9bb..c1b2828fa 100644 --- a/gsi/gsi_util/Android.bp +++ b/gsi/gsi_util/Android.bp @@ -17,6 +17,7 @@ python_binary_host { 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 @@ python_binary_host { required: [ "adb", "avbtool", + "checkvintf", "simg2img", ], version: { diff --git a/gsi/gsi_util/gsi_util.py b/gsi/gsi_util/gsi_util.py index 6dc09311e..81c8ffcad 100755 --- a/gsi/gsi_util/gsi_util.py +++ b/gsi/gsi_util/gsi_util.py @@ -28,7 +28,7 @@ class GsiUtil(object): # 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 000000000..e69de29bb 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 000000000..d3dbede97 --- /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 000000000..cb79cb1c9 --- /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 000000000..1150e6489 --- /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 000000000..d05533f03 --- /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 000000000..d51807e92 --- /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)