blob: eb686cecc3b8c8c876ab566d158657430c47c523 [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(
Remi NGUYEN VAN0bd90f12022-08-10 20:15:46 +090031 'jars', nargs='+',
32 help='Path to pre-jarjar JAR. Multiple jars can be specified.')
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +090033 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(
Remi NGUYEN VAN0bd90f12022-08-10 20:15:46 +090040 '--apistubs', action='append', default=[],
41 help='Path to API stubs jar. Classes that are API will not be jarjared. Can be repeated to '
42 'specify multiple jars.')
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +090043 parser.add_argument(
Remi NGUYEN VAN0bd90f12022-08-10 20:15:46 +090044 '--unsupportedapi',
45 help='Column(:)-separated paths to UnsupportedAppUsage hidden API .txt lists. '
46 'Classes that have UnsupportedAppUsage API will not be jarjared.')
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +090047 parser.add_argument(
Remi NGUYEN VAN0bd90f12022-08-10 20:15:46 +090048 '--excludes', action='append', default=[],
49 help='Path to files listing classes that should not be jarjared. Can be repeated to '
50 'specify multiple files.'
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +090051 'Each file should contain one full-match regex per line. Empty lines or lines '
52 'starting with "#" are ignored.')
53 return parser.parse_args(argv)
54
55
56def _list_toplevel_jar_classes(jar):
57 """List all classes in a .class .jar file that are not inner classes."""
58 return {_get_toplevel_class(c) for c in _list_jar_classes(jar)}
59
60def _list_jar_classes(jar):
61 with ZipFile(jar, 'r') as zip:
62 files = zip.namelist()
63 assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
64 'expected an intermediate zip of .class files'
65 class_len = len('.class')
66 return [f.replace('/', '.')[:-class_len] for f in files
67 if f.endswith('.class') and not f.endswith('/package-info.class')]
68
69
70def _list_hiddenapi_classes(txt_file):
71 out = set()
72 with open(txt_file, 'r') as f:
73 for line in f:
74 if not line.strip():
75 continue
76 assert line.startswith('L') and ';' in line, f'Class name not recognized: {line}'
77 clazz = line.replace('/', '.').split(';')[0][1:]
78 out.add(_get_toplevel_class(clazz))
79 return out
80
81
82def _get_toplevel_class(clazz):
83 """Return the name of the toplevel (not an inner class) enclosing class of the given class."""
84 if '$' not in clazz:
85 return clazz
86 return clazz.split('$')[0]
87
88
89def _get_excludes(path):
90 out = []
91 with open(path, 'r') as f:
92 for line in f:
93 stripped = line.strip()
94 if not stripped or stripped.startswith('#'):
95 continue
96 out.append(re.compile(stripped))
97 return out
98
99
100def make_jarjar_rules(args):
101 excluded_classes = set()
102 for apistubs_file in args.apistubs:
103 excluded_classes.update(_list_toplevel_jar_classes(apistubs_file))
104
Remi NGUYEN VAN0bd90f12022-08-10 20:15:46 +0900105 unsupportedapi_files = (args.unsupportedapi and args.unsupportedapi.split(':')) or []
106 for unsupportedapi_file in unsupportedapi_files:
107 if unsupportedapi_file:
108 excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +0900109
110 exclude_regexes = []
111 for exclude_file in args.excludes:
112 exclude_regexes.extend(_get_excludes(exclude_file))
113
114 with open(args.output, 'w') as outfile:
115 for jar in args.jars:
116 jar_classes = _list_jar_classes(jar)
117 jar_classes.sort()
118 for clazz in jar_classes:
Remi NGUYEN VAN3ba00cb2022-07-21 18:57:41 +0900119 if (not clazz.startswith(args.prefix + '.') and
120 _get_toplevel_class(clazz) not in excluded_classes and
Remi NGUYEN VAN11f162b2022-05-24 16:47:33 +0900121 not any(r.fullmatch(clazz) for r in exclude_regexes)):
122 outfile.write(f'rule {clazz} {args.prefix}.@0\n')
123 # Also include jarjar rules for unit tests of the class, so the package matches
124 outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
125 outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
126
127
128def _main():
129 # Pass in None to use argv
130 args = parse_arguments(None)
131 make_jarjar_rules(args)
132
133
134if __name__ == '__main__':
135 _main()