js/xpconnect/src/qsgen.py
author Boris Zbarsky <bzbarsky@mit.edu>
Thu, 06 Dec 2012 15:21:18 -0500
changeset 109921 69f1c373450db03a9b00a7e52b56441981ef49a7
parent 109482 08aea86dd6afffab35d40d8f4ae5cf595f429c6c
permissions -rw-r--r--
Bug 816842. Clean up jsval return values in quickstubs a bit. r=bholley, a=bajaj

#!/usr/bin/env/python
# qsgen.py - Generate XPConnect quick stubs.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# =About quick stubs=
# qsgen.py generates "quick stubs", custom SpiderMonkey getters, setters, and
# methods for specified XPCOM interface members.  These quick stubs serve at
# runtime as replacements for the XPConnect functions XPC_WN_GetterSetter and
# XPC_WN_CallMethod, which are the extremely generic (and slow) SpiderMonkey
# getter/setter/methods otherwise used for all XPCOM member accesses from JS.
#
# There are two ways quick stubs win:
#   1. Pure, transparent optimization by partial evaluation.
#   2. Cutting corners.
#
# == Partial evaluation ==
# Partial evaluation is when you execute part of a program early (before or at
# compile time) so that you don't have to execute it at run time.  In this
# case, everything that involves interpreting xptcall data (for example, the
# big methodInfo loops in XPCWrappedNative::CallMethod and the switch statement
# in XPCConert::JSData2Native) might as well happen at build time, since all
# the type information for any given member is already known.  That's what this
# script does.  It gets the information from IDL instead of XPT files.  Apart
# from that, the code in this script is very similar to what you'll find in
# XPConnect itself.  The advantage is that it runs once, at build time, not in
# tight loops at run time.
#
# == Cutting corners ==
# The XPConnect versions have to be slow because they do tons of work that's
# only necessary in a few cases.  The quick stubs skip a lot of that work.  So
# quick stubs necessarily differ from XPConnect in potentially observable ways.
# For many specific interface members, the differences are not observable from
# scripts or don't matter enough to worry about; but you do have to be careful
# which members you decide to generate quick stubs for.
#
# The complete list of known differences, as of this writing, after an
# assiduous search:
#
# - Quick stubs affect the handling of naming conflicts--that is, which C++
#   method gets called when a script uses an XPCOM feature that is declared in
#   more than one of the interfaces the object implements.  Without quick
#   stubs, XPConnect just walks the interfaces in the order they're listed by
#   nsClassInfo.  You get the first interface that implements a feature with
#   that name.  With quick stubs, it's the same except that non-quick-stubbed
#   features are shadowed.
#
# - Quick stub methods are JSFastNative, which means that when a quick stub
#   method is called, no JS stack frame is created.  This doesn't affect
#   Mozilla security checks because they look for scripted JSStackFrames, not
#   native ones.
#
#   It does affect the 'stack' property of JavaScript exceptions, though: the
#   stubbed member will not appear.  (Note that if the stubbed member itself
#   fails, the member name will appear in the 'message' property.)
#
# - Many quick stubs don't create an XPCCallContext.  In those cases, no entry
#   is added to the XPCCallContext stack.  So native implementations of
#   quick-stubbed methods must avoid nsXPConnect::GetCurrentNativeCallContext.
#
#   (Even when a quick stub does have an XPCCallContext, it never pushes it all
#   the way to READY_TO_CALL state, so a lot of its members are garbage.  But
#   this doesn't endanger native implementations of non-quick-stubbed methods
#   that use GetCurrentNativeCallContext and are called indirectly from
#   quick-stubbed methods, because only the current top XPCCallContext is
#   exposed--nsAXPCNativeCallContext does not expose
#   XPCCallContext::GetPrevCallContext.)
#
# - Quick stubs never suspend the JS request.  So they are only suitable for
#   main-thread-only interfaces.
#
# - Quick stubs don't call XPCContext::SetLastResult.  This is visible on the
#   Components object.
#
# - Quick stubs skip a security check that XPConnect does in
#   XPCWrappedNative::CallMethod.  This means the security manager doesn't have
#   an opportunity to veto accesses to members for which quick stubs exist.
#
# - There are many features of IDL that XPConnect supports but qsgen does not,
#   including dependent types, arrays, and out parameters.
#
# - Since quick stubs are JSPropertyOps, we have to do additional work to make
#   __lookup[GS]etter__ work on them.


import xpidl
import header
import os, re
import sys

# === Preliminaries

# --makedepend-output support.
make_dependencies = []
make_targets = []

def warn(msg):
    sys.stderr.write(msg + '\n')

def unaliasType(t):
    while t.kind == 'typedef':
        t = t.realtype
    assert t is not None
    return t

def isVoidType(type):
    """ Return True if the given xpidl type is void. """
    return type.kind == 'builtin' and type.name == 'void'

def isInterfaceType(t):
    t = unaliasType(t)
    assert t.kind in ('builtin', 'native', 'interface', 'forward')
    return t.kind in ('interface', 'forward')

def isSpecificInterfaceType(t, name):
    """ True if `t` is an interface type with the given name, or a forward
    declaration or typedef aliasing it.

    `name` must not be the name of a typedef but the actual name of the
    interface.
    """
    t = unaliasType(t)
    return t.kind in ('interface', 'forward') and t.name == name

def getBuiltinOrNativeTypeName(t):
    t = unaliasType(t)
    if t.kind == 'builtin':
        return t.name
    elif t.kind == 'native':
        assert t.specialtype is not None
        return '[%s]' % t.specialtype
    else:
        return None


# === Reading the file

class UserError(Exception):
    pass

def findIDL(includePath, irregularFilenames, interfaceName):
    filename = irregularFilenames.get(interfaceName, interfaceName) + '.idl'
    for d in includePath:
        # Not os.path.join: we need a forward slash even on Windows because
        # this filename ends up in makedepend output.
        path = d + '/' + filename
        if os.path.exists(path):
            return path
    raise UserError("No IDL file found for interface %s "
                    "in include path %r"
                    % (interfaceName, includePath))

def loadIDL(parser, includePath, filename):
    make_dependencies.append(filename)
    text = open(filename, 'r').read()
    idl = parser.parse(text, filename=filename)
    idl.resolve(includePath, parser)
    return idl

def removeStubMember(memberId, member):
    if member not in member.iface.stubMembers:
        raise UserError("Trying to remove member %s from interface %s, but it was never added"
                        % (member.name, member.iface.name))
    member.iface.stubMembers.remove(member)

def addStubMember(memberId, member):
    if member.kind == 'method' and not member.implicit_jscontext and not isVariantType(member.realtype):
        for param in member.params:
            for attrname, value in vars(param).items():
                if value is True:
                    if attrname == 'optional':
                        continue

                    raise UserError("Method %s, parameter %s: "
                                    "unrecognized property %r"
                                    % (memberId, param.name, attrname))

    # Add this member to the list.
    member.iface.stubMembers.append(member)

def checkStubMember(member, isCustom):
    memberId = member.iface.name + "." + member.name
    if member.kind not in ('method', 'attribute'):
        raise UserError("Member %s is %r, not a method or attribute."
                        % (memberId, member.kind))
    if member.noscript:
        raise UserError("%s %s is noscript."
                        % (member.kind.capitalize(), memberId))
    if member.notxpcom:
        raise UserError(
            "%s %s: notxpcom methods are not supported."
            % (member.kind.capitalize(), memberId))

    if (member.kind == 'attribute'
          and not member.readonly
          and isSpecificInterfaceType(member.realtype, 'nsIVariant')
          and not isCustom):
        raise UserError(
            "Attribute %s: Non-readonly attributes of type nsIVariant "
            "are not supported."
            % memberId)

    # Check for unknown properties.
    for attrname, value in vars(member).items():
        if value is True and attrname not in ('readonly','optional_argc',
                                              'implicit_jscontext',
                                              'getter', 'stringifier'):
            raise UserError("%s %s: unrecognized property %r"
                            % (member.kind.capitalize(), memberId,
                               attrname))

def parseMemberId(memberId):
    """ Split the geven member id into its parts. """
    pieces = memberId.split('.')
    if len(pieces) < 2:
        raise UserError("Member %r: Missing dot." % memberId)
    if len(pieces) > 2:
        raise UserError("Member %r: Dots out of control." % memberId)
    return tuple(pieces)

class Configuration:
    def __init__(self, filename, includePath):
        self.includePath = includePath
        config = {}
        execfile(filename, config)
        # required settings
        for name in ('name', 'members'):
            if name not in config:
                raise UserError(filename + ": `%s` was not defined." % name)
            setattr(self, name, config[name])
        # optional settings
        self.irregularFilenames = config.get('irregularFilenames', {})
        self.customIncludes = config.get('customIncludes', [])
        self.customQuickStubs = config.get('customQuickStubs', [])
        self.customReturnInterfaces = config.get('customReturnInterfaces', [])
        self.customMethodCalls = config.get('customMethodCalls', {})

def readConfigFile(filename, includePath, cachedir):
    # Read the config file.
    conf = Configuration(filename, includePath)

    # Now read IDL files to connect the information in the config file to
    # actual XPCOM interfaces, methods, and attributes.
    interfaces = []
    interfacesByName = {}
    parser = xpidl.IDLParser(cachedir)

    def getInterface(interfaceName, errorLoc):
        iface = interfacesByName.get(interfaceName)
        if iface is None:
            idlFile = findIDL(conf.includePath, conf.irregularFilenames,
                              interfaceName)
            idl = loadIDL(parser, conf.includePath, idlFile)
            if not idl.hasName(interfaceName):
                raise UserError("The interface %s was not found "
                                "in the idl file %r."
                                % (interfaceName, idlFile))
            iface = idl.getName(interfaceName, errorLoc)
            iface.stubMembers = []
            interfaces.append(iface)
            interfacesByName[interfaceName] = iface
        return iface

    stubbedInterfaces = []

    for memberId in conf.members:
        add = True
        interfaceName, memberName = parseMemberId(memberId)

        # If the interfaceName starts with -, then remove this entry from the list
        if interfaceName[0] == '-':
            add = False
            interfaceName = interfaceName[1:]

        iface = getInterface(interfaceName, errorLoc='looking for %r' % memberId)

        if not iface.attributes.scriptable:
            raise UserError("Interface %s is not scriptable. "
                            "IDL file: %r." % (interfaceName, idlFile))

        if memberName == '*':
            if not add:
                raise UserError("Can't use negation in stub list with wildcard, in %s.*" % interfaceName)

            # Stub all scriptable members of this interface.
            for member in iface.members:
                if member.kind in ('method', 'attribute') and not member.noscript:
                    cmc = conf.customMethodCalls.get(interfaceName + "_" + header.methodNativeName(member), None)

                    addStubMember(iface.name + '.' + member.name, member)

                    if member.iface not in stubbedInterfaces:
                        stubbedInterfaces.append(member.iface)
        else:
            # Look up a member by name.
            if memberName not in iface.namemap:
                idlFile = iface.idl.parser.lexer.filename
                raise UserError("Interface %s has no member %r. "
                                "(See IDL file %r.)"
                                % (interfaceName, memberName, idlFile))
            member = iface.namemap.get(memberName, None)
            if add:
                if member in iface.stubMembers:
                    raise UserError("Member %s is specified more than once."
                                    % memberId)

                cmc = conf.customMethodCalls.get(interfaceName + "_" + header.methodNativeName(member), None)

                addStubMember(memberId, member)
                if member.iface not in stubbedInterfaces:
                    stubbedInterfaces.append(member.iface)
            else:
                removeStubMember(memberId, member)

    # Now go through and check all the interfaces' members
    for iface in stubbedInterfaces:
        for member in iface.stubMembers:
            cmc = conf.customMethodCalls.get(iface.name + "_" + header.methodNativeName(member), None)
            skipgen = cmc is not None and cmc.get('skipgen', False)
            checkStubMember(member, skipgen)

    for iface in conf.customReturnInterfaces:
        # just ensure that it exists so that we can grab it later
        iface = getInterface(iface, errorLoc='looking for %s' % (iface,))

    return conf, interfaces


# === Generating the header file

def writeHeaderFile(filename, name):
    print "Creating header file", filename

    headerMacro = '__gen_%s__' % filename.replace('.', '_')
    f = open(filename, 'w')
    try:
        f.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
                "#ifndef " + headerMacro + "\n"
                "#define " + headerMacro + "\n\n"
                "JSBool " + name + "_DefineQuickStubs("
                "JSContext *cx, JSObject *proto, unsigned flags, "
                "uint32_t count, const nsID **iids);\n\n"
                "void " + name + "_MarkInterfaces();\n\n"
                "void " + name + "_ClearInterfaces();\n\n"
                "inline void " + name + "_InitInterfaces()\n"
                "{\n"
                "  " + name + "_ClearInterfaces();\n"
                "}\n\n"
                "#endif\n")
    finally:
        f.close()

# === Generating the source file

class StringTable:
    def __init__(self):
        self.current_index = 0;
        self.table = {}
        self.reverse_table = {}

    def c_strlen(self, string):
        return len(string) + 1

    def stringIndex(self, string):
        if string in self.table:
            return self.table[string]
        else:
            result = self.current_index
            self.table[string] = result
            self.current_index += self.c_strlen(string)
            return result

    def writeDefinition(self, f, name):
        entries = self.table.items()
        entries.sort(key=lambda x:x[1])
        # Avoid null-in-string warnings with GCC and potentially
        # overlong string constants; write everything out the long way.
        def explodeToCharArray(string):
            return ", ".join(map(lambda x:"'%s'" % x, string))
        f.write("static const char %s[] = {\n" % name)
        for (string, offset) in entries[:-1]:
            f.write("  /* %5d */ %s, '\\0',\n"
                    % (offset, explodeToCharArray(string)))
        f.write("  /* %5d */ %s, '\\0' };\n\n"
                % (entries[-1][1], explodeToCharArray(entries[-1][0])))

def substitute(template, vals):
    """ Simple replacement for string.Template, which isn't in Python 2.3. """
    def replacement(match):
        return vals[match.group(1)]
    return re.sub(r'\${(\w+)}', replacement, template)

# From JSData2Native.
argumentUnboxingTemplates = {
    'octet':
        "    uint32_t ${name}_u32;\n"
        "    if (!JS_ValueToECMAUint32(cx, ${argVal}, &${name}_u32))\n"
        "        return JS_FALSE;\n"
        "    uint8_t ${name} = (uint8_t) ${name}_u32;\n",

    'short':
        "    int32_t ${name}_i32;\n"
        "    if (!JS_ValueToECMAInt32(cx, ${argVal}, &${name}_i32))\n"
        "        return JS_FALSE;\n"
        "    int16_t ${name} = (int16_t) ${name}_i32;\n",

    'unsigned short':
        "    uint32_t ${name}_u32;\n"
        "    if (!JS_ValueToECMAUint32(cx, ${argVal}, &${name}_u32))\n"
        "        return JS_FALSE;\n"
        "    uint16_t ${name} = (uint16_t) ${name}_u32;\n",

    'long':
        "    int32_t ${name};\n"
        "    if (!JS_ValueToECMAInt32(cx, ${argVal}, &${name}))\n"
        "        return JS_FALSE;\n",

    'unsigned long':
        "    uint32_t ${name};\n"
        "    if (!JS_ValueToECMAUint32(cx, ${argVal}, &${name}))\n"
        "        return JS_FALSE;\n",

    'long long':
        "    int64_t ${name};\n"
        "    if (!JS::ToInt64(cx, ${argVal}, &${name}))\n"
        "        return JS_FALSE;\n",

    'unsigned long long':
        "    uint64_t ${name};\n"
        "    if (!JS::ToUint64(cx, ${argVal}, &${name}))\n"
        "        return JS_FALSE;\n",

    'float':
        "    double ${name}_dbl;\n"
        "    if (!JS_ValueToNumber(cx, ${argVal}, &${name}_dbl))\n"
        "        return JS_FALSE;\n"
        "    float ${name} = (float) ${name}_dbl;\n",

    'double':
        "    double ${name};\n"
        "    if (!JS_ValueToNumber(cx, ${argVal}, &${name}))\n"
        "        return JS_FALSE;\n",

    'boolean':
        "    JSBool ${name};\n"
        "    JS_ValueToBoolean(cx, ${argVal}, &${name});\n",

    '[astring]':
        "    xpc_qsAString ${name}(cx, ${argVal}, ${argPtr});\n"
        "    if (!${name}.IsValid())\n"
        "        return JS_FALSE;\n",

    '[domstring]':
        "    xpc_qsDOMString ${name}(cx, ${argVal}, ${argPtr},\n"
        "                            xpc_qsDOMString::e${nullBehavior},\n"
        "                            xpc_qsDOMString::e${undefinedBehavior});\n"
        "    if (!${name}.IsValid())\n"
        "        return JS_FALSE;\n",

    'string':
        "    JSAutoByteString ${name}_bytes;\n"
        "    if (!xpc_qsJsvalToCharStr(cx, ${argVal}, &${name}_bytes))\n"
        "        return JS_FALSE;\n"
        "    char *${name} = ${name}_bytes.ptr();\n",

    'wstring':
        "    const PRUnichar *${name};\n"
        "    if (!xpc_qsJsvalToWcharStr(cx, ${argVal}, ${argPtr}, &${name}))\n"
        "        return JS_FALSE;\n",

    '[cstring]':
        "    xpc_qsACString ${name}(cx, ${argVal}, ${argPtr});\n"
        "    if (!${name}.IsValid())\n"
        "        return JS_FALSE;\n",

    '[utf8string]':
        "    xpc_qsAUTF8String ${name}(cx, ${argVal}, ${argPtr});\n"
        "    if (!${name}.IsValid())\n"
        "        return JS_FALSE;\n",

    '[jsval]':
        "    jsval ${name} = ${argVal};\n"
    }

# From JSData2Native.
#
# Omitted optional arguments are treated as though the caller had passed JS
# `null`; this behavior is from XPCWrappedNative::CallMethod. The 'jsval' type,
# however, defaults to 'undefined'.
#
def writeArgumentUnboxing(f, i, name, type, haveCcx, optional, rvdeclared,
                          nullBehavior, undefinedBehavior):
    # f - file to write to
    # i - int or None - Indicates the source jsval.  If i is an int, the source
    #     jsval is argv[i]; otherwise it is *vp.  But if Python i >= C++ argc,
    #     which can only happen if optional is True, the argument is missing;
    #     use JSVAL_NULL as the source jsval instead.
    # name - str - name of the native C++ variable to create.
    # type - xpidl.{Interface,Native,Builtin} - IDL type of argument
    # optional - bool - True if the parameter is optional.
    # rvdeclared - bool - False if no |nsresult rv| has been declared earlier.

    typeName = getBuiltinOrNativeTypeName(type)

    isSetter = (i is None)

    if isSetter:
        argPtr = "vp"
        argVal = "*vp"
    elif optional:
        if typeName == "[jsval]":
            val = "JSVAL_VOID"
        else:
            val = "JSVAL_NULL"
        argVal = "(%d < argc ? argv[%d] : %s)" % (i, i, val)
        argPtr = "(%d < argc ? &argv[%d] : NULL)" % (i, i)
    else:
        argVal = "argv[%d]" % i
        argPtr = "&" + argVal

    params = {
        'name': name,
        'argVal': argVal,
        'argPtr': argPtr,
        'nullBehavior': nullBehavior or 'DefaultNullBehavior',
        'undefinedBehavior': undefinedBehavior or 'DefaultUndefinedBehavior'
        }

    if typeName is not None:
        template = argumentUnboxingTemplates.get(typeName)
        if template is not None:
            f.write(substitute(template, params))
            return rvdeclared
        # else fall through; the type isn't supported yet.
    elif isInterfaceType(type):
        if type.name == 'nsIVariant':
            # Totally custom.
            assert haveCcx
            template = (
                "    nsCOMPtr<nsIVariant> ${name}(already_AddRefed<nsIVariant>("
                "XPCVariant::newVariant(ccx, ${argVal})));\n"
                "    if (!${name}) {\n"
                "        xpc_qsThrowBadArgWithCcx(ccx, NS_ERROR_XPC_BAD_CONVERT_JS, %d);\n"
                "        return JS_FALSE;\n"
                "    }") % i
            f.write(substitute(template, params))
            return rvdeclared
        elif type.name == 'nsIAtom':
            # Should have special atomizing behavior.  Fall through.
            pass
        else:
            if not rvdeclared:
                f.write("    nsresult rv;\n");
            f.write("    %s *%s;\n" % (type.name, name))
            f.write("    xpc_qsSelfRef %sref;\n" % name)
            f.write("    rv = xpc_qsUnwrapArg<%s>("
                    "cx, %s, &%s, &%sref.ptr, %s);\n"
                    % (type.name, argVal, name, name, argPtr))
            f.write("    if (NS_FAILED(rv)) {\n")
            if isSetter:
                f.write("        xpc_qsThrowBadSetterValue("
                        "cx, rv, JSVAL_TO_OBJECT(*tvr.jsval_addr()), id);\n")
            elif haveCcx:
                f.write("        xpc_qsThrowBadArgWithCcx(ccx, rv, %d);\n" % i)
            else:
                f.write("        xpc_qsThrowBadArg(cx, rv, vp, %d);\n" % i)
            f.write("        return JS_FALSE;\n"
                    "    }\n")
            return True

    warn("Unable to unbox argument of type %s (native type %s)" % (type.name, typeName))
    if i is None:
        src = '*vp'
    else:
        src = 'argv[%d]' % i
    f.write("    !; // TODO - Unbox argument %s = %s\n" % (name, src))
    return rvdeclared

def writeResultDecl(f, type, varname):
    if isVoidType(type):
        return  # nothing to declare

    t = unaliasType(type)
    if t.kind == 'builtin':
        if not t.nativename.endswith('*'):
            if type.kind == 'typedef':
                typeName = type.name  # use it
            else:
                typeName = t.nativename
            f.write("    %s %s;\n" % (typeName, varname))
            return
    elif t.kind == 'native':
        name = getBuiltinOrNativeTypeName(t)
        if name in ('[domstring]', '[astring]'):
            f.write("    nsString %s;\n" % varname)
            return
        elif name == '[jsval]':
            f.write("    jsval %s;\n" % varname)
            return
    elif t.kind in ('interface', 'forward'):
        f.write("    nsCOMPtr<%s> %s;\n" % (type.name, varname))
        return

    warn("Unable to declare result of type %s" % type.name)
    f.write("    !; // TODO - Declare out parameter `%s`.\n" % varname)

def outParamForm(name, type):
    type = unaliasType(type)
    if type.kind == 'builtin':
        return '&' + name
    elif type.kind == 'native':
        if getBuiltinOrNativeTypeName(type) == '[jsval]':
            return '&' + name
        elif type.modifier == 'ref':
            return name
        else:
            return '&' + name
    else:
        return 'getter_AddRefs(%s)' % name

# From NativeData2JS.
resultConvTemplates = {
    'void':
            "    ${jsvalRef} = JSVAL_VOID;\n"
            "    return JS_TRUE;\n",

    'octet':
        "    ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n"
        "    return JS_TRUE;\n",

    'short':
        "    ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n"
        "    return JS_TRUE;\n",

    'long':
        "    ${jsvalRef} = INT_TO_JSVAL(result);\n"
        "    return JS_TRUE;\n",

    'long long':
        "    return xpc_qsInt64ToJsval(cx, result, ${jsvalPtr});\n",

    'unsigned short':
        "    ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n"
        "    return JS_TRUE;\n",

    'unsigned long':
        "    ${jsvalRef} = UINT_TO_JSVAL(result);\n"
        "    return JS_TRUE;\n",

    'unsigned long long':
        "    return xpc_qsUint64ToJsval(cx, result, ${jsvalPtr});\n",

    'float':
        "    ${jsvalRef} = JS_NumberValue(result);\n"
        "    return JS_TRUE;\n",

    'double':
        "    ${jsvalRef} =  JS_NumberValue(result);\n"
        "    return JS_TRUE;\n",

    'boolean':
        "    ${jsvalRef} = (result ? JSVAL_TRUE : JSVAL_FALSE);\n"
        "    return JS_TRUE;\n",

    '[astring]':
        "    return xpc::StringToJsval(cx, result, ${jsvalPtr});\n",

    '[domstring]':
        "    return xpc::StringToJsval(cx, result, ${jsvalPtr});\n",

    '[jsval]':
        "    ${jsvalRef} = result;\n"
        "    return JS_WrapValue(cx, ${jsvalPtr});\n"
    }

def isVariantType(t):
    return isSpecificInterfaceType(t, 'nsIVariant')

def writeResultConv(f, type, jsvalPtr, jsvalRef):
    """ Emit code to convert the C++ variable `result` to a jsval.

    The emitted code contains a return statement; it returns JS_TRUE on
    success, JS_FALSE on error.
    """
    # From NativeData2JS.
    typeName = getBuiltinOrNativeTypeName(type)
    if typeName is not None:
        template = resultConvTemplates.get(typeName)
        if template is not None:
            values = {'jsvalRef': jsvalRef,
                      'jsvalPtr': jsvalPtr}
            f.write(substitute(template, values))
            return
        # else fall through; this type isn't supported yet
    elif isInterfaceType(type):
        if isVariantType(type):
            f.write("    return xpc_qsVariantToJsval(lccx, result, %s);\n"
                    % jsvalPtr)
            return
        else:
            f.write("    if (!result) {\n"
                    "      *%s = JSVAL_NULL;\n"
                    "      return JS_TRUE;\n"
                    "    }\n"
                    "    nsWrapperCache* cache = xpc_qsGetWrapperCache(result);\n"
                    "    if (xpc_FastGetCachedWrapper(cache, obj, %s)) {\n"
                    "      return JS_TRUE;\n"
                    "    }\n"
                    "    // After this point do not use 'result'!\n"
                    "    qsObjectHelper helper(result, cache);\n"
                    "    return xpc_qsXPCOMObjectToJsval(lccx, "
                    "helper, &NS_GET_IID(%s), &interfaces[k_%s], %s);\n"
                    % (jsvalPtr, jsvalPtr, type.name, type.name, jsvalPtr))
            return

    warn("Unable to convert result of type %s" % type.name)
    f.write("    !; // TODO - Convert `result` to jsval, store in `%s`.\n"
            % jsvalRef)
    f.write("    return xpc_qsThrow(cx, NS_ERROR_UNEXPECTED); // FIXME\n")

def anyParamRequiresCcx(member):
    for p in member.params:
        if isVariantType(p.realtype):
            return True
    return False

def memberNeedsCcx(member):
    return member.kind == 'method' and anyParamRequiresCcx(member)

def memberNeedsCallee(member):
    return memberNeedsCcx(member) or isInterfaceType(member.realtype)

def validateParam(member, param):
    def pfail(msg):
        raise UserError(
            member.iface.name + '.' + member.name + ": "
            "parameter " + param.name + ": " + msg)

    if param.iid_is is not None:
        pfail("iid_is parameters are not supported.")
    if param.size_is is not None:
        pfail("size_is parameters are not supported.")
    if param.retval:
        pfail("Unexpected retval parameter!")
    if param.paramtype in ('out', 'inout'):
        pfail("Out parameters are not supported.")
    if param.const or param.array or param.shared:
        pfail("I am a simple caveman.")

def setOptimizationForMSVC(f, b):
    """ Write a pragma that turns optimizations off (if b is False) or
    on (if b is True) for MSVC.
    """
    if b:
        pragmaParam = "on"
    else:
        pragmaParam = "off"
    f.write("#ifdef _MSC_VER\n")
    f.write('# pragma optimize("", %s)\n'%pragmaParam)
    f.write("#endif\n")

def writeQuickStub(f, customMethodCalls, member, stubName, isSetter=False):
    """ Write a single quick stub (a custom SpiderMonkey getter/setter/method)
    for the specified XPCOM interface-member. 
    """
    # Workaround for suspected compiler bug.
    # See https://bugzilla.mozilla.org/show_bug.cgi?id=750019
    disableOptimizationForMSVC = (stubName == 'nsIDOMHTMLDocument_Write')

    isAttr = (member.kind == 'attribute')
    isMethod = (member.kind == 'method')
    assert isAttr or isMethod
    isGetter = isAttr and not isSetter

    signature = "static JSBool\n"
    if isAttr:
        # JSPropertyOp signature.
        if isSetter:
            signature += "%s(JSContext *cx, JSHandleObject obj, JSHandleId id, JSBool strict,%s JSMutableHandleValue vp_)\n"
        else:
            signature += "%s(JSContext *cx, JSHandleObject obj, JSHandleId id,%s JSMutableHandleValue vp_)\n"
    else:
        # JSFastNative.
        signature += "%s(JSContext *cx, unsigned argc,%s jsval *vp)\n"

    customMethodCall = customMethodCalls.get(stubName, None)

    if customMethodCall is None:
        customMethodCall = customMethodCalls.get(member.iface.name + '_', None)
        if customMethodCall is not None:
            if isMethod:
                code = customMethodCall.get('code', None)
            elif isGetter:
                code = customMethodCall.get('getter_code', None)
            else:
                code = customMethodCall.get('setter_code', None)
        else:
            code = None

        if code is not None:
            templateName = member.iface.name
            if isGetter:
                templateName += '_Get'
            elif isSetter:
                templateName += '_Set'

            # Generate the code for the stub, calling the template function
            # that's shared between the stubs. The stubs can't have additional
            # arguments, only the template function can.
            callTemplate = signature % (stubName, '')
            callTemplate += "{\n"

            nativeName = (member.binaryname is not None and member.binaryname
                          or header.firstCap(member.name))
            argumentValues = (customMethodCall['additionalArgumentValues']
                              % nativeName)
            if isAttr:
                callTemplate += ("    return %s(cx, obj, id%s, %s, vp_);\n"
                                 % (templateName, ", strict" if isSetter else "", argumentValues))
            else:
                callTemplate += ("    return %s(cx, argc, %s, vp);\n"
                                 % (templateName, argumentValues))
            callTemplate += "}\n\n"

            # Fall through and create the template function stub called from the
            # real stubs, but only generate the stub once. Otherwise, just write
            # out the call to the template function and return.
            templateGenerated = templateName + '_generated'
            if templateGenerated in customMethodCall:
                f.write(callTemplate)
                return
            customMethodCall[templateGenerated] = True

            stubName = templateName
        else:
            callTemplate = ""
    else:
        callTemplate = ""
        code = customMethodCall.get('code', None)

    unwrapThisFailureFatal = (customMethodCall is None or
                              customMethodCall.get('unwrapThisFailureFatal', True));
    if (not unwrapThisFailureFatal and not isAttr):
        raise UserError(member.iface.name + '.' + member.name + ": "
                        "Unwrapping this failure must be fatal for methods")

    # Function prolog.

    # Only template functions can have additional arguments.
    if customMethodCall is None or not 'additionalArguments' in customMethodCall:
        additionalArguments = ''
    else:
        additionalArguments = " %s," % customMethodCall['additionalArguments']
    if disableOptimizationForMSVC:
        setOptimizationForMSVC(f, False)
    f.write(signature % (stubName, additionalArguments))
    f.write("{\n")
    f.write("    XPC_QS_ASSERT_CONTEXT_OK(cx);\n")

    # Convert JSMutableHandleValue to jsval*
    if isAttr:
        f.write("    jsval *vp = vp_.address();\n")

    # For methods, compute "this".
    if isMethod:
        f.write("    JSObject *obj = JS_THIS_OBJECT(cx, vp);\n"
                "    if (!obj)\n"
                "        return JS_FALSE;\n")

    # Create ccx if needed.
    haveCcx = memberNeedsCcx(member)
    if haveCcx and not unwrapThisFailureFatal:
        raise UserError(member.iface.name + '.' + member.name + ": "
                        "Unwrapping this failure must be fatal when we have a ccx")

    if haveCcx:
        f.write("    XPCCallContext ccx(JS_CALLER, cx, obj, "
                "JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));\n")
        if isInterfaceType(member.realtype):
            f.write("    XPCLazyCallContext lccx(ccx);\n")

    # Get the 'self' pointer.
    if customMethodCall is None or not 'thisType' in customMethodCall:
        f.write("    %s *self;\n" % member.iface.name)
    else:
        f.write("    %s *self;\n" % customMethodCall['thisType'])
    f.write("    xpc_qsSelfRef selfref;\n")
    # Don't use FromCcx for getters or setters; the way we construct the ccx in
    # a getter/setter causes it to find the wrong wrapper in some cases.
    if haveCcx:
        # Undocumented, but the interpreter puts 'this' at argv[-1],
        # which is vp[1]; and it's ok to overwrite it.
        f.write("    if (!xpc_qsUnwrapThisFromCcx(ccx, &self, &selfref.ptr, "
                "&vp[1]))\n")
        f.write("        return JS_FALSE;\n")
    else:
        if isGetter:
            pthisval = 'vp'
        elif isSetter:
            f.write("    JS::AutoValueRooter tvr(cx);\n")
            pthisval = 'tvr.jsval_addr()'
        else:
            pthisval = '&vp[1]' # as above, ok to overwrite vp[1]

        if unwrapThisFailureFatal:
            unwrapFatalArg = "true"
        else:
            unwrapFatalArg = "false"

        if not isSetter and isInterfaceType(member.realtype):
            f.write("    XPCLazyCallContext lccx(JS_CALLER, cx, obj);\n")
            f.write("    if (!xpc_qsUnwrapThis(cx, obj, &self, "
                    "&selfref.ptr, %s, &lccx, %s))\n" % (pthisval, unwrapFatalArg))
        else:
            f.write("    if (!xpc_qsUnwrapThis(cx, obj, &self, "
                    "&selfref.ptr, %s, nullptr, %s))\n" % (pthisval, unwrapFatalArg))
        f.write("        return JS_FALSE;\n")

        if not unwrapThisFailureFatal:
            f.write("      if (!self) {\n")
            if (isGetter):
                f.write("        *vp = JSVAL_NULL;\n")
            f.write("        return JS_TRUE;\n")
            f.write("    }\n");

    if isMethod:
        # If there are any required arguments, check argc.
        requiredArgs = len(member.params)
        while requiredArgs and member.params[requiredArgs-1].optional:
            requiredArgs -= 1
        if requiredArgs:
            f.write("    if (argc < %d)\n" % requiredArgs)
            f.write("        return xpc_qsThrow(cx, "
                    "NS_ERROR_XPC_NOT_ENOUGH_ARGS);\n")

    # Convert in-parameters.
    rvdeclared = False
    if isMethod:
        if len(member.params) > 0:
            f.write("    jsval *argv = JS_ARGV(cx, vp);\n")
        for i, param in enumerate(member.params):
            argName = 'arg%d' % i
            argTypeKey = argName + 'Type'
            if customMethodCall is None or not argTypeKey in customMethodCall:
                validateParam(member, param)
                realtype = param.realtype
            else:
                realtype = xpidl.Forward(name=customMethodCall[argTypeKey],
                                         location='', doccomments='')
            # Emit code to convert this argument from jsval.
            rvdeclared = writeArgumentUnboxing(
                f, i, argName, realtype,
                haveCcx=haveCcx,
                optional=param.optional,
                rvdeclared=rvdeclared,
                nullBehavior=param.null,
                undefinedBehavior=param.undefined)
    elif isSetter:
        rvdeclared = writeArgumentUnboxing(f, None, 'arg0', member.realtype,
                                           haveCcx=False, optional=False,
                                           rvdeclared=rvdeclared,
                                           nullBehavior=member.null,
                                           undefinedBehavior=member.undefined)

    canFail = customMethodCall is None or customMethodCall.get('canFail', True)
    if canFail and not rvdeclared:
        f.write("    nsresult rv;\n")
        rvdeclared = True

    if code is not None:
        f.write("%s\n" % code)

    if code is None or (isGetter and callTemplate is ""):
        debugGetter = code is not None
        if debugGetter:
            f.write("#ifdef DEBUG\n")
            f.write("    nsresult debug_rv;\n")
            f.write("    nsCOMPtr<%s> debug_self;\n"
                    "    CallQueryInterface(self, getter_AddRefs(debug_self));\n"
                    % member.iface.name);
            prefix = 'debug_'
        else:
            prefix = ''

        resultname = prefix + 'result'
        selfname = prefix + 'self'
        nsresultname = prefix + 'rv'

        # Prepare out-parameter.
        if isMethod or isGetter:
            writeResultDecl(f, member.realtype, resultname)

        # Call the method.
        if isMethod:
            comName = header.methodNativeName(member)
            argv = ['arg' + str(i) for i, p in enumerate(member.params)]
            if member.implicit_jscontext:
                argv.append('cx')
            if member.optional_argc:
                argv.append('NS_MIN<uint32_t>(argc, %d) - %d' % 
                            (len(member.params), requiredArgs))
            if not isVoidType(member.realtype):
                argv.append(outParamForm(resultname, member.realtype))
            args = ', '.join(argv)
        else:
            comName = header.attributeNativeName(member, isGetter)
            if isGetter:
                args = outParamForm(resultname, member.realtype)
            else:
                args = "arg0"
            if member.implicit_jscontext:
                args = "cx, " + args

        f.write("    ")
        if canFail or debugGetter:
            f.write("%s = " % nsresultname)
        f.write("%s->%s(%s);\n" % (selfname, comName, args))

        if debugGetter:
            checkSuccess = "NS_SUCCEEDED(debug_rv)"
            if canFail:
                checkSuccess += " == NS_SUCCEEDED(rv)"
            f.write("    NS_ASSERTION(%s && "
                    "xpc_qsSameResult(debug_result, result),\n"
                    "                 \"Got the wrong answer from the custom "
                    "method call!\");\n" % checkSuccess)
            f.write("#endif\n")

    if canFail:
        # Check for errors.
        f.write("    if (NS_FAILED(rv))\n")
        if isMethod:
            if haveCcx:
                f.write("        return xpc_qsThrowMethodFailedWithCcx("
                        "ccx, rv);\n")
            else:
                f.write("        return xpc_qsThrowMethodFailed("
                        "cx, rv, vp);\n")
        else:
            if isGetter:
                thisval = '*vp'
            else:
                thisval = '*tvr.jsval_addr()'
            f.write("        return xpc_qsThrowGetterSetterFailed(cx, rv, " +
                    "JSVAL_TO_OBJECT(%s), id);\n" % thisval)

    # Convert the return value.
    if isMethod or isGetter:
        writeResultConv(f, member.realtype, 'vp', '*vp')
    else:
        f.write("    return JS_TRUE;\n")

    # Epilog.
    f.write("}\n")
    if disableOptimizationForMSVC:
        setOptimizationForMSVC(f, True)
    f.write("\n")

    # Now write out the call to the template function.
    if customMethodCall is not None:
        f.write(callTemplate)

def writeAttrStubs(f, customMethodCalls, stringtable, attr):
    cmc = customMethodCalls.get(attr.iface.name + "_" + header.methodNativeName(attr), None)
    custom = cmc and cmc.get('skipgen', False)

    getterName = (attr.iface.name + '_'
                  + header.attributeNativeName(attr, True))
    if not custom:
        writeQuickStub(f, customMethodCalls, attr, getterName)
    if attr.readonly:
        setterName = 'xpc_qsGetterOnlyPropertyStub'
    else:
        setterName = (attr.iface.name + '_'
                      + header.attributeNativeName(attr, False))
        if not custom:
            writeQuickStub(f, customMethodCalls, attr, setterName, isSetter=True)

    ps = ('{%d, %s, %s}'
          % (stringtable.stringIndex(attr.name), getterName, setterName))
    return ps

def writeMethodStub(f, customMethodCalls, stringtable, method):
    """ Write a method stub to `f`. Return an xpc_qsFunctionSpec initializer. """

    cmc = customMethodCalls.get(method.iface.name + "_" + header.methodNativeName(method), None)
    custom = cmc and cmc.get('skipgen', False)

    stubName = method.iface.name + '_' + header.methodNativeName(method)
    if not custom:
        writeQuickStub(f, customMethodCalls, method, stubName)
    fs = '{%d, %d, %s}' % (stringtable.stringIndex(method.name),
                           len(method.params), stubName)
    return fs

def writeStubsForInterface(f, customMethodCalls, stringtable, iface):
    f.write("// === interface %s\n\n" % iface.name)
    propspecs = []
    funcspecs = []
    for member in iface.stubMembers:
        if member.kind == 'attribute':
            ps = writeAttrStubs(f, customMethodCalls, stringtable, member)
            propspecs.append(ps)
        elif member.kind == 'method':
            fs = writeMethodStub(f, customMethodCalls, stringtable, member)
            funcspecs.append(fs)
        else:
            raise TypeError('expected attribute or method, not %r'
                            % member.__class__.__name__)

    iface.propspecs = propspecs
    iface.funcspecs = funcspecs

def hashIID(iid):
    # See nsIDKey::HashCode in nsHashtable.h.
    return int(iid[:8], 16)

uuid_re = re.compile(r'^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$')

def writeResultXPCInterfacesArray(f, conf, resulttypes):
    f.write("// === XPCNativeInterface cache \n\n")
    count = len(resulttypes)
    if count > 0:
        f.write("static XPCNativeInterface* interfaces[%d];\n\n" % count)
    f.write("void %s_MarkInterfaces()\n"
            "{\n" % conf.name)
    if count > 0:
        f.write("    for (uint32_t i = 0; i < %d; ++i)\n"
                "        if (interfaces[i])\n"
                "            interfaces[i]->Mark();\n" % count)
    f.write("}\n")
    f.write("void %s_ClearInterfaces()\n"
            "{\n" % conf.name)
    if count > 0:
        f.write("    memset(interfaces, 0, %d * "
                "sizeof(XPCNativeInterface*));\n" % count)
    f.write("}\n\n")
    i = 0
    for type in resulttypes:
        f.write("static const uint32_t k_%s = %d;\n" % (type, i))
        i += 1
    if count > 0:
        f.write("\n\n")

def writeSpecs(f, elementType, varname, spec_type, spec_indices, interfaces):
    index = 0
    f.write("static const %s %s[] = {\n" % (elementType, varname))
    for iface in interfaces:
        specs = getattr(iface, spec_type)
        if specs:
            spec_indices[iface.name] = index
            f.write("    // %s (index %d)\n" % (iface.name,index))
            for s in specs:
                f.write("    %s,\n" % s)
            index += len(specs)
    f.write("};\n\n")

def writeDefiner(f, conf, stringtable, interfaces):
    f.write("// === Definer\n\n")

    # Write out the properties and functions
    propspecs_indices = {}
    funcspecs_indices = {}
    prop_array_name = "all_properties"
    func_array_name = "all_functions"
    writeSpecs(f, "xpc_qsPropertySpec", prop_array_name,
               "propspecs", propspecs_indices, interfaces)
    writeSpecs(f, "xpc_qsFunctionSpec", func_array_name,
               "funcspecs", funcspecs_indices, interfaces)

    # generate the static hash table
    loadFactor = 0.6
    size = int(len(interfaces) / loadFactor)
    buckets = [[] for i in range(size)]
    for iface in interfaces:
        # This if-statement discards interfaces specified with
        # "nsInterfaceName.*" that don't have any stub-able members.
        if iface.stubMembers:
            h = hashIID(iface.attributes.uuid)
            buckets[h % size].append(iface)

    # Calculate where each interface's entry will show up in tableData.  Where
    # there are hash collisions, the extra entries are added at the end of the
    # table.
    entryIndexes = {}
    arraySize = size
    for i, bucket in enumerate(buckets):
        if bucket:
            entryIndexes[bucket[0].attributes.uuid] = i
            for iface in bucket[1:]:
                entryIndexes[iface.attributes.uuid] = arraySize
                arraySize += 1

    entries = ["    {{0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}, "
               "0, 0, 0, 0, XPC_QS_NULL_INDEX, XPC_QS_NULL_INDEX}"
               for i in range(arraySize)]
    for i, bucket in enumerate(buckets):
        for j, iface in enumerate(bucket):
            # iid field
            uuid = iface.attributes.uuid.lower()
            m = uuid_re.match(uuid)
            assert m is not None
            m0, m1, m2, m3, m4 = m.groups()
            m3arr = ('{0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}'
                     % (m3[0:2], m3[2:4], m4[0:2], m4[2:4],
                        m4[4:6], m4[6:8], m4[8:10], m4[10:12]))
            iid = ('{0x%s, 0x%s, 0x%s, %s}' % (m0, m1, m2, m3arr))

            # properties fields
            prop_index = 0
            prop_n_entries = 0
            if iface.propspecs:
                prop_index = propspecs_indices[iface.name]
                prop_n_entries = len(iface.propspecs)

            # member fields
            func_index = 0
            func_n_entries = 0
            if iface.funcspecs:
                func_index = funcspecs_indices[iface.name]
                func_n_entries = len(iface.funcspecs)

            # parentInterface field
            baseName = iface.base
            while baseName is not None:
                piface = iface.idl.getName(baseName, None)
                k = entryIndexes.get(piface.attributes.uuid)
                if k is not None:
                    parentInterface = str(k)
                    break
                baseName = piface.base
            else:
                parentInterface = "XPC_QS_NULL_INDEX"

            # chain field
            if j == len(bucket) - 1:
                chain = "XPC_QS_NULL_INDEX"
            else:
                k = entryIndexes[bucket[j+1].attributes.uuid]
                chain = str(k)

            # add entry
            entry = "    /* %s */ {%s, %d, %d, %d, %d, %s, %s}" % (
                iface.name, iid, prop_index, prop_n_entries,
                func_index, func_n_entries, parentInterface, chain)
            entries[entryIndexes[iface.attributes.uuid]] = entry

    f.write("static const xpc_qsHashEntry tableData[] = {\n")
    f.write(",\n".join(entries))
    f.write("\n    };\n\n")
    f.write("// Make sure our table indices aren't overflowed\n"
            "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].parentInterface))));\n"
            "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].chain))));\n\n")

    # The string table for property and method names.
    table_name = "stringtab"
    stringtable.writeDefinition(f, table_name)
    structNames = [prop_array_name, func_array_name]
    for name in structNames:
        f.write("PR_STATIC_ASSERT(sizeof(%s) < (1 << (8 * sizeof(%s[0].name_index))));\n"
                % (table_name, name))
    f.write("\n")

    # the definer function (entry point to this quick stubs file)
    f.write("namespace xpc {\n")
    f.write("bool %s_DefineQuickStubs(" % conf.name)
    f.write("JSContext *cx, JSObject *proto, unsigned flags, uint32_t count, "
            "const nsID **iids)\n"
            "{\n")
    f.write("    return !!xpc_qsDefineQuickStubs("
            "cx, proto, flags, count, iids, %d, tableData, %s, %s, %s);\n" % (
            size, prop_array_name, func_array_name, table_name))
    f.write("}\n")
    f.write("} // namespace xpc\n\n\n")


stubTopTemplate = '''\
/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */
#include "jsapi.h"
#include "qsWinUndefs.h"
#include "prtypes.h"
#include "nsID.h"
#include "%s"
#include "nscore.h"
#include "nsCOMPtr.h"
#include "nsDependentString.h"
#include "xpcprivate.h"  // for XPCCallContext
#include "XPCQuickStubs.h"
#include "nsWrapperCacheInlines.h"
'''

def writeStubFile(filename, headerFilename, conf, interfaces):
    print "Creating stub file", filename
    make_targets.append(filename)

    f = open(filename, 'w')
    filesIncluded = set()

    def includeType(type):
        type = unaliasType(type)
        if type.kind in ('builtin', 'native'):
            return None
        file = conf.irregularFilenames.get(type.name, type.name) + '.h'
        if file not in filesIncluded:
            f.write('#include "%s"\n' % file)
            filesIncluded.add(file)
        return type

    def writeIncludesForMember(member):
        assert member.kind in ('attribute', 'method')
        resulttype = includeType(member.realtype)
        if member.kind == 'method':
            for p in member.params:
                includeType(p.realtype)
        return resulttype

    def writeIncludesForInterface(iface):
        assert iface.kind == 'interface'
        resulttypes = []
        for member in iface.stubMembers:
            resulttype = writeIncludesForMember(member)
            if resulttype is not None and not isVariantType(resulttype):
                resulttypes.append(resulttype.name)
                
        includeType(iface)

        return resulttypes

    try:
        f.write(stubTopTemplate % os.path.basename(headerFilename))
        N = 256
        resulttypes = []
        for iface in interfaces:
            resulttypes.extend(writeIncludesForInterface(iface))
        resulttypes.extend(conf.customReturnInterfaces)
        for customInclude in conf.customIncludes:
            f.write('#include "%s"\n' % customInclude)
        f.write("\n\n")
        writeResultXPCInterfacesArray(f, conf, frozenset(resulttypes))
        for customQS in conf.customQuickStubs:
            f.write('#include "%s"\n' % customQS)
        stringtable = StringTable()
        for iface in interfaces:
            writeStubsForInterface(f, conf.customMethodCalls, stringtable, iface)
        writeDefiner(f, conf, stringtable, interfaces)
    finally:
        f.close()

def makeQuote(filename):
    return filename.replace(' ', '\\ ')  # enjoy!

def writeMakeDependOutput(filename):
    print "Creating makedepend file", filename
    f = open(filename, 'w')
    try:
        if len(make_targets) > 0:
            f.write("%s:" % makeQuote(make_targets[0]))
            for filename in make_dependencies:
                f.write(' \\\n\t\t%s' % makeQuote(filename))
            f.write('\n\n')
            for filename in make_targets[1:]:
                f.write('%s: %s\n' % (makeQuote(filename), makeQuote(make_targets[0])))
    finally:
        f.close()

def main():
    from optparse import OptionParser
    o = OptionParser(usage="usage: %prog [options] configfile")
    o.add_option('-o', "--stub-output",
                 type='string', dest='stub_output', default=None,
                 help="Quick stub C++ source output file", metavar="FILE")
    o.add_option('--header-output', type='string', default=None,
                 help="Quick stub header output file", metavar="FILE")
    o.add_option('--makedepend-output', type='string', default=None,
                 help="gnumake dependencies output file", metavar="FILE")
    o.add_option('--idlpath', type='string', default='.',
                 help="colon-separated directories to search for idl files",
                 metavar="PATH")
    o.add_option('--cachedir', dest='cachedir', default='',
                 help="Directory in which to cache lex/parse tables.")
    o.add_option("--verbose-errors", action='store_true', default=False,
                 help="When an error happens, display the Python traceback.")
    (options, filenames) = o.parse_args()

    if len(filenames) != 1:
        o.error("Exactly one config filename is needed.")
    filename = filenames[0]

    if options.stub_output is None:
        if filename.endswith('.qsconf') or filename.endswith('.py'):
            options.stub_output = filename.rsplit('.', 1)[0] + '.cpp'
        else:
            options.stub_output = filename + '.cpp'
    if options.header_output is None:
        options.header_output = re.sub(r'(\.c|\.cpp)?$', '.h',
                                       options.stub_output)

    if options.cachedir != '':
        sys.path.append(options.cachedir)
        if not os.path.isdir(options.cachedir):
            os.makedirs(options.cachedir)

    try:
        includePath = options.idlpath.split(':')
        conf, interfaces = readConfigFile(filename,
                                          includePath=includePath,
                                          cachedir=options.cachedir)
        writeStubFile(options.stub_output, options.header_output,
                      conf, interfaces)
        writeHeaderFile(options.header_output, conf.name)
        if options.makedepend_output is not None:
            writeMakeDependOutput(options.makedepend_output)
    except Exception, exc:
        if options.verbose_errors:
            raise
        elif isinstance(exc, (UserError, xpidl.IDLError)):
            warn(str(exc))
        elif isinstance(exc, OSError):
            warn("%s: %s" % (exc.__class__.__name__, exc))
        else:
            raise
        sys.exit(1)

if __name__ == '__main__':
    main()