1#!/usr/bin/env python3 2 3# 4# Copyright (C) 2018 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19from __future__ import print_function 20 21import argparse 22import collections 23import os 24import re 25import sys 26import xml.dom.minidom 27 28from blueprint import RecursiveParser, evaluate_defaults, fill_module_namespaces 29 30 31_GROUPS = ['system_only', 'vendor_only', 'both'] 32 33 34def parse_manifest_xml(manifest_path): 35 """Build a dictionary that maps directories into projects.""" 36 dir_project_dict = {} 37 parsed_xml = xml.dom.minidom.parse(manifest_path) 38 projects = parsed_xml.getElementsByTagName('project') 39 for project in projects: 40 name = project.getAttribute('name') 41 path = project.getAttribute('path') 42 if path: 43 dir_project_dict[path] = name 44 else: 45 dir_project_dict[name] = name 46 return dir_project_dict 47 48 49class DirProjectMatcher(object): 50 def __init__(self, dir_project_dict): 51 self._projects = sorted(dir_project_dict.items(), reverse=True) 52 self._matcher = re.compile( 53 '|'.join('(' + re.escape(path) + '(?:/|$))' 54 for path, project in self._projects)) 55 56 def find(self, path): 57 match = self._matcher.match(path) 58 if match: 59 return self._projects[match.lastindex - 1][1] 60 return None 61 62 63def parse_blueprint(root_bp_path): 64 """Parse Android.bp files.""" 65 parser = RecursiveParser() 66 parser.parse_file(root_bp_path) 67 parsed_items = evaluate_defaults(parser.modules) 68 return fill_module_namespaces(root_bp_path, parsed_items) 69 70 71def _get_property(attrs, *names, **kwargs): 72 try: 73 result = attrs 74 for name in names: 75 result = result[name] 76 return result 77 except KeyError: 78 return kwargs.get('default', None) 79 80 81class GitProject(object): 82 def __init__(self): 83 self.system_only = set() 84 self.vendor_only = set() 85 self.both = set() 86 87 def add_module(self, path, rule, attrs): 88 name = _get_property(attrs, 'name') 89 ent = (rule, path, name) 90 91 if rule in {'llndk_library', 'hidl_interface'}: 92 self.both.add(ent) 93 elif rule.endswith('_binary') or \ 94 rule.endswith('_library') or \ 95 rule.endswith('_library_shared') or \ 96 rule.endswith('_library_static') or \ 97 rule.endswith('_headers'): 98 if _get_property(attrs, 'vendor') or \ 99 _get_property(attrs, 'proprietary') or \ 100 _get_property(attrs, 'soc_specific') or \ 101 _get_property(attrs, 'device_specific'): 102 self.vendor_only.add(ent) 103 elif _get_property(attrs, 'vendor_available') or \ 104 _get_property(attrs, 'vndk', 'enabled'): 105 self.both.add(ent) 106 else: 107 self.system_only.add(ent) 108 109 def __repr__(self): 110 return ('GitProject(' + 111 'system_only=' + repr(self.system_only) + ', ' 112 'vendor_only=' + repr(self.vendor_only) + ', ' 113 'both=' + repr(self.both) + ')') 114 115 116def _parse_args(): 117 parser = argparse.ArgumentParser() 118 parser.add_argument('-b', '--blueprint', required=True, 119 help='Path to root Android.bp') 120 parser.add_argument('-m', '--manifest', required=True, 121 help='Path to repo manifest xml file') 122 group = parser.add_mutually_exclusive_group() 123 group.add_argument('--skip-no-overlaps', action='store_true', 124 help='Skip projects without overlaps') 125 group.add_argument('--has-group', choices=_GROUPS, 126 help='List projects that some modules are in the group') 127 group.add_argument('--only-has-group', choices=_GROUPS, 128 help='List projects that all modules are in the group') 129 group.add_argument('--without-group', choices=_GROUPS, 130 help='List projects that no modules are in the group') 131 return parser.parse_args() 132 133 134def _dump_module_set(name, modules): 135 if not modules: 136 return 137 print('\t' + name) 138 for rule, path, name in sorted(modules): 139 print('\t\t' + rule, path, name) 140 141 142def main(): 143 args = _parse_args() 144 145 # Load repo manifest xml file 146 dir_matcher = DirProjectMatcher(parse_manifest_xml(args.manifest)) 147 148 # Classify Android.bp modules 149 git_projects = collections.defaultdict(GitProject) 150 151 root_dir = os.path.dirname(os.path.abspath(args.blueprint)) 152 root_prefix_len = len(root_dir) + 1 153 154 has_error = False 155 156 for rule, attrs in parse_blueprint(args.blueprint): 157 path = _get_property(attrs, '_path')[root_prefix_len:] 158 project = dir_matcher.find(path) 159 if project is None: 160 print('error: Path {!r} does not belong to any git projects.' 161 .format(path), file=sys.stderr) 162 has_error = True 163 continue 164 git_projects[project].add_module(path, rule, attrs) 165 166 # Print output 167 total_projects = 0 168 for project, modules in sorted(git_projects.items()): 169 if args.skip_no_overlaps: 170 if (int(len(modules.system_only) > 0) + 171 int(len(modules.vendor_only) > 0) + 172 int(len(modules.both) > 0)) <= 1: 173 continue 174 elif args.has_group: 175 if not getattr(modules, args.has_group): 176 continue 177 elif args.only_has_group: 178 if any(getattr(modules, group) 179 for group in _GROUPS if group != args.only_has_group): 180 continue 181 if not getattr(modules, args.only_has_group): 182 continue 183 elif args.without_group: 184 if getattr(modules, args.without_group): 185 continue 186 187 print(project, len(modules.system_only), len(modules.vendor_only), 188 len(modules.both)) 189 _dump_module_set('system_only', modules.system_only) 190 _dump_module_set('vendor_only', modules.vendor_only) 191 _dump_module_set('both', modules.both) 192 193 total_projects += 1 194 195 print('Total:', total_projects) 196 197 if has_error: 198 sys.exit(2) 199 200if __name__ == '__main__': 201 main() 202