blob: d6e52c866ed2baad931aea02dc3e7bede04a978b [file] [log] [blame]
#!/usr/bin/env python
# Copyright 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.
"""This script searches for unused art assets listed in a .grd file.
It uses git grep to look for references to the IDR resource id or the base
filename. If neither is found, the file is reported unused.
Requires a git checkout. Must be run from your checkout's "src" root.
cd /work/chrome/src
tools/resources/ ash/resources/ash_resources.grd
__author__ = ' (James Cook)'
import os
import re
import subprocess
import sys
def GetBaseResourceId(resource_id):
"""Removes common suffixes from a resource ID.
Removes suffixies that may be added by macros like IMAGE_GRID or IMAGE_BORDER.
For example, converts IDR_FOO_LEFT and IDR_FOO_RIGHT to just IDR_FOO.
resource_id: String resource ID.
A string with the base part of the resource ID.
suffixes = [
'_TL', '_T', '_TR',
'_L', '_M', '_R',
'_BL', '_B', '_BR']
# Note: This does not check _HOVER, _PRESSED, _HOT, etc. as those are never
# used in macros.
for suffix in suffixes:
if resource_id.endswith(suffix):
resource_id = resource_id[:-len(suffix)]
return resource_id
def FindFilesWithContents(string_a, string_b):
"""Returns list of paths of files that contain |string_a| or |string_b|.
Uses --name-only to print the file paths. The default behavior of git grep
is to OR together multiple patterns.
string_a: A string to search for (not a regular expression).
string_b: As above.
A list of file paths as strings.
matching_files = subprocess.check_output([
'git', 'grep', '--name-only', '--fixed-strings', '-e', string_a,
'-e', string_b])
files_list = matching_files.split('\n')
# The output ends in a newline, so slice that off.
files_list = files_list[:-1]
return files_list
def GetUnusedResources(grd_filepath):
"""Returns a list of resources that are unused in the code.
Prints status lines to the console because this function is quite slow.
grd_filepath: Path to a .grd file listing resources.
A list of pairs of [resource_id, filepath] for the unused resources.
unused_resources = []
grd_file = open(grd_filepath, 'r')
grd_data =
print 'Checking:'
# Match the resource id and file path out of substrings like:
#"IDR_FOO_123" file="common/foo.png"...
# by matching between the quotation marks.
pattern = re.compile(
r"""name="([^"]*)" # Match resource ID between quotes.
\s* # Run of whitespace, including newlines.
file="([^"]*)" # Match file path between quotes.""",
# Use finditer over the file contents because there may be newlines between
# the name and file attributes.
searched = set()
for result in pattern.finditer(grd_data):
# Extract the IDR resource id and file path.
resource_id =
filepath =
filename = os.path.basename(filepath)
base_resource_id = GetBaseResourceId(resource_id)
# Do not bother repeating searches.
key = (base_resource_id, filename)
if key in searched:
# Print progress as we go along.
print resource_id
# Ensure the resource isn't used anywhere by checking both for the resource
# id (which should appear in C++ code) and the raw filename (in case the
# file is referenced in a script, test HTML file, etc.).
matching_files = FindFilesWithContents(base_resource_id, filename)
# Each file is matched once in the resource file itself. If there are no
# other matching files, it is unused.
if len(matching_files) == 1:
# Give the user some happy news.
print 'Unused!'
unused_resources.append([resource_id, filepath])
return unused_resources
def GetScaleDirectories(resources_path):
"""Returns a list of paths to per-scale-factor resource directories.
Assumes the directory names end in '_percent', for example,
ash/resources/default_200_percent or
resources_path: The base path of interest.
A list of paths relative to the 'src' directory.
file_list = os.listdir(resources_path)
scale_directories = []
for file_entry in file_list:
file_path = os.path.join(resources_path, file_entry)
if os.path.isdir(file_path) and file_path.endswith('_percent'):
return scale_directories
def main():
# The script requires exactly one parameter, the .grd file path.
if len(sys.argv) != 2:
print 'Usage: tools/resources/ <path/to/grd>'
grd_filepath = sys.argv[1]
# Try to ensure we are in a source checkout.
current_dir = os.getcwd()
if os.path.basename(current_dir) != 'src':
print 'Script must be run in your "src" directory.'
# We require a git checkout to use git grep.
if not os.path.exists(current_dir + '/.git'):
print 'You must use a git checkout for this script to run.'
print current_dir + '/.git', 'not found.'
# Look up the scale-factor directories.
resources_path = os.path.dirname(grd_filepath)
scale_directories = GetScaleDirectories(resources_path)
if not scale_directories:
print 'No scale directories (like "default_100_percent") found.'
# |unused_resources| stores pairs of [resource_id, filepath] for resource ids
# that are not referenced in the code.
unused_resources = GetUnusedResources(grd_filepath)
if not unused_resources:
print 'All resources are used.'
# Dump our output for the user.
print 'Unused resource ids:'
for resource_id, filepath in unused_resources:
print resource_id
# Print a list of 'git rm' command lines to remove unused assets.
print 'Unused files:'
for resource_id, filepath in unused_resources:
for directory in scale_directories:
print 'git rm ' + os.path.join(directory, filepath)
if __name__ == '__main__':