OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Lists unused Java strings and other resources.""" | 6 """Lists unused Java strings and other resources.""" |
7 | 7 |
8 import optparse | 8 import optparse |
9 import re | 9 import re |
10 import subprocess | 10 import subprocess |
11 import sys | 11 import sys |
12 | 12 |
13 | 13 |
14 def GetApkResources(apk_path): | 14 def GetLibraryResources(r_txt_paths): |
15 """Returns the types and names of resources packaged in an APK. | 15 """Returns the resources packaged in a list of libraries. |
16 | 16 |
17 Args: | 17 Args: |
18 apk_path: path to the APK. | 18 r_txt_paths: paths to each library's generated R.txt file which lists the |
19 resources it contains. | |
19 | 20 |
20 Returns: | 21 Returns: |
21 The resources in the APK as a list of tuples (type, name). Example: | 22 The resources in the libraries as a list of tuples (type, name). Example: |
22 [('drawable', 'arrow'), ('layout', 'month_picker'), ...] | 23 [('drawable', 'arrow'), ('layout', 'month_picker'), ...] |
23 """ | 24 """ |
24 p = subprocess.Popen( | 25 resources = [] |
25 ['aapt', 'dump', 'resources', apk_path], | 26 for r_txt_path in r_txt_paths: |
26 stdout=subprocess.PIPE) | 27 with open(r_txt_path, 'r') as f: |
27 dump_out, _ = p.communicate() | 28 for line in f: |
28 assert p.returncode == 0, 'aapt dump failed' | 29 line = line.strip() |
29 matches = re.finditer( | 30 if not line: |
30 r'^\s+spec resource 0x[0-9a-fA-F]+ [\w.]+:(?P<type>\w+)/(?P<name>\w+)', | 31 continue |
31 dump_out, re.MULTILINE) | 32 data_type, res_type, name, _ = line.split(None, 3) |
cjhopman
2013/09/06 00:38:08
what's the format of these lines in R.txt?
newt (away)
2013/09/06 00:49:50
int string sign_in 0x7f0800ac
int styleable Theme_
| |
32 return [m.group('type', 'name') for m in matches] | 33 assert data_type in ('int', 'int[]') |
34 # Hide attrs, which are redundant with styleables and always appear | |
35 # unused, and hide ids, which are innocuous even if unused. | |
36 if res_type in ('attr', 'id'): | |
37 continue | |
38 resources.append((res_type, name)) | |
39 return resources | |
33 | 40 |
34 | 41 |
35 def GetUsedResources(source_paths, resource_types): | 42 def GetUsedResources(source_paths, resource_types): |
36 """Returns the types and names of resources used in Java or resource files. | 43 """Returns the types and names of resources used in Java or resource files. |
37 | 44 |
38 Args: | 45 Args: |
39 source_paths: a list of files or folders collectively containing all the | 46 source_paths: a list of files or folders collectively containing all the |
40 Java files, resource files, and the AndroidManifest.xml. | 47 Java files, resource files, and the AndroidManifest.xml. |
41 resource_types: a list of resource types to look for. Example: | 48 resource_types: a list of resource types to look for. Example: |
42 ['string', 'drawable'] | 49 ['string', 'drawable'] |
(...skipping 29 matching lines...) Expand all Loading... | |
72 def FormatResources(resources): | 79 def FormatResources(resources): |
73 """Formats a list of resources for printing. | 80 """Formats a list of resources for printing. |
74 | 81 |
75 Args: | 82 Args: |
76 resources: a list of resources, given as (type, name) tuples. | 83 resources: a list of resources, given as (type, name) tuples. |
77 """ | 84 """ |
78 return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)]) | 85 return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)]) |
79 | 86 |
80 | 87 |
81 def ParseArgs(args): | 88 def ParseArgs(args): |
82 usage = 'usage: %prog [-v] APK_PATH SOURCE_PATH...' | 89 parser = optparse.OptionParser() |
83 parser = optparse.OptionParser(usage=usage) | |
84 parser.add_option('-v', help='Show verbose output', action='store_true') | 90 parser.add_option('-v', help='Show verbose output', action='store_true') |
91 parser.add_option('-s', '--source-path', help='Specify a source folder path ' | |
92 '(e.g. ui/android/java)', action='append', default=[]) | |
93 parser.add_option('-r', '--r-txt-path', help='Specify a "first-party" R.txt ' | |
94 'file (e.g. out/Debug/content_shell_apk/R.txt)', | |
95 action='append', default=[]) | |
96 parser.add_option('-t', '--third-party-r-txt-path', help='Specify an R.txt ' | |
97 'file for a third party library', action='append', | |
98 default=[]) | |
85 options, args = parser.parse_args(args=args) | 99 options, args = parser.parse_args(args=args) |
86 if len(args) < 2: | 100 if args: |
87 parser.error('must provide APK_PATH and SOURCE_PATH arguments') | 101 parser.error('positional arguments not allowed') |
88 return options.v, args[0], args[1:] | 102 if not options.source_path: |
103 parser.error('at least one source folder path must be specified with -s') | |
104 if not options.r_txt_path: | |
105 parser.error('at least one R.txt path must be specified with -r') | |
106 return (options.v, options.source_path, options.r_txt_path, | |
107 options.third_party_r_txt_path) | |
89 | 108 |
90 | 109 |
91 def main(args=None): | 110 def main(args=None): |
92 verbose, apk_path, source_paths = ParseArgs(args) | 111 verbose, source_paths, r_txt_paths, third_party_r_txt_paths = ParseArgs(args) |
93 apk_resources = GetApkResources(apk_path) | 112 defined_resources = (set(GetLibraryResources(r_txt_paths)) - |
94 resource_types = list(set([r[0] for r in apk_resources])) | 113 set(GetLibraryResources(third_party_r_txt_paths))) |
95 used_resources = GetUsedResources(source_paths, resource_types) | 114 resource_types = list(set([r[0] for r in defined_resources])) |
96 unused_resources = set(apk_resources) - set(used_resources) | 115 used_resources = set(GetUsedResources(source_paths, resource_types)) |
97 undefined_resources = set(used_resources) - set(apk_resources) | 116 unused_resources = defined_resources - used_resources |
117 undefined_resources = used_resources - defined_resources | |
98 | 118 |
99 # aapt dump fails silently. Notify the user if things look wrong. | 119 # aapt dump fails silently. Notify the user if things look wrong. |
100 if not apk_resources: | 120 if not defined_resources: |
101 print >> sys.stderr, ( | 121 print >> sys.stderr, ( |
102 'Warning: No resources found in the APK. Did you provide the correct ' | 122 'Warning: No resources found. Did you provide the correct R.txt paths?') |
103 'APK path?') | |
104 if not used_resources: | 123 if not used_resources: |
105 print >> sys.stderr, ( | 124 print >> sys.stderr, ( |
106 'Warning: No resources references from Java or resource files. Did you ' | 125 'Warning: No resources referenced from Java or resource files. Did you ' |
107 'provide the correct source paths?') | 126 'provide the correct source paths?') |
108 if undefined_resources: | 127 if undefined_resources: |
109 print >> sys.stderr, ( | 128 print >> sys.stderr, ( |
110 'Warning: found %d "undefined" resources that are referenced by Java ' | 129 'Warning: found %d "undefined" resources that are referenced by Java ' |
111 'files or by other resources, but are not in the APK. Run with -v to ' | 130 'files or by other resources, but are not defined anywhere. Run with ' |
112 'see them.' % len(undefined_resources)) | 131 '-v to see them.' % len(undefined_resources)) |
113 | 132 |
114 if verbose: | 133 if verbose: |
115 print '%d undefined resources:' % len(undefined_resources) | 134 print '%d undefined resources:' % len(undefined_resources) |
116 print FormatResources(undefined_resources), '\n' | 135 print FormatResources(undefined_resources), '\n' |
117 print '%d resources packaged into the APK:' % len(apk_resources) | 136 print '%d resources defined:' % len(defined_resources) |
118 print FormatResources(apk_resources), '\n' | 137 print FormatResources(defined_resources), '\n' |
119 print '%d used resources:' % len(used_resources) | 138 print '%d used resources:' % len(used_resources) |
120 print FormatResources(used_resources), '\n' | 139 print FormatResources(used_resources), '\n' |
121 print '%d unused resources:' % len(unused_resources) | 140 print '%d unused resources:' % len(unused_resources) |
122 print FormatResources(unused_resources) | 141 print FormatResources(unused_resources) |
123 | 142 |
124 | 143 |
125 if __name__ == '__main__': | 144 if __name__ == '__main__': |
126 main() | 145 main() |
OLD | NEW |