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
19"""This script scans all Android.bp in an android source tree and check the
20correctness of dependencies."""
21
22import copy
23
24from blueprint import RecursiveParser, evaluate_defaults, fill_module_namespaces
25
26
27class Module(object):
28    """The class for Blueprint module definition."""
29
30    def __init__(self, rule, attrs):
31        """Initialize from a module definition."""
32        self.rule = rule
33        self._attrs = attrs
34
35
36    def get_property(self, *names, **kwargs):
37        """Get a property in the module definition."""
38        try:
39            result = self._attrs
40            for name in names:
41                result = result[name]
42            return result
43        except KeyError:
44            return kwargs.get('default', None)
45
46
47    def is_vndk(self):
48        """Check whether this module is a VNDK shared library."""
49        return bool(self.get_property('vndk', 'enabled'))
50
51
52    def is_vndk_sp(self):
53        """Check whether this module is a VNDK-SP shared library."""
54        return bool(self.get_property('vndk', 'support_system_process'))
55
56
57    def is_vendor(self):
58        """Check whether this module is a vendor module."""
59        return bool(self.get_property('vendor') or
60                    self.get_property('proprietary'))
61
62
63    def is_vendor_available(self):
64        """Check whether this module is vendor available."""
65        return bool(self.get_property('vendor_available'))
66
67
68    def has_vendor_variant(self):
69        """Check whether the module is VNDK or vendor available."""
70        return self.is_vndk() or self.is_vendor_available()
71
72
73    def get_name(self):
74        """Get the module name."""
75        return self.get_property('name')
76
77
78    def get_dependencies(self):
79        """Get module dependencies."""
80
81        shared_libs = set(self.get_property('shared_libs', default=[]))
82        static_libs = set(self.get_property('static_libs', default=[]))
83        header_libs = set(self.get_property('header_libs', default=[]))
84
85        target_vendor = self.get_property('target', 'vendor')
86        if target_vendor:
87            shared_libs -= set(target_vendor.get('exclude_shared_libs', []))
88            static_libs -= set(target_vendor.get('exclude_static_libs', []))
89            header_libs -= set(target_vendor.get('exclude_header_libs', []))
90
91        return (sorted(shared_libs), sorted(static_libs), sorted(header_libs))
92
93
94class ModuleClassifier(object):
95    """Dictionaries (all_libs, vndk_libs, vndk_sp_libs,
96    vendor_available_libs, and llndk_libs) for modules."""
97
98
99    def __init__(self):
100        self.all_libs = {}
101        self.vndk_libs = {}
102        self.vndk_sp_libs = {}
103        self.vendor_available_libs = {}
104        self.llndk_libs = {}
105
106
107    def add_module(self, name, module):
108        """Add a module to one or more dictionaries."""
109
110        # If this is an llndk_library, add the module to llndk_libs and return.
111        if module.rule == 'llndk_library':
112            if name in self.llndk_libs:
113                raise ValueError('lldnk name {!r} conflicts'.format(name))
114            self.llndk_libs[name] = module
115            return
116
117        # Check the module name uniqueness.
118        prev_module = self.all_libs.get(name)
119        if prev_module:
120            # If there are two modules with the same module name, pick the one
121            # without _prebuilt_library_shared.
122            if module.rule.endswith('_prebuilt_library_shared'):
123                return
124            if not prev_module.rule.endswith('_prebuilt_library_shared'):
125                raise ValueError('module name {!r} conflicts'.format(name))
126
127        # Add the module to dictionaries.
128        self.all_libs[name] = module
129
130        if module.is_vndk():
131            self.vndk_libs[name] = module
132
133        if module.is_vndk_sp():
134            self.vndk_sp_libs[name] = module
135
136        if module.is_vendor_available():
137            self.vendor_available_libs[name] = module
138
139
140    def _add_modules_from_parsed_pairs(self, parsed_items, namespaces):
141        """Add modules from the parsed (rule, attrs) pairs."""
142
143        for rule, attrs in parsed_items:
144            name = attrs.get('name')
145            if name is None:
146                continue
147
148            namespace = attrs.get('_namespace')
149            if namespace not in namespaces:
150                continue
151
152            if rule == 'llndk_library':
153                self.add_module(name, Module(rule, attrs))
154            if rule in {'llndk_library', 'ndk_library'}:
155                continue
156
157            if rule.endswith('_library') or \
158               rule.endswith('_library_shared') or \
159               rule.endswith('_library_static') or \
160               rule.endswith('_headers'):
161                self.add_module(name, Module(rule, attrs))
162                continue
163
164            if rule == 'hidl_interface':
165                attrs['vendor_available'] = True
166                self.add_module(name, Module(rule, attrs))
167
168                adapter_module_name = name + '-adapter-helper'
169                adapter_module_dict = copy.deepcopy(attrs)
170                adapter_module_dict['name'] = adapter_module_name
171                self.add_module(adapter_module_name,
172                                Module(rule, adapter_module_dict))
173                continue
174
175
176    def parse_root_bp(self, root_bp_path, namespaces=None):
177        """Parse blueprint files and add module definitions."""
178
179        namespaces = {''} if namespaces is None else set(namespaces)
180
181        parser = RecursiveParser()
182        parser.parse_file(root_bp_path)
183        parsed_items = evaluate_defaults(parser.modules)
184        parsed_items = fill_module_namespaces(root_bp_path, parsed_items)
185
186        self._add_modules_from_parsed_pairs(parsed_items, namespaces)
187
188
189    @classmethod
190    def create_from_root_bp(cls, root_bp_path, namespaces=None):
191        """Create a ModuleClassifier from a root blueprint file."""
192        result = cls()
193        result.parse_root_bp(root_bp_path, namespaces)
194        return result
195