blob: fa9a9d6cb22fe9ece2a4196947eb43c5978f7582 [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
Jose Fonsecafe66fc02016-05-18 11:00:03 +0100362 # http://stackoverflow.com/a/19839946
363 p = subprocess.Popen(
364 ["x86_64-w64-mingw32-g++", "-x", "c++", "-E", "-Wp,-v", '-', '-fsyntax-only'],
365 stdin=open(os.devnull, 'rt'),
366 stdout=open(os.devnull, 'wt'),
367 stderr=subprocess.PIPE)
368 includes.append('/usr/share/castxml/clang/include')
369 for line in p.stderr:
370 if line.startswith(' '):
371 include = line.strip()
372 if os.path.isdir(include):
373 if os.path.exists(os.path.join(include, 'ia32intrin.h')):
374 # XXX: We must use Clang's intrinsic headers
375 continue
376 includes.append(os.path.normpath(include))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000377
378 winver = 0x0602
379
380 defines += [
381 # emulate MinGW
382 '__MINGW32__',
383 '_WIN32',
384 '_WIN64',
385 '__declspec(x)=',
386 # Avoid namespace pollution when including windows.h
387 # http://support.microsoft.com/kb/166474
388 'WIN32_LEAN_AND_MEAN',
389 # Set Windows version to 8.1
390 '_WIN32_WINNT=0x%04X' % winver,
391 'WINVER=0x%04X' % winver,
392 'NTDDI_VERSION=0x%04X0000' % winver,
393 # Avoid C++ helper classes
394 'D3D10_NO_HELPERS',
395 'D3D11_NO_HELPERS',
396 'D3D11_VIDEO_NO_HELPERS',
397 ]
398
399 # XXX: Change compiler?
400 #compiler = 'cl'
401
402 # XXX: This doesn't seem to work well
403 cxxflags += [
404 #'-m32',
405 #'-target', 'x86_64-pc-mingw32',
406 ]
407
408 sys.stderr.write('Include path:\n')
409 for include in includes:
410 sys.stderr.write(' %s\n' % include)
411 sys.stderr.write('Definitions:\n')
412 for define in defines:
413 sys.stderr.write(' %s\n' % define)
414
415 import logging
416 utils.loggers.set_level(logging.DEBUG)
417
418 # Find the location of the xml generator (castxml or gccxml)
419 generator_path, generator_name = utils.find_xml_generator("castxml")
420
421 # Configure the xml generator
422 config = parser.xml_generator_configuration_t(
423 xml_generator_path=generator_path,
424 xml_generator=generator_name,
425 define_symbols = defines,
426 include_paths = includes,
427 cflags = ' '.join(cxxflags),
428 compiler = compiler,
429 #keep_xml = True,
430 )
431
432 script_dir = os.path.dirname(__file__)
433 headers = [
434 os.path.join(script_dir, '..', '..', 'compat', 'winsdk_compat.h'),
435 os.path.join(script_dir, 'cxx2api.h'),
436 ]
437 main_header = args[0]
438 headers.append(main_header)
439
440 decls = parser.parse(headers, config, parser.COMPILATION_MODE.ALL_AT_ONCE)
441 global_ns = declarations.get_global_namespace(decls)
442
443 def decl_filter(decl):
444 location = decl.location
445 if location is None:
446 return False
447 return os.path.basename(location.file_name) in args
448
449 module, _ = os.path.splitext(main_header)
450 visitor = decl2_dumper_t(module)
451 visitor.start()
452 for decl in global_ns.declarations:
453 if not decl_filter(decl):
454 continue
455 visitor.decl = decl
456 algorithm.apply_visitor(visitor, decl)
457 visitor.finish()
458
459
460if __name__ == '__main__':
461 main()