#!/usr/bin/python # # Authors: # Stef Walter # # 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 . # # # 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 # # # 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(" offsetof(struct %s, %s),", iface.c_name(), meth.c_name()) 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, include_header=None): 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\"") if include_header: out("#include \"%s\"", os.path.basename(include_header)) 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 header_vtable(iface, methods): out("") out("/* vtable for %s */", iface.name) out("struct %s {", iface.c_name()) out(" struct sbus_vtable vtable; /* derive from sbus_vtable */") # All methods for meth in iface.methods: out(" sbus_msg_handler_fn %s;", meth.c_name()) # TODO: Property getters and setters will go here out("};") def header_constants(iface): out("") out("/* constants for %s */", iface.name) out("#define %s \"%s\"", iface.c_name().upper(), iface.name) for meth in iface.methods: out("#define %s \"%s\"", meth.fq_c_name().upper(), meth.name) for sig in iface.signals: out("#define %s \"%s\"", sig.fq_c_name().upper(), sig.name) for prop in iface.properties: out("#define %s \"%s\"", prop.fq_c_name().upper(), prop.name) 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 Constants") out(" *") out(" * Various constants of interface and method names mostly for use by clients") out(" */") for iface in ifaces: header_constants(iface) out("") out("/* ------------------------------------------------------------------------") out(" * DBus Vtable handler structures") out(" *") out(" * These structures are filled in by implementors of the different") out(" * dbus interfaces to handle method calls.") out(" *") out(" * Handler functions of type sbus_msg_handler_fn accept raw messages,") out(" * other handlers will be typed appropriately. If a handler that is") out(" * set to NULL is invoked it will result in a") out(" * org.freedesktop.DBus.Error.NotSupported error for the caller.") out(" */") for iface in ifaces: if iface.methods: header_vtable(iface, iface.methods) 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") parser.add_option("--include", dest="include", default=None, help="name of a header to #include", metavar="HEADER") (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, options.include) 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)