blob: 4c2cf5405771f29e31da7c7a7caa567186f3f04d [file] [log] [blame]
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +09001#
2# Copyright (C) 2022 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 generates jarjar rule files to add a jarjar prefix to all classes, except those
17that are API, unsupported API or otherwise excluded."""
18
19import argparse
20import io
21import re
22import subprocess
23from xml import sax
24from xml.sax.handler import ContentHandler
25from zipfile import ZipFile
26
27
28def parse_arguments(argv):
29 parser = argparse.ArgumentParser()
30 parser.add_argument(
31 '--jars', nargs='+',
32 help='Path to pre-jarjar JAR. Can be followed by multiple space-separated paths.')
33 parser.add_argument(
34 '--prefix', required=True,
35 help='Package prefix to use for jarjared classes, '
36 'for example "com.android.connectivity" (does not end with a dot).')
37 parser.add_argument(
38 '--output', required=True, help='Path to output jarjar rules file.')
39 parser.add_argument(
40 '--apistubs', nargs='*', default=[],
41 help='Path to API stubs jar. Classes that are API will not be jarjared. Can be followed by '
42 'multiple space-separated paths.')
43 parser.add_argument(
44 '--unsupportedapi', nargs='*', default=[],
45 help='Path to UnsupportedAppUsage hidden API .txt lists. '
46 'Classes that have UnsupportedAppUsage API will not be jarjared. Can be followed by '
47 'multiple space-separated paths.')
48 parser.add_argument(
49 '--excludes', nargs='*', default=[],
50 help='Path to files listing classes that should not be jarjared. Can be followed by '
51 'multiple space-separated paths. '
52 'Each file should contain one full-match regex per line. Empty lines or lines '
53 'starting with "#" are ignored.')
54 return parser.parse_args(argv)
55
56
57def _list_toplevel_jar_classes(jar):
58 """List all classes in a .class .jar file that are not inner classes."""
59 return {_get_toplevel_class(c) for c in _list_jar_classes(jar)}
60
61def _list_jar_classes(jar):
62 with ZipFile(jar, 'r') as zip:
63 files = zip.namelist()
64 assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
65 'expected an intermediate zip of .class files'
66 class_len = len('.class')
67 return [f.replace('/', '.')[:-class_len] for f in files
68 if f.endswith('.class') and not f.endswith('/package-info.class')]
69
70
71def _list_hiddenapi_classes(txt_file):
72 out = set()
73 with open(txt_file, 'r') as f:
74 for line in f:
75 if not line.strip():
76 continue
77 assert line.startswith('L') and ';' in line, f'Class name not recognized: {line}'
78 clazz = line.replace('/', '.').split(';')[0][1:]
79 out.add(_get_toplevel_class(clazz))
80 return out
81
82
83def _get_toplevel_class(clazz):
84 """Return the name of the toplevel (not an inner class) enclosing class of the given class."""
85 if '$' not in clazz:
86 return clazz
87 return clazz.split('$')[0]
88
89
90def _get_excludes(path):
91 out = []
92 with open(path, 'r') as f:
93 for line in f:
94 stripped = line.strip()
95 if not stripped or stripped.startswith('#'):
96 continue
97 out.append(re.compile(stripped))
98 return out
99
100
101def make_jarjar_rules(args):
102 excluded_classes = set()
103 for apistubs_file in args.apistubs:
104 excluded_classes.update(_list_toplevel_jar_classes(apistubs_file))
105
106 for unsupportedapi_file in args.unsupportedapi:
107 excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
108
109 exclude_regexes = []
110 for exclude_file in args.excludes:
111 exclude_regexes.extend(_get_excludes(exclude_file))
112
113 with open(args.output, 'w') as outfile:
114 for jar in args.jars:
115 jar_classes = _list_jar_classes(jar)
116 jar_classes.sort()
117 for clazz in jar_classes:
118 if (_get_toplevel_class(clazz) not in excluded_classes and
119 not any(r.fullmatch(clazz) for r in exclude_regexes)):
120 outfile.write(f'rule {clazz} {args.prefix}.@0\n')
121 # Also include jarjar rules for unit tests of the class, so the package matches
122 outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
123 outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
124
125
126def _main():
127 # Pass in None to use argv
128 args = parse_arguments(None)
129 make_jarjar_rules(args)
130
131
132if __name__ == '__main__':
133 _main()