specs: Add script to automate generation of specs from headers.

Based on clang, castxml, and pygccxml.
diff --git a/specs/scripts/cxx2api.py b/specs/scripts/cxx2api.py
new file mode 100755
index 0000000..6a97934
--- /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()