- add sources.
[platform/framework/web/crosswalk.git] / src / tools / resources / find_unused_resources.py
1 #!/usr/bin/env python
2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """This script searches for unused art assets listed in a .grd file.
7
8 It uses git grep to look for references to the IDR resource id or the base
9 filename. If neither is found, the file is reported unused.
10
11 Requires a git checkout. Must be run from your checkout's "src" root.
12
13 Example:
14   cd /work/chrome/src
15   tools/resources/find_unused_resouces.py ash/resources/ash_resources.grd
16 """
17
18 __author__ = 'jamescook@chromium.org (James Cook)'
19
20
21 import os
22 import re
23 import subprocess
24 import sys
25
26
27 def GetBaseResourceId(resource_id):
28   """Removes common suffixes from a resource ID.
29
30   Removes suffixies that may be added by macros like IMAGE_GRID or IMAGE_BORDER.
31   For example, converts IDR_FOO_LEFT and IDR_FOO_RIGHT to just IDR_FOO.
32
33   Args:
34     resource_id: String resource ID.
35
36   Returns:
37     A string with the base part of the resource ID.
38   """
39   suffixes = [
40       '_TOP_LEFT', '_TOP', '_TOP_RIGHT',
41       '_LEFT', '_CENTER', '_RIGHT',
42       '_BOTTOM_LEFT', '_BOTTOM', '_BOTTOM_RIGHT',
43       '_TL', '_T', '_TR',
44       '_L', '_M', '_R',
45       '_BL', '_B', '_BR']
46   # Note: This does not check _HOVER, _PRESSED, _HOT, etc. as those are never
47   # used in macros.
48   for suffix in suffixes:
49     if resource_id.endswith(suffix):
50       resource_id = resource_id[:-len(suffix)]
51   return resource_id
52
53
54 def FindFilesWithContents(string_a, string_b):
55   """Returns list of paths of files that contain |string_a| or |string_b|.
56
57   Uses --name-only to print the file paths. The default behavior of git grep
58   is to OR together multiple patterns.
59
60   Args:
61     string_a: A string to search for (not a regular expression).
62     string_b: As above.
63
64   Returns:
65     A list of file paths as strings.
66   """
67   matching_files = subprocess.check_output([
68       'git', 'grep', '--name-only', '--fixed-strings', '-e', string_a,
69       '-e', string_b])
70   files_list = matching_files.split('\n')
71   # The output ends in a newline, so slice that off.
72   files_list = files_list[:-1]
73   return files_list
74
75
76 def GetUnusedResources(grd_filepath):
77   """Returns a list of resources that are unused in the code.
78
79   Prints status lines to the console because this function is quite slow.
80
81   Args:
82     grd_filepath: Path to a .grd file listing resources.
83
84   Returns:
85     A list of pairs of [resource_id, filepath] for the unused resources.
86   """
87   unused_resources = []
88   grd_file = open(grd_filepath, 'r')
89   grd_data = grd_file.read()
90   print 'Checking:'
91   # Match the resource id and file path out of substrings like:
92   # ...name="IDR_FOO_123" file="common/foo.png"...
93   # by matching between the quotation marks.
94   pattern = re.compile(
95       r"""name="([^"]*)"  # Match resource ID between quotes.
96       \s*                 # Run of whitespace, including newlines.
97       file="([^"]*)"      # Match file path between quotes.""",
98       re.VERBOSE)
99   # Use finditer over the file contents because there may be newlines between
100   # the name and file attributes.
101   for result in pattern.finditer(grd_data):
102     # Extract the IDR resource id and file path.
103     resource_id = result.group(1)
104     filepath = result.group(2)
105     filename = os.path.basename(filepath)
106     # Print progress as we go along.
107     print resource_id
108     # Ensure the resource isn't used anywhere by checking both for the resource
109     # id (which should appear in C++ code) and the raw filename (in case the
110     # file is referenced in a script, test HTML file, etc.).
111     base_resource_id = GetBaseResourceId(resource_id)
112     matching_files = FindFilesWithContents(base_resource_id, filename)
113     # Each file is matched once in the resource file itself. If there are no
114     # other matching files, it is unused.
115     if len(matching_files) == 1:
116       # Give the user some happy news.
117       print 'Unused!'
118       unused_resources.append([resource_id, filepath])
119
120   return unused_resources
121
122
123 def GetScaleDirectories(resources_path):
124   """Returns a list of paths to per-scale-factor resource directories.
125
126   Assumes the directory names end in '_percent', for example,
127   ash/resources/default_200_percent or
128   chrome/app/theme/resources/touch_140_percent
129
130   Args:
131     resources_path: The base path of interest.
132
133   Returns:
134     A list of paths relative to the 'src' directory.
135   """
136   file_list = os.listdir(resources_path)
137   scale_directories = []
138   for file_entry in file_list:
139     file_path = os.path.join(resources_path, file_entry)
140     if os.path.isdir(file_path) and file_path.endswith('_percent'):
141       scale_directories.append(file_path)
142
143   scale_directories.sort()
144   return scale_directories
145
146
147 def main():
148   # The script requires exactly one parameter, the .grd file path.
149   if len(sys.argv) != 2:
150     print 'Usage: tools/resources/find_unused_resources.py <path/to/grd>'
151     sys.exit(1)
152   grd_filepath = sys.argv[1]
153
154   # Try to ensure we are in a source checkout.
155   current_dir = os.getcwd()
156   if os.path.basename(current_dir) != 'src':
157     print 'Script must be run in your "src" directory.'
158     sys.exit(1)
159
160   # We require a git checkout to use git grep.
161   if not os.path.exists(current_dir + '/.git'):
162     print 'You must use a git checkout for this script to run.'
163     print current_dir + '/.git', 'not found.'
164     sys.exit(1)
165
166   # Look up the scale-factor directories.
167   resources_path = os.path.dirname(grd_filepath)
168   scale_directories = GetScaleDirectories(resources_path)
169   if not scale_directories:
170     print 'No scale directories (like "default_100_percent") found.'
171     sys.exit(1)
172
173   # |unused_resources| stores pairs of [resource_id, filepath] for resource ids
174   # that are not referenced in the code.
175   unused_resources = GetUnusedResources(grd_filepath)
176   if not unused_resources:
177     print 'All resources are used.'
178     sys.exit(0)
179
180   # Dump our output for the user.
181   print
182   print 'Unused resource ids:'
183   for resource_id, filepath in unused_resources:
184     print resource_id
185   # Print a list of 'git rm' command lines to remove unused assets.
186   print
187   print 'Unused files:'
188   for resource_id, filepath in unused_resources:
189     for directory in scale_directories:
190       print 'git rm ' + os.path.join(directory, filepath)
191
192
193 if __name__ == '__main__':
194   main()