Roll src/third_party/WebKit f36d5e0:68b67cd (svn 193299:193303)
[chromium-blink-merge.git] / tools / resources / list_unused_grit_header.py
blob49cf088dbf2d45acf37072d740a9c13880c48ed6
1 #!/usr/bin/env python
2 # Copyright 2014 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.
6 """A tool to scan source files for unneeded grit includes.
8 Example:
9 cd /work/chrome/src
10 tools/resources/list_unused_grit_header.py ui/strings/ui_strings.grd chrome ui
11 """
13 import os
14 import sys
15 import xml.etree.ElementTree
17 from find_unused_resources import GetBaseResourceId
19 IF_ELSE_TAGS = ('if', 'else')
22 def Usage(prog_name):
23 print prog_name, 'GRD_FILE PATHS_TO_SCAN'
26 def FilterResourceIds(resource_id):
27 """If the resource starts with IDR_, find its base resource id."""
28 if resource_id.startswith('IDR_'):
29 return GetBaseResourceId(resource_id)
30 return resource_id
33 def GetResourcesForNode(node, parent_file, resource_tag):
34 """Recursively iterate through a node and extract resource names.
36 Args:
37 node: The node to iterate through.
38 parent_file: The file that contains node.
39 resource_tag: The resource tag to extract names from.
41 Returns:
42 A list of resource names.
43 """
44 resources = []
45 for child in node.getchildren():
46 if child.tag == resource_tag:
47 resources.append(child.attrib['name'])
48 elif child.tag in IF_ELSE_TAGS:
49 resources.extend(GetResourcesForNode(child, parent_file, resource_tag))
50 elif child.tag == 'part':
51 parent_dir = os.path.dirname(parent_file)
52 part_file = os.path.join(parent_dir, child.attrib['file'])
53 part_tree = xml.etree.ElementTree.parse(part_file)
54 part_root = part_tree.getroot()
55 assert part_root.tag == 'grit-part'
56 resources.extend(GetResourcesForNode(part_root, part_file, resource_tag))
57 else:
58 raise Exception('unknown tag:', child.tag)
60 # Handle the special case for resources of type "FOO_{LEFT,RIGHT,TOP}".
61 if resource_tag == 'structure':
62 resources = [FilterResourceIds(resource_id) for resource_id in resources]
63 return resources
66 def FindNodeWithTag(node, tag):
67 """Look through a node's children for a child node with a given tag.
69 Args:
70 root: The node to examine.
71 tag: The tag on a child node to look for.
73 Returns:
74 A child node with the given tag, or None.
75 """
76 result = None
77 for n in node.getchildren():
78 if n.tag == tag:
79 assert not result
80 result = n
81 return result
84 def GetResourcesForGrdFile(tree, grd_file):
85 """Find all the message and include resources from a given grit file.
87 Args:
88 tree: The XML tree.
89 grd_file: The file that contains the XML tree.
91 Returns:
92 A list of resource names.
93 """
94 root = tree.getroot()
95 assert root.tag == 'grit'
96 release_node = FindNodeWithTag(root, 'release')
97 assert release_node != None
99 resources = set()
100 for node_type in ('message', 'include', 'structure'):
101 resources_node = FindNodeWithTag(release_node, node_type + 's')
102 if resources_node != None:
103 resources = resources.union(
104 set(GetResourcesForNode(resources_node, grd_file, node_type)))
105 return resources
108 def GetOutputFileForNode(node):
109 """Find the output file starting from a given node.
111 Args:
112 node: The root node to scan from.
114 Returns:
115 A grit header file name.
117 output_file = None
118 for child in node.getchildren():
119 if child.tag == 'output':
120 if child.attrib['type'] == 'rc_header':
121 assert output_file is None
122 output_file = child.attrib['filename']
123 elif child.tag in IF_ELSE_TAGS:
124 child_output_file = GetOutputFileForNode(child)
125 if not child_output_file:
126 continue
127 assert output_file is None
128 output_file = child_output_file
129 else:
130 raise Exception('unknown tag:', child.tag)
131 return output_file
134 def GetOutputHeaderFile(tree):
135 """Find the output file for a given tree.
137 Args:
138 tree: The tree to scan.
140 Returns:
141 A grit header file name.
143 root = tree.getroot()
144 assert root.tag == 'grit'
145 output_node = FindNodeWithTag(root, 'outputs')
146 assert output_node != None
147 return GetOutputFileForNode(output_node)
150 def ShouldScanFile(filename):
151 """Return if the filename has one of the extensions below."""
152 extensions = ['.cc', '.cpp', '.h', '.mm']
153 file_extension = os.path.splitext(filename)[1]
154 return file_extension in extensions
157 def NeedsGritInclude(grit_header, resources, filename):
158 """Return whether a file needs a given grit header or not.
160 Args:
161 grit_header: The grit header file name.
162 resources: The list of resource names in grit_header.
163 filename: The file to scan.
165 Returns:
166 True if the file should include the grit header.
168 # A list of special keywords that implies the file needs grit headers.
169 # To be more thorough, one would need to run a pre-processor.
170 SPECIAL_KEYWORDS = (
171 '#include "ui_localizer_table.h"', # ui_localizer.mm
172 'DEFINE_RESOURCE_ID', # chrome/browser/android/resource_mapper.cc
174 with open(filename, 'rb') as f:
175 grit_header_line = grit_header + '"\n'
176 has_grit_header = False
177 while True:
178 line = f.readline()
179 if not line:
180 break
181 if line.endswith(grit_header_line):
182 has_grit_header = True
183 break
185 if not has_grit_header:
186 return True
187 rest_of_the_file = f.read()
188 return (any(resource in rest_of_the_file for resource in resources) or
189 any(keyword in rest_of_the_file for keyword in SPECIAL_KEYWORDS))
192 def main(argv):
193 if len(argv) < 3:
194 Usage(argv[0])
195 return 1
196 grd_file = argv[1]
197 paths_to_scan = argv[2:]
198 for f in paths_to_scan:
199 if not os.path.exists(f):
200 print 'Error: %s does not exist' % f
201 return 1
203 tree = xml.etree.ElementTree.parse(grd_file)
204 grit_header = GetOutputHeaderFile(tree)
205 if not grit_header:
206 print 'Error: %s does not generate any output headers.' % grit_header
207 return 1
208 resources = GetResourcesForGrdFile(tree, grd_file)
210 files_with_unneeded_grit_includes = []
211 for path_to_scan in paths_to_scan:
212 if os.path.isdir(path_to_scan):
213 for root, dirs, files in os.walk(path_to_scan):
214 if '.git' in dirs:
215 dirs.remove('.git')
216 full_paths = [os.path.join(root, f) for f in files if ShouldScanFile(f)]
217 files_with_unneeded_grit_includes.extend(
218 [f for f in full_paths
219 if not NeedsGritInclude(grit_header, resources, f)])
220 elif os.path.isfile(path_to_scan):
221 if not NeedsGritInclude(grit_header, resources, path_to_scan):
222 files_with_unneeded_grit_includes.append(path_to_scan)
223 else:
224 print 'Warning: Skipping %s' % path_to_scan
226 if files_with_unneeded_grit_includes:
227 print '\n'.join(files_with_unneeded_grit_includes)
228 return 2
229 return 0
232 if __name__ == '__main__':
233 sys.exit(main(sys.argv))