Bug 1444745 - Part 3: Replace the XPT file format with a JSON based one, r=mccr8

This patch adds a python script based on the old typelib.py script which takes
in a parsed XPIDL file, and generates a json-based XPT file to use as a build
intermediate. I did my best to keep the generated format simple.
This commit is contained in:
Nika Layzell 2018-04-04 18:32:40 -04:00
Родитель c29896177a
Коммит 95b7d44e5d
2 изменённых файлов: 255 добавлений и 9 удалений

Просмотреть файл

@ -15,13 +15,12 @@ import sys
from io import BytesIO
from xpidl import jsonxpt
from buildconfig import topsrcdir
from xpidl.header import print_header
from xpidl.rust import print_rust_bindings
from xpidl.rust_macros import print_rust_macros_bindings
from xpidl.typelib import write_typelib
from xpidl.xpidl import IDLParser
from xpt import xpt_link
from mozbuild.makeutil import Makefile
from mozbuild.pythonutil import iter_modules_in_path
@ -32,7 +31,7 @@ def process(input_dir, inc_paths, cache_dir, header_dir, xpcrs_dir,
xpt_dir, deps_dir, module, stems):
p = IDLParser(outputdir=cache_dir)
xpts = {}
xpts = []
mk = Makefile()
rule = mk.create_rule()
@ -51,10 +50,7 @@ def process(input_dir, inc_paths, cache_dir, header_dir, xpcrs_dir,
rs_rt_path = os.path.join(xpcrs_dir, 'rt', '%s.rs' % stem)
rs_bt_path = os.path.join(xpcrs_dir, 'bt', '%s.rs' % stem)
xpt = BytesIO()
write_typelib(idl, xpt, path)
xpt.seek(0)
xpts[stem] = xpt
xpts.append(jsonxpt.build_typelib(idl))
rule.add_dependencies(idl.deps)
@ -67,9 +63,9 @@ def process(input_dir, inc_paths, cache_dir, header_dir, xpcrs_dir,
with FileAvoidWrite(rs_bt_path) as fh:
print_rust_macros_bindings(idl, fh, path)
# TODO use FileAvoidWrite once it supports binary mode.
xpt_path = os.path.join(xpt_dir, '%s.xpt' % module)
xpt_link(xpts.values()).write(xpt_path)
with FileAvoidWrite(xpt_path) as fh:
jsonxpt.write(jsonxpt.link(xpts), fh)
rule.add_targets([xpt_path])
if deps_dir:

Просмотреть файл

@ -0,0 +1,250 @@
#!/usr/bin/env python
# jsonxpt.py - Generate json XPT typelib files from IDL.
#
# 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/.
"""Generate a json XPT typelib for an IDL file"""
import os
import sys
import xpidl
import json
import itertools
# A map of xpidl.py types to xpt enum variants
TypeMap = {
# builtins
'boolean': 'TD_BOOL',
'void': 'TD_VOID',
'int16_t': 'TD_INT16',
'int32_t': 'TD_INT32',
'int64_t': 'TD_INT64',
'uint8_t': 'TD_UINT8',
'uint16_t': 'TD_UINT16',
'uint32_t': 'TD_UINT32',
'uint64_t': 'TD_UINT64',
'octet': 'TD_UINT8',
'short': 'TD_INT16',
'long': 'TD_INT32',
'long long': 'TD_INT64',
'unsigned short': 'TD_UINT16',
'unsigned long': 'TD_UINT32',
'unsigned long long': 'TD_UINT64',
'float': 'TD_FLOAT',
'double': 'TD_DOUBLE',
'char': 'TD_CHAR',
'string': 'TD_PSTRING',
'wchar': 'TD_WCHAR',
'wstring': 'TD_PWSTRING',
# special types
'nsid': 'TD_PNSIID',
'domstring': 'TD_DOMSTRING',
'astring': 'TD_ASTRING',
'utf8string': 'TD_UTF8STRING',
'cstring': 'TD_CSTRING',
'jsval': 'TD_JSVAL',
}
def flags(*flags):
return [flag for flag, cond in flags if cond]
def get_type(type, calltype, iid_is=None, size_is=None):
while isinstance(type, xpidl.Typedef):
type = type.realtype
if isinstance(type, xpidl.Builtin):
ret = { 'tag': TypeMap[type.name] }
if type.name in ['string', 'wstring'] and size_is is not None:
ret['tag'] += '_SIZE_IS'
ret['size_is'] = size_is
return ret
if isinstance(type, xpidl.Array):
# NB: For an Array<T> we pass down the iid_is to get the type of T.
# This allows Arrays of InterfaceIs types to work.
return {
'tag': 'TD_ARRAY',
'size_is': size_is,
'element': get_type(type.type, calltype, iid_is),
}
if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward):
return {
'tag': 'TD_INTERFACE_TYPE',
'name': type.name,
}
if isinstance(type, xpidl.Native):
if type.specialtype:
return {
'tag': TypeMap[type.specialtype]
}
elif iid_is is not None:
return {
'tag': 'TD_INTERFACE_IS_TYPE',
'iid_is': iid_is,
}
else:
return { 'tag': 'TD_VOID' }
raise Exception("Unknown type!")
def mk_param(type, in_=0, out=0, optional=0):
return {
'type': type,
'flags': flags(
('in', in_),
('out', out),
('optional', optional),
),
}
def mk_method(name, params, getter=0, setter=0, notxpcom=0,
hidden=0, optargc=0, context=0, hasretval=0):
return {
'name': name,
# NOTE: We don't include any return value information here, as we'll
# never call the methods if they're marked notxpcom, and all xpcom
# methods return the same type (nsresult).
# XXX: If we ever use these files for other purposes than xptcodegen we
# may want to write that info.
'params': params,
'flags': flags(
('getter', getter),
('setter', setter),
('notxpcom', notxpcom),
('hidden', hidden),
('optargc', optargc),
('jscontext', context),
('hasretval', hasretval),
),
}
def attr_param_idx(p, m, attr):
if hasattr(p, attr) and getattr(p, attr):
for i, param in enumerate(m.params):
if param.name == getattr(p, attr):
return i
return None
def build_interface(iface):
if iface.namemap is None:
raise Exception("Interface was not resolved.")
# State used while building an interface
consts = []
methods = []
def build_const(c):
consts.append({
'name': c.name,
'type': get_type(c.basetype, ''),
'value': c.getValue(), # All of our consts are numbers
})
def build_method(m):
params = []
for p in m.params:
params.append(mk_param(
get_type(
p.realtype, p.paramtype,
iid_is=attr_param_idx(p, m, 'iid_is'),
size_is=attr_param_idx(p, m, 'size_is')),
in_=p.paramtype.count("in"),
out=p.paramtype.count("out"),
optional=p.optional,
))
hasretval = len(m.params) > 0 and m.params[-1].retval
if not m.notxpcom and m.realtype.name != 'void':
hasretval = True
params.append(mk_param(get_type(m.realtype, 'out'), out=1))
methods.append(mk_method(
m.name, params, notxpcom=m.notxpcom, hidden=m.noscript,
optargc=m.optional_argc, context=m.implicit_jscontext,
hasretval=hasretval))
def build_attr(a):
# Write the getter
param = mk_param(get_type(a.realtype, 'out'), out=1)
methods.append(mk_method(a.name, [param], getter=1, hidden=a.noscript,
context=a.implicit_jscontext, hasretval=1))
# And maybe the setter
if not a.readonly:
param = mk_param(get_type(a.realtype, 'in'), in_=1)
methods.append(mk_method(a.name, [param], setter=1, hidden=a.noscript,
context=a.implicit_jscontext))
implicit_builtinclass = False
for member in iface.members:
if isinstance(member, xpidl.ConstMember):
build_const(member)
elif isinstance(member, xpidl.Attribute):
build_attr(member)
elif isinstance(member, xpidl.Method):
build_method(member)
# XXX(hacky): If we have a notxpcom method (other than
# nsISupports::{AddRef,Release}), we need to implicitly mark
# ourselves as builtinclass, as we cannot be implemented in JS.
if member.notxpcom and iface.name != "nsISupports":
implicit_builtinclass = True
elif isinstance(member, xpidl.CDATA):
pass
else:
raise Exception("Unexpected interface member: %s" % member)
assert iface.attributes.shim is not None or iface.attributes.shimfile is None
return {
'name': iface.name,
'uuid': iface.attributes.uuid,
'methods': methods,
'consts': consts,
'parent': iface.base,
'shim': iface.attributes.shim,
'shimfile': iface.attributes.shimfile,
'flags': flags(
('scriptable', iface.attributes.scriptable),
('function', iface.attributes.function),
('builtinclass', iface.attributes.builtinclass or implicit_builtinclass),
('main_process_only', iface.attributes.main_process_scriptable_only),
)
}
# These functions are the public interface of this module. They are very simple
# functions, but are exported so that if we need to do something more
# complex in them in the future we can.
# Given a parsed IDL file, generate and return the typelib for that file.
def build_typelib(idl):
def exported(p):
if p.kind != 'interface':
return False
# Only export scriptable or shim interfaces
return p.attributes.scriptable or p.attributes.shim
return [build_interface(p) for p in idl.productions if exported(p)]
# Link a list of typelibs together into a single typelib
def link(typelibs):
linked = list(itertools.chain.from_iterable(typelibs))
assert len(set(iface['name'] for iface in linked)) == len(linked), \
"Multiple typelibs containing the same interface were linked together"
return linked
# Write the typelib into the fd file
def write(typelib, fd):
json.dump(typelib, fd, indent=2)