123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- #!/usr/bin/env python
- # Copyright (c) 2013 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Lists unused Java strings and other resources."""
- from __future__ import print_function
- import optparse
- import re
- import subprocess
- import sys
- def GetLibraryResources(r_txt_paths):
- """Returns the resources packaged in a list of libraries.
- Args:
- r_txt_paths: paths to each library's generated R.txt file which lists the
- resources it contains.
- Returns:
- The resources in the libraries as a list of tuples (type, name). Example:
- [('drawable', 'arrow'), ('layout', 'month_picker'), ...]
- """
- resources = []
- for r_txt_path in r_txt_paths:
- with open(r_txt_path, 'r') as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- data_type, res_type, name, _ = line.split(None, 3)
- assert data_type in ('int', 'int[]')
- # Hide attrs, which are redundant with styleables and always appear
- # unused, and hide ids, which are innocuous even if unused.
- if res_type in ('attr', 'id'):
- continue
- resources.append((res_type, name))
- return resources
- def GetUsedResources(source_paths, resource_types):
- """Returns the types and names of resources used in Java or resource files.
- Args:
- source_paths: a list of files or folders collectively containing all the
- Java files, resource files, and the AndroidManifest.xml.
- resource_types: a list of resource types to look for. Example:
- ['string', 'drawable']
- Returns:
- The resources referenced by the Java and resource files as a list of tuples
- (type, name). Example:
- [('drawable', 'app_icon'), ('layout', 'month_picker'), ...]
- """
- type_regex = '|'.join(map(re.escape, resource_types))
- patterns = [
- # @+drawable/id and @drawable/id
- r'@((\+?))(%s)/(\w+)' % type_regex,
- # package.R.style.id
- r'\b((\w+\.)*)R\.(%s)\.(\w+)' % type_regex,
- # <style name="child" parent="id">
- r'<(())(%s).*parent="(\w+)">' % type_regex,
- ]
- resources = []
- for pattern in patterns:
- p = subprocess.Popen(
- ['grep', '-REIhoe', pattern] + source_paths,
- stdout=subprocess.PIPE)
- grep_out, grep_err = p.communicate()
- # Check stderr instead of return code, since return code is 1 when no
- # matches are found.
- assert not grep_err, 'grep failed'
- matches = re.finditer(pattern, grep_out)
- for match in matches:
- package = match.group(1)
- if package == 'android.':
- continue
- type_ = match.group(3)
- name = match.group(4)
- resources.append((type_, name))
- return resources
- def FormatResources(resources):
- """Formats a list of resources for printing.
- Args:
- resources: a list of resources, given as (type, name) tuples.
- """
- return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)])
- def ParseArgs(args):
- parser = optparse.OptionParser()
- parser.add_option('-v', help='Show verbose output', action='store_true')
- parser.add_option('-s', '--source-path', help='Specify a source folder path '
- '(e.g. ui/android/java)', action='append', default=[])
- parser.add_option('-r', '--r-txt-path', help='Specify a "first-party" R.txt '
- 'file (e.g. out/Debug/content_shell_apk/R.txt)',
- action='append', default=[])
- parser.add_option('-t', '--third-party-r-txt-path', help='Specify an R.txt '
- 'file for a third party library', action='append',
- default=[])
- options, args = parser.parse_args(args=args)
- if args:
- parser.error('positional arguments not allowed')
- if not options.source_path:
- parser.error('at least one source folder path must be specified with -s')
- if not options.r_txt_path:
- parser.error('at least one R.txt path must be specified with -r')
- return (options.v, options.source_path, options.r_txt_path,
- options.third_party_r_txt_path)
- def main(args=None):
- verbose, source_paths, r_txt_paths, third_party_r_txt_paths = ParseArgs(args)
- defined_resources = (set(GetLibraryResources(r_txt_paths)) -
- set(GetLibraryResources(third_party_r_txt_paths)))
- resource_types = list(set([r[0] for r in defined_resources]))
- used_resources = set(GetUsedResources(source_paths, resource_types))
- unused_resources = defined_resources - used_resources
- undefined_resources = used_resources - defined_resources
- # aapt dump fails silently. Notify the user if things look wrong.
- if not defined_resources:
- print(
- 'Warning: No resources found. Did you provide the correct R.txt paths?',
- file=sys.stderr)
- if not used_resources:
- print(
- 'Warning: No resources referenced from Java or resource files. Did you '
- 'provide the correct source paths?',
- file=sys.stderr)
- if undefined_resources:
- print(
- 'Warning: found %d "undefined" resources that are referenced by Java '
- 'files or by other resources, but are not defined anywhere. Run with '
- '-v to see them.' % len(undefined_resources),
- file=sys.stderr)
- if verbose:
- print('%d undefined resources:' % len(undefined_resources))
- print(FormatResources(undefined_resources), '\n')
- print('%d resources defined:' % len(defined_resources))
- print(FormatResources(defined_resources), '\n')
- print('%d used resources:' % len(used_resources))
- print(FormatResources(used_resources), '\n')
- print('%d unused resources:' % len(unused_resources))
- print(FormatResources(unused_resources))
- if __name__ == '__main__':
- main()
|