blob: c4cbf739bceb695231e7213956db6c3b20bb6453 [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
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
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):
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
Jose Fonseca7c76b902017-10-09 18:00:42 +0100470 return os.path.basename(location.file_name) in 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()