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
22from __future__ import print_function
23
24import argparse
25import itertools
26import sys
27
28from blueprint import RecursiveParser, evaluate_defaults
29
30
31def _is_vndk(module):
32    """Get the `vndk.enabled` module property."""
33    try:
34        return bool(module['vndk']['enabled'])
35    except KeyError:
36        return False
37
38
39def _is_vndk_sp(module):
40    """Get the `vndk.support_system_process` module property."""
41    try:
42        return bool(module['vndk']['support_system_process'])
43    except KeyError:
44        return False
45
46
47def _is_vendor(module):
48    """Get the `vendor` module property."""
49    try:
50        return module.get('vendor', False) or module.get('proprietary', False)
51    except KeyError:
52        return False
53
54
55def _is_vendor_available(module):
56    """Get the `vendor_available` module property."""
57    try:
58        return bool(module['vendor_available'])
59    except KeyError:
60        return False
61
62
63def _has_vendor_variant(module):
64    """Check whether the module is VNDK or vendor available."""
65    return _is_vndk(module) or _is_vendor_available(module)
66
67
68def _get_dependencies(module):
69    """Get module dependencies."""
70
71    shared_libs = set(module.get('shared_libs', []))
72    static_libs = set(module.get('static_libs', []))
73    header_libs = set(module.get('header_libs', []))
74
75    try:
76        target_vendor = module['target']['vendor']
77        shared_libs -= set(target_vendor.get('exclude_shared_libs', []))
78        static_libs -= set(target_vendor.get('exclude_static_libs', []))
79        header_libs -= set(target_vendor.get('exclude_header_libs', []))
80    except KeyError:
81        pass
82
83    return (sorted(shared_libs), sorted(static_libs), sorted(header_libs))
84
85
86def _build_module_dict(modules):
87    """Build module dictionaries that map module names to modules."""
88    all_libs = {}
89    llndk_libs = {}
90
91    for rule, module in modules:
92        name = module.get('name')
93        if name is None:
94            continue
95
96        if rule == 'llndk_library':
97            llndk_libs[name] = (rule, module)
98        if rule in {'llndk_library', 'ndk_library'}:
99            continue
100
101        if rule.endswith('_library') or \
102           rule.endswith('_library_shared') or \
103           rule.endswith('_library_static') or \
104           rule.endswith('_headers'):
105            all_libs[name] = (rule, module)
106
107        if rule == 'hidl_interface':
108            all_libs[name] = (rule, module)
109            all_libs[name + '-adapter-helper'] = (rule, module)
110            module['vendor_available'] = True
111
112    return (all_libs, llndk_libs)
113
114
115def _check_module_deps(all_libs, llndk_libs, module):
116    """Check the dependencies of a module."""
117
118    bad_deps = set()
119    shared_deps, static_deps, header_deps = _get_dependencies(module)
120
121    # Check vendor module dependencies requirements.
122    for dep_name in itertools.chain(shared_deps, static_deps, header_deps):
123        if dep_name in llndk_libs:
124            continue
125        dep_module = all_libs[dep_name][1]
126        if _is_vendor(dep_module):
127            continue
128        if _is_vendor_available(dep_module):
129            continue
130        if _is_vndk(dep_module) and not _is_vendor(module):
131            continue
132        bad_deps.add(dep_name)
133
134    # Check VNDK dependencies requirements.
135    if _is_vndk(module) and not _is_vendor(module):
136        is_vndk_sp = _is_vndk_sp(module)
137        for dep_name in shared_deps:
138            if dep_name in llndk_libs:
139                continue
140            dep_module = all_libs[dep_name][1]
141            if not _is_vndk(dep_module):
142                # VNDK must be self-contained.
143                bad_deps.add(dep_name)
144                break
145            if is_vndk_sp and not _is_vndk_sp(dep_module):
146                # VNDK-SP must be self-contained.
147                bad_deps.add(dep_name)
148                break
149
150    return bad_deps
151
152
153def _check_modules_deps(modules):
154    """Check the dependencies of modules."""
155
156    all_libs, llndk_libs = _build_module_dict(modules)
157
158    # Check the dependencies of modules
159    all_bad_deps = []
160    for name, (_, module) in all_libs.items():
161        if not _has_vendor_variant(module) and not _is_vendor(module):
162            continue
163
164        bad_deps = _check_module_deps(all_libs, llndk_libs, module)
165
166        if bad_deps:
167            all_bad_deps.append((name, sorted(bad_deps)))
168
169    return sorted(all_bad_deps)
170
171
172def _parse_args():
173    """Parse command line options."""
174    parser = argparse.ArgumentParser()
175    parser.add_argument('root_bp', help='android source tree root')
176    return parser.parse_args()
177
178
179def main():
180    """Main function."""
181
182    args = _parse_args()
183
184    parser = RecursiveParser()
185    parser.parse_file(args.root_bp)
186
187    all_bad_deps = _check_modules_deps(evaluate_defaults(parser.modules))
188    for name, bad_deps in all_bad_deps:
189        print('ERROR: {!r} must not depend on {}'.format(name, bad_deps),
190              file=sys.stderr)
191
192    if all_bad_deps:
193        sys.exit(1)
194
195
196if __name__ == '__main__':
197    main()
198