diff options
Diffstat (limited to 'src/sbus/sbus_codegen')
-rwxr-xr-x | src/sbus/sbus_codegen | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/src/sbus/sbus_codegen b/src/sbus/sbus_codegen new file mode 100755 index 000000000..a1e2d02a0 --- /dev/null +++ b/src/sbus/sbus_codegen @@ -0,0 +1,506 @@ +#!/usr/bin/python + +# +# Authors: +# Stef Walter <stefw@redhat.com> +# +# Copyright (C) 2014 Red Hat +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# +# Some parser code from GLib +# +# Copyright (C) 2008-2011 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General +# Public License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. +# +# Portions by: David Zeuthen <davidz@redhat.com> +# + +# +# DBus interfaces are defined here: +# +# http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format +# +# The introspection data format has become the standard way to represent a +# DBus interface. For many examples see /usr/share/dbus-1/interfaces/ on a +# typical linux machine. +# +# A word about annotations. These are extra flags or values that can be +# assigned to anything. So far, the codegen supports this annotation: +# +# org.freedesktop.DBus.GLib.CSymbol +# - An annotation specified in the specification that tells us what C symbol +# to generate for a given interface or method. By default the codegen will +# build up a symbol name from the DBus name. +# + +import optparse +import os +import re +import StringIO +import sys +import xml.parsers.expat + +# ----------------------------------------------------------------------------- +# Objects + +class DBusXmlException(Exception): + line = 0 + file = None + + # Lets us print problems like a compiler would + def __str__(self): + message = Exception.__str__(self) + if self.file and self.line: + return "%s:%d: %s" % (self.file, self.line, message) + elif self.file: + return "%s: %s" % (self.file, message) + else: + return message + +class Base: + def __init__(self, name): + if not name: + raise DBusXmlException('No name on element') + self.name = name + self.annotations = { } + def c_name(self): + return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol", self.name) + +class Arg(Base): + def __init__(self, method, name, signature): + Base.__init__(self, name) + self.method = method + self.signature = signature + +class Method(Base): + def __init__(self, iface, name): + Base.__init__(self, name) + self.iface = iface + self.in_args = [] + self.out_args = [] + def fq_c_name(self): + return "%s_%s" % (self.iface.c_name(), self.c_name()) + +class Signal(Base): + def __init__(self, iface, name): + Base.__init__(self, name) + self.iface = iface + self.args = [] + def fq_c_name(self): + return "%s_%s" % (self.iface.c_name(), self.c_name()) + +class Property(Base): + def __init__(self, iface, name, signature, access): + Base.__init__(self, name) + self.iface = iface + self.signature = signature + self.readable = False + self.writable = False + if access == 'readwrite': + self.readable = True + self.writable = True + elif access == 'read': + self.readable = True + elif access == 'write': + self.writable = True + else: + raise DBusXmlException('Invalid access type %s'%self.access) + def fq_c_name(self): + return "%s_%s" % (self.iface.c_name(), self.c_name()) + +class Interface(Base): + def __init__(self, name): + Base.__init__(self, name) + self.methods = [] + self.signals = [] + self.properties = [] + def c_name(self): + return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol", + self.name.replace(".", "_")) + +# ----------------------------------------------------------------------------- +# Code Generation + +def out(format, *args): + str = format % args + sys.stdout.write(str) + sys.stdout.write("\n") + +def source_args(parent, args, suffix): + out("") + out("/* arguments for %s.%s */", parent.iface.name, parent.name) + out("const struct sbus_arg_meta %s%s[] = {", parent.fq_c_name(), suffix) + for arg in args: + out(" { \"%s\", \"%s\" },", arg.name, arg.signature) + out(" { NULL, }") + out("};") + +def source_methods(iface, methods): + for meth in methods: + if meth.in_args: + source_args(meth, meth.in_args, "__in") + if meth.out_args: + source_args(meth, meth.out_args, "__out") + + out("") + out("/* methods for %s */", iface.name) + out("const struct sbus_method_meta %s__methods[] = {", iface.c_name()) + for meth in methods: + out(" {") + out(" \"%s\", /* name */", meth.name) + if meth.in_args: + out(" %s__in,", meth.fq_c_name()) + else: + out(" NULL, /* no in_args */") + if meth.out_args: + out(" %s__out,", meth.fq_c_name()) + else: + out(" NULL, /* no out_args */") + out(" },") + out(" { NULL, }") + out("};") + +def source_signals(iface, signals): + for sig in iface.signals: + if sig.args: + source_args(sig, sig.args, "__args") + + out("") + out("/* signals for %s */", iface.name) + out("const struct sbus_signal_meta %s__signals[] = {", iface.c_name()) + for sig in signals: + out(" {") + out(" \"%s\", /* name */", sig.name) + if sig.args: + out(" %s__args", sig.fq_c_name()) + else: + out(" NULL, /* no args */") + out(" },") + out(" { NULL, }") + out("};") + +def source_properties(iface, properties): + out("") + out("/* property info for %s */", iface.name) + out("const struct sbus_property_meta %s__properties[] = {", iface.c_name()) + for prop in properties: + out(" {") + out(" \"%s\", /* name */", prop.name) + out(" \"%s\", /* signature */", prop.signature) + if prop.readable and prop.writable: + out(" SBUS_PROPERTY_READABLE | SBUS_PROPERTY_WRITABLE,") + elif prop.readable: + out(" SBUS_PROPERTY_READABLE,") + elif prop.writable: + out(" SBUS_PROPERTY_WRITABLE,") + else: + assert False, "should not be reached" + out(" },") + out(" { NULL, }") + out("};") + +def header_interface(iface): + out("") + out("/* interface info for %s */", iface.name) + out("extern const struct sbus_interface_meta %s_meta;", iface.c_name()) + +def source_interface(iface): + out("") + out("/* interface info for %s */", iface.name) + out("const struct sbus_interface_meta %s_meta = {", iface.c_name()) + out(" \"%s\", /* name */", iface.name) + if iface.methods: + out(" %s__methods,", iface.c_name()) + else: + out(" NULL, /* no methods */") + if iface.signals: + out(" %s__signals,", iface.c_name()) + else: + out(" NULL, /* no signals */") + if iface.properties: + out(" %s__properties", iface.c_name()) + else: + out(" NULL, /* no propetries */") + out("};") + +def generate_source(ifaces, filename): + basename = os.path.basename(filename) + + out("/* The following definitions are auto-generated from %s */", basename) + out("") + + out("#include \"util/util.h\"") + out("#include \"sbus/sssd_dbus.h\"") + out("#include \"sbus/sssd_dbus_meta.h\"") + + for iface in ifaces: + + # The methods + if iface.methods: + source_methods(iface, iface.methods) + + # The signals array + if iface.signals: + source_signals(iface, iface.signals) + + # The properties array + if iface.properties: + source_properties(iface, iface.properties) + + # The sbus_interface structure + source_interface(iface) + +def generate_header(ifaces, filename): + basename = os.path.basename(filename) + guard = "__%s__" % re.sub(r'([^_A-Z0-9])', "_", basename.upper()) + + out("/* The following declarations are auto-generated from %s */", basename) + out("") + out("#ifndef %s", guard) + out("#define %s", guard) + out("") + out("#include \"sbus/sssd_dbus.h\"") + + out("") + out("/* ------------------------------------------------------------------------") + out(" * DBus Interface Metadata") + out(" *") + out(" * These structure definitions are filled in with the information about") + out(" * the interfaces, methods, properties and so on.") + out(" *") + out(" * The actual definitions are found in the accompanying C file next") + out(" * to this header.") + out(" */") + + for iface in ifaces: + header_interface(iface) + + out("") + out("#endif /* %s */", guard) + +# ----------------------------------------------------------------------------- +# XML Interface Parsing + +STATE_TOP = 'top' +STATE_NODE = 'node' +STATE_INTERFACE = 'interface' +STATE_METHOD = 'method' +STATE_SIGNAL = 'signal' +STATE_PROPERTY = 'property' +STATE_ARG = 'arg' +STATE_ANNOTATION = 'annotation' +STATE_IGNORED = 'ignored' + +def expect_attr(attrs, name): + if name not in attrs: + raise DBusXmlException("Missing attribute '%s'" % name) + if attrs[name] == "": + raise DBusXmlException("Empty attribute '%s'" % name) + return attrs[name] + +class DBusXMLParser: + def __init__(self, filename): + parser = xml.parsers.expat.ParserCreate() + parser.CommentHandler = self.handle_comment + parser.CharacterDataHandler = self.handle_char_data + parser.StartElementHandler = self.handle_start_element + parser.EndElementHandler = self.handle_end_element + + self.parsed_interfaces = [] + self.cur_object = None + + self.state = STATE_TOP + self.state_stack = [] + self.cur_object = None + self.cur_object_stack = [] + self.arg_count = 0 + + try: + with open(filename, "r") as f: + parser.ParseFile(f) + except DBusXmlException, ex: + ex.line = parser.CurrentLineNumber + ex.file = filename + raise + except xml.parsers.expat.ExpatError, ex: + exc = DBusXmlException(str(ex)) + exc.line = ex.lineno + exc.file = filename + raise exc + + def handle_comment(self, data): + pass + + def handle_char_data(self, data): + pass + + def handle_start_element(self, name, attrs): + old_state = self.state + old_cur_object = self.cur_object + if self.state == STATE_IGNORED: + self.state = STATE_IGNORED + elif self.cur_object and name == STATE_ANNOTATION: + val = attrs.get('value', '') + self.cur_object.annotations[expect_attr(attrs, 'name')] = val + self.state = STATE_IGNORED + elif self.state == STATE_TOP: + if name == STATE_NODE: + self.state = STATE_NODE + else: + self.state = STATE_IGNORED + elif self.state == STATE_NODE: + if name == STATE_INTERFACE: + self.state = STATE_INTERFACE + iface = Interface(expect_attr(attrs, 'name')) + self.cur_object = iface + self.parsed_interfaces.append(iface) + else: + self.state = STATE_IGNORED + + elif self.state == STATE_INTERFACE: + if name == STATE_METHOD: + self.state = STATE_METHOD + method = Method(self.cur_object, expect_attr(attrs, 'name')) + self.cur_object.methods.append(method) + self.cur_object = method + self.arg_count = 0 + elif name == STATE_SIGNAL: + self.state = STATE_SIGNAL + signal = Signal(self.cur_object, expect_attr(attrs, 'name')) + self.cur_object.signals.append(signal) + self.cur_object = signal + self.arg_count = 0 + elif name == STATE_PROPERTY: + self.state = STATE_PROPERTY + prop = Property(self.cur_object, + expect_attr(attrs, 'name'), + expect_attr(attrs, 'type'), + expect_attr(attrs, 'access')) + self.cur_object.properties.append(prop) + self.cur_object = prop + else: + self.state = STATE_IGNORED + + elif self.state == STATE_METHOD: + if name == STATE_ARG: + self.state = STATE_ARG + arg = Arg(self.cur_object, + expect_attr(attrs, 'name'), + expect_attr(attrs, 'type')) + direction = attrs.get('direction', 'in') + if direction == 'in': + self.cur_object.in_args.append(arg) + elif direction == 'out': + self.cur_object.out_args.append(arg) + else: + raise DBusXmlException('Invalid direction "%s"' % direction) + self.cur_object = arg + else: + self.state = STATE_IGNORED + + elif self.state == STATE_SIGNAL: + if name == STATE_ARG: + self.state = STATE_ARG + arg = Arg(self.cur_object, + expect_attr(attrs, 'name'), + expect_attr(attrs, 'type')) + self.cur_object.args.append(arg) + self.cur_object = arg + else: + self.state = STATE_IGNORED + + elif self.state == STATE_PROPERTY: + self.state = STATE_IGNORED + + elif self.state == STATE_ARG: + self.state = STATE_IGNORED + + else: + assert False, 'Unhandled state "%s" while entering element with name "%s"' % (self.state, name) + + self.state_stack.append(old_state) + self.cur_object_stack.append(old_cur_object) + + def handle_end_element(self, name): + self.state = self.state_stack.pop() + self.cur_object = self.cur_object_stack.pop() + +def parse_options(): + parser = optparse.OptionParser("usage: %prog [options] introspect.xml ...") + parser.set_description("sbus_codegen generates sbus interface structures \ + from standard XML Introspect data.") + parser.add_option("--mode", + dest="mode", default="header", + help="'header' or 'source' (default: header)", + metavar="MODE") + parser.add_option("--output", + dest="output", default=None, + help="Set output file name (default: stdout)", + metavar="FILE") + (options, args) = parser.parse_args() + + if not args: + print >> sys.stderr, "sbus_codegen: no input file specified" + sys.exit(2) + + if options.mode not in ["header", "source"]: + print >> sys.stderr, "sbus_codegen: specify --mode=header or --mode=source" + + return options, args + +def main(): + options, args = parse_options() + + if options.output: + sys.stdout = buf = StringIO.StringIO() + + for filename in args: + parser = DBusXMLParser(filename) + + if options.mode == "header": + generate_header(parser.parsed_interfaces, filename) + elif options.mode == "source": + generate_source(parser.parsed_interfaces, filename) + else: + assert False, "should not be reached" + + # Write output at end to be nice to 'make' + if options.output: + output = open(options.output, "w") + output.write(buf.getvalue()) + output.close() + +if __name__ == "__main__": + try: + main() + except DBusXmlException, ex: + print >> sys.stderr, str(ex) + sys.exit(1) |