1 #!/usr/bin/python 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 4 # found in the LICENSE file. 5 6 """Lists unused Java strings and other resources.""" 7 8 import optparse 9 import re 10 import subprocess 11 import sys 12 13 14 def GetLibraryResources(r_txt_paths): 15 """Returns the resources packaged in a list of libraries. 16 17 Args: 18 r_txt_paths: paths to each library's generated R.txt file which lists the 19 resources it contains. 20 21 Returns: 22 The resources in the libraries as a list of tuples (type, name). Example: 23 [('drawable', 'arrow'), ('layout', 'month_picker'), ...] 24 """ 25 resources = [] 26 for r_txt_path in r_txt_paths: 27 with open(r_txt_path, 'r') as f: 28 for line in f: 29 line = line.strip() 30 if not line: 31 continue 32 data_type, res_type, name, _ = line.split(None, 3) 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 40 41 42 def GetUsedResources(source_paths, resource_types): 43 """Returns the types and names of resources used in Java or resource files. 44 45 Args: 46 source_paths: a list of files or folders collectively containing all the 47 Java files, resource files, and the AndroidManifest.xml. 48 resource_types: a list of resource types to look for. Example: 49 ['string', 'drawable'] 50 51 Returns: 52 The resources referenced by the Java and resource files as a list of tuples 53 (type, name). Example: 54 [('drawable', 'app_icon'), ('layout', 'month_picker'), ...] 55 """ 56 type_regex = '|'.join(map(re.escape, resource_types)) 57 patterns = [r'@(())(%s)/(\w+)' % type_regex, 58 r'\b((\w+\.)*)R\.(%s)\.(\w+)' % type_regex] 59 resources = [] 60 for pattern in patterns: 61 p = subprocess.Popen( 62 ['grep', '-REIhoe', pattern] + source_paths, 63 stdout=subprocess.PIPE) 64 grep_out, grep_err = p.communicate() 65 # Check stderr instead of return code, since return code is 1 when no 66 # matches are found. 67 assert not grep_err, 'grep failed' 68 matches = re.finditer(pattern, grep_out) 69 for match in matches: 70 package = match.group(1) 71 if package == 'android.': 72 continue 73 type_ = match.group(3) 74 name = match.group(4) 75 resources.append((type_, name)) 76 return resources 77 78 79 def FormatResources(resources): 80 """Formats a list of resources for printing. 81 82 Args: 83 resources: a list of resources, given as (type, name) tuples. 84 """ 85 return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)]) 86 87 88 def ParseArgs(args): 89 parser = optparse.OptionParser() 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=[]) 99 options, args = parser.parse_args(args=args) 100 if args: 101 parser.error('positional arguments not allowed') 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) 108 109 110 def main(args=None): 111 verbose, source_paths, r_txt_paths, third_party_r_txt_paths = ParseArgs(args) 112 defined_resources = (set(GetLibraryResources(r_txt_paths)) - 113 set(GetLibraryResources(third_party_r_txt_paths))) 114 resource_types = list(set([r[0] for r in defined_resources])) 115 used_resources = set(GetUsedResources(source_paths, resource_types)) 116 unused_resources = defined_resources - used_resources 117 undefined_resources = used_resources - defined_resources 118 119 # aapt dump fails silently. Notify the user if things look wrong. 120 if not defined_resources: 121 print >> sys.stderr, ( 122 'Warning: No resources found. Did you provide the correct R.txt paths?') 123 if not used_resources: 124 print >> sys.stderr, ( 125 'Warning: No resources referenced from Java or resource files. Did you ' 126 'provide the correct source paths?') 127 if undefined_resources: 128 print >> sys.stderr, ( 129 'Warning: found %d "undefined" resources that are referenced by Java ' 130 'files or by other resources, but are not defined anywhere. Run with ' 131 '-v to see them.' % len(undefined_resources)) 132 133 if verbose: 134 print '%d undefined resources:' % len(undefined_resources) 135 print FormatResources(undefined_resources), '\n' 136 print '%d resources defined:' % len(defined_resources) 137 print FormatResources(defined_resources), '\n' 138 print '%d used resources:' % len(used_resources) 139 print FormatResources(used_resources), '\n' 140 print '%d unused resources:' % len(unused_resources) 141 print FormatResources(unused_resources) 142 143 144 if __name__ == '__main__': 145 main() 146