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.
10 tools/resources/list_unused_grit_header.py ui/strings/ui_strings.grd chrome ui
15 import xml
.etree
.ElementTree
17 from find_unused_resources
import GetBaseResourceId
19 IF_ELSE_TAGS
= ('if', 'else')
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
)
33 def GetResourcesForNode(node
, parent_file
, resource_tag
):
34 """Recursively iterate through a node and extract resource names.
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.
42 A list of resource names.
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
))
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
]
66 def FindNodeWithTag(node
, tag
):
67 """Look through a node's children for a child node with a given tag.
70 root: The node to examine.
71 tag: The tag on a child node to look for.
74 A child node with the given tag, or None.
77 for n
in node
.getchildren():
84 def GetResourcesForGrdFile(tree
, grd_file
):
85 """Find all the message and include resources from a given grit file.
89 grd_file: The file that contains the XML tree.
92 A list of resource names.
95 assert root
.tag
== 'grit'
96 release_node
= FindNodeWithTag(root
, 'release')
97 assert release_node
!= None
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
)))
108 def GetOutputFileForNode(node
):
109 """Find the output file starting from a given node.
112 node: The root node to scan from.
115 A grit header file name.
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
:
127 assert output_file
is None
128 output_file
= child_output_file
130 raise Exception('unknown tag:', child
.tag
)
134 def GetOutputHeaderFile(tree
):
135 """Find the output file for a given tree.
138 tree: The tree to scan.
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.
161 grit_header: The grit header file name.
162 resources: The list of resource names in grit_header.
163 filename: The file to scan.
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.
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
181 if line
.endswith(grit_header_line
):
182 has_grit_header
= True
185 if not has_grit_header
:
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
))
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
203 tree
= xml
.etree
.ElementTree
.parse(grd_file
)
204 grit_header
= GetOutputHeaderFile(tree
)
206 print 'Error: %s does not generate any output headers.' % grit_header
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
):
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
)
224 print 'Warning: Skipping %s' % path_to_scan
226 if files_with_unneeded_grit_includes
:
227 print '\n'.join(files_with_unneeded_grit_includes
)
232 if __name__
== '__main__':
233 sys
.exit(main(sys
.argv
))