blob: fcad15c63a50b44def25631375eba65067784c40 [file] [log] [blame]
Jose Fonseca839a3d82016-03-05 18:01:14 +00001#!/usr/bin/env python
Jose Fonsecac0cfbd22016-05-18 11:05:35 +01002
3from __future__ import print_function
4
Jose Fonseca839a3d82016-03-05 18:01:14 +00005copyright = '''
6##########################################################################
7#
8# Copyright 2009-2016 VMware, Inc.
9# All Rights Reserved.
10#
11# Permission is hereby granted, free of charge, to any person obtaining a copy
12# of this software and associated documentation files (the "Software"), to deal
13# in the Software without restriction, including without limitation the rights
14# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15# copies of the Software, and to permit persons to whom the Software is
16# furnished to do so, subject to the following conditions:
17#
18# The above copyright notice and this permission notice shall be included in
19# all copies or substantial portions of the Software.
20#
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27# THE SOFTWARE.
28#
29##########################################################################/
30'''
31
32
33#
34# Generates API specs from headers using castxml/pygccxml.
35#
36# Usage:
37#
38# sudo apt-get install castxml mingw-w64-i686-dev
39# pip install 'pygccxml>=1.7.1'
40# python specs/scripts/cxx2api.py -Idxsdk/Include dcomp.h dcomptypes.h dcompanimation.h
41#
42# See also:
43# - http://pygccxml.readthedocs.org/en/develop/index.html
44# - https://github.com/CastXML/CastXML/blob/master/doc/manual/castxml.1.rst
45#
46
47import os.path
48import sys
49import cStringIO as StringIO
50import subprocess
51
52from pygccxml import utils
53from pygccxml import parser
54from pygccxml import declarations
55
56from pygccxml.declarations import algorithm
57from pygccxml.declarations import decl_visitor
58from pygccxml.declarations import type_traits
59from pygccxml.declarations import type_visitor
60
61
62class decl_dumper_t(decl_visitor.decl_visitor_t):
63
64 def __init__(self, decl = None):
65 decl_visitor.decl_visitor_t.__init__(self)
66 self.decl = decl
67 self.result = None
68
69 def clone(self):
70 return decl_dumper_t(self.decl)
71
72 def visit_class(self):
73 class_ = self.decl
74 assert class_.class_type in ('struct', 'union')
75 self.result = class_.name
76
77 def visit_class_declaration(self):
78 class_ = self.decl
79 self.result = class_.name
80
81 def visit_typedef(self):
82 typedef = self.decl
83 self.result = typedef.name
84
85 def visit_enumeration(self):
86 self.result = self.decl.name
87
88
89def dump_decl(decl):
90 visitor = decl_dumper_t(decl)
91 algorithm.apply_visitor(visitor, decl)
92 return visitor.result
93
94
95class type_dumper_t(type_visitor.type_visitor_t):
96
97 def __init__(self, type):
98 type_visitor.type_visitor_t.__init__(self)
99 self.type = type
100 self.result = None
101
102 def clone(self):
103 return type_dumper_t(self.type)
104
105 def visit_void(self):
106 self.result = 'Void'
107
108 def visit_char(self):
109 self.result = 'Char'
110
111 def visit_unsigned_char(self):
112 self.result = 'UChar'
113
114 def visit_signed_char(self):
115 self.result = 'SChar'
116
117 def visit_wchar(self):
118 raise NotImplementedError
119
120 def visit_short_int(self):
121 self.result = 'Short'
122
123 def visit_short_unsigned_int(self):
124 self.result = 'UShort'
125
126 def visit_bool(self):
127 raise NotImplementedError
128
129 def visit_int(self):
130 self.result = 'Int'
131
132 def visit_unsigned_int(self):
133 self.result = 'UInt'
134
135 def visit_long_int(self):
136 self.result = 'Long'
137
138 def visit_long_unsigned_int(self):
139 self.result = 'ULong'
140
141 def visit_long_long_int(self):
142 self.result = 'LongLong'
143
144 def visit_long_long_unsigned_int(self):
145 self.result = 'ULongLong'
146
147 def visit_float(self):
148 self.result = "Float"
149
150 def visit_double(self):
151 self.result = "Double"
152
153 def visit_array(self):
154 raise NotImplementedError
155
156 def visit_pointer(self):
157 base_type = dump_type(self.type.base)
158 # TODO: Use ObjPointer where appropriate
159 #if isinstance(self.type.base, declarations.cpptypes.declarated_t):
160 # decl = self.type.base.declaration
161 # if isinstance(decl, declarations.typedef.typedef_t):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100162 # print(decl.type, type(decl.type))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000163 # if isinstance(decl, declarations.class_declaration.class_t):
164 # if decl.public_members:
165 # self.result = 'ObjPointer(%s)' % decl.name
166 # return
167 # if isinstance(decl, declarations.class_declaration.class_declaration_t):
168 # if decl.public_members:
169 # self.result = 'ObjPointer(%s)' % decl.name
170 # return
171 if base_type.startswith('IDComposition') or \
172 base_type.startswith('IDXGI') or \
173 base_type == 'IUnknown':
174 self.result = 'ObjPointer(%s)' % base_type
175 return
176 self.result = 'Pointer(%s)' % base_type
177
178 def visit_reference(self):
179 self.result = 'Reference(%s)' % dump_type(self.type.base)
180
181 def visit_const(self):
182 self.result = 'Const(%s)' % dump_type(self.type.base)
183
184 def visit_declarated(self):
185 decl = self.type.declaration
186 self.result = dump_decl(decl)
187
188
189def dump_type(type):
190 visitor = type_dumper_t(type)
191 algorithm.apply_visitor(visitor, type)
192 # XXX: RECT becomes tagRECT somehow
193 if visitor.result == 'tagRECT':
194 return 'RECT'
195 return visitor.result
196
197
198def is_interface(class_):
199 if not class_.name.startswith('I'):
200 return
201 if len(class_.bases) != 1:
202 return False
203 # TODO: Ensure interface derives from IUnknown
204 return True
205
206
207class decl2_dumper_t(decl_visitor.decl_visitor_t):
208
209 def __init__(self, name):
210 decl_visitor.decl_visitor_t.__init__(self)
211
212 self.name = name
213
214 # The current declaration
215 self.decl = None
216
217 self.interfaces = StringIO.StringIO()
218 self.methods = StringIO.StringIO()
219 self.functions = StringIO.StringIO()
220
221 def start(self):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100222 print(copyright.strip())
223 print()
224 print()
225 print(r'from winapi import *')
226 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000227
228 def finish(self):
229 sys.stdout.write(self.interfaces.getvalue())
230 sys.stdout.write('\n')
231 sys.stdout.write(self.methods.getvalue())
232
233 name = self.name
234 sys.stdout.write('%s = Module(%r)\n' % (name, name))
235 sys.stdout.write('%s.addFunctions([\n' % (name,))
236 sys.stdout.write(self.functions.getvalue())
237 sys.stdout.write('])\n\n')
238
239 def clone(self):
240 return decl_dumper_t(self.decl)
241
242 def visit_class(self):
243 class_ = self.decl
244 assert class_.class_type in ('struct', 'union')
245
246 if is_interface(class_):
247 self.visit_interface()
248 elif class_.name != '':
249 self.visit_struct(class_.name, class_)
250
251 def visit_struct(self, decl_name, decl):
252 struct = decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100253 print(r'%s = Struct(%r, [' % (decl_name, decl_name))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000254 for variable in struct.variables():
255 var_type = dump_type(variable.type)
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100256 print(r' (%s, %r),' % (var_type, variable.name))
257 print(r'])')
258 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000259
260 def visit_interface(self):
261 class_ = self.decl
262 assert len(class_.bases) == 1
263 base = class_.bases[0]
264
265 s = self.interfaces
266 s.write('%s = Interface(%r, %s)\n' % (class_.name, class_.name, base.related_class.name))
267
268 s = self.methods
269 s.write('%s.methods += [\n' % (class_.name,))
270 for member in class_.public_members:
271 if member.virtuality != 'pure virtual':
272 continue
273 ret_type = dump_type(member.return_type)
274 arg_types = self.convert_args(member.arguments)
275 s.write(' StdMethod(%s, %r, [%s]),\n' % (ret_type, member.name, arg_types))
276 s.write(']\n\n')
277
278 def convert_args(self, args):
279 # TODO: use __attribute__ ((annotate ("out")))
280 # See also:
281 # - https://github.com/CastXML/CastXML/issues/25
282 # XXX: Requires a castxml version newer than the one in Ubuntu 15.10
283 arg_types = []
284 for arg in args:
285 if arg.attributes is not None:
286 sys.stderr.write('warning: found %s attribute %r\n' % (arg.name, arg.attributes))
287 res_arg_type = dump_type(arg.type)
288 res_arg = '(%s, %r)' % (res_arg_type, arg.name)
289
290 # Infer output arguments
291 if res_arg_type.startswith('Pointer(') and \
292 not res_arg_type.startswith('Pointer(Const('):
293 res_arg = 'Out' + res_arg
294
295 arg_types.append(res_arg)
296
297 arg_types = ', '.join(arg_types)
298 return arg_types
299
300 def visit_class_declaration(self):
301 pass
302
303 def visit_typedef(self):
304 typedef = self.decl
305 base_type = dump_type(typedef.type)
306 if base_type == typedef.name:
307 # Ignore `typedef struct Foo Foo;`
308 return
309 if base_type == '':
310 if isinstance(typedef.type, declarations.cpptypes.declarated_t):
311 base_decl = typedef.type.declaration
312 self.visit_struct(typedef.name, base_decl)
313 return
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100314 print(r'%s = Alias(%r, %s)' % (typedef.name, typedef.name, base_type))
315 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000316
317 def visit_enumeration(self):
318 enum = self.decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100319 print(r'%s = Enum(%r, [' % (enum.name, enum.name))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000320 for name, value in enum.values:
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100321 print(r' %r,' % (name,))
322 print(r'])')
323 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000324
325 def visit_variable(self):
326 pass
327
328 def visit_free_function(self):
329 function = self.decl
330 if function.has_inline:
331 return
332
333 s = self.functions
334 ret_type = dump_type(function.return_type)
335 arg_types = self.convert_args(function.arguments)
336 s.write(' StdFunction(%s, %r, [%s]),\n' % (ret_type, function.name, arg_types))
337
338
339def main():
340 defines = []
341 includes = []
342 cxxflags = [
343 '-Wno-unknown-attributes',
344 '-Wno-unused-value',
345 '-Wno-macro-redefined',
346 ]
347 compiler = 'g++'
348
349 args = sys.argv[1:]
350 while args and args[0].startswith('-'):
351 arg = args.pop(0)
352 if arg.startswith('-I'):
353 include = arg[2:]
354 includes.append(include)
355 elif arg.startswith('-D'):
356 define = arg[2:]
357 defines.append(define)
358 else:
359 sys.stderr.write('error: unknown option %r\n' % arg)
360 sys.exit(1)
361
362 winsdk = True
363 if winsdk:
364 # Set up Clang compiler flags to use MinGW runtime
Jose Fonsecafe66fc02016-05-18 11:00:03 +0100365 # http://stackoverflow.com/a/19839946
366 p = subprocess.Popen(
367 ["x86_64-w64-mingw32-g++", "-x", "c++", "-E", "-Wp,-v", '-', '-fsyntax-only'],
368 stdin=open(os.devnull, 'rt'),
369 stdout=open(os.devnull, 'wt'),
370 stderr=subprocess.PIPE)
371 includes.append('/usr/share/castxml/clang/include')
372 for line in p.stderr:
373 if line.startswith(' '):
374 include = line.strip()
375 if os.path.isdir(include):
376 if os.path.exists(os.path.join(include, 'ia32intrin.h')):
377 # XXX: We must use Clang's intrinsic headers
378 continue
379 includes.append(os.path.normpath(include))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000380
381 winver = 0x0602
382
383 defines += [
384 # emulate MinGW
385 '__MINGW32__',
386 '_WIN32',
387 '_WIN64',
388 '__declspec(x)=',
389 # Avoid namespace pollution when including windows.h
390 # http://support.microsoft.com/kb/166474
391 'WIN32_LEAN_AND_MEAN',
392 # Set Windows version to 8.1
393 '_WIN32_WINNT=0x%04X' % winver,
394 'WINVER=0x%04X' % winver,
395 'NTDDI_VERSION=0x%04X0000' % winver,
396 # Avoid C++ helper classes
397 'D3D10_NO_HELPERS',
398 'D3D11_NO_HELPERS',
399 'D3D11_VIDEO_NO_HELPERS',
400 ]
401
402 # XXX: Change compiler?
403 #compiler = 'cl'
404
405 # XXX: This doesn't seem to work well
406 cxxflags += [
407 #'-m32',
408 #'-target', 'x86_64-pc-mingw32',
409 ]
410
411 sys.stderr.write('Include path:\n')
412 for include in includes:
413 sys.stderr.write(' %s\n' % include)
414 sys.stderr.write('Definitions:\n')
415 for define in defines:
416 sys.stderr.write(' %s\n' % define)
417
418 import logging
419 utils.loggers.set_level(logging.DEBUG)
420
421 # Find the location of the xml generator (castxml or gccxml)
422 generator_path, generator_name = utils.find_xml_generator("castxml")
423
424 # Configure the xml generator
425 config = parser.xml_generator_configuration_t(
426 xml_generator_path=generator_path,
427 xml_generator=generator_name,
428 define_symbols = defines,
429 include_paths = includes,
430 cflags = ' '.join(cxxflags),
431 compiler = compiler,
432 #keep_xml = True,
433 )
434
435 script_dir = os.path.dirname(__file__)
436 headers = [
437 os.path.join(script_dir, '..', '..', 'compat', 'winsdk_compat.h'),
438 os.path.join(script_dir, 'cxx2api.h'),
439 ]
440 main_header = args[0]
441 headers.append(main_header)
442
443 decls = parser.parse(headers, config, parser.COMPILATION_MODE.ALL_AT_ONCE)
444 global_ns = declarations.get_global_namespace(decls)
445
446 def decl_filter(decl):
447 location = decl.location
448 if location is None:
449 return False
450 return os.path.basename(location.file_name) in args
451
452 module, _ = os.path.splitext(main_header)
453 visitor = decl2_dumper_t(module)
454 visitor.start()
455 for decl in global_ns.declarations:
456 if not decl_filter(decl):
457 continue
458 visitor.decl = decl
459 algorithm.apply_visitor(visitor, decl)
460 visitor.finish()
461
462
463if __name__ == '__main__':
464 main()