summaryrefslogtreecommitdiff
path: root/specs
diff options
context:
space:
mode:
authorJose Fonseca <jfonseca@vmware.com>2016-03-05 18:01:14 +0000
committerJose Fonseca <jfonseca@vmware.com>2016-03-05 18:03:06 +0000
commit839a3d8747d329e9fc8d2ae029ae3dd867821d97 (patch)
treebb41773497a252605aa20c85924868293c863c52 /specs
parent16c8fc626700ee2111e2d9b4125f74fb0a9a2770 (diff)
specs: Add script to automate generation of specs from headers.
Based on clang, castxml, and pygccxml.
Diffstat (limited to 'specs')
-rw-r--r--specs/scripts/cxx2api.h39
-rwxr-xr-xspecs/scripts/cxx2api.py477
2 files changed, 516 insertions, 0 deletions
diff --git a/specs/scripts/cxx2api.h b/specs/scripts/cxx2api.h
new file mode 100644
index 00000000..33a36da5
--- /dev/null
+++ b/specs/scripts/cxx2api.h
@@ -0,0 +1,39 @@
+/**************************************************************************
+ *
+ * Copyright 2016 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+#pragma once
+
+// XXX: castxml version from Ubuntu 15.10 is too old for any of this
+
+#if 0
+#ifndef __castxml__
+#error "Must only be used inside castxml"
+#endif
+#endif
+
+#undef _Outptr_
+#undef annotate
+#undef __atribute__
+#define _Outptr_ __attribute__ ((annotate("out")))
diff --git a/specs/scripts/cxx2api.py b/specs/scripts/cxx2api.py
new file mode 100755
index 00000000..6a979345
--- /dev/null
+++ b/specs/scripts/cxx2api.py
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+copyright = '''
+##########################################################################
+#
+# Copyright 2009-2016 VMware, Inc.
+# All Rights Reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+##########################################################################/
+'''
+
+
+#
+# Generates API specs from headers using castxml/pygccxml.
+#
+# Usage:
+#
+# sudo apt-get install castxml mingw-w64-i686-dev
+# pip install 'pygccxml>=1.7.1'
+# python specs/scripts/cxx2api.py -Idxsdk/Include dcomp.h dcomptypes.h dcompanimation.h
+#
+# See also:
+# - http://pygccxml.readthedocs.org/en/develop/index.html
+# - https://github.com/CastXML/CastXML/blob/master/doc/manual/castxml.1.rst
+#
+
+import os.path
+import sys
+import cStringIO as StringIO
+import subprocess
+
+from pygccxml import utils
+from pygccxml import parser
+from pygccxml import declarations
+
+from pygccxml.declarations import algorithm
+from pygccxml.declarations import decl_visitor
+from pygccxml.declarations import type_traits
+from pygccxml.declarations import type_visitor
+
+
+class decl_dumper_t(decl_visitor.decl_visitor_t):
+
+ def __init__(self, decl = None):
+ decl_visitor.decl_visitor_t.__init__(self)
+ self.decl = decl
+ self.result = None
+
+ def clone(self):
+ return decl_dumper_t(self.decl)
+
+ def visit_class(self):
+ class_ = self.decl
+ assert class_.class_type in ('struct', 'union')
+ self.result = class_.name
+
+ def visit_class_declaration(self):
+ class_ = self.decl
+ self.result = class_.name
+
+ def visit_typedef(self):
+ typedef = self.decl
+ self.result = typedef.name
+
+ def visit_enumeration(self):
+ self.result = self.decl.name
+
+
+def dump_decl(decl):
+ visitor = decl_dumper_t(decl)
+ algorithm.apply_visitor(visitor, decl)
+ return visitor.result
+
+
+class type_dumper_t(type_visitor.type_visitor_t):
+
+ def __init__(self, type):
+ type_visitor.type_visitor_t.__init__(self)
+ self.type = type
+ self.result = None
+
+ def clone(self):
+ return type_dumper_t(self.type)
+
+ def visit_void(self):
+ self.result = 'Void'
+
+ def visit_char(self):
+ self.result = 'Char'
+
+ def visit_unsigned_char(self):
+ self.result = 'UChar'
+
+ def visit_signed_char(self):
+ self.result = 'SChar'
+
+ def visit_wchar(self):
+ raise NotImplementedError
+
+ def visit_short_int(self):
+ self.result = 'Short'
+
+ def visit_short_unsigned_int(self):
+ self.result = 'UShort'
+
+ def visit_bool(self):
+ raise NotImplementedError
+
+ def visit_int(self):
+ self.result = 'Int'
+
+ def visit_unsigned_int(self):
+ self.result = 'UInt'
+
+ def visit_long_int(self):
+ self.result = 'Long'
+
+ def visit_long_unsigned_int(self):
+ self.result = 'ULong'
+
+ def visit_long_long_int(self):
+ self.result = 'LongLong'
+
+ def visit_long_long_unsigned_int(self):
+ self.result = 'ULongLong'
+
+ def visit_float(self):
+ self.result = "Float"
+
+ def visit_double(self):
+ self.result = "Double"
+
+ def visit_array(self):
+ raise NotImplementedError
+
+ def visit_pointer(self):
+ base_type = dump_type(self.type.base)
+ # TODO: Use ObjPointer where appropriate
+ #if isinstance(self.type.base, declarations.cpptypes.declarated_t):
+ # decl = self.type.base.declaration
+ # if isinstance(decl, declarations.typedef.typedef_t):
+ # print decl.type, type(decl.type)
+ # if isinstance(decl, declarations.class_declaration.class_t):
+ # if decl.public_members:
+ # self.result = 'ObjPointer(%s)' % decl.name
+ # return
+ # if isinstance(decl, declarations.class_declaration.class_declaration_t):
+ # if decl.public_members:
+ # self.result = 'ObjPointer(%s)' % decl.name
+ # return
+ if base_type.startswith('IDComposition') or \
+ base_type.startswith('IDXGI') or \
+ base_type == 'IUnknown':
+ self.result = 'ObjPointer(%s)' % base_type
+ return
+ self.result = 'Pointer(%s)' % base_type
+
+ def visit_reference(self):
+ self.result = 'Reference(%s)' % dump_type(self.type.base)
+
+ def visit_const(self):
+ self.result = 'Const(%s)' % dump_type(self.type.base)
+
+ def visit_declarated(self):
+ decl = self.type.declaration
+ self.result = dump_decl(decl)
+
+
+def dump_type(type):
+ visitor = type_dumper_t(type)
+ algorithm.apply_visitor(visitor, type)
+ # XXX: RECT becomes tagRECT somehow
+ if visitor.result == 'tagRECT':
+ return 'RECT'
+ return visitor.result
+
+
+def is_interface(class_):
+ if not class_.name.startswith('I'):
+ return
+ if len(class_.bases) != 1:
+ return False
+ # TODO: Ensure interface derives from IUnknown
+ return True
+
+
+class decl2_dumper_t(decl_visitor.decl_visitor_t):
+
+ def __init__(self, name):
+ decl_visitor.decl_visitor_t.__init__(self)
+
+ self.name = name
+
+ # The current declaration
+ self.decl = None
+
+ self.interfaces = StringIO.StringIO()
+ self.methods = StringIO.StringIO()
+ self.functions = StringIO.StringIO()
+
+ def start(self):
+ print copyright.strip()
+ print
+ print
+ print r'from winapi import *'
+ print
+
+ def finish(self):
+ sys.stdout.write(self.interfaces.getvalue())
+ sys.stdout.write('\n')
+ sys.stdout.write(self.methods.getvalue())
+
+ name = self.name
+ sys.stdout.write('%s = Module(%r)\n' % (name, name))
+ sys.stdout.write('%s.addFunctions([\n' % (name,))
+ sys.stdout.write(self.functions.getvalue())
+ sys.stdout.write('])\n\n')
+
+ def clone(self):
+ return decl_dumper_t(self.decl)
+
+ def visit_class(self):
+ class_ = self.decl
+ assert class_.class_type in ('struct', 'union')
+
+ if is_interface(class_):
+ self.visit_interface()
+ elif class_.name != '':
+ self.visit_struct(class_.name, class_)
+
+ def visit_struct(self, decl_name, decl):
+ struct = decl
+ print r'%s = Struct(%r, [' % (decl_name, decl_name)
+ for variable in struct.variables():
+ var_type = dump_type(variable.type)
+ print r' (%s, %r),' % (var_type, variable.name)
+ print r'])'
+ print
+
+ def visit_interface(self):
+ class_ = self.decl
+ assert len(class_.bases) == 1
+ base = class_.bases[0]
+
+ s = self.interfaces
+ s.write('%s = Interface(%r, %s)\n' % (class_.name, class_.name, base.related_class.name))
+
+ s = self.methods
+ s.write('%s.methods += [\n' % (class_.name,))
+ for member in class_.public_members:
+ if member.virtuality != 'pure virtual':
+ continue
+ ret_type = dump_type(member.return_type)
+ arg_types = self.convert_args(member.arguments)
+ s.write(' StdMethod(%s, %r, [%s]),\n' % (ret_type, member.name, arg_types))
+ s.write(']\n\n')
+
+ def convert_args(self, args):
+ # TODO: use __attribute__ ((annotate ("out")))
+ # See also:
+ # - https://github.com/CastXML/CastXML/issues/25
+ # XXX: Requires a castxml version newer than the one in Ubuntu 15.10
+ arg_types = []
+ for arg in args:
+ if arg.attributes is not None:
+ sys.stderr.write('warning: found %s attribute %r\n' % (arg.name, arg.attributes))
+ res_arg_type = dump_type(arg.type)
+ res_arg = '(%s, %r)' % (res_arg_type, arg.name)
+
+ # Infer output arguments
+ if res_arg_type.startswith('Pointer(') and \
+ not res_arg_type.startswith('Pointer(Const('):
+ res_arg = 'Out' + res_arg
+
+ arg_types.append(res_arg)
+
+ arg_types = ', '.join(arg_types)
+ return arg_types
+
+ def visit_class_declaration(self):
+ pass
+
+ def visit_typedef(self):
+ typedef = self.decl
+ base_type = dump_type(typedef.type)
+ if base_type == typedef.name:
+ # Ignore `typedef struct Foo Foo;`
+ return
+ if base_type == '':
+ if isinstance(typedef.type, declarations.cpptypes.declarated_t):
+ base_decl = typedef.type.declaration
+ self.visit_struct(typedef.name, base_decl)
+ return
+ print r'%s = Alias(%r, %s)' % (typedef.name, typedef.name, base_type)
+ print
+
+ def visit_enumeration(self):
+ enum = self.decl
+ print r'%s = Enum(%r, [' % (enum.name, enum.name)
+ for name, value in enum.values:
+ print r' %r,' % (name,)
+ print r'])'
+ print
+
+ def visit_variable(self):
+ pass
+
+ def visit_free_function(self):
+ function = self.decl
+ if function.has_inline:
+ return
+
+ s = self.functions
+ ret_type = dump_type(function.return_type)
+ arg_types = self.convert_args(function.arguments)
+ s.write(' StdFunction(%s, %r, [%s]),\n' % (ret_type, function.name, arg_types))
+
+
+def main():
+ defines = []
+ includes = []
+ cxxflags = [
+ '-Wno-unknown-attributes',
+ '-Wno-unused-value',
+ '-Wno-macro-redefined',
+ ]
+ compiler = 'g++'
+
+ args = sys.argv[1:]
+ while args and args[0].startswith('-'):
+ arg = args.pop(0)
+ if arg.startswith('-I'):
+ include = arg[2:]
+ includes.append(include)
+ elif arg.startswith('-D'):
+ define = arg[2:]
+ defines.append(define)
+ else:
+ sys.stderr.write('error: unknown option %r\n' % arg)
+ sys.exit(1)
+
+ winsdk = True
+ if winsdk:
+ # Set up Clang compiler flags to use MinGW runtime
+ if 0:
+ # XXX: This doesn't work
+ # http://stackoverflow.com/a/19839946
+ p = subprocess.Popen(
+ ["x86_64-w64-mingw32-g++", "-x", "c++", "-E", "-Wp,-v", '-', '-fsyntax-only'],
+ stdin = open(os.devnull, 'rt'),
+ stdout = open(os.devnull, 'wt'),
+ stderr=subprocess.PIPE)
+ includes.append('/usr/share/castxml/clang/include')
+ for line in p.stderr:
+ if line.startswith(' '):
+ include = line.strip()
+ if os.path.isdir(include):
+ includes.append(os.path.normpath(include))
+ elif 0:
+ # XXX: This matches what wclang does, but doensn't work neither
+ cxxflags += [
+ "-target", "x86_64-w64-mingw32",
+ "-nostdinc",
+ "-isystem", "/usr/lib/clang/3.6.0/include",
+ "-isystem", "/usr/x86_64-w64-mingw32/include",
+ "-isystem", "/usr/lib/gcc/x86_64-w64-mingw32/4.9-win32/include/c++",
+ "-isystem", "/usr/lib/gcc/x86_64-w64-mingw32/4.9-win32/include/c++/x86_64-w64-mingw32",
+ ]
+ else:
+ # This works somehow, but seems brittle
+ includes += [
+ '/usr/x86_64-w64-mingw32/include',
+ '/usr/lib/gcc/x86_64-w64-mingw32/4.9-win32/include/c++',
+ '/usr/lib/gcc/x86_64-w64-mingw32/4.9-win32/include/c++/x86_64-w64-mingw32',
+ ]
+
+ winver = 0x0602
+
+ defines += [
+ # emulate MinGW
+ '__MINGW32__',
+ '_WIN32',
+ '_WIN64',
+ '__declspec(x)=',
+ # Avoid namespace pollution when including windows.h
+ # http://support.microsoft.com/kb/166474
+ 'WIN32_LEAN_AND_MEAN',
+ # Set Windows version to 8.1
+ '_WIN32_WINNT=0x%04X' % winver,
+ 'WINVER=0x%04X' % winver,
+ 'NTDDI_VERSION=0x%04X0000' % winver,
+ # Avoid C++ helper classes
+ 'D3D10_NO_HELPERS',
+ 'D3D11_NO_HELPERS',
+ 'D3D11_VIDEO_NO_HELPERS',
+ ]
+
+ # XXX: Change compiler?
+ #compiler = 'cl'
+
+ # XXX: This doesn't seem to work well
+ cxxflags += [
+ #'-m32',
+ #'-target', 'x86_64-pc-mingw32',
+ ]
+
+ sys.stderr.write('Include path:\n')
+ for include in includes:
+ sys.stderr.write(' %s\n' % include)
+ sys.stderr.write('Definitions:\n')
+ for define in defines:
+ sys.stderr.write(' %s\n' % define)
+
+ import logging
+ utils.loggers.set_level(logging.DEBUG)
+
+ # Find the location of the xml generator (castxml or gccxml)
+ generator_path, generator_name = utils.find_xml_generator("castxml")
+
+ # Configure the xml generator
+ config = parser.xml_generator_configuration_t(
+ xml_generator_path=generator_path,
+ xml_generator=generator_name,
+ define_symbols = defines,
+ include_paths = includes,
+ cflags = ' '.join(cxxflags),
+ compiler = compiler,
+ #keep_xml = True,
+ )
+
+ script_dir = os.path.dirname(__file__)
+ headers = [
+ os.path.join(script_dir, '..', '..', 'compat', 'winsdk_compat.h'),
+ os.path.join(script_dir, 'cxx2api.h'),
+ ]
+ main_header = args[0]
+ headers.append(main_header)
+
+ decls = parser.parse(headers, config, parser.COMPILATION_MODE.ALL_AT_ONCE)
+ global_ns = declarations.get_global_namespace(decls)
+
+ def decl_filter(decl):
+ location = decl.location
+ if location is None:
+ return False
+ return os.path.basename(location.file_name) in args
+
+ module, _ = os.path.splitext(main_header)
+ visitor = decl2_dumper_t(module)
+ visitor.start()
+ for decl in global_ns.declarations:
+ if not decl_filter(decl):
+ continue
+ visitor.decl = decl
+ algorithm.apply_visitor(visitor, decl)
+ visitor.finish()
+
+
+if __name__ == '__main__':
+ main()