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