| #!/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)) |