blob: 8fc195c447ad6bfff6554b30f237434d5b91fb85 [file] [log] [blame]
Jose Fonseca7a6f8422020-05-21 09:14:00 +01001#!/usr/bin/env python2
Jose Fonsecac0cfbd22016-05-18 11:05:35 +01002
Piotr Podsiadły0b8b0192019-01-03 20:39:55 +01003
Jose Fonsecac0cfbd22016-05-18 11:05:35 +01004
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#
Jose Fonseca7a6f8422020-05-21 09:14:00 +010038# sudo apt-get install python2 castxml mingw-w64-i686-dev
39# python2 -m pip install 'pygccxml==1.9.1'
40# python2 specs/scripts/cxx2api.py -Idxsdk/Include -DD2D_USE_C_DEFINITIONS dcomp.h dcomptypes.h dcompanimation.h
Jose Fonseca839a3d82016-03-05 18:01:14 +000041#
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
Jose Fonseca839a3d82016-03-05 18:01:14 +000049import subprocess
50
Jose Fonseca7a6f8422020-05-21 09:14:00 +010051from cStringIO import StringIO
52
Jose Fonseca839a3d82016-03-05 18:01:14 +000053from pygccxml import utils
54from pygccxml import parser
55from pygccxml import declarations
56
57from pygccxml.declarations import algorithm
58from pygccxml.declarations import decl_visitor
59from pygccxml.declarations import type_traits
60from pygccxml.declarations import type_visitor
61
62
63class decl_dumper_t(decl_visitor.decl_visitor_t):
64
65 def __init__(self, decl = None):
66 decl_visitor.decl_visitor_t.__init__(self)
67 self.decl = decl
68 self.result = None
69
70 def clone(self):
71 return decl_dumper_t(self.decl)
72
73 def visit_class(self):
74 class_ = self.decl
75 assert class_.class_type in ('struct', 'union')
76 self.result = class_.name
77
78 def visit_class_declaration(self):
79 class_ = self.decl
80 self.result = class_.name
81
82 def visit_typedef(self):
83 typedef = self.decl
84 self.result = typedef.name
85
86 def visit_enumeration(self):
87 self.result = self.decl.name
88
89
90def dump_decl(decl):
91 visitor = decl_dumper_t(decl)
92 algorithm.apply_visitor(visitor, decl)
93 return visitor.result
94
95
96class type_dumper_t(type_visitor.type_visitor_t):
97
98 def __init__(self, type):
99 type_visitor.type_visitor_t.__init__(self)
100 self.type = type
101 self.result = None
102
103 def clone(self):
104 return type_dumper_t(self.type)
105
106 def visit_void(self):
107 self.result = 'Void'
108
109 def visit_char(self):
110 self.result = 'Char'
111
112 def visit_unsigned_char(self):
113 self.result = 'UChar'
114
115 def visit_signed_char(self):
116 self.result = 'SChar'
117
118 def visit_wchar(self):
119 raise NotImplementedError
120
121 def visit_short_int(self):
122 self.result = 'Short'
123
124 def visit_short_unsigned_int(self):
125 self.result = 'UShort'
126
127 def visit_bool(self):
128 raise NotImplementedError
129
130 def visit_int(self):
131 self.result = 'Int'
132
133 def visit_unsigned_int(self):
134 self.result = 'UInt'
135
136 def visit_long_int(self):
137 self.result = 'Long'
138
139 def visit_long_unsigned_int(self):
140 self.result = 'ULong'
141
142 def visit_long_long_int(self):
143 self.result = 'LongLong'
144
145 def visit_long_long_unsigned_int(self):
146 self.result = 'ULongLong'
147
148 def visit_float(self):
149 self.result = "Float"
150
151 def visit_double(self):
152 self.result = "Double"
153
154 def visit_array(self):
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100155 base_type = dump_type(self.type.base)
156 length = self.type.size
157 try:
158 int(length)
159 except ValueError:
160 length = '"%s"' % length
161 self.result = 'Array(%s, %s)' % (base_type, length)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000162
163 def visit_pointer(self):
164 base_type = dump_type(self.type.base)
165 # TODO: Use ObjPointer where appropriate
166 #if isinstance(self.type.base, declarations.cpptypes.declarated_t):
167 # decl = self.type.base.declaration
168 # if isinstance(decl, declarations.typedef.typedef_t):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100169 # print(decl.type, type(decl.type))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000170 # if isinstance(decl, declarations.class_declaration.class_t):
171 # if decl.public_members:
172 # self.result = 'ObjPointer(%s)' % decl.name
173 # return
174 # if isinstance(decl, declarations.class_declaration.class_declaration_t):
175 # if decl.public_members:
176 # self.result = 'ObjPointer(%s)' % decl.name
177 # return
178 if base_type.startswith('IDComposition') or \
179 base_type.startswith('IDXGI') or \
180 base_type == 'IUnknown':
181 self.result = 'ObjPointer(%s)' % base_type
182 return
183 self.result = 'Pointer(%s)' % base_type
184
185 def visit_reference(self):
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100186 base_type = dump_type(self.type.base)
187 if base_type == 'Const(IID)':
188 self.result = 'REFIID'
189 elif base_type == 'Const(GUID)':
190 self.result = 'REFGUID'
191 else:
192 self.result = 'Reference(%s)' % base_type
Jose Fonseca839a3d82016-03-05 18:01:14 +0000193
194 def visit_const(self):
195 self.result = 'Const(%s)' % dump_type(self.type.base)
196
197 def visit_declarated(self):
198 decl = self.type.declaration
199 self.result = dump_decl(decl)
200
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100201 def visit_free_function_type(self):
202 self.result = 'Opaque("%s")' % self.type
203
Jose Fonseca839a3d82016-03-05 18:01:14 +0000204
205def dump_type(type):
206 visitor = type_dumper_t(type)
207 algorithm.apply_visitor(visitor, type)
208 # XXX: RECT becomes tagRECT somehow
209 if visitor.result == 'tagRECT':
210 return 'RECT'
211 return visitor.result
212
213
214def is_interface(class_):
215 if not class_.name.startswith('I'):
216 return
217 if len(class_.bases) != 1:
218 return False
219 # TODO: Ensure interface derives from IUnknown
220 return True
221
222
223class decl2_dumper_t(decl_visitor.decl_visitor_t):
224
225 def __init__(self, name):
226 decl_visitor.decl_visitor_t.__init__(self)
227
228 self.name = name
229
230 # The current declaration
231 self.decl = None
232
Jose Fonseca7a6f8422020-05-21 09:14:00 +0100233 self.interfaces = StringIO()
234 self.methods = StringIO()
235 self.functions = StringIO()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000236
237 def start(self):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100238 print(copyright.strip())
239 print()
240 print()
241 print(r'from winapi import *')
242 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000243
244 def finish(self):
245 sys.stdout.write(self.interfaces.getvalue())
246 sys.stdout.write('\n')
247 sys.stdout.write(self.methods.getvalue())
248
249 name = self.name
250 sys.stdout.write('%s = Module(%r)\n' % (name, name))
251 sys.stdout.write('%s.addFunctions([\n' % (name,))
252 sys.stdout.write(self.functions.getvalue())
253 sys.stdout.write('])\n\n')
254
255 def clone(self):
256 return decl_dumper_t(self.decl)
257
258 def visit_class(self):
259 class_ = self.decl
260 assert class_.class_type in ('struct', 'union')
261
262 if is_interface(class_):
263 self.visit_interface()
264 elif class_.name != '':
265 self.visit_struct(class_.name, class_)
266
267 def visit_struct(self, decl_name, decl):
268 struct = decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100269 print(r'%s = Struct(%r, [' % (decl_name, decl_name))
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100270 for variable in struct.variables(allow_empty=True):
Jose Fonseca016ae762018-02-13 12:59:13 +0000271 var_type = dump_type(variable.decl_type)
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100272 print(r' (%s, %r),' % (var_type, variable.name))
273 print(r'])')
274 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000275
276 def visit_interface(self):
277 class_ = self.decl
278 assert len(class_.bases) == 1
279 base = class_.bases[0]
280
281 s = self.interfaces
282 s.write('%s = Interface(%r, %s)\n' % (class_.name, class_.name, base.related_class.name))
283
284 s = self.methods
285 s.write('%s.methods += [\n' % (class_.name,))
286 for member in class_.public_members:
287 if member.virtuality != 'pure virtual':
288 continue
289 ret_type = dump_type(member.return_type)
290 arg_types = self.convert_args(member.arguments)
291 s.write(' StdMethod(%s, %r, [%s]),\n' % (ret_type, member.name, arg_types))
292 s.write(']\n\n')
293
294 def convert_args(self, args):
295 # TODO: use __attribute__ ((annotate ("out")))
296 # See also:
297 # - https://github.com/CastXML/CastXML/issues/25
298 # XXX: Requires a castxml version newer than the one in Ubuntu 15.10
299 arg_types = []
300 for arg in args:
301 if arg.attributes is not None:
302 sys.stderr.write('warning: found %s attribute %r\n' % (arg.name, arg.attributes))
Jose Fonseca016ae762018-02-13 12:59:13 +0000303 res_arg_type = dump_type(arg.decl_type)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000304 res_arg = '(%s, %r)' % (res_arg_type, arg.name)
305
306 # Infer output arguments
307 if res_arg_type.startswith('Pointer(') and \
308 not res_arg_type.startswith('Pointer(Const('):
309 res_arg = 'Out' + res_arg
310
311 arg_types.append(res_arg)
312
313 arg_types = ', '.join(arg_types)
314 return arg_types
315
316 def visit_class_declaration(self):
317 pass
318
319 def visit_typedef(self):
320 typedef = self.decl
Jose Fonseca016ae762018-02-13 12:59:13 +0000321 base_type = dump_type(typedef.decl_type)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000322 if base_type == typedef.name:
323 # Ignore `typedef struct Foo Foo;`
324 return
325 if base_type == '':
Jose Fonseca016ae762018-02-13 12:59:13 +0000326 if isinstance(typedef.decl_type, declarations.cpptypes.declarated_t):
327 base_decl = typedef.decl_type.declaration
Jose Fonseca839a3d82016-03-05 18:01:14 +0000328 self.visit_struct(typedef.name, base_decl)
329 return
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100330 print(r'%s = Alias(%r, %s)' % (typedef.name, typedef.name, base_type))
331 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000332
333 def visit_enumeration(self):
334 enum = self.decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100335 print(r'%s = Enum(%r, [' % (enum.name, enum.name))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000336 for name, value in enum.values:
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100337 print(r' %r,' % (name,))
338 print(r'])')
339 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000340
341 def visit_variable(self):
342 pass
343
344 def visit_free_function(self):
345 function = self.decl
346 if function.has_inline:
347 return
348
349 s = self.functions
350 ret_type = dump_type(function.return_type)
351 arg_types = self.convert_args(function.arguments)
352 s.write(' StdFunction(%s, %r, [%s]),\n' % (ret_type, function.name, arg_types))
353
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100354 def visit_free_operator(self):
355 pass
356
Jose Fonseca839a3d82016-03-05 18:01:14 +0000357
358def main():
359 defines = []
360 includes = []
361 cxxflags = [
362 '-Wno-unknown-attributes',
363 '-Wno-unused-value',
364 '-Wno-macro-redefined',
365 ]
366 compiler = 'g++'
367
368 args = sys.argv[1:]
369 while args and args[0].startswith('-'):
370 arg = args.pop(0)
371 if arg.startswith('-I'):
372 include = arg[2:]
373 includes.append(include)
374 elif arg.startswith('-D'):
375 define = arg[2:]
376 defines.append(define)
377 else:
378 sys.stderr.write('error: unknown option %r\n' % arg)
379 sys.exit(1)
380
381 winsdk = True
382 if winsdk:
383 # Set up Clang compiler flags to use MinGW runtime
Jose Fonsecafe66fc02016-05-18 11:00:03 +0100384 # http://stackoverflow.com/a/19839946
385 p = subprocess.Popen(
386 ["x86_64-w64-mingw32-g++", "-x", "c++", "-E", "-Wp,-v", '-', '-fsyntax-only'],
387 stdin=open(os.devnull, 'rt'),
388 stdout=open(os.devnull, 'wt'),
Jose Fonseca7a6f8422020-05-21 09:14:00 +0100389 stderr=subprocess.PIPE,
390 universal_newlines=True)
Jose Fonsecafe66fc02016-05-18 11:00:03 +0100391 includes.append('/usr/share/castxml/clang/include')
392 for line in p.stderr:
393 if line.startswith(' '):
394 include = line.strip()
395 if os.path.isdir(include):
396 if os.path.exists(os.path.join(include, 'ia32intrin.h')):
397 # XXX: We must use Clang's intrinsic headers
398 continue
399 includes.append(os.path.normpath(include))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000400
401 winver = 0x0602
402
403 defines += [
404 # emulate MinGW
405 '__MINGW32__',
406 '_WIN32',
407 '_WIN64',
408 '__declspec(x)=',
409 # Avoid namespace pollution when including windows.h
410 # http://support.microsoft.com/kb/166474
411 'WIN32_LEAN_AND_MEAN',
412 # Set Windows version to 8.1
413 '_WIN32_WINNT=0x%04X' % winver,
414 'WINVER=0x%04X' % winver,
415 'NTDDI_VERSION=0x%04X0000' % winver,
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100416 # Prevent headers from requiring a rpcndr.h version beyond MinGW's
417 '__REQUIRED_RPCNDR_H_VERSION__=475',
Jose Fonseca839a3d82016-03-05 18:01:14 +0000418 # Avoid C++ helper classes
419 'D3D10_NO_HELPERS',
420 'D3D11_NO_HELPERS',
421 'D3D11_VIDEO_NO_HELPERS',
422 ]
423
424 # XXX: Change compiler?
425 #compiler = 'cl'
426
427 # XXX: This doesn't seem to work well
428 cxxflags += [
429 #'-m32',
430 #'-target', 'x86_64-pc-mingw32',
Jose Fonseca7a6f8422020-05-21 09:14:00 +0100431
432 '-Wno-pragma-pack',
Jose Fonseca839a3d82016-03-05 18:01:14 +0000433 ]
434
435 sys.stderr.write('Include path:\n')
436 for include in includes:
437 sys.stderr.write(' %s\n' % include)
438 sys.stderr.write('Definitions:\n')
439 for define in defines:
440 sys.stderr.write(' %s\n' % define)
441
442 import logging
443 utils.loggers.set_level(logging.DEBUG)
444
445 # Find the location of the xml generator (castxml or gccxml)
446 generator_path, generator_name = utils.find_xml_generator("castxml")
447
448 # Configure the xml generator
449 config = parser.xml_generator_configuration_t(
450 xml_generator_path=generator_path,
451 xml_generator=generator_name,
452 define_symbols = defines,
453 include_paths = includes,
454 cflags = ' '.join(cxxflags),
455 compiler = compiler,
456 #keep_xml = True,
457 )
458
459 script_dir = os.path.dirname(__file__)
460 headers = [
Jose Fonseca839a3d82016-03-05 18:01:14 +0000461 os.path.join(script_dir, 'cxx2api.h'),
462 ]
463 main_header = args[0]
464 headers.append(main_header)
465
466 decls = parser.parse(headers, config, parser.COMPILATION_MODE.ALL_AT_ONCE)
467 global_ns = declarations.get_global_namespace(decls)
468
469 def decl_filter(decl):
470 location = decl.location
471 if location is None:
472 return False
Piotr Podsiadły0b8b0192019-01-03 20:39:55 +0100473 return os.path.basename(location.file_name) in list(map(os.path.basename, args))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000474
475 module, _ = os.path.splitext(main_header)
476 visitor = decl2_dumper_t(module)
477 visitor.start()
478 for decl in global_ns.declarations:
479 if not decl_filter(decl):
480 continue
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100481
482 if sys.stdout.isatty():
483 print('# ' + str(decl))
484
Jose Fonseca839a3d82016-03-05 18:01:14 +0000485 visitor.decl = decl
486 algorithm.apply_visitor(visitor, decl)
487 visitor.finish()
488
489
490if __name__ == '__main__':
491 main()