Index: tools/android/find_unused_resources.py |
diff --git a/tools/android/find_unused_resources.py b/tools/android/find_unused_resources.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..1e8fa48abc3ce7b29a8ccac66c07f2b324ad4ff7 |
--- /dev/null |
+++ b/tools/android/find_unused_resources.py |
@@ -0,0 +1,145 @@ |
+#!/usr/bin/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.""" |
+ |
+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 = [r'@(())(%s)/(\w+)' % type_regex, |
+ r'\b((\w+\.)*)R\.(%s)\.(\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 >> sys.stderr, ( |
+ 'Warning: No resources found. Did you provide the correct R.txt paths?') |
+ if not used_resources: |
+ print >> sys.stderr, ( |
+ 'Warning: No resources referenced from Java or resource files. Did you ' |
+ 'provide the correct source paths?') |
+ if undefined_resources: |
+ print >> sys.stderr, ( |
+ '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)) |
+ |
+ 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() |