blob: 9720615de3250f1476d2126cf4c50a2059864a29 [file] [log] [blame]
Jose Fonseca839a3d82016-03-05 18:01:14 +00001#!/usr/bin/env python
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#
38# sudo apt-get install castxml mingw-w64-i686-dev
Jose Fonseca016ae762018-02-13 12:59:13 +000039# pip install 'pygccxml==1.9.1'
40# python 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
Piotr Podsiadły0b8b0192019-01-03 20:39:55 +010049import io as StringIO
Jose Fonseca839a3d82016-03-05 18:01:14 +000050import 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):
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100154 base_type = dump_type(self.type.base)
155 length = self.type.size
156 try:
157 int(length)
158 except ValueError:
159 length = '"%s"' % length
160 self.result = 'Array(%s, %s)' % (base_type, length)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000161
162 def visit_pointer(self):
163 base_type = dump_type(self.type.base)
164 # TODO: Use ObjPointer where appropriate
165 #if isinstance(self.type.base, declarations.cpptypes.declarated_t):
166 # decl = self.type.base.declaration
167 # if isinstance(decl, declarations.typedef.typedef_t):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100168 # print(decl.type, type(decl.type))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000169 # if isinstance(decl, declarations.class_declaration.class_t):
170 # if decl.public_members:
171 # self.result = 'ObjPointer(%s)' % decl.name
172 # return
173 # if isinstance(decl, declarations.class_declaration.class_declaration_t):
174 # if decl.public_members:
175 # self.result = 'ObjPointer(%s)' % decl.name
176 # return
177 if base_type.startswith('IDComposition') or \
178 base_type.startswith('IDXGI') or \
179 base_type == 'IUnknown':
180 self.result = 'ObjPointer(%s)' % base_type
181 return
182 self.result = 'Pointer(%s)' % base_type
183
184 def visit_reference(self):
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100185 base_type = dump_type(self.type.base)
186 if base_type == 'Const(IID)':
187 self.result = 'REFIID'
188 elif base_type == 'Const(GUID)':
189 self.result = 'REFGUID'
190 else:
191 self.result = 'Reference(%s)' % base_type
Jose Fonseca839a3d82016-03-05 18:01:14 +0000192
193 def visit_const(self):
194 self.result = 'Const(%s)' % dump_type(self.type.base)
195
196 def visit_declarated(self):
197 decl = self.type.declaration
198 self.result = dump_decl(decl)
199
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100200 def visit_free_function_type(self):
201 self.result = 'Opaque("%s")' % self.type
202
Jose Fonseca839a3d82016-03-05 18:01:14 +0000203
204def dump_type(type):
205 visitor = type_dumper_t(type)
206 algorithm.apply_visitor(visitor, type)
207 # XXX: RECT becomes tagRECT somehow
208 if visitor.result == 'tagRECT':
209 return 'RECT'
210 return visitor.result
211
212
213def is_interface(class_):
214 if not class_.name.startswith('I'):
215 return
216 if len(class_.bases) != 1:
217 return False
218 # TODO: Ensure interface derives from IUnknown
219 return True
220
221
222class decl2_dumper_t(decl_visitor.decl_visitor_t):
223
224 def __init__(self, name):
225 decl_visitor.decl_visitor_t.__init__(self)
226
227 self.name = name
228
229 # The current declaration
230 self.decl = None
231
232 self.interfaces = StringIO.StringIO()
233 self.methods = StringIO.StringIO()
234 self.functions = StringIO.StringIO()
235
236 def start(self):
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100237 print(copyright.strip())
238 print()
239 print()
240 print(r'from winapi import *')
241 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000242
243 def finish(self):
244 sys.stdout.write(self.interfaces.getvalue())
245 sys.stdout.write('\n')
246 sys.stdout.write(self.methods.getvalue())
247
248 name = self.name
249 sys.stdout.write('%s = Module(%r)\n' % (name, name))
250 sys.stdout.write('%s.addFunctions([\n' % (name,))
251 sys.stdout.write(self.functions.getvalue())
252 sys.stdout.write('])\n\n')
253
254 def clone(self):
255 return decl_dumper_t(self.decl)
256
257 def visit_class(self):
258 class_ = self.decl
259 assert class_.class_type in ('struct', 'union')
260
261 if is_interface(class_):
262 self.visit_interface()
263 elif class_.name != '':
264 self.visit_struct(class_.name, class_)
265
266 def visit_struct(self, decl_name, decl):
267 struct = decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100268 print(r'%s = Struct(%r, [' % (decl_name, decl_name))
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100269 for variable in struct.variables(allow_empty=True):
Jose Fonseca016ae762018-02-13 12:59:13 +0000270 var_type = dump_type(variable.decl_type)
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100271 print(r' (%s, %r),' % (var_type, variable.name))
272 print(r'])')
273 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000274
275 def visit_interface(self):
276 class_ = self.decl
277 assert len(class_.bases) == 1
278 base = class_.bases[0]
279
280 s = self.interfaces
281 s.write('%s = Interface(%r, %s)\n' % (class_.name, class_.name, base.related_class.name))
282
283 s = self.methods
284 s.write('%s.methods += [\n' % (class_.name,))
285 for member in class_.public_members:
286 if member.virtuality != 'pure virtual':
287 continue
288 ret_type = dump_type(member.return_type)
289 arg_types = self.convert_args(member.arguments)
290 s.write(' StdMethod(%s, %r, [%s]),\n' % (ret_type, member.name, arg_types))
291 s.write(']\n\n')
292
293 def convert_args(self, args):
294 # TODO: use __attribute__ ((annotate ("out")))
295 # See also:
296 # - https://github.com/CastXML/CastXML/issues/25
297 # XXX: Requires a castxml version newer than the one in Ubuntu 15.10
298 arg_types = []
299 for arg in args:
300 if arg.attributes is not None:
301 sys.stderr.write('warning: found %s attribute %r\n' % (arg.name, arg.attributes))
Jose Fonseca016ae762018-02-13 12:59:13 +0000302 res_arg_type = dump_type(arg.decl_type)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000303 res_arg = '(%s, %r)' % (res_arg_type, arg.name)
304
305 # Infer output arguments
306 if res_arg_type.startswith('Pointer(') and \
307 not res_arg_type.startswith('Pointer(Const('):
308 res_arg = 'Out' + res_arg
309
310 arg_types.append(res_arg)
311
312 arg_types = ', '.join(arg_types)
313 return arg_types
314
315 def visit_class_declaration(self):
316 pass
317
318 def visit_typedef(self):
319 typedef = self.decl
Jose Fonseca016ae762018-02-13 12:59:13 +0000320 base_type = dump_type(typedef.decl_type)
Jose Fonseca839a3d82016-03-05 18:01:14 +0000321 if base_type == typedef.name:
322 # Ignore `typedef struct Foo Foo;`
323 return
324 if base_type == '':
Jose Fonseca016ae762018-02-13 12:59:13 +0000325 if isinstance(typedef.decl_type, declarations.cpptypes.declarated_t):
326 base_decl = typedef.decl_type.declaration
Jose Fonseca839a3d82016-03-05 18:01:14 +0000327 self.visit_struct(typedef.name, base_decl)
328 return
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100329 print(r'%s = Alias(%r, %s)' % (typedef.name, typedef.name, base_type))
330 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000331
332 def visit_enumeration(self):
333 enum = self.decl
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100334 print(r'%s = Enum(%r, [' % (enum.name, enum.name))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000335 for name, value in enum.values:
Jose Fonsecac0cfbd22016-05-18 11:05:35 +0100336 print(r' %r,' % (name,))
337 print(r'])')
338 print()
Jose Fonseca839a3d82016-03-05 18:01:14 +0000339
340 def visit_variable(self):
341 pass
342
343 def visit_free_function(self):
344 function = self.decl
345 if function.has_inline:
346 return
347
348 s = self.functions
349 ret_type = dump_type(function.return_type)
350 arg_types = self.convert_args(function.arguments)
351 s.write(' StdFunction(%s, %r, [%s]),\n' % (ret_type, function.name, arg_types))
352
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100353 def visit_free_operator(self):
354 pass
355
Jose Fonseca839a3d82016-03-05 18:01:14 +0000356
357def main():
358 defines = []
359 includes = []
360 cxxflags = [
361 '-Wno-unknown-attributes',
362 '-Wno-unused-value',
363 '-Wno-macro-redefined',
364 ]
365 compiler = 'g++'
366
367 args = sys.argv[1:]
368 while args and args[0].startswith('-'):
369 arg = args.pop(0)
370 if arg.startswith('-I'):
371 include = arg[2:]
372 includes.append(include)
373 elif arg.startswith('-D'):
374 define = arg[2:]
375 defines.append(define)
376 else:
377 sys.stderr.write('error: unknown option %r\n' % arg)
378 sys.exit(1)
379
380 winsdk = True
381 if winsdk:
382 # Set up Clang compiler flags to use MinGW runtime
Jose Fonsecafe66fc02016-05-18 11:00:03 +0100383 # http://stackoverflow.com/a/19839946
384 p = subprocess.Popen(
385 ["x86_64-w64-mingw32-g++", "-x", "c++", "-E", "-Wp,-v", '-', '-fsyntax-only'],
386 stdin=open(os.devnull, 'rt'),
387 stdout=open(os.devnull, 'wt'),
388 stderr=subprocess.PIPE)
389 includes.append('/usr/share/castxml/clang/include')
390 for line in p.stderr:
391 if line.startswith(' '):
392 include = line.strip()
393 if os.path.isdir(include):
394 if os.path.exists(os.path.join(include, 'ia32intrin.h')):
395 # XXX: We must use Clang's intrinsic headers
396 continue
397 includes.append(os.path.normpath(include))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000398
399 winver = 0x0602
400
401 defines += [
402 # emulate MinGW
403 '__MINGW32__',
404 '_WIN32',
405 '_WIN64',
406 '__declspec(x)=',
407 # Avoid namespace pollution when including windows.h
408 # http://support.microsoft.com/kb/166474
409 'WIN32_LEAN_AND_MEAN',
410 # Set Windows version to 8.1
411 '_WIN32_WINNT=0x%04X' % winver,
412 'WINVER=0x%04X' % winver,
413 'NTDDI_VERSION=0x%04X0000' % winver,
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100414 # Prevent headers from requiring a rpcndr.h version beyond MinGW's
415 '__REQUIRED_RPCNDR_H_VERSION__=475',
Jose Fonseca839a3d82016-03-05 18:01:14 +0000416 # Avoid C++ helper classes
417 'D3D10_NO_HELPERS',
418 'D3D11_NO_HELPERS',
419 'D3D11_VIDEO_NO_HELPERS',
420 ]
421
422 # XXX: Change compiler?
423 #compiler = 'cl'
424
425 # XXX: This doesn't seem to work well
426 cxxflags += [
427 #'-m32',
428 #'-target', 'x86_64-pc-mingw32',
429 ]
430
431 sys.stderr.write('Include path:\n')
432 for include in includes:
433 sys.stderr.write(' %s\n' % include)
434 sys.stderr.write('Definitions:\n')
435 for define in defines:
436 sys.stderr.write(' %s\n' % define)
437
438 import logging
439 utils.loggers.set_level(logging.DEBUG)
440
441 # Find the location of the xml generator (castxml or gccxml)
442 generator_path, generator_name = utils.find_xml_generator("castxml")
443
444 # Configure the xml generator
445 config = parser.xml_generator_configuration_t(
446 xml_generator_path=generator_path,
447 xml_generator=generator_name,
448 define_symbols = defines,
449 include_paths = includes,
450 cflags = ' '.join(cxxflags),
451 compiler = compiler,
452 #keep_xml = True,
453 )
454
455 script_dir = os.path.dirname(__file__)
456 headers = [
457 os.path.join(script_dir, '..', '..', 'compat', 'winsdk_compat.h'),
458 os.path.join(script_dir, 'cxx2api.h'),
459 ]
460 main_header = args[0]
461 headers.append(main_header)
462
463 decls = parser.parse(headers, config, parser.COMPILATION_MODE.ALL_AT_ONCE)
464 global_ns = declarations.get_global_namespace(decls)
465
466 def decl_filter(decl):
467 location = decl.location
468 if location is None:
469 return False
Piotr Podsiadły0b8b0192019-01-03 20:39:55 +0100470 return os.path.basename(location.file_name) in list(map(os.path.basename, args))
Jose Fonseca839a3d82016-03-05 18:01:14 +0000471
472 module, _ = os.path.splitext(main_header)
473 visitor = decl2_dumper_t(module)
474 visitor.start()
475 for decl in global_ns.declarations:
476 if not decl_filter(decl):
477 continue
Jose Fonseca5c881ed2016-05-18 14:55:02 +0100478
479 if sys.stdout.isatty():
480 print('# ' + str(decl))
481
Jose Fonseca839a3d82016-03-05 18:01:14 +0000482 visitor.decl = decl
483 algorithm.apply_visitor(visitor, decl)
484 visitor.finish()
485
486
487if __name__ == '__main__':
488 main()