• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2024 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16""" This script collects all of the classes fully-qualified path provided in a JAR, applies
17post-processing then bundles that list in a java-compilable file."""
18
19import argparse
20from pathlib import Path
21import re
22from zipfile import ZipFile
23
24
25def parse_arguments(argv):
26    parser = argparse.ArgumentParser()
27    parser.add_argument(
28        'jars', nargs='+',
29        help='Path to JARs. Multiple jars can be specified.')
30    parser.add_argument(
31        '--prefix', required=True,
32        help='Package prefix to use to append to the classes, '
33             'for example "com.android.connectivity" (does not end with a dot).')
34    parser.add_argument(
35        '--output', required=True, help='Path to output java-compilable file.')
36    parser.add_argument(
37        '--prefix-excludes', action='append', default=[],
38        help='Path to files listing classes that should not have their package prefixed. '
39             'Can be repeated to specify multiple files.'
40             'Each file should contain one full-match regex per line. Empty lines or lines '
41             'starting with "#" are ignored.')
42    parser.add_argument(
43        '--excludes', action='append', default=[],
44        help='Path to files listing classes that should be excluded '
45             'Can be repeated to specify multiple files.'
46             'Each file should contain one full-match regex per line. Empty lines or lines '
47             'starting with "#" are ignored.')
48    parser.add_argument(
49        '--java-package', default="android.net.http",
50        help='The package name of the generated java file.')
51    return parser.parse_args(argv)
52
53
54def _list_jar_classes(jar):
55    with ZipFile(jar, 'r') as zip:
56        files = zip.namelist()
57        assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
58                                           'expected an intermediate zip of .class files'
59        class_suffix = '.class'
60        return [f.removesuffix(class_suffix).replace('/', '.') for f in files
61                if f.endswith(class_suffix) and not f.endswith('/package-info.class')]
62
63
64def _get_excludes(path):
65    out = []
66    with open(path, 'r') as f:
67        for line in f:
68            stripped = line.strip()
69            if not stripped or stripped.startswith('#'):
70                continue
71            out.append(re.compile(stripped))
72    return out
73
74
75def _write_classes_list(package_name, output_path, classes_list):
76    with open(output_path, 'w') as outfile:
77        outfile.write(
78            f"""
79package {package_name};
80
81public class {Path(output_path).stem} {{
82    public static final String[] ALL_CLASSES = {{{",".join(classes_list)}}};
83}}
84""")
85
86
87def make_classes_list(args):
88    exclude_regexes = []
89    for exclude_file in args.prefix_excludes:
90        exclude_regexes.extend(_get_excludes(exclude_file))
91
92    exclude_collecting_regexes = []
93    for exclude_file in args.excludes:
94        exclude_collecting_regexes.extend(_get_excludes(exclude_file))
95
96    processed_classes = []
97    for jar in args.jars:
98        jar_classes = _list_jar_classes(jar)
99        jar_classes.sort()
100        for clazz in jar_classes:
101            if any(r.fullmatch(clazz) for r in exclude_collecting_regexes):
102                continue
103
104            if (clazz.startswith(args.prefix + '.') or
105                    any(r.fullmatch(clazz) for r in exclude_regexes)):
106                processed_classes.append(f'"{clazz}"')
107            else:
108                processed_classes.append(f'"{args.prefix}.{clazz}"')
109    return processed_classes
110
111
112def _main():
113    # Pass in None to use argv
114    args = parse_arguments(None)
115    _write_classes_list(args.java_package, args.output, make_classes_list(args))
116
117
118if __name__ == '__main__':
119    _main()
120