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.
6 """ Generator for C style prototypes and definitions """
13 from idl_log
import ErrOut
, InfoOut
, WarnOut
14 from idl_node
import IDLAttribute
, IDLNode
15 from idl_ast
import IDLAst
16 from idl_option
import GetOption
, Option
, ParseOptions
17 from idl_outfile
import IDLOutFile
18 from idl_parser
import ParseFiles
19 from idl_c_proto
import CGen
, GetNodeComments
, CommentLines
, Comment
20 from idl_generator
import Generator
, GeneratorByFile
21 from idl_visitor
import IDLVisitor
23 Option('dstroot', 'Base directory of output', default
=os
.path
.join('..', 'c'))
24 Option('guard', 'Include guard prefix', default
=os
.path
.join('ppapi', 'c'))
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.
34 class ProtoResolver(IDLVisitor
):
36 IDLVisitor
.__init
__(self
)
38 self
.interface_map
= {}
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(), [])
46 self
.struct_map
[typeref
.GetName()] = nodelist
49 typeref
= node
.typelist
.GetReleases()[0]
50 if typeref
.IsA('Interface'):
51 nodelist
= self
.struct_map
.get(typeref
.GetName(), [])
53 self
.interface_map
[typeref
.GetName()] = nodelist
58 def 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
)
67 def GetHeaderFromNode(filenode
, relpath
=None):
68 return GetPathFromNode(filenode
, relpath
, ext
='.h')
71 def 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'
78 if node
.cls
in ['Typedef', 'Interface', 'Struct', 'Enum']:
79 if last_group
!= node
.cls
:
80 pre
+= CommentLines(['*',' @addtogroup %ss' % node
.cls
, ' @{', ''])
88 def GenerateHeader(out
, filenode
, releases
):
93 # Generate definitions.
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
102 # End/Start group marker
104 last_group
= WriteGroupMarker(out
, node
, last_group
)
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':
114 if item
: out
.Write(item
)
118 # Otherwise we are defining a file level object, so generate the
119 # correct document notation.
121 item
= cgen
.Define(node
, releases
, prefix
=pref
, comment
=True)
122 if not item
: continue
123 asize
= node
.GetProperty('assert_size()')
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'
132 form
= 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n'
134 form
= 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n'
135 item
+= form
% (name
, asize
[0])
137 if item
: out
.Write(item
)
139 out
.Write(CommentLines(['*',' @}', '']) + '\n')
142 def CheckTypedefs(filenode
, releases
):
143 """Checks that typedefs don't specify callbacks that take some structs.
145 See http://crbug.com/233439 for details.
148 for node
in filenode
.GetListOf('Typedef'):
149 build_list
= node
.GetUniqueReleases(releases
)
150 callnode
= node
.GetOneOf('Callspec')
152 for param
in callnode
.GetListOf('Param'):
153 if param
.GetListOf('Array'):
155 if cgen
.GetParamMode(param
) != 'in':
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()))
166 def CheckPassByValue(filenode
, releases
):
167 """Checks that new pass-by-value structs are not introduced.
169 See http://crbug.com/233439 for details.
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
:
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())
191 class HGen(GeneratorByFile
):
193 Generator
.__init
__(self
, 'C Header', 'cgen', 'Generate the C headers.')
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
))
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
)
212 def GenerateHead(self
, out
, filenode
, releases
, options
):
213 __pychecker__
= 'unusednames=options'
215 proto
= ProtoResolver()
216 proto
.Visit(filenode
, None)
219 gpath
= GetOption('guard')
220 def_guard
= GetHeaderFromNode(filenode
, relpath
=gpath
)
221 def_guard
= def_guard
.replace(os
.sep
,'_').replace('.','_').upper() + '_'
223 cright_node
= filenode
.GetChildren()[0]
224 assert(cright_node
.IsA('Copyright'))
225 fileinfo
= filenode
.GetChildren()[1]
226 assert(fileinfo
.IsA('Comment'))
228 out
.Write('%s\n' % cgen
.Copyright(cright_node
))
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
))
237 out
.Write('/* %s,\n * %s\n */\n\n' % (from_text
, modified_text
))
239 out
.Write('#ifndef %s\n#define %s\n\n' % (def_guard
, def_guard
))
240 # Generate set of includes
243 for release
in releases
:
244 deps |
= filenode
.GetDeps(release
)
248 depfile
= dep
.GetProperty('FILE')
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')
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')
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
)
266 # Generate Prototypes
268 out
.Write('\n/* Struct prototypes */\n')
269 for struct
in proto
.struct_map
:
270 out
.Write('struct %s;\n' % struct
)
272 # Create a macro for the highest available release number.
273 if filenode
.GetProperty('NAME').endswith('pp_macros.idl'):
274 releasestr
= ' '.join(releases
)
276 release_numbers
= re
.findall('[\d\_]+', releasestr
)
277 release
= re
.findall('\d+', release_numbers
[-1])[0]
279 out
.Write('\n#define PPAPI_RELEASE %s\n' % release
)
281 # Generate all interface defines
283 for node
in filenode
.GetListOf('Interface'):
285 macro
= cgen
.GetInterfaceMacro(node
)
286 unique
= node
.GetUniqueReleases(releases
)
288 # Skip this interface if there are no matching versions
289 if not unique
: continue
291 last_stable_ver
= None
294 channel
= node
.GetProperty('FILE').release_map
.GetChannel(rel
)
299 version
= node
.GetVersion(rel
)
300 name
= cgen
.GetInterfaceString(node
, version
)
301 strver
= str(version
).replace('.', '_')
302 channel
= node
.GetProperty('FILE').release_map
.GetChannel(rel
)
304 # Skip dev channel interface versions that are
305 # Not the newest version, and
306 # Don't have an equivalent stable version.
307 if rel
!= last_dev_rel
and not node
.DevInterfaceMatchesStable(rel
):
309 value_string
= '"%s" /* dev */' % name
311 value_string
= '"%s"' % name
312 last_stable_ver
= strver
313 idefs
+= cgen
.GetDefine('%s_%s' % (macro
, strver
), value_string
)
315 idefs
+= cgen
.GetDefine(macro
, '%s_%s' % (macro
, last_stable_ver
))
320 # Generate the @file comment
321 out
.Write('%s\n' % Comment(fileinfo
, prefix
='*\n @file'))
323 def GenerateBody(self
, out
, filenode
, releases
, options
):
324 __pychecker__
= 'unusednames=options'
325 GenerateHeader(out
, filenode
, releases
)
327 def GenerateTail(self
, out
, filenode
, releases
, options
):
328 __pychecker__
= 'unusednames=options,releases'
329 gpath
= GetOption('guard')
330 def_guard
= GetPathFromNode(filenode
, relpath
=gpath
, ext
='.h')
331 def_guard
= def_guard
.replace(os
.sep
,'_').replace('.','_').upper() + '_'
332 out
.Write('#endif /* %s */\n\n' % def_guard
)
338 # Default invocation will verify the golden files are unchanged.
341 args
= ['--wnone', '--diff', '--test', '--dstroot=.']
345 idldir
= os
.path
.split(sys
.argv
[0])[0]
346 idldir
= os
.path
.join(idldir
, 'test_cgen', '*.idl')
347 filenames
= glob
.glob(idldir
)
348 ast
= ParseFiles(filenames
)
349 if hgen
.GenerateRelease(ast
, 'M14', {}):
350 print "Golden file for M14 failed."
353 print "Golden file for M14 passed."
356 idldir
= os
.path
.split(sys
.argv
[0])[0]
357 idldir
= os
.path
.join(idldir
, 'test_cgen_range', '*.idl')
358 filenames
= glob
.glob(idldir
)
360 ast
= ParseFiles(filenames
)
361 if hgen
.GenerateRange(ast
, ['M13', 'M14', 'M15', 'M16', 'M17'], {}):
362 print "Golden file for M13-M17 failed."
365 print "Golden file for M13-M17 passed."
369 if __name__
== '__main__':
370 sys
.exit(main(sys
.argv
[1:]))