check_grd_for_unused_strings.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python
  2. # Copyright (c) 2012 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.
  5. """Without any args, this simply loads the IDs out of a bunch of the Chrome GRD
  6. files, and then checks the subset of the code that loads the strings to try
  7. and figure out what isn't in use any more.
  8. You can give paths to GRD files and source directories to control what is
  9. check instead.
  10. """
  11. from __future__ import print_function
  12. import os
  13. import re
  14. import sys
  15. import xml.sax
  16. # Extra messages along the way
  17. # 1 - Print ids that are found in sources but not in the found id set
  18. # 2 - Files that aren't processes (don't match the source name regex)
  19. DEBUG = 0
  20. class GrdIDExtractor(xml.sax.handler.ContentHandler):
  21. """Extracts the IDs from messages in GRIT files"""
  22. def __init__(self):
  23. self.id_set_ = set()
  24. def startElement(self, name, attrs):
  25. if name == 'message':
  26. self.id_set_.add(attrs['name'])
  27. def allIDs(self):
  28. """Return all the IDs found"""
  29. return self.id_set_.copy()
  30. def CheckForUnusedGrdIDsInSources(grd_files, src_dirs):
  31. """Will collect the message ids out of the given GRD files and then scan
  32. the source directories to try and figure out what ids are not currently
  33. being used by any source.
  34. grd_files:
  35. A list of GRD files to collect the ids from.
  36. src_dirs:
  37. A list of directories to walk looking for source files.
  38. """
  39. # Collect all the ids into a large map
  40. all_ids = set()
  41. file_id_map = {}
  42. for y in grd_files:
  43. handler = GrdIDExtractor()
  44. xml.sax.parse(y, handler)
  45. files_ids = handler.allIDs()
  46. file_id_map[y] = files_ids
  47. all_ids |= files_ids
  48. # The regex that will be used to check sources
  49. id_regex = re.compile('IDS_[A-Z0-9_]+')
  50. # Make sure the regex matches every id found.
  51. got_err = False
  52. for x in all_ids:
  53. match = id_regex.search(x)
  54. if match is None:
  55. print('ERROR: "%s" did not match our regex' % x)
  56. got_err = True
  57. if not match.group(0) is x:
  58. print('ERROR: "%s" did not fully match our regex' % x)
  59. got_err = True
  60. if got_err:
  61. return 1
  62. # The regex for deciding what is a source file
  63. src_regex = re.compile('\.(([chm])|(mm)|(cc)|(cp)|(cpp)|(xib)|(py))$')
  64. ids_left = all_ids.copy()
  65. # Scanning time.
  66. for src_dir in src_dirs:
  67. for root, dirs, files in os.walk(src_dir):
  68. # Remove svn directories from recursion
  69. if '.svn' in dirs:
  70. dirs.remove('.svn')
  71. for file in files:
  72. if src_regex.search(file.lower()):
  73. full_path = os.path.join(root, file)
  74. src_file_contents = open(full_path).read()
  75. for match in sorted(set(id_regex.findall(src_file_contents))):
  76. if match in ids_left:
  77. ids_left.remove(match)
  78. if DEBUG:
  79. if not match in all_ids:
  80. print('%s had "%s", which was not in the found IDs' % \
  81. (full_path, match))
  82. elif DEBUG > 1:
  83. full_path = os.path.join(root, file)
  84. print('Skipping %s.' % full_path)
  85. # Anything left?
  86. if len(ids_left) > 0:
  87. print('The following ids are in GRD files, but *appear* to be unused:')
  88. for file_path, file_ids in file_id_map.iteritems():
  89. missing = ids_left.intersection(file_ids)
  90. if len(missing) > 0:
  91. print(' %s:' % file_path)
  92. print('\n'.join(' %s' % (x) for x in sorted(missing)))
  93. return 0
  94. def main():
  95. # script lives in src/tools
  96. tools_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
  97. src_dir = os.path.dirname(tools_dir)
  98. # Collect the args into the right buckets
  99. src_dirs = []
  100. grd_files = []
  101. for arg in sys.argv[1:]:
  102. if arg.lower().endswith('.grd') or arg.lower().endswith('.grdp'):
  103. grd_files.append(arg)
  104. else:
  105. src_dirs.append(arg)
  106. # If no GRD files were given, default them:
  107. if len(grd_files) == 0:
  108. ash_base_dir = os.path.join(src_dir, 'ash')
  109. ash_shortcut_viewer_dir = os.path.join(ash_base_dir, 'shortcut_viewer')
  110. chrome_dir = os.path.join(src_dir, 'chrome')
  111. chrome_app_dir = os.path.join(chrome_dir, 'app')
  112. chrome_app_res_dir = os.path.join(chrome_app_dir, 'resources')
  113. device_base_dir = os.path.join(src_dir, 'device')
  114. services_dir = os.path.join(src_dir, 'services')
  115. ui_dir = os.path.join(src_dir, 'ui')
  116. ui_strings_dir = os.path.join(ui_dir, 'strings')
  117. ui_chromeos_dir = os.path.join(ui_dir, 'chromeos')
  118. grd_files = [
  119. os.path.join(ash_base_dir, 'ash_strings.grd'),
  120. os.path.join(ash_shortcut_viewer_dir, 'shortcut_viewer_strings.grd'),
  121. os.path.join(chrome_app_dir, 'chromium_strings.grd'),
  122. os.path.join(chrome_app_dir, 'generated_resources.grd'),
  123. os.path.join(chrome_app_dir, 'google_chrome_strings.grd'),
  124. os.path.join(chrome_app_res_dir, 'locale_settings.grd'),
  125. os.path.join(chrome_app_res_dir, 'locale_settings_chromiumos.grd'),
  126. os.path.join(chrome_app_res_dir, 'locale_settings_google_chromeos.grd'),
  127. os.path.join(chrome_app_res_dir, 'locale_settings_linux.grd'),
  128. os.path.join(chrome_app_res_dir, 'locale_settings_mac.grd'),
  129. os.path.join(chrome_app_res_dir, 'locale_settings_win.grd'),
  130. os.path.join(chrome_app_dir, 'theme', 'theme_resources.grd'),
  131. os.path.join(chrome_dir, 'browser', 'browser_resources.grd'),
  132. os.path.join(chrome_dir, 'common', 'common_resources.grd'),
  133. os.path.join(chrome_dir, 'renderer', 'resources',
  134. 'renderer_resources.grd'),
  135. os.path.join(device_base_dir, 'bluetooth', 'bluetooth_strings.grd'),
  136. os.path.join(device_base_dir, 'fido', 'fido_strings.grd'),
  137. os.path.join(services_dir, 'services_strings.grd'),
  138. os.path.join(src_dir, 'chromeos', 'chromeos_strings.grd'),
  139. os.path.join(src_dir, 'extensions', 'strings',
  140. 'extensions_strings.grd'),
  141. os.path.join(src_dir, 'ui', 'resources', 'ui_resources.grd'),
  142. os.path.join(src_dir, 'ui', 'webui', 'resources',
  143. 'webui_resources.grd'),
  144. os.path.join(ui_strings_dir, 'app_locale_settings.grd'),
  145. os.path.join(ui_strings_dir, 'ax_strings.grd'),
  146. os.path.join(ui_strings_dir, 'ui_strings.grd'),
  147. os.path.join(ui_chromeos_dir, 'ui_chromeos_strings.grd'),
  148. ]
  149. # If no source directories were given, default them:
  150. if len(src_dirs) == 0:
  151. src_dirs = [
  152. os.path.join(src_dir, 'app'),
  153. os.path.join(src_dir, 'ash'),
  154. os.path.join(src_dir, 'chrome'),
  155. os.path.join(src_dir, 'components'),
  156. os.path.join(src_dir, 'content'),
  157. os.path.join(src_dir, 'device'),
  158. os.path.join(src_dir, 'extensions'),
  159. os.path.join(src_dir, 'ui'),
  160. # nsNSSCertHelper.cpp has a bunch of ids
  161. os.path.join(src_dir, 'third_party', 'mozilla_security_manager'),
  162. os.path.join(chrome_dir, 'installer'),
  163. ]
  164. return CheckForUnusedGrdIDsInSources(grd_files, src_dirs)
  165. if __name__ == '__main__':
  166. sys.exit(main())