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