Chromium Code Reviews| Index: tools/unused-grit-header.py |
| diff --git a/tools/unused-grit-header.py b/tools/unused-grit-header.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..86b9b0a4c6aeb7a374df6bb00437b625249e1516 |
| --- /dev/null |
| +++ b/tools/unused-grit-header.py |
| @@ -0,0 +1,190 @@ |
| +#!/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.""" |
|
Nico
2014/08/24 23:58:25
Include a usage line
Lei Zhang
2014/08/26 02:35:24
Usage() is literally 6 lines below...
|
| + |
| +import os |
| +import sys |
| +import xml.etree.ElementTree |
| + |
|
Nico
2014/08/24 23:58:26
2 empty lines between toplevels
Lei Zhang
2014/08/26 02:35:25
Done.
|
| +def Usage(prog_name): |
| + print prog_name, 'GRD_FILE DIRS_TO_SCAN' |
|
Nico
2014/08/24 23:58:25
Consider using optparse or argparse (see e.g. tool
Lei Zhang
2014/08/26 02:35:24
There's no options. If we do add options later, I'
Nico
2014/08/26 02:43:37
It also offers usage printing (you can hand it the
|
| + |
| + |
| +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 == 'if': |
| + 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) |
| + return resources |
| + |
| + |
| +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' |
| + for node in root.getchildren(): |
| + if node.tag == 'release': |
| + release_node = node |
| + break |
| + assert release_node != None |
| + |
| + for node in release_node.getchildren(): |
| + if node.tag == 'messages': |
| + messages_node = node |
| + break |
| + messages = set() |
| + if messages_node != None: |
| + messages = set(GetResourcesForNode(messages_node, grd_file, 'message')) |
| + |
| + for node in release_node.getchildren(): |
| + if node.tag == 'includes': |
| + includes_node = node |
| + break |
|
Nico
2014/08/24 23:58:25
Since you have this for loop 3 times, consider hav
Lei Zhang
2014/08/26 02:35:24
Done.
|
| + includes = set() |
| + if includes_node != None: |
| + includes = set(GetResourcesForNode(includes_node, grd_file, 'include')) |
| + return messages.union(includes) |
| + |
| + |
| +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 == None |
| + output_file = child.attrib['filename'] |
| + elif child.tag == 'if': |
|
Nico
2014/08/24 23:58:26
do you need to look at 'else' children of if nodes
Lei Zhang
2014/08/26 02:35:25
I don't think grit has <else> nodes? It may be pos
Nico
2014/08/26 02:43:37
see https://codereview.chromium.org/11155024
|
| + child_output_file = GetOutputFileForNode(child) |
| + if not child_output_file: |
| + continue |
| + assert output_file == None |
|
Nico
2014/08/24 23:58:25
nit: it's more common to see "output_file is None"
Lei Zhang
2014/08/26 02:35:25
Done.
|
| + 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' |
| + for node in root.getchildren(): |
| + if node.tag == 'outputs': |
| + output_node = node |
| + break |
|
Nico
2014/08/24 23:58:26
Maybe `assert not output_node` before the assignme
Lei Zhang
2014/08/26 02:35:24
Done. And elsewhere above.
|
| + 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 |
| + |
|
Nico
2014/08/24 23:58:26
add a newline
Lei Zhang
2014/08/26 02:35:24
Done.
|
| +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. |
| + """ |
| + 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) |
| + |
| + |
| +def main(argv): |
| + if len(argv) < 3: |
| + Usage(argv[0]) |
| + sys.exit(1) |
| + grd_file = argv[1] |
| + dirs_to_scan = argv[2:] |
| + for f in dirs_to_scan: |
| + if not os.path.exists(f): |
| + print 'Error: %s does not exist' % f |
| + sys.exit(1) |
| + |
| + tree = xml.etree.ElementTree.parse(grd_file) |
| + grit_header = GetOutputHeaderFile(tree) |
| + resources = GetResourcesForGrdFile(tree, grd_file) |
| + |
| + files_with_unneeded_grit_includes = [] |
| + for dir_to_scan in dirs_to_scan: |
| + for root, dirs, files in os.walk(dir_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)]) |
| + if files_with_unneeded_grit_includes: |
| + print '\n'.join(files_with_unneeded_grit_includes) |
| + sys.exit(2) |
| + sys.exit(0) |
| + |
|
Nico
2014/08/24 23:58:25
add newline
Lei Zhang
2014/08/26 02:35:24
Done.
|
| +if __name__ == '__main__': |
| + main(sys.argv) |
|
Nico
2014/08/24 23:58:25
nit: the usual thing is `sys.exit(main(sys.argv))
Lei Zhang
2014/08/26 02:35:24
Done.
|