127 lines
4.3 KiB
Python
Executable File
127 lines
4.3 KiB
Python
Executable File
#!/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 GetApkResources(apk_path):
|
|
"""Returns the types and names of resources packaged in an APK.
|
|
|
|
Args:
|
|
apk_path: path to the APK.
|
|
|
|
Returns:
|
|
The resources in the APK as a list of tuples (type, name). Example:
|
|
[('drawable', 'arrow'), ('layout', 'month_picker'), ...]
|
|
"""
|
|
p = subprocess.Popen(
|
|
['aapt', 'dump', 'resources', apk_path],
|
|
stdout=subprocess.PIPE)
|
|
dump_out, _ = p.communicate()
|
|
assert p.returncode == 0, 'aapt dump failed'
|
|
matches = re.finditer(
|
|
r'^\s+spec resource 0x[0-9a-fA-F]+ [\w.]+:(?P<type>\w+)/(?P<name>\w+)',
|
|
dump_out, re.MULTILINE)
|
|
return [m.group('type', 'name') for m in matches]
|
|
|
|
|
|
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):
|
|
usage = 'usage: %prog [-v] APK_PATH SOURCE_PATH...'
|
|
parser = optparse.OptionParser(usage=usage)
|
|
parser.add_option('-v', help='Show verbose output', action='store_true')
|
|
options, args = parser.parse_args(args=args)
|
|
if len(args) < 2:
|
|
parser.error('must provide APK_PATH and SOURCE_PATH arguments')
|
|
return options.v, args[0], args[1:]
|
|
|
|
|
|
def main(args=None):
|
|
verbose, apk_path, source_paths = ParseArgs(args)
|
|
apk_resources = GetApkResources(apk_path)
|
|
resource_types = list(set([r[0] for r in apk_resources]))
|
|
used_resources = GetUsedResources(source_paths, resource_types)
|
|
unused_resources = set(apk_resources) - set(used_resources)
|
|
undefined_resources = set(used_resources) - set(apk_resources)
|
|
|
|
# aapt dump fails silently. Notify the user if things look wrong.
|
|
if not apk_resources:
|
|
print >> sys.stderr, (
|
|
'Warning: No resources found in the APK. Did you provide the correct '
|
|
'APK path?')
|
|
if not used_resources:
|
|
print >> sys.stderr, (
|
|
'Warning: No resources references 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 in the APK. 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 packaged into the APK:' % len(apk_resources)
|
|
print FormatResources(apk_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()
|