File: list_unused_grit_header.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (236 lines) | stat: -rwxr-xr-x 6,807 bytes parent folder | download | duplicates (6)
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
234
235
236
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors
# 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
"""

from __future__ import print_function

import os
import sys
import xml.etree.ElementTree

from find_unused_resources import GetBaseResourceId

IF_ELSE_THEN_TAGS = ('if', 'else', 'then')


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_THEN_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_THEN_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
      'DECLARE_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.' % grd_file)
    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))