| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ Generator for C style prototypes and definitions """ |
| |
| import glob |
| import os |
| import re |
| import sys |
| |
| from idl_log import ErrOut, InfoOut, WarnOut |
| from idl_node import IDLAttribute, IDLNode |
| from idl_ast import IDLAst |
| from idl_option import GetOption, Option, ParseOptions |
| from idl_outfile import IDLOutFile |
| from idl_parser import ParseFiles |
| from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment |
| from idl_generator import Generator, GeneratorByFile |
| from idl_visitor import IDLVisitor |
| |
| Option('dstroot', 'Base directory of output', default=os.path.join('..', 'c')) |
| Option('guard', 'Include guard prefix', default=os.path.join('ppapi', 'c')) |
| |
| |
| # |
| # PrototypeResolver |
| # |
| # A specialized visitor which traverses the AST, building a mapping of |
| # Release names to Versions numbers and calculating a min version. |
| # The mapping is applied to the File nodes within the AST. |
| # |
| class ProtoResolver(IDLVisitor): |
| def __init__(self): |
| IDLVisitor.__init__(self) |
| self.struct_map = {} |
| self.interface_map = {} |
| |
| def Arrive(self, node, ignore): |
| if node.IsA('Member') and node.GetProperty('ref'): |
| typeref = node.typelist.GetReleases()[0] |
| if typeref.IsA('Struct'): |
| nodelist = self.struct_map.get(typeref.GetName(), []) |
| nodelist.append(node) |
| self.struct_map[typeref.GetName()] = nodelist |
| |
| if node.IsA('Param'): |
| typeref = node.typelist.GetReleases()[0] |
| if typeref.IsA('Interface'): |
| nodelist = self.struct_map.get(typeref.GetName(), []) |
| nodelist.append(node) |
| self.interface_map[typeref.GetName()] = nodelist |
| |
| return None |
| |
| |
| def GetPathFromNode(filenode, relpath=None, ext=None): |
| path, name = os.path.split(filenode.GetProperty('NAME')) |
| if ext: name = os.path.splitext(name)[0] + ext |
| if path: name = os.path.join(path, name) |
| if relpath: name = os.path.join(relpath, name) |
| name = os.path.normpath(name) |
| return name |
| |
| |
| def GetHeaderFromNode(filenode, relpath=None): |
| return GetPathFromNode(filenode, relpath, ext='.h') |
| |
| |
| def WriteGroupMarker(out, node, last_group): |
| # If we are part of a group comment marker... |
| if last_group and last_group != node.cls: |
| pre = CommentLines(['*',' @}', '']) + '\n' |
| else: |
| pre = '\n' |
| |
| if node.cls in ['Typedef', 'Interface', 'Struct', 'Enum']: |
| if last_group != node.cls: |
| pre += CommentLines(['*',' @addtogroup %ss' % node.cls, ' @{', '']) |
| last_group = node.cls |
| else: |
| last_group = None |
| out.Write(pre) |
| return last_group |
| |
| |
| def GenerateHeader(out, filenode, releases): |
| cgen = CGen() |
| pref = '' |
| do_comments = True |
| |
| # Generate definitions. |
| last_group = None |
| top_types = ['Typedef', 'Interface', 'Struct', 'Enum', 'Inline'] |
| for node in filenode.GetListOf(*top_types): |
| # Skip if this node is not in this release |
| if not node.InReleases(releases): |
| print "Skiping %s" % node |
| continue |
| |
| # End/Start group marker |
| if do_comments: |
| last_group = WriteGroupMarker(out, node, last_group) |
| |
| if node.IsA('Inline'): |
| item = node.GetProperty('VALUE') |
| # If 'C++' use __cplusplus wrapper |
| if node.GetName() == 'cc': |
| item = '#ifdef __cplusplus\n%s\n#endif /* __cplusplus */\n\n' % item |
| # If not C++ or C, then skip it |
| elif not node.GetName() == 'c': |
| continue |
| if item: out.Write(item) |
| continue |
| |
| # |
| # Otherwise we are defining a file level object, so generate the |
| # correct document notation. |
| # |
| item = cgen.Define(node, releases, prefix=pref, comment=True) |
| if not item: continue |
| asize = node.GetProperty('assert_size()') |
| if asize: |
| name = '%s%s' % (pref, node.GetName()) |
| if node.IsA('Struct'): |
| form = 'PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(%s, %s);\n' |
| elif node.IsA('Enum'): |
| if node.GetProperty('notypedef'): |
| form = 'PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(%s, %s);\n' |
| else: |
| form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n' |
| else: |
| form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n' |
| item += form % (name, asize[0]) |
| |
| if item: out.Write(item) |
| if last_group: |
| out.Write(CommentLines(['*',' @}', '']) + '\n') |
| |
| |
| def CheckTypedefs(filenode, releases): |
| """Checks that typedefs don't specify callbacks that take some structs. |
| |
| See http://crbug.com/233439 for details. |
| """ |
| cgen = CGen() |
| for node in filenode.GetListOf('Typedef'): |
| build_list = node.GetUniqueReleases(releases) |
| callnode = node.GetOneOf('Callspec') |
| if callnode: |
| for param in callnode.GetListOf('Param'): |
| if param.GetListOf('Array'): |
| continue |
| if cgen.GetParamMode(param) != 'in': |
| continue |
| t = param.GetType(build_list[0]) |
| while t.IsA('Typedef'): |
| t = t.GetType(build_list[0]) |
| if t.IsA('Struct') and t.GetProperty('passByValue'): |
| raise Exception('%s is a struct in callback %s. ' |
| 'See http://crbug.com/233439' % |
| (t.GetName(), node.GetName())) |
| |
| |
| def CheckPassByValue(filenode, releases): |
| """Checks that new pass-by-value structs are not introduced. |
| |
| See http://crbug.com/233439 for details. |
| """ |
| cgen = CGen() |
| # DO NOT add any more entries to this whitelist. |
| # http://crbug.com/233439 |
| type_whitelist = ['PP_ArrayOutput', 'PP_CompletionCallback', |
| 'PP_Ext_EventListener', 'PP_FloatPoint', |
| 'PP_Point', 'PP_TouchPoint', 'PP_Var'] |
| nodes_to_check = filenode.GetListOf('Struct') |
| nodes_to_check.extend(filenode.GetListOf('Union')) |
| for node in nodes_to_check: |
| if node.GetName() in type_whitelist: |
| continue |
| build_list = node.GetUniqueReleases(releases) |
| if node.GetProperty('passByValue'): |
| raise Exception('%s is a new passByValue struct or union. ' |
| 'See http://crbug.com/233439' % node.GetName()) |
| if node.GetProperty('returnByValue'): |
| raise Exception('%s is a new returnByValue struct or union. ' |
| 'See http://crbug.com/233439' % node.GetName()) |
| |
| |
| class HGen(GeneratorByFile): |
| def __init__(self): |
| Generator.__init__(self, 'C Header', 'cgen', 'Generate the C headers.') |
| |
| def GenerateFile(self, filenode, releases, options): |
| CheckTypedefs(filenode, releases) |
| CheckPassByValue(filenode, releases) |
| savename = GetHeaderFromNode(filenode, GetOption('dstroot')) |
| my_min, my_max = filenode.GetMinMax(releases) |
| if my_min > releases[-1] or my_max < releases[0]: |
| if os.path.isfile(savename): |
| print "Removing stale %s for this range." % filenode.GetName() |
| os.remove(os.path.realpath(savename)) |
| return False |
| |
| out = IDLOutFile(savename) |
| self.GenerateHead(out, filenode, releases, options) |
| self.GenerateBody(out, filenode, releases, options) |
| self.GenerateTail(out, filenode, releases, options) |
| return out.Close() |
| |
| def GenerateHead(self, out, filenode, releases, options): |
| __pychecker__ = 'unusednames=options' |
| |
| proto = ProtoResolver() |
| proto.Visit(filenode, None) |
| |
| cgen = CGen() |
| gpath = GetOption('guard') |
| def_guard = GetHeaderFromNode(filenode, relpath=gpath) |
| def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_' |
| |
| cright_node = filenode.GetChildren()[0] |
| assert(cright_node.IsA('Copyright')) |
| fileinfo = filenode.GetChildren()[1] |
| assert(fileinfo.IsA('Comment')) |
| |
| out.Write('%s\n' % cgen.Copyright(cright_node)) |
| |
| # Wrap the From ... modified ... comment if it would be >80 characters. |
| from_text = 'From %s' % GetPathFromNode(filenode).replace(os.sep, '/') |
| modified_text = 'modified %s.' % ( |
| filenode.GetProperty('DATETIME')) |
| if len(from_text) + len(modified_text) < 74: |
| out.Write('/* %s %s */\n\n' % (from_text, modified_text)) |
| else: |
| out.Write('/* %s,\n * %s\n */\n\n' % (from_text, modified_text)) |
| |
| out.Write('#ifndef %s\n#define %s\n\n' % (def_guard, def_guard)) |
| # Generate set of includes |
| |
| deps = set() |
| for release in releases: |
| deps |= filenode.GetDeps(release) |
| |
| includes = set([]) |
| for dep in deps: |
| depfile = dep.GetProperty('FILE') |
| if depfile: |
| includes.add(depfile) |
| includes = [GetHeaderFromNode( |
| include, relpath=gpath).replace(os.sep, '/') for include in includes] |
| includes.append('ppapi/c/pp_macros.h') |
| |
| # Assume we need stdint if we "include" C or C++ code |
| if filenode.GetListOf('Include'): |
| includes.append('ppapi/c/pp_stdint.h') |
| |
| includes = sorted(set(includes)) |
| cur_include = GetHeaderFromNode(filenode, |
| relpath=gpath).replace(os.sep, '/') |
| for include in includes: |
| if include == cur_include: continue |
| out.Write('#include "%s"\n' % include) |
| |
| # Generate Prototypes |
| if proto.struct_map: |
| out.Write('\n/* Struct prototypes */\n') |
| for struct in proto.struct_map: |
| out.Write('struct %s;\n' % struct) |
| |
| # Create a macro for the highest available release number. |
| if filenode.GetProperty('NAME').endswith('pp_macros.idl'): |
| releasestr = ' '.join(releases) |
| if releasestr: |
| release_numbers = re.findall('[\d\_]+', releasestr) |
| release = re.findall('\d+', release_numbers[-1])[0] |
| if release: |
| out.Write('#define PPAPI_RELEASE %s\n' % release) |
| |
| # Generate all interface defines |
| out.Write('\n') |
| for node in filenode.GetListOf('Interface'): |
| idefs = '' |
| macro = cgen.GetInterfaceMacro(node) |
| unique = node.GetUniqueReleases(releases) |
| |
| # Skip this interface if there are no matching versions |
| if not unique: continue |
| |
| # Skip this interface if it should have no interface string. |
| if node.GetProperty('no_interface_string'): continue |
| |
| last_stable_ver = None |
| last_dev_rel = None |
| for rel in unique: |
| channel = node.GetProperty('FILE').release_map.GetChannel(rel) |
| if channel == 'dev': |
| last_dev_rel = rel |
| |
| for rel in unique: |
| version = node.GetVersion(rel) |
| name = cgen.GetInterfaceString(node, version) |
| strver = str(version).replace('.', '_') |
| channel = node.GetProperty('FILE').release_map.GetChannel(rel) |
| if channel == 'dev': |
| # Skip dev channel interface versions that are |
| # Not the newest version, and |
| # Don't have an equivalent stable version. |
| if rel != last_dev_rel and not node.DevInterfaceMatchesStable(rel): |
| continue |
| value_string = '"%s" /* dev */' % name |
| else: |
| value_string = '"%s"' % name |
| last_stable_ver = strver |
| idefs += cgen.GetDefine('%s_%s' % (macro, strver), value_string) |
| if last_stable_ver: |
| idefs += cgen.GetDefine(macro, '%s_%s' % (macro, last_stable_ver)) |
| idefs += '\n' |
| |
| out.Write(idefs) |
| |
| # Generate the @file comment |
| out.Write('%s\n' % Comment(fileinfo, prefix='*\n @file')) |
| |
| def GenerateBody(self, out, filenode, releases, options): |
| __pychecker__ = 'unusednames=options' |
| GenerateHeader(out, filenode, releases) |
| |
| def GenerateTail(self, out, filenode, releases, options): |
| __pychecker__ = 'unusednames=options,releases' |
| gpath = GetOption('guard') |
| def_guard = GetPathFromNode(filenode, relpath=gpath, ext='.h') |
| def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_' |
| out.Write('#endif /* %s */\n\n' % def_guard) |
| |
| |
| hgen = HGen() |
| |
| def main(args): |
| # Default invocation will verify the golden files are unchanged. |
| failed = 0 |
| if not args: |
| args = ['--wnone', '--diff', '--test', '--dstroot=.'] |
| |
| ParseOptions(args) |
| |
| idldir = os.path.split(sys.argv[0])[0] |
| idldir = os.path.join(idldir, 'test_cgen', '*.idl') |
| filenames = glob.glob(idldir) |
| ast = ParseFiles(filenames) |
| if hgen.GenerateRelease(ast, 'M14', {}): |
| print "Golden file for M14 failed." |
| failed = 1 |
| else: |
| print "Golden file for M14 passed." |
| |
| |
| idldir = os.path.split(sys.argv[0])[0] |
| idldir = os.path.join(idldir, 'test_cgen_range', '*.idl') |
| filenames = glob.glob(idldir) |
| |
| ast = ParseFiles(filenames) |
| if hgen.GenerateRange(ast, ['M13', 'M14', 'M15', 'M16', 'M17'], {}): |
| print "Golden file for M13-M17 failed." |
| failed =1 |
| else: |
| print "Golden file for M13-M17 passed." |
| |
| return failed |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |