#!/usr/bin/env python
# Copyright 2013 The Chromium Authors
# 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()