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""" Generator for C style prototypes and definitions """ 7 8import glob 9import os 10import re 11import sys 12 13from idl_log import ErrOut, InfoOut, WarnOut 14from idl_node import IDLAttribute, IDLNode 15from idl_ast import IDLAst 16from idl_option import GetOption, Option, ParseOptions 17from idl_outfile import IDLOutFile 18from idl_parser import ParseFiles 19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment 20from idl_generator import Generator, GeneratorByFile 21from idl_visitor import IDLVisitor 22 23Option('dstroot', 'Base directory of output', default=os.path.join('..', 'c')) 24Option('guard', 'Include guard prefix', default=os.path.join('ppapi', 'c')) 25 26 27# 28# PrototypeResolver 29# 30# A specialized visitor which traverses the AST, building a mapping of 31# Release names to Versions numbers and calculating a min version. 32# The mapping is applied to the File nodes within the AST. 33# 34class ProtoResolver(IDLVisitor): 35 def __init__(self): 36 IDLVisitor.__init__(self) 37 self.struct_map = {} 38 self.interface_map = {} 39 40 def Arrive(self, node, ignore): 41 if node.IsA('Member') and node.GetProperty('ref'): 42 typeref = node.typelist.GetReleases()[0] 43 if typeref.IsA('Struct'): 44 nodelist = self.struct_map.get(typeref.GetName(), []) 45 nodelist.append(node) 46 self.struct_map[typeref.GetName()] = nodelist 47 48 if node.IsA('Param'): 49 typeref = node.typelist.GetReleases()[0] 50 if typeref.IsA('Interface'): 51 nodelist = self.struct_map.get(typeref.GetName(), []) 52 nodelist.append(node) 53 self.interface_map[typeref.GetName()] = nodelist 54 55 return None 56 57 58def GetPathFromNode(filenode, relpath=None, ext=None): 59 path, name = os.path.split(filenode.GetProperty('NAME')) 60 if ext: name = os.path.splitext(name)[0] + ext 61 if path: name = os.path.join(path, name) 62 if relpath: name = os.path.join(relpath, name) 63 name = os.path.normpath(name) 64 return name 65 66 67def GetHeaderFromNode(filenode, relpath=None): 68 return GetPathFromNode(filenode, relpath, ext='.h') 69 70 71def WriteGroupMarker(out, node, last_group): 72 # If we are part of a group comment marker... 73 if last_group and last_group != node.cls: 74 pre = CommentLines(['*',' @}', '']) + '\n' 75 else: 76 pre = '\n' 77 78 if node.cls in ['Typedef', 'Interface', 'Struct', 'Enum']: 79 if last_group != node.cls: 80 pre += CommentLines(['*',' @addtogroup %ss' % node.cls, ' @{', '']) 81 last_group = node.cls 82 else: 83 last_group = None 84 out.Write(pre) 85 return last_group 86 87 88def GenerateHeader(out, filenode, releases): 89 cgen = CGen() 90 pref = '' 91 do_comments = True 92 93 # Generate definitions. 94 last_group = None 95 top_types = ['Typedef', 'Interface', 'Struct', 'Enum', 'Inline'] 96 for node in filenode.GetListOf(*top_types): 97 # Skip if this node is not in this release 98 if not node.InReleases(releases): 99 print "Skiping %s" % node 100 continue 101 102 # End/Start group marker 103 if do_comments: 104 last_group = WriteGroupMarker(out, node, last_group) 105 106 if node.IsA('Inline'): 107 item = node.GetProperty('VALUE') 108 # If 'C++' use __cplusplus wrapper 109 if node.GetName() == 'cc': 110 item = '#ifdef __cplusplus\n%s\n#endif /* __cplusplus */\n\n' % item 111 # If not C++ or C, then skip it 112 elif not node.GetName() == 'c': 113 continue 114 if item: out.Write(item) 115 continue 116 117 # 118 # Otherwise we are defining a file level object, so generate the 119 # correct document notation. 120 # 121 item = cgen.Define(node, releases, prefix=pref, comment=True) 122 if not item: continue 123 asize = node.GetProperty('assert_size()') 124 if asize: 125 name = '%s%s' % (pref, node.GetName()) 126 if node.IsA('Struct'): 127 form = 'PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(%s, %s);\n' 128 elif node.IsA('Enum'): 129 if node.GetProperty('notypedef'): 130 form = 'PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(%s, %s);\n' 131 else: 132 form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n' 133 else: 134 form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n' 135 item += form % (name, asize[0]) 136 137 if item: out.Write(item) 138 if last_group: 139 out.Write(CommentLines(['*',' @}', '']) + '\n') 140 141 142def CheckTypedefs(filenode, releases): 143 """Checks that typedefs don't specify callbacks that take some structs. 144 145 See http://crbug.com/233439 for details. 146 """ 147 cgen = CGen() 148 for node in filenode.GetListOf('Typedef'): 149 build_list = node.GetUniqueReleases(releases) 150 callnode = node.GetOneOf('Callspec') 151 if callnode: 152 for param in callnode.GetListOf('Param'): 153 if param.GetListOf('Array'): 154 continue 155 if cgen.GetParamMode(param) != 'in': 156 continue 157 t = param.GetType(build_list[0]) 158 while t.IsA('Typedef'): 159 t = t.GetType(build_list[0]) 160 if t.IsA('Struct') and t.GetProperty('passByValue'): 161 raise Exception('%s is a struct in callback %s. ' 162 'See http://crbug.com/233439' % 163 (t.GetName(), node.GetName())) 164 165 166def CheckPassByValue(filenode, releases): 167 """Checks that new pass-by-value structs are not introduced. 168 169 See http://crbug.com/233439 for details. 170 """ 171 cgen = CGen() 172 # DO NOT add any more entries to this whitelist. 173 # http://crbug.com/233439 174 type_whitelist = ['PP_ArrayOutput', 'PP_CompletionCallback', 175 'PP_Ext_EventListener', 'PP_FloatPoint', 176 'PP_Point', 'PP_TouchPoint', 'PP_Var'] 177 nodes_to_check = filenode.GetListOf('Struct') 178 nodes_to_check.extend(filenode.GetListOf('Union')) 179 for node in nodes_to_check: 180 if node.GetName() in type_whitelist: 181 continue 182 build_list = node.GetUniqueReleases(releases) 183 if node.GetProperty('passByValue'): 184 raise Exception('%s is a new passByValue struct or union. ' 185 'See http://crbug.com/233439' % node.GetName()) 186 if node.GetProperty('returnByValue'): 187 raise Exception('%s is a new returnByValue struct or union. ' 188 'See http://crbug.com/233439' % node.GetName()) 189 190 191class HGen(GeneratorByFile): 192 def __init__(self): 193 Generator.__init__(self, 'C Header', 'cgen', 'Generate the C headers.') 194 195 def GenerateFile(self, filenode, releases, options): 196 CheckTypedefs(filenode, releases) 197 CheckPassByValue(filenode, releases) 198 savename = GetHeaderFromNode(filenode, GetOption('dstroot')) 199 my_min, my_max = filenode.GetMinMax(releases) 200 if my_min > releases[-1] or my_max < releases[0]: 201 if os.path.isfile(savename): 202 print "Removing stale %s for this range." % filenode.GetName() 203 os.remove(os.path.realpath(savename)) 204 return False 205 206 out = IDLOutFile(savename) 207 self.GenerateHead(out, filenode, releases, options) 208 self.GenerateBody(out, filenode, releases, options) 209 self.GenerateTail(out, filenode, releases, options) 210 return out.Close() 211 212 def GenerateHead(self, out, filenode, releases, options): 213 __pychecker__ = 'unusednames=options' 214 215 proto = ProtoResolver() 216 proto.Visit(filenode, None) 217 218 cgen = CGen() 219 gpath = GetOption('guard') 220 def_guard = GetHeaderFromNode(filenode, relpath=gpath) 221 def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_' 222 223 cright_node = filenode.GetChildren()[0] 224 assert(cright_node.IsA('Copyright')) 225 fileinfo = filenode.GetChildren()[1] 226 assert(fileinfo.IsA('Comment')) 227 228 out.Write('%s\n' % cgen.Copyright(cright_node)) 229 230 # Wrap the From ... modified ... comment if it would be >80 characters. 231 from_text = 'From %s' % GetPathFromNode(filenode).replace(os.sep, '/') 232 modified_text = 'modified %s.' % ( 233 filenode.GetProperty('DATETIME')) 234 if len(from_text) + len(modified_text) < 74: 235 out.Write('/* %s %s */\n\n' % (from_text, modified_text)) 236 else: 237 out.Write('/* %s,\n * %s\n */\n\n' % (from_text, modified_text)) 238 239 out.Write('#ifndef %s\n#define %s\n\n' % (def_guard, def_guard)) 240 # Generate set of includes 241 242 deps = set() 243 for release in releases: 244 deps |= filenode.GetDeps(release) 245 246 includes = set([]) 247 for dep in deps: 248 depfile = dep.GetProperty('FILE') 249 if depfile: 250 includes.add(depfile) 251 includes = [GetHeaderFromNode( 252 include, relpath=gpath).replace(os.sep, '/') for include in includes] 253 includes.append('ppapi/c/pp_macros.h') 254 255 # Assume we need stdint if we "include" C or C++ code 256 if filenode.GetListOf('Include'): 257 includes.append('ppapi/c/pp_stdint.h') 258 259 includes = sorted(set(includes)) 260 cur_include = GetHeaderFromNode(filenode, 261 relpath=gpath).replace(os.sep, '/') 262 for include in includes: 263 if include == cur_include: continue 264 out.Write('#include "%s"\n' % include) 265 266 # Generate Prototypes 267 if proto.struct_map: 268 out.Write('\n/* Struct prototypes */\n') 269 for struct in proto.struct_map: 270 out.Write('struct %s;\n' % struct) 271 272 # Create a macro for the highest available release number. 273 if filenode.GetProperty('NAME').endswith('pp_macros.idl'): 274 releasestr = ' '.join(releases) 275 if releasestr: 276 release_numbers = re.findall('[\d\_]+', releasestr) 277 release = re.findall('\d+', release_numbers[-1])[0] 278 if release: 279 out.Write('\n#define PPAPI_RELEASE %s\n' % release) 280 281 # Generate all interface defines 282 out.Write('\n') 283 for node in filenode.GetListOf('Interface'): 284 idefs = '' 285 macro = cgen.GetInterfaceMacro(node) 286 unique = node.GetUniqueReleases(releases) 287 288 # Skip this interface if there are no matching versions 289 if not unique: continue 290 291 # Skip this interface if it should have no interface string. 292 if node.GetProperty('no_interface_string'): continue 293 294 last_stable_ver = None 295 last_dev_rel = None 296 for rel in unique: 297 channel = node.GetProperty('FILE').release_map.GetChannel(rel) 298 if channel == 'dev': 299 last_dev_rel = rel 300 301 for rel in unique: 302 version = node.GetVersion(rel) 303 name = cgen.GetInterfaceString(node, version) 304 strver = str(version).replace('.', '_') 305 channel = node.GetProperty('FILE').release_map.GetChannel(rel) 306 if channel == 'dev': 307 # Skip dev channel interface versions that are 308 # Not the newest version, and 309 # Don't have an equivalent stable version. 310 if rel != last_dev_rel and not node.DevInterfaceMatchesStable(rel): 311 continue 312 value_string = '"%s" /* dev */' % name 313 else: 314 value_string = '"%s"' % name 315 last_stable_ver = strver 316 idefs += cgen.GetDefine('%s_%s' % (macro, strver), value_string) 317 if last_stable_ver: 318 idefs += cgen.GetDefine(macro, '%s_%s' % (macro, last_stable_ver)) 319 idefs += '\n' 320 321 out.Write(idefs) 322 323 # Generate the @file comment 324 out.Write('%s\n' % Comment(fileinfo, prefix='*\n @file')) 325 326 def GenerateBody(self, out, filenode, releases, options): 327 __pychecker__ = 'unusednames=options' 328 GenerateHeader(out, filenode, releases) 329 330 def GenerateTail(self, out, filenode, releases, options): 331 __pychecker__ = 'unusednames=options,releases' 332 gpath = GetOption('guard') 333 def_guard = GetPathFromNode(filenode, relpath=gpath, ext='.h') 334 def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_' 335 out.Write('#endif /* %s */\n\n' % def_guard) 336 337 338hgen = HGen() 339 340def main(args): 341 # Default invocation will verify the golden files are unchanged. 342 failed = 0 343 if not args: 344 args = ['--wnone', '--diff', '--test', '--dstroot=.'] 345 346 ParseOptions(args) 347 348 idldir = os.path.split(sys.argv[0])[0] 349 idldir = os.path.join(idldir, 'test_cgen', '*.idl') 350 filenames = glob.glob(idldir) 351 ast = ParseFiles(filenames) 352 if hgen.GenerateRelease(ast, 'M14', {}): 353 print "Golden file for M14 failed." 354 failed = 1 355 else: 356 print "Golden file for M14 passed." 357 358 359 idldir = os.path.split(sys.argv[0])[0] 360 idldir = os.path.join(idldir, 'test_cgen_range', '*.idl') 361 filenames = glob.glob(idldir) 362 363 ast = ParseFiles(filenames) 364 if hgen.GenerateRange(ast, ['M13', 'M14', 'M15', 'M16', 'M17'], {}): 365 print "Golden file for M13-M17 failed." 366 failed =1 367 else: 368 print "Golden file for M13-M17 passed." 369 370 return failed 371 372if __name__ == '__main__': 373 sys.exit(main(sys.argv[1:])) 374 375