idl_c_header.py revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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  # TODO(teravest): Fix the following callback to pass PP_Var by pointer
149  # instead of by value.
150  node_whitelist = ['PP_Ext_Alarms_OnAlarm_Func_Dev_0_1']
151  for node in filenode.GetListOf('Typedef'):
152    if node.GetName() in node_whitelist:
153      continue
154    build_list = node.GetUniqueReleases(releases)
155    callnode = node.GetOneOf('Callspec')
156    if callnode:
157      for param in callnode.GetListOf('Param'):
158        if param.GetListOf('Array'):
159          continue
160        if cgen.GetParamMode(param) != 'in':
161          continue
162        t = param.GetType(build_list[0])
163        while t.IsA('Typedef'):
164          t = t.GetType(build_list[0])
165        if t.IsA('Struct'):
166          raise Exception('%s is a struct in callback %s. '
167                          'See http://crbug.com/233439' %
168                          (t.GetName(), node.GetName()))
169
170
171def CheckPassByValue(filenode, releases):
172  """Checks that new pass-by-value structs are not introduced.
173
174  See http://crbug.com/233439 for details.
175  """
176  cgen = CGen()
177  # DO NOT add any more entries to this whitelist.
178  # http://crbug.com/233439
179  type_whitelist = ['PP_ArrayOutput', 'PP_CompletionCallback',
180                    'PP_Ext_EventListener', 'PP_FloatPoint',
181                    'PP_Graphics3DTrustedState', 'PP_Point', 'PP_TouchPoint',
182                    'PP_Var']
183  nodes_to_check = filenode.GetListOf('Struct')
184  nodes_to_check.extend(filenode.GetListOf('Union'))
185  for node in nodes_to_check:
186    if node.GetName() in type_whitelist:
187      continue
188    build_list = node.GetUniqueReleases(releases)
189    if node.GetProperty('passByValue'):
190      raise Exception('%s is a new passByValue struct or union. '
191                      'See http://crbug.com/233439' % node.GetName())
192    if node.GetProperty('returnByValue'):
193      raise Exception('%s is a new returnByValue struct or union. '
194                      'See http://crbug.com/233439' % node.GetName())
195
196
197class HGen(GeneratorByFile):
198  def __init__(self):
199    Generator.__init__(self, 'C Header', 'cgen', 'Generate the C headers.')
200
201  def GenerateFile(self, filenode, releases, options):
202    CheckTypedefs(filenode, releases)
203    CheckPassByValue(filenode, releases)
204    savename = GetHeaderFromNode(filenode, GetOption('dstroot'))
205    my_min, my_max = filenode.GetMinMax(releases)
206    if my_min > releases[-1] or my_max < releases[0]:
207      if os.path.isfile(savename):
208        print "Removing stale %s for this range." % filenode.GetName()
209        os.remove(os.path.realpath(savename))
210      return False
211
212    out = IDLOutFile(savename)
213    self.GenerateHead(out, filenode, releases, options)
214    self.GenerateBody(out, filenode, releases, options)
215    self.GenerateTail(out, filenode, releases, options)
216    return out.Close()
217
218  def GenerateHead(self, out, filenode, releases, options):
219    __pychecker__ = 'unusednames=options'
220
221    proto = ProtoResolver()
222    proto.Visit(filenode, None)
223
224    cgen = CGen()
225    gpath = GetOption('guard')
226    def_guard = GetHeaderFromNode(filenode, relpath=gpath)
227    def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_'
228
229    cright_node = filenode.GetChildren()[0]
230    assert(cright_node.IsA('Copyright'))
231    fileinfo = filenode.GetChildren()[1]
232    assert(fileinfo.IsA('Comment'))
233
234    out.Write('%s\n' % cgen.Copyright(cright_node))
235
236    # Wrap the From ... modified ... comment if it would be >80 characters.
237    from_text = 'From %s' % GetPathFromNode(filenode).replace(os.sep, '/')
238    modified_text = 'modified %s.' % (
239        filenode.GetProperty('DATETIME'))
240    if len(from_text) + len(modified_text) < 74:
241      out.Write('/* %s %s */\n\n' % (from_text, modified_text))
242    else:
243      out.Write('/* %s,\n *   %s\n */\n\n' % (from_text, modified_text))
244
245    out.Write('#ifndef %s\n#define %s\n\n' % (def_guard, def_guard))
246    # Generate set of includes
247
248    deps = set()
249    for release in releases:
250      deps |= filenode.GetDeps(release)
251
252    includes = set([])
253    for dep in deps:
254      depfile = dep.GetProperty('FILE')
255      if depfile:
256        includes.add(depfile)
257    includes = [GetHeaderFromNode(
258        include, relpath=gpath).replace(os.sep, '/') for include in includes]
259    includes.append('ppapi/c/pp_macros.h')
260
261    # Assume we need stdint if we "include" C or C++ code
262    if filenode.GetListOf('Include'):
263      includes.append('ppapi/c/pp_stdint.h')
264
265    includes = sorted(set(includes))
266    cur_include = GetHeaderFromNode(filenode,
267                                    relpath=gpath).replace(os.sep, '/')
268    for include in includes:
269      if include == cur_include: continue
270      out.Write('#include "%s"\n' % include)
271
272    # Generate Prototypes
273    if proto.struct_map:
274      out.Write('\n/* Struct prototypes */\n')
275      for struct in proto.struct_map:
276        out.Write('struct %s;\n' % struct)
277
278    # Create a macro for the highest available release number.
279    if filenode.GetProperty('NAME').endswith('pp_macros.idl'):
280      releasestr = ' '.join(releases)
281      if releasestr:
282        release_numbers = re.findall('[\d\_]+', releasestr)
283        release = re.findall('\d+', release_numbers[-1])[0]
284        if release:
285          out.Write('\n#define PPAPI_RELEASE %s\n' % release)
286
287    # Generate all interface defines
288    out.Write('\n')
289    for node in filenode.GetListOf('Interface'):
290      idefs = ''
291      macro = cgen.GetInterfaceMacro(node)
292      unique = node.GetUniqueReleases(releases)
293
294      # Skip this interface if there are no matching versions
295      if not unique: continue
296
297      for rel in unique:
298        version = node.GetVersion(rel)
299        name = cgen.GetInterfaceString(node, version)
300        strver = str(version).replace('.', '_')
301        idefs += cgen.GetDefine('%s_%s' % (macro, strver), '"%s"' % name)
302      idefs += cgen.GetDefine(macro, '%s_%s' % (macro, strver)) + '\n'
303      out.Write(idefs)
304
305    # Generate the @file comment
306    out.Write('%s\n' % Comment(fileinfo, prefix='*\n @file'))
307
308  def GenerateBody(self, out, filenode, releases, options):
309    __pychecker__ = 'unusednames=options'
310    GenerateHeader(out, filenode, releases)
311
312  def GenerateTail(self, out, filenode, releases, options):
313    __pychecker__ = 'unusednames=options,releases'
314    gpath = GetOption('guard')
315    def_guard = GetPathFromNode(filenode, relpath=gpath, ext='.h')
316    def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_'
317    out.Write('#endif  /* %s */\n\n' % def_guard)
318
319
320hgen = HGen()
321
322def main(args):
323  # Default invocation will verify the golden files are unchanged.
324  failed = 0
325  if not args:
326    args = ['--wnone', '--diff', '--test', '--dstroot=.']
327
328  ParseOptions(args)
329
330  idldir = os.path.split(sys.argv[0])[0]
331  idldir = os.path.join(idldir, 'test_cgen', '*.idl')
332  filenames = glob.glob(idldir)
333  ast = ParseFiles(filenames)
334  if hgen.GenerateRelease(ast, 'M14', {}):
335    print "Golden file for M14 failed."
336    failed = 1
337  else:
338    print "Golden file for M14 passed."
339
340
341  idldir = os.path.split(sys.argv[0])[0]
342  idldir = os.path.join(idldir, 'test_cgen_range', '*.idl')
343  filenames = glob.glob(idldir)
344
345  ast = ParseFiles(filenames)
346  if hgen.GenerateRange(ast, ['M13', 'M14', 'M15'], {}):
347    print "Golden file for M13-M15 failed."
348    failed =1
349  else:
350    print "Golden file for M13-M15 passed."
351
352  return failed
353
354if __name__ == '__main__':
355  sys.exit(main(sys.argv[1:]))
356
357