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