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