Dirk Pranke | 45acd53 | 2021-03-24 06:52:40 | [diff] [blame] | 1 | #!/usr/bin/env python |
Avi Drissman | eb18c15 | 2022-09-15 20:11:09 | [diff] [blame] | 2 | # Copyright 2013 The Chromium Authors |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Lists unused Java strings and other resources.""" |
| 7 | |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 8 | from __future__ import print_function |
| 9 | |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 10 | import optparse |
| 11 | import re |
| 12 | import subprocess |
| 13 | import sys |
| 14 | |
| 15 | |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 16 | def GetLibraryResources(r_txt_paths): |
| 17 | """Returns the resources packaged in a list of libraries. |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 18 | |
| 19 | Args: |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 20 | r_txt_paths: paths to each library's generated R.txt file which lists the |
| 21 | resources it contains. |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 22 | |
| 23 | Returns: |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 24 | The resources in the libraries as a list of tuples (type, name). Example: |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 25 | [('drawable', 'arrow'), ('layout', 'month_picker'), ...] |
| 26 | """ |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 27 | resources = [] |
| 28 | for r_txt_path in r_txt_paths: |
| 29 | with open(r_txt_path, 'r') as f: |
| 30 | for line in f: |
| 31 | line = line.strip() |
| 32 | if not line: |
| 33 | continue |
| 34 | data_type, res_type, name, _ = line.split(None, 3) |
| 35 | assert data_type in ('int', 'int[]') |
| 36 | # Hide attrs, which are redundant with styleables and always appear |
| 37 | # unused, and hide ids, which are innocuous even if unused. |
| 38 | if res_type in ('attr', 'id'): |
| 39 | continue |
| 40 | resources.append((res_type, name)) |
| 41 | return resources |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 42 | |
| 43 | |
| 44 | def GetUsedResources(source_paths, resource_types): |
| 45 | """Returns the types and names of resources used in Java or resource files. |
| 46 | |
| 47 | Args: |
| 48 | source_paths: a list of files or folders collectively containing all the |
| 49 | Java files, resource files, and the AndroidManifest.xml. |
| 50 | resource_types: a list of resource types to look for. Example: |
| 51 | ['string', 'drawable'] |
| 52 | |
| 53 | Returns: |
| 54 | The resources referenced by the Java and resource files as a list of tuples |
| 55 | (type, name). Example: |
| 56 | [('drawable', 'app_icon'), ('layout', 'month_picker'), ...] |
| 57 | """ |
| 58 | type_regex = '|'.join(map(re.escape, resource_types)) |
Peter Wen | f857c64 | 2017-05-25 17:34:05 | [diff] [blame] | 59 | patterns = [ |
| 60 | # @+drawable/id and @drawable/id |
| 61 | r'@((\+?))(%s)/(\w+)' % type_regex, |
| 62 | # package.R.style.id |
| 63 | r'\b((\w+\.)*)R\.(%s)\.(\w+)' % type_regex, |
| 64 | # <style name="child" parent="id"> |
| 65 | r'<(())(%s).*parent="(\w+)">' % type_regex, |
| 66 | ] |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 67 | resources = [] |
| 68 | for pattern in patterns: |
| 69 | p = subprocess.Popen( |
| 70 | ['grep', '-REIhoe', pattern] + source_paths, |
| 71 | stdout=subprocess.PIPE) |
| 72 | grep_out, grep_err = p.communicate() |
| 73 | # Check stderr instead of return code, since return code is 1 when no |
| 74 | # matches are found. |
| 75 | assert not grep_err, 'grep failed' |
| 76 | matches = re.finditer(pattern, grep_out) |
| 77 | for match in matches: |
| 78 | package = match.group(1) |
| 79 | if package == 'android.': |
| 80 | continue |
| 81 | type_ = match.group(3) |
| 82 | name = match.group(4) |
| 83 | resources.append((type_, name)) |
| 84 | return resources |
| 85 | |
| 86 | |
| 87 | def FormatResources(resources): |
| 88 | """Formats a list of resources for printing. |
| 89 | |
| 90 | Args: |
| 91 | resources: a list of resources, given as (type, name) tuples. |
| 92 | """ |
| 93 | return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)]) |
| 94 | |
| 95 | |
| 96 | def ParseArgs(args): |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 97 | parser = optparse.OptionParser() |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 98 | parser.add_option('-v', help='Show verbose output', action='store_true') |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 99 | parser.add_option('-s', '--source-path', help='Specify a source folder path ' |
| 100 | '(e.g. ui/android/java)', action='append', default=[]) |
| 101 | parser.add_option('-r', '--r-txt-path', help='Specify a "first-party" R.txt ' |
| 102 | 'file (e.g. out/Debug/content_shell_apk/R.txt)', |
| 103 | action='append', default=[]) |
| 104 | parser.add_option('-t', '--third-party-r-txt-path', help='Specify an R.txt ' |
| 105 | 'file for a third party library', action='append', |
| 106 | default=[]) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 107 | options, args = parser.parse_args(args=args) |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 108 | if args: |
| 109 | parser.error('positional arguments not allowed') |
| 110 | if not options.source_path: |
| 111 | parser.error('at least one source folder path must be specified with -s') |
| 112 | if not options.r_txt_path: |
| 113 | parser.error('at least one R.txt path must be specified with -r') |
| 114 | return (options.v, options.source_path, options.r_txt_path, |
| 115 | options.third_party_r_txt_path) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 116 | |
| 117 | |
| 118 | def main(args=None): |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 119 | verbose, source_paths, r_txt_paths, third_party_r_txt_paths = ParseArgs(args) |
| 120 | defined_resources = (set(GetLibraryResources(r_txt_paths)) - |
| 121 | set(GetLibraryResources(third_party_r_txt_paths))) |
| 122 | resource_types = list(set([r[0] for r in defined_resources])) |
| 123 | used_resources = set(GetUsedResources(source_paths, resource_types)) |
| 124 | unused_resources = defined_resources - used_resources |
| 125 | undefined_resources = used_resources - defined_resources |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 126 | |
| 127 | # aapt dump fails silently. Notify the user if things look wrong. |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 128 | if not defined_resources: |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 129 | print( |
| 130 | 'Warning: No resources found. Did you provide the correct R.txt paths?', |
| 131 | file=sys.stderr) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 132 | if not used_resources: |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 133 | print( |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 134 | 'Warning: No resources referenced from Java or resource files. Did you ' |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 135 | 'provide the correct source paths?', |
| 136 | file=sys.stderr) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 137 | if undefined_resources: |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 138 | print( |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 139 | 'Warning: found %d "undefined" resources that are referenced by Java ' |
[email protected] | 0f00e53 | 2013-09-06 20:29:59 | [diff] [blame] | 140 | 'files or by other resources, but are not defined anywhere. Run with ' |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 141 | '-v to see them.' % len(undefined_resources), |
| 142 | file=sys.stderr) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 143 | |
| 144 | if verbose: |
Raul Tambre | 9dd16d2 | 2019-09-22 17:18:52 | [diff] [blame] | 145 | print('%d undefined resources:' % len(undefined_resources)) |
| 146 | print(FormatResources(undefined_resources), '\n') |
| 147 | print('%d resources defined:' % len(defined_resources)) |
| 148 | print(FormatResources(defined_resources), '\n') |
| 149 | print('%d used resources:' % len(used_resources)) |
| 150 | print(FormatResources(used_resources), '\n') |
| 151 | print('%d unused resources:' % len(unused_resources)) |
| 152 | print(FormatResources(unused_resources)) |
[email protected] | c4ff1f5 | 2013-04-11 00:33:12 | [diff] [blame] | 153 | |
| 154 | |
| 155 | if __name__ == '__main__': |
| 156 | main() |