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