Home | History | Annotate | Download | only in generators
      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 import sys
      7 
      8 from idl_log import ErrOut, InfoOut, WarnOut
      9 from idl_option import GetOption, Option, ParseOptions
     10 from idl_parser import ParseFiles
     11 
     12 GeneratorList = []
     13 
     14 Option('out', 'List of output files', default='')
     15 Option('release', 'Which release to generate.', default='')
     16 Option('range', 'Which ranges in the form of MIN,MAX.', default='start,end')
     17 
     18 class Generator(object):
     19   """Base class for generators.
     20 
     21   This class provides a mechanism for adding new generator objects to the IDL
     22   driver.  To use this class override the GenerateRelease and GenerateRange
     23   members, and instantiate one copy of the class in the same module which
     24   defines it to register the generator.  After the AST is generated, call the
     25   static Run member which will check every registered generator to see which
     26   ones have been enabled through command-line options.  To enable a generator
     27   use the switches:
     28     --<sname> : To enable with defaults
     29     --<sname>_opt=<XXX,YYY=y> : To enable with generator specific options.
     30 
     31   NOTE:  Generators still have access to global options
     32   """
     33 
     34   def __init__(self, name, sname, desc):
     35     self.name = name
     36     self.run_switch = Option(sname, desc)
     37     self.opt_switch = Option(sname + '_opt', 'Options for %s.' % sname,
     38                              default='')
     39     GeneratorList.append(self)
     40     self.errors = 0
     41     self.skip_list = []
     42 
     43   def Error(self, msg):
     44     ErrOut.Log('Error %s : %s' % (self.name, msg))
     45     self.errors += 1
     46 
     47   def GetRunOptions(self):
     48     options = {}
     49     option_list = self.opt_switch.Get()
     50     if option_list:
     51       option_list = option_list.split(',')
     52       for opt in option_list:
     53         offs = opt.find('=')
     54         if offs > 0:
     55           options[opt[:offs]] = opt[offs+1:]
     56         else:
     57           options[opt] = True
     58       return options
     59     if self.run_switch.Get():
     60       return options
     61     return None
     62 
     63   def Generate(self, ast, options):
     64     self.errors = 0
     65 
     66     rangestr = GetOption('range')
     67     releasestr = GetOption('release')
     68 
     69     print "Found releases: %s" % ast.releases
     70 
     71     # Generate list of files to ignore due to errors
     72     for filenode in ast.GetListOf('File'):
     73       # If this file has errors, skip it
     74       if filenode.GetProperty('ERRORS') > 0:
     75         self.skip_list.append(filenode)
     76         continue
     77 
     78     # Check for a range option which over-rides a release option
     79     if not releasestr and rangestr:
     80       range_list = rangestr.split(',')
     81       if len(range_list) != 2:
     82         self.Error('Failed to generate for %s, incorrect range: "%s"' %
     83                    (self.name, rangestr))
     84       else:
     85         vmin = range_list[0]
     86         vmax = range_list[1]
     87 
     88         # Generate 'start' and 'end' represent first and last found.
     89         if vmin == 'start':
     90             vmin = ast.releases[0]
     91         if vmax == 'end':
     92             vmax = ast.releases[-1]
     93 
     94         vmin = ast.releases.index(vmin)
     95         vmax = ast.releases.index(vmax) + 1
     96         releases = ast.releases[vmin:vmax]
     97         InfoOut.Log('Generate range %s of %s.' % (rangestr, self.name))
     98         ret = self.GenerateRange(ast, releases, options)
     99         if ret < 0:
    100           self.Error('Failed to generate range %s : %s.' %(vmin, vmax))
    101         else:
    102           InfoOut.Log('%s wrote %d files.' % (self.name, ret))
    103     # Otherwise this should be a single release generation
    104     else:
    105       if releasestr == 'start':
    106         releasestr = ast.releases[0]
    107       if releasestr == 'end':
    108         releasestr = ast.releases[-1]
    109 
    110       if releasestr > ast.releases[-1]:
    111         InfoOut.Log('There is no unique release for %s, using last release.' %
    112                     releasestr)
    113         releasestr = ast.releases[-1]
    114 
    115       if releasestr not in ast.releases:
    116         self.Error('Release %s not in [%s].' %
    117                    (releasestr, ', '.join(ast.releases)))
    118 
    119       if releasestr:
    120         InfoOut.Log('Generate release %s of %s.' % (releasestr, self.name))
    121         ret = self.GenerateRelease(ast, releasestr, options)
    122         if ret < 0:
    123           self.Error('Failed to generate release %s.' % releasestr)
    124         else:
    125           InfoOut.Log('%s wrote %d files.' % (self.name, ret))
    126 
    127       else:
    128         self.Error('No range or release specified for %s.' % releasestr)
    129     return self.errors
    130 
    131   def GenerateRelease(self, ast, release, options):
    132     __pychecker__ = 'unusednames=ast,release,options'
    133     self.Error("Undefined release generator.")
    134     return 0
    135 
    136   def GenerateRange(self, ast, releases, options):
    137     __pychecker__ = 'unusednames=ast,releases,options'
    138     self.Error("Undefined range generator.")
    139     return 0
    140 
    141   @staticmethod
    142   def Run(ast):
    143     fail_count = 0
    144 
    145     # Check all registered generators if they should run.
    146     for gen in GeneratorList:
    147       options = gen.GetRunOptions()
    148       if options is not None:
    149         if gen.Generate(ast, options):
    150           fail_count += 1
    151     return fail_count
    152 
    153 
    154 class GeneratorByFile(Generator):
    155   """A simplified generator that generates one output file per IDL source file.
    156 
    157   A subclass of Generator for use of generators which have a one to one
    158   mapping between IDL sources and output files.
    159 
    160   Derived classes should define GenerateFile.
    161   """
    162 
    163   def GenerateFile(self, filenode, releases, options):
    164     """Generates an output file from the IDL source.
    165 
    166     Returns true if the generated file is different than the previously
    167     generated file.
    168     """
    169     __pychecker__ = 'unusednames=filenode,releases,options'
    170     self.Error("Undefined release generator.")
    171     return 0
    172 
    173   def GenerateRelease(self, ast, release, options):
    174     return self.GenerateRange(ast, [release], options)
    175 
    176   def GenerateRange(self, ast, releases, options):
    177     # Get list of out files
    178     outlist = GetOption('out')
    179     if outlist: outlist = outlist.split(',')
    180 
    181     skipList = []
    182     cnt = 0
    183     for filenode in ast.GetListOf('File'):
    184       # Ignore files with errors
    185       if filenode in self.skip_list:
    186         continue
    187 
    188       # Skip this file if not required
    189       if outlist and filenode.GetName() not in outlist:
    190         continue
    191 
    192       # Create the output file and increment out count if there was a delta
    193       if self.GenerateFile(filenode, releases, options):
    194         cnt = cnt + 1
    195 
    196     for filenode in skipList:
    197       errcnt = filenode.GetProperty('ERRORS')
    198       ErrOut.Log('%s : Skipped because of %d errors.' % (
    199           filenode.GetName(), errcnt))
    200 
    201     if skipList:
    202       return -len(skipList)
    203 
    204     if GetOption('diff'):
    205       return -cnt
    206     return cnt
    207 
    208 
    209 check_release = 0
    210 check_range = 0
    211 
    212 class GeneratorReleaseTest(Generator):
    213   def GenerateRelease(self, ast, release, options = {}):
    214     __pychecker__ = 'unusednames=ast,release,options'
    215     global check_release
    216     check_map = {
    217       'so_long': True,
    218       'MyOpt': 'XYZ',
    219       'goodbye': True
    220     }
    221     check_release = 1
    222     for item in check_map:
    223       check_item = check_map[item]
    224       option_item = options.get(item, None)
    225       if check_item != option_item:
    226         print 'Option %s is %s, expecting %s' % (item, option_item, check_item)
    227         check_release = 0
    228 
    229     if release != 'M14':
    230       check_release = 0
    231     return check_release == 1
    232 
    233   def GenerateRange(self, ast, releases, options):
    234     __pychecker__ = 'unusednames=ast,releases,options'
    235     global check_range
    236     check_range = 1
    237     return True
    238 
    239 def Test():
    240   __pychecker__ = 'unusednames=args'
    241   global check_release
    242   global check_range
    243 
    244   ParseOptions(['--testgen_opt=so_long,MyOpt=XYZ,goodbye'])
    245   if Generator.Run('AST') != 0:
    246     print 'Generate release: Failed.\n'
    247     return -1
    248 
    249   if check_release != 1 or check_range != 0:
    250     print 'Gererate release: Failed to run.\n'
    251     return -1
    252 
    253   check_release = 0
    254   ParseOptions(['--testgen_opt="HELLO"', '--range=M14,M16'])
    255   if Generator.Run('AST') != 0:
    256     print 'Generate range: Failed.\n'
    257     return -1
    258 
    259   if check_release != 0 or check_range != 1:
    260     print 'Gererate range: Failed to run.\n'
    261     return -1
    262 
    263   print 'Generator test: Pass'
    264   return 0
    265 
    266 
    267 def Main(args):
    268   if not args: return Test()
    269   filenames = ParseOptions(args)
    270   ast = ParseFiles(filenames)
    271 
    272   return Generator.Run(ast)
    273 
    274 
    275 if __name__ == '__main__':
    276   GeneratorReleaseTest('Test Gen', 'testgen', 'Generator Class Test.')
    277   sys.exit(Main(sys.argv[1:]))
    278