Home | History | Annotate | Download | only in android
      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