From 1cbfd683b8e8358c6e768d59090b1915990f201e Mon Sep 17 00:00:00 2001 From: Logan Chien Date: Wed, 20 Jun 2018 21:33:21 +0800 Subject: [PATCH] sourcedr: Add manifest split analyzer This commit adds a manifest split analysis tool. This tool classifies the Android.bp modules into 3 categories (i.e. system-only modules, vendor-only modules, and both-side module) and list the git repositories that define modules in two or more categories. Test: analyze_manifest_split.py \ -m ${ANDROID_BUILD_TOP}/.repo/manifest.xml \ -b ${ANDROID_BUILD_TOP}/Android.bp Change-Id: I74c4acc3cac8fc2795cbc03806e71685e1910818 --- .../blueprint/analyze_manifest_split.py | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100755 vndk/tools/sourcedr/sourcedr/blueprint/analyze_manifest_split.py diff --git a/vndk/tools/sourcedr/sourcedr/blueprint/analyze_manifest_split.py b/vndk/tools/sourcedr/sourcedr/blueprint/analyze_manifest_split.py new file mode 100755 index 000000000..c281bec29 --- /dev/null +++ b/vndk/tools/sourcedr/sourcedr/blueprint/analyze_manifest_split.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2018 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. +# + +from __future__ import print_function + +import argparse +import collections +import os +import re +import xml.dom.minidom + +from blueprint import RecursiveParser, evaluate_defaults, fill_module_namespaces + + +def parse_manifest_xml(manifest_path): + """Build a dictionary that maps directories into projects.""" + dir_project_dict = {} + parsed_xml = xml.dom.minidom.parse(manifest_path) + projects = parsed_xml.getElementsByTagName('project') + for project in projects: + name = project.getAttribute('name') + path = project.getAttribute('path') + if path: + dir_project_dict[path] = name + else: + dir_project_dict[name] = name + return dir_project_dict + + +class DirProjectMatcher(object): + def __init__(self, dir_project_dict): + self._projects = sorted(dir_project_dict.items(), reverse=True) + self._matcher = re.compile( + '|'.join('(' + re.escape(path) + '(?:/|$))' + for path, project in self._projects)) + + def find(self, path): + match = self._matcher.match(path) + if match: + return self._projects[match.lastindex - 1][1] + return None + + +def parse_blueprint(root_bp_path): + """Parse Android.bp files.""" + parser = RecursiveParser() + parser.parse_file(root_bp_path) + parsed_items = evaluate_defaults(parser.modules) + return fill_module_namespaces(root_bp_path, parsed_items) + + +def _get_property(attrs, *names, **kwargs): + try: + result = attrs + for name in names: + result = result[name] + return result + except KeyError: + return kwargs.get('default', None) + + +class GitProject(object): + def __init__(self): + self.system_only = set() + self.vendor_only = set() + self.both = set() + + def add_module(self, path, rule, attrs): + name = _get_property(attrs, 'name') + ent = (rule, path, name) + + if rule in {'llndk_library', 'hidl_interface'}: + self.both.add(ent) + elif rule.endswith('_binary') or \ + rule.endswith('_library') or \ + rule.endswith('_library_shared') or \ + rule.endswith('_library_static') or \ + rule.endswith('_headers'): + if _get_property(attrs, 'vendor') or \ + _get_property(attrs, 'proprietary') or \ + _get_property(attrs, 'soc_specific') or \ + _get_property(attrs, 'device_specific'): + self.vendor_only.add(ent) + elif _get_property(attrs, 'vendor_available') or \ + _get_property(attrs, 'vndk', 'enabled'): + self.both.add(ent) + else: + self.system_only.add(ent) + + def __repr__(self): + return ('GitProject(' + + 'system_only=' + repr(self.system_only) + ', ' + 'vendor_only=' + repr(self.vendor_only) + ', ' + 'both=' + repr(self.both) + ')') + + +def _parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-b', '--blueprint', required=True, + help='Path to root Android.bp') + parser.add_argument('-m', '--manifest', required=True, + help='Path to repo manifest xml file') + parser.add_argument('--skip-no-overlaps', action='store_true', + help='Skip projects without overlaps') + return parser.parse_args() + + +def _dump_module_set(name, modules): + if not modules: + return + print('\t' + name) + for rule, path, name in sorted(modules): + print('\t\t' + rule, path, name) + + +def main(): + args = _parse_args() + + # Load repo manifest xml file + dir_matcher = DirProjectMatcher(parse_manifest_xml(args.manifest)) + + # Classify Android.bp modules + git_projects = collections.defaultdict(GitProject) + + root_dir = os.path.dirname(os.path.abspath(args.blueprint)) + root_prefix_len = len(root_dir) + 1 + + for rule, attrs in parse_blueprint(args.blueprint): + path = _get_property(attrs, '_path')[root_prefix_len:] + project = dir_matcher.find(path) + git_projects[project].add_module(path, rule, attrs) + + # Print output + for project, modules in sorted(git_projects.items()): + if args.skip_no_overlaps and (int(len(modules.system_only) > 0) + + int(len(modules.vendor_only) > 0) + + int(len(modules.both) > 0)) <= 1: + continue + print(project, len(modules.system_only), len(modules.vendor_only), + len(modules.both)) + _dump_module_set('system_only', modules.system_only) + _dump_module_set('vendor_only', modules.vendor_only) + _dump_module_set('both', modules.both) + + +if __name__ == '__main__': + main()