Home | History | Annotate | Download | only in tools
      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
      7 files, and then checks the subset of the code that loads the strings to try
      8 and figure out what isn't in use any more.
      9 You can give paths to GRD files and source directories to control what is
     10 check instead.
     11 """
     12 
     13 import os
     14 import re
     15 import sys
     16 import 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)
     21 DEBUG = 0
     22 
     23 
     24 class 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 
     38 def 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 
    113 def 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 
    185 if __name__ == '__main__':
    186   sys.exit(main())
    187