OLD | NEW |
| (Empty) |
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. | |
5 | |
6 """A tool to scan source files for unneeded grit includes.""" | |
7 | |
8 import os | |
9 import sys | |
10 import xml.etree.ElementTree | |
11 | |
12 IF_ELSE_TAGS = ('if', 'else') | |
13 | |
14 | |
15 def Usage(prog_name): | |
16 print prog_name, 'GRD_FILE DIRS_TO_SCAN' | |
17 | |
18 | |
19 def GetResourcesForNode(node, parent_file, resource_tag): | |
20 """Recursively iterate through a node and extract resource names. | |
21 | |
22 Args: | |
23 node: The node to iterate through. | |
24 parent_file: The file that contains node. | |
25 resource_tag: The resource tag to extract names from. | |
26 | |
27 Returns: | |
28 A list of resource names. | |
29 """ | |
30 resources = [] | |
31 for child in node.getchildren(): | |
32 if child.tag == resource_tag: | |
33 resources.append(child.attrib['name']) | |
34 elif child.tag in IF_ELSE_TAGS: | |
35 resources.extend(GetResourcesForNode(child, parent_file, resource_tag)) | |
36 elif child.tag == 'part': | |
37 parent_dir = os.path.dirname(parent_file) | |
38 part_file = os.path.join(parent_dir, child.attrib['file']) | |
39 part_tree = xml.etree.ElementTree.parse(part_file) | |
40 part_root = part_tree.getroot() | |
41 assert part_root.tag == 'grit-part' | |
42 resources.extend(GetResourcesForNode(part_root, part_file, resource_tag)) | |
43 else: | |
44 raise Exception('unknown tag:', child.tag) | |
45 return resources | |
46 | |
47 | |
48 def FindNodeWithTag(node, tag): | |
49 """Look through a node's children for a child node with a given tag. | |
50 | |
51 Args: | |
52 root: The node to examine. | |
53 tag: The tag on a child node to look for. | |
54 | |
55 Returns: | |
56 A child node with the given tag, or None. | |
57 """ | |
58 result = None | |
59 for n in node.getchildren(): | |
60 if n.tag == tag: | |
61 assert not result | |
62 result = n | |
63 return result | |
64 | |
65 | |
66 def GetResourcesForGrdFile(tree, grd_file): | |
67 """Find all the message and include resources from a given grit file. | |
68 | |
69 Args: | |
70 tree: The XML tree. | |
71 grd_file: The file that contains the XML tree. | |
72 | |
73 Returns: | |
74 A list of resource names. | |
75 """ | |
76 root = tree.getroot() | |
77 assert root.tag == 'grit' | |
78 release_node = FindNodeWithTag(root, 'release') | |
79 assert release_node != None | |
80 | |
81 resources = set() | |
82 for node_type in ('message', 'include', 'structure'): | |
83 resources_node = FindNodeWithTag(release_node, node_type + 's') | |
84 if resources_node != None: | |
85 resources = resources.union( | |
86 set(GetResourcesForNode(resources_node, grd_file, node_type))) | |
87 return resources | |
88 | |
89 | |
90 def GetOutputFileForNode(node): | |
91 """Find the output file starting from a given node. | |
92 | |
93 Args: | |
94 node: The root node to scan from. | |
95 | |
96 Returns: | |
97 A grit header file name. | |
98 """ | |
99 output_file = None | |
100 for child in node.getchildren(): | |
101 if child.tag == 'output': | |
102 if child.attrib['type'] == 'rc_header': | |
103 assert output_file is None | |
104 output_file = child.attrib['filename'] | |
105 elif child.tag in IF_ELSE_TAGS: | |
106 child_output_file = GetOutputFileForNode(child) | |
107 if not child_output_file: | |
108 continue | |
109 assert output_file is None | |
110 output_file = child_output_file | |
111 else: | |
112 raise Exception('unknown tag:', child.tag) | |
113 return output_file | |
114 | |
115 | |
116 def GetOutputHeaderFile(tree): | |
117 """Find the output file for a given tree. | |
118 | |
119 Args: | |
120 tree: The tree to scan. | |
121 | |
122 Returns: | |
123 A grit header file name. | |
124 """ | |
125 root = tree.getroot() | |
126 assert root.tag == 'grit' | |
127 output_node = FindNodeWithTag(root, 'outputs') | |
128 assert output_node != None | |
129 return GetOutputFileForNode(output_node) | |
130 | |
131 | |
132 def ShouldScanFile(filename): | |
133 """Return if the filename has one of the extensions below.""" | |
134 extensions = ['.cc', '.cpp', '.h', '.mm'] | |
135 file_extension = os.path.splitext(filename)[1] | |
136 return file_extension in extensions | |
137 | |
138 | |
139 def NeedsGritInclude(grit_header, resources, filename): | |
140 """Return whether a file needs a given grit header or not. | |
141 | |
142 Args: | |
143 grit_header: The grit header file name. | |
144 resources: The list of resource names in grit_header. | |
145 filename: The file to scan. | |
146 | |
147 Returns: | |
148 True if the file should include the grit header. | |
149 """ | |
150 # A list of special keywords that implies the file needs grit headers. | |
151 # To be more thorough, one would need to run a pre-processor. | |
152 SPECIAL_KEYWORDS = ( | |
153 'IMAGE_GRID(', # Macro in nine_image_painter_factory.h | |
154 '#include "ui_localizer_table.h"', # ui_localizer.mm | |
155 'DEFINE_RESOURCE_ID', # chrome/browser/android/resource_mapper.cc | |
156 ) | |
157 with open(filename, 'rb') as f: | |
158 grit_header_line = grit_header + '"\n' | |
159 has_grit_header = False | |
160 while True: | |
161 line = f.readline() | |
162 if not line: | |
163 break | |
164 if line.endswith(grit_header_line): | |
165 has_grit_header = True | |
166 break | |
167 | |
168 if not has_grit_header: | |
169 return True | |
170 rest_of_the_file = f.read() | |
171 return (any(resource in rest_of_the_file for resource in resources) or | |
172 any(keyword in rest_of_the_file for keyword in SPECIAL_KEYWORDS)) | |
173 | |
174 | |
175 def main(argv): | |
176 if len(argv) < 3: | |
177 Usage(argv[0]) | |
178 return 1 | |
179 grd_file = argv[1] | |
180 dirs_to_scan = argv[2:] | |
181 for f in dirs_to_scan: | |
182 if not os.path.exists(f): | |
183 print 'Error: %s does not exist' % f | |
184 return 1 | |
185 | |
186 tree = xml.etree.ElementTree.parse(grd_file) | |
187 grit_header = GetOutputHeaderFile(tree) | |
188 if not grit_header: | |
189 print 'Error: %s does not generate any output headers.' % grit_header | |
190 return 1 | |
191 resources = GetResourcesForGrdFile(tree, grd_file) | |
192 | |
193 files_with_unneeded_grit_includes = [] | |
194 for dir_to_scan in dirs_to_scan: | |
195 for root, dirs, files in os.walk(dir_to_scan): | |
196 if '.git' in dirs: | |
197 dirs.remove('.git') | |
198 full_paths = [os.path.join(root, f) for f in files if ShouldScanFile(f)] | |
199 files_with_unneeded_grit_includes.extend( | |
200 [f for f in full_paths | |
201 if not NeedsGritInclude(grit_header, resources, f)]) | |
202 if files_with_unneeded_grit_includes: | |
203 print '\n'.join(files_with_unneeded_grit_includes) | |
204 return 2 | |
205 return 0 | |
206 | |
207 | |
208 if __name__ == '__main__': | |
209 sys.exit(main(sys.argv)) | |
OLD | NEW |