Add a QT tool. (Christoph Wiedemann)
[scons.git] / src / engine / SCons / Builder.py
index 65d4d41368d6cfa4a6df1e047fae3f0b36602df6..2bcd9935bfd47aaa62392d97c82ae4aedb85d721 100644 (file)
@@ -1,11 +1,25 @@
 """SCons.Builder
 
-XXX
+Builder object subsystem.
+
+A Builder object is a callable that encapsulates information about how
+to execute actions to create a Node (file) from other Nodes (files), and
+how to create those dependencies for tracking.
+
+The main entry point here is the Builder() factory method.  This
+provides a procedural interface that creates the right underlying
+Builder object based on the keyword arguments supplied and the types of
+the arguments.
+
+The goal is for this external interface to be simple enough that the
+vast majority of users can create new Builders as necessary to support
+building new types of files in their configurations, without having to
+dive any deeper into this subsystem.
 
 """
 
 #
-# Copyright (c) 2001 Steven Knight
+# __COPYRIGHT__
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -31,244 +45,519 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 
-import os
+import os.path
+from SCons.Errors import InternalError, UserError
+
+import SCons.Action
+import SCons.Executor
+import SCons.Node
 import SCons.Node.FS
-from SCons.Util import PathList, scons_str2nodes, scons_varrepl
-import string
-import types
+import SCons.Util
+import SCons.Warnings
+
+class _Null:
+    pass
+
+_null = _Null
+
+class DictCmdGenerator:
+    """This is a callable class that can be used as a
+    command generator function.  It holds on to a dictionary
+    mapping file suffixes to Actions.  It uses that dictionary
+    to return the proper action based on the file suffix of
+    the source file."""
+    
+    def __init__(self, action_dict):
+        self.action_dict = action_dict
+
+    def src_suffixes(self):
+        return self.action_dict.keys()
 
+    def add_action(self, suffix, action):
+        """Add a suffix-action pair to the mapping.
+        """
+        self.action_dict[suffix] = action
+
+    def __call__(self, target, source, env, for_signature):
+        ext = None
+        for src in map(str, source):
+            my_ext = SCons.Util.splitext(src)[1]
+            if ext and my_ext != ext:
+                raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
+            ext = my_ext
+
+        if not ext:
+            raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
+        try:
+            return self.action_dict[ext]
+        except KeyError:
+            # Before raising the user error, try to perform Environment
+            # substitution on the keys of action_dict.
+            s_dict = {}
+            for (k,v) in self.action_dict.items():
+                s_k = env.subst(k)
+                if s_dict.has_key(s_k):
+                    # XXX Note that we do only raise errors, when variables
+                    # point to the same suffix. If one suffix is a
+                    # literal and a variable suffix contains this literal
+                    # we don't raise an error (cause the literal 'wins')
+                    raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (s_dict[s_k][0], k, s_k))
+                s_dict[s_k] = (k,v)
+            try:
+                return s_dict[ext][1]
+            except KeyError:
+                raise UserError("While building `%s': Don't know how to build a file with suffix %s." % (repr(map(str, target)), repr(ext)))
+
+    def __cmp__(self, other):
+        return cmp(self.action_dict, other.action_dict)
+
+def Builder(**kw):
+    """A factory for builder objects."""
+    composite = None
+    if kw.has_key('generator'):
+        if kw.has_key('action'):
+            raise UserError, "You must not specify both an action and a generator."
+        kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
+        del kw['generator']
+    elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
+        composite = DictCmdGenerator(kw['action'])
+        kw['action'] = SCons.Action.CommandGenerator(composite)
+        kw['src_suffix'] = composite.src_suffixes()
+
+    if kw.has_key('emitter') and \
+       SCons.Util.is_String(kw['emitter']):
+        # This allows users to pass in an Environment
+        # variable reference (like "$FOO") as an emitter.
+        # We will look in that Environment variable for
+        # a callable to use as the actual emitter.
+        var = SCons.Util.get_environment_var(kw['emitter'])
+        if not var:
+            raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
+        kw['emitter'] = EmitterProxy(var)
+
+    if kw.has_key('src_builder'):
+        ret = apply(MultiStepBuilder, (), kw)
+    else:
+        ret = apply(BuilderBase, (), kw)
 
+    if composite:
+        ret = CompositeBuilder(ret, composite)
 
-class Builder:
+    return ret
+
+def _init_nodes(builder, env, overrides, tlist, slist):
+    """Initialize lists of target and source nodes with all of
+    the proper Builder information.
+    """
+
+    # First, figure out if there are any errors in the way the targets
+    # were specified.
+    for t in tlist:
+        if t.side_effect:
+            raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
+        if t.has_builder():
+            if t.env != env:
+                raise UserError, "Two different environments were specified for the same target: %s"%str(t)
+            elif t.overrides != overrides:
+                raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
+            elif builder.scanner and builder.scanner != t.target_scanner:
+                raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
+
+            if builder.multi:
+                if t.builder != builder:
+                    if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
+                        raise UserError, "Two different target sets have a target in common: %s"%str(t)
+                    else:
+                        raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.get_name(env), builder.get_name(env), str(t))
+            elif t.sources != slist:
+                raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
+
+    # The targets are fine, so find or make the appropriate Executor to
+    # build this particular list of targets from this particular list of
+    # sources.
+    executor = None
+    if builder.multi:
+        try:
+            executor = tlist[0].get_executor(create = 0)
+        except AttributeError:
+            pass
+        else:
+            executor.add_sources(slist)
+    if executor is None:
+        executor = SCons.Executor.Executor(builder,
+                                           tlist[0].generate_build_env(env),
+                                           overrides,
+                                           tlist,
+                                           slist)
+
+    # Now set up the relevant information in the target Nodes themselves.
+    for t in tlist:
+        t.overrides = overrides
+        t.cwd = SCons.Node.FS.default_fs.getcwd()
+        t.builder_set(builder)
+        t.env_set(env)
+        t.add_source(slist)
+        t.set_executor(executor)
+        if builder.scanner:
+            t.target_scanner = builder.scanner
+
+    # Last, add scanners from the Environment to the source Nodes.
+    for s in slist:
+        src_key = s.scanner_key()        # the file suffix
+        scanner = env.get_scanner(src_key)
+        if scanner:
+            s.source_scanner = scanner
+
+
+def _adjust_suffix(suff):
+    if suff and not suff[0] in [ '.', '$' ]:
+        return '.' + suff
+    return suff
+
+class EmitterProxy:
+    """This is a callable class that can act as a
+    Builder emitter.  It holds on to a string that
+    is a key into an Environment dictionary, and will
+    look there at actual build time to see if it holds
+    a callable.  If so, we will call that as the actual
+    emitter."""
+    def __init__(self, var):
+        self.var = SCons.Util.to_String(var)
+
+    def __call__(self, target, source, env):
+        emitter = self.var
+
+        # Recursively substitute the variable.
+        # We can't use env.subst() because it deals only
+        # in strings.  Maybe we should change that?
+        while SCons.Util.is_String(emitter) and \
+              env.has_key(emitter):
+            emitter = env[emitter]
+        if not callable(emitter):
+            return (target, source)
+
+        return emitter(target, source, env)
+
+    def __cmp__(self, other):
+        return cmp(self.var, other.var)
+
+class BuilderBase:
     """Base class for Builders, objects that create output
     nodes (files) from input nodes (files).
     """
 
-    def __init__(self, name = None,
-                       action = None,
-                       input_suffix = None,
-                       output_suffix = None,
-                       node_factory = SCons.Node.FS.default_fs.File):
-       self.name = name
-       self.action = Action(action)
-       self.insuffix = input_suffix
-       self.outsuffix = output_suffix
-       self.node_factory = node_factory
-       if not self.insuffix is None and self.insuffix[0] != '.':
-           self.insuffix = '.' + self.insuffix
-       if not self.outsuffix is None and self.outsuffix[0] != '.':
-           self.outsuffix = '.' + self.outsuffix
+    def __init__(self,  action = None,
+                        prefix = '',
+                        suffix = '',
+                        src_suffix = '',
+                        node_factory = SCons.Node.FS.default_fs.File,
+                        target_factory = None,
+                        source_factory = None,
+                        scanner = None,
+                        emitter = None,
+                        multi = 0,
+                        env = None,
+                        overrides = {}):
+        self.action = SCons.Action.Action(action)
+        self.multi = multi
+        self.prefix = prefix
+        self.suffix = suffix
+        self.env = env
+        self.overrides = overrides
+
+        self.set_src_suffix(src_suffix)
+
+        self.target_factory = target_factory or node_factory
+        self.source_factory = source_factory or node_factory
+        self.scanner = scanner
+
+        self.emitter = emitter
+
+    def __nonzero__(self):
+        raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
+
+    def get_name(self, env):
+        """Attempts to get the name of the Builder.
+
+        Look at the BUILDERS variable of env, expecting it to be a
+        dictionary containing this Builder, and return the key of the
+        dictionary."""
+
+        try:
+            index = env['BUILDERS'].values().index(self)
+            return env['BUILDERS'].keys()[index]
+        except (AttributeError, KeyError, ValueError):
+            return str(self.__class__)
 
-    def __cmp__(self, other):
-       return cmp(self.__dict__, other.__dict__)
-
-    def __call__(self, env, target = None, source = None):
-       tlist = scons_str2nodes(target, self.node_factory)
-       slist = scons_str2nodes(source, self.node_factory)
-       for t in tlist:
-           t.builder_set(self)
-           t.env_set(env)
-           t.derived = 1
-           t.add_source(slist)
-
-       if len(tlist) == 1:
-           tlist = tlist[0]
-       return tlist
-
-    def execute(self, **kw):
-       """Execute a builder's action to create an output object.
-       """
-       return apply(self.action.execute, (), kw)
-
-class BuilderProxy:
-    """This base class serves as a proxy to a builder object,
-    exposing the same interface, but just forwarding calls to
-    the underlying object."""
-    def __init__(self, builder):
-        self.subject = builder
-
-    def __call__(self, env, target = None, source = None):
-        return self.subject.__call__(env, target, source)
-
-    def execute(self, **kw):
-        return apply(self.subject.execute, (), kw)
-    
     def __cmp__(self, other):
         return cmp(self.__dict__, other.__dict__)
 
-    def __getattr__(self, name):
-        assert 'subject' in self.__dict__.keys(), \
-               "You must call __init__() on the BuilderProxy base."
-        return getattr(self.subject, name)
-    
-class TargetNamingBuilder(BuilderProxy):
-    """This is a simple Builder Proxy that decorates the names
-    of a Builder's targets prior to passing them to the underlying
-    Builder.  You can use this to simplify the syntax of a target
-    file.  For instance, you might want to call a Library builder
-    with a target of "foo" and expect to get "libfoo.a" back."""
-
-    def __init__(self, builder, prefix='', suffix=''):
+    def _create_nodes(self, env, overrides, target = None, source = None):
+        """Create and return lists of target and source nodes.
         """
-        builder - The underlying Builder object
+        def adjustixes(files, pre, suf):
+            if not files:
+                return []
+            ret = []
+            if not SCons.Util.is_List(files):
+                files = [files]
+
+            for f in files:
+                if SCons.Util.is_String(f):
+                    if pre:
+                        path, fn = os.path.split(os.path.normpath(f))
+                        if fn[:len(pre)] != pre:
+                            f = os.path.join(path, pre + fn)
+                    # Only append a suffix if the file does not have one.
+                    if suf and not SCons.Util.splitext(f)[1]:
+                        if f[-len(suf):] != suf:
+                            f = f + suf
+                ret.append(f)
+            return ret
+
+        env = env.Override(overrides)
+
+        pre = self.get_prefix(env)
+        suf = self.get_suffix(env)
+        src_suf = self.get_src_suffix(env)
+
+        source = adjustixes(source, None, src_suf)
+        if target is None:
+            s = source[0]
+            if isinstance(s, SCons.Node.Node):
+                s = str(s)
+            dir, s = os.path.split(s)
+            target = pre + os.path.splitext(s)[0] + suf
+            if dir:
+                target = [ os.path.join(dir, target) ]
+        else:
+            target = adjustixes(target, pre, suf)
+
+        slist = SCons.Node.arg2nodes(source, self.source_factory)
+        tlist = SCons.Node.arg2nodes(target, self.target_factory)
+
+        if self.emitter:
+            # The emitter is going to do str(node), but because we're
+            # being called *from* a builder invocation, the new targets
+            # don't yet have a builder set on them and will look like
+            # source files.  Fool the emitter's str() calls by setting
+            # up a temporary builder on the new targets.
+            new_targets = []
+            for t in tlist:
+                if not t.is_derived():
+                    t.builder = self
+                    new_targets.append(t)
         
-        prefix - text to prepend to target names (may contain
-        environment variables)
+            target, source = self.emitter(target=tlist, source=slist, env=env)
+
+            # Now delete the temporary builders that we attached to any
+            # new targets, so that _init_nodes() doesn't do weird stuff
+            # to them because it thinks they already have builders.
+            for t in new_targets:
+                if t.builder is self:
+                    # Only delete the temporary builder if the emitter
+                    # didn't change it on us.
+                    t.builder = None
+
+            # Have to call arg2nodes yet again, since it is legal for
+            # emitters to spit out strings as well as Node instances.
+            slist = SCons.Node.arg2nodes(source, self.source_factory)
+            tlist = SCons.Node.arg2nodes(target, self.target_factory)
+
+        return tlist, slist
+
+    def __call__(self, env, target = None, source = _null, **overrides):
+        if source is _null:
+            source = target
+            target = None
+        tlist, slist = self._create_nodes(env, overrides, target, source)
+
+        if len(tlist) == 1:
+            _init_nodes(self, env, overrides, tlist, slist)
+            tlist = tlist[0]
+        else:
+            _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
+
+        return tlist
+
+    def src_suffixes(self, env):
+        return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
+                   self.src_suffix)
+
+    def set_src_suffix(self, src_suffix):
+        if not src_suffix:
+            src_suffix = []
+        elif not SCons.Util.is_List(src_suffix):
+            src_suffix = [ src_suffix ]
+        self.src_suffix = src_suffix
+
+    def get_src_suffix(self, env):
+        """Get the first src_suffix in the list of src_suffixes."""
+        ret = self.src_suffixes(env)
+        if not ret:
+            return ''
+        return ret[0]
+
+    def get_suffix(self, env):
+        return env.subst(_adjust_suffix(self.suffix))
+
+    def get_prefix(self, env):
+        return env.subst(self.prefix)
+
+    def targets(self, node):
+        """Return the list of targets for this builder instance.
+
+        For most normal builders, this is just the supplied node.
+        """
+        return [ node ]
+
+class ListBuilder(SCons.Util.Proxy):
+    """A Proxy to support building an array of targets (for example,
+    foo.o and foo.h from foo.y) from a single Action execution.
+    """
+
+    def __init__(self, builder, env, tlist):
+        SCons.Util.Proxy.__init__(self, builder)
+        self.builder = builder
+        self.scanner = builder.scanner
+        self.env = env
+        self.tlist = tlist
+        self.multi = builder.multi
 
-        suffix - text to append to target names (may contain
-        environment variables)
+    def targets(self, node):
+        """Return the list of targets for this builder instance.
         """
-        BuilderProxy.__init__(self, builder)
-        self.prefix=prefix
-        self.suffix=suffix
-
-    def __call__(self, env, target = None, source = None):
-        tlist = scons_str2nodes(target, self.subject.node_factory)
-        tlist_decorated = []
-        for tnode in tlist:
-            path, fn = os.path.split(tnode.path)
-            tlist_decorated.append(self.subject.
-                                   node_factory(os.path.join(path,
-                                                             env.subst(self.prefix) +
-                                                             fn +
-                                                             env.subst(self.suffix))))
-        return self.subject.__call__(env, target=tlist_decorated, source=source)
-
-class MultiStepBuilder(Builder):
+        return self.tlist
+
+    def __cmp__(self, other):
+        return cmp(self.__dict__, other.__dict__)
+
+    def get_name(self, env):
+        """Attempts to get the name of the Builder."""
+
+        return "ListBuilder(%s)" % self.builder.get_name(env)
+
+class MultiStepBuilder(BuilderBase):
     """This is a builder subclass that can build targets in
-    multiple steps according to the suffixes of the source files.
-    Given one or more "subordinate" builders in its constructor,
-    this class will apply those builders to any files matching
-    the builder's input_suffix, using a file of the same name
-    as the source, but with input_suffix changed to output_suffix.
-    The targets of these builders then become sources for this
-    builder.
+    multiple steps.  The src_builder parameter to the constructor
+    accepts a builder that is called to build sources supplied to
+    this builder.  The targets of that first build then become
+    the sources of this builder.
+
+    If this builder has a src_suffix supplied, then the src_builder
+    builder is NOT invoked if the suffix of a source file matches
+    src_suffix.
     """
-    def __init__(self,  name = None,
-                       action = None,
-                       input_suffix = None,
-                       output_suffix = None,
+    def __init__(self,  src_builder,
+                        action = None,
+                        prefix = '',
+                        suffix = '',
+                        src_suffix = '',
                         node_factory = SCons.Node.FS.default_fs.File,
-                        builders = []):
-        Builder.__init__(self, name, action, input_suffix, output_suffix,
-                         node_factory)
-        self.builder_dict = {}
-        for bld in builders:
-            if bld.insuffix and bld.outsuffix:
-                self.builder_dict[bld.insuffix] = bld
-
-    def __call__(self, env, target = None, source = None):
-        slist = scons_str2nodes(source, self.node_factory)
+                        target_factory = None,
+                        source_factory = None,
+                        scanner=None,
+                        emitter=None):
+        BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
+                             node_factory, target_factory, source_factory,
+                             scanner, emitter)
+        if not SCons.Util.is_List(src_builder):
+            src_builder = [ src_builder ]
+        self.src_builder = src_builder
+        self.sdict = {}
+        self.cached_src_suffixes = {} # source suffixes keyed on id(env)
+
+    def __call__(self, env, target = None, source = _null, **kw):
+        if source is _null:
+            source = target
+            target = None
+
+        slist = SCons.Node.arg2nodes(source, self.source_factory)
         final_sources = []
+
+        try:
+            sdict = self.sdict[id(env)]
+        except KeyError:
+            sdict = {}
+            self.sdict[id(env)] = sdict
+            for bld in self.src_builder:
+                if SCons.Util.is_String(bld):
+                    try:
+                        bld = env['BUILDERS'][bld]
+                    except KeyError:
+                        continue
+                for suf in bld.src_suffixes(env):
+                    sdict[suf] = bld
+
+        src_suffixes = self.src_suffixes(env)
+
         for snode in slist:
-            path, ext = os.path.splitext(snode.path)
-            if self.builder_dict.has_key(ext):
-                bld = self.builder_dict[ext]
-                tgt = bld(env,
-                          target=[ path+bld.outsuffix, ],
-                          source=snode)
-                if not type(tgt) is types.ListType:
+            path, ext = SCons.Util.splitext(snode.get_abspath())
+            if sdict.has_key(ext):
+                src_bld = sdict[ext]
+                tgt = apply(src_bld, (env, path, snode), kw)
+                # Only supply the builder with sources it is capable
+                # of building.
+                if SCons.Util.is_List(tgt):
+                    tgt = filter(lambda x, suf=src_suffixes:
+                                 SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
+                                 tgt)
+                if not SCons.Util.is_List(tgt):
                     final_sources.append(tgt)
                 else:
                     final_sources.extend(tgt)
             else:
                 final_sources.append(snode)
-        return Builder.__call__(self, env, target=target,
-                                source=final_sources)
-
-print_actions = 1;
-execute_actions = 1;
-
-
-
-def Action(act):
-    """A factory for action objects."""
-    if type(act) == types.StringType:
-       l = string.split(act, "\n")
-       if len(l) > 1:
-           act = l
-    if callable(act):
-       return FunctionAction(act)
-    elif type(act) == types.StringType:
-       return CommandAction(act)
-    elif type(act) == types.ListType:
-       return ListAction(act)
-    else:
-       return None
 
-class ActionBase:
-    """Base class for actions that create output objects.
-    
-    We currently expect Actions will only be accessible through
-    Builder objects, so they don't yet merit their own module."""
-    def __cmp__(self, other):
-       return cmp(self.__dict__, other.__dict__)
+        return apply(BuilderBase.__call__,
+                     (self, env, target, final_sources), kw)
 
-    def show(self, string):
-       print string
+    def get_src_builders(self, env):
+        """Return all the src_builders for this Builder.
 
-class CommandAction(ActionBase):
-    """Class for command-execution actions."""
-    def __init__(self, string):
-       self.command = string
-
-    def execute(self, **kw):
-        try:
-            t = kw['target']
-            if type(t) is types.StringType:
-                t = [t]
-            tgt = PathList(map(os.path.normpath, t))
-        except:
-            tgt = PathList()
+        This is essentially a recursive descent of the src_builder "tree."
+        """
+        ret = []
+        for bld in self.src_builder:
+            if SCons.Util.is_String(bld):
+                # All Environments should have a BUILDERS
+                # variable, so no need to check for it.
+                try:
+                    bld = env['BUILDERS'][bld]
+                except KeyError:
+                    continue
+            ret.append(bld)
+        return ret
+
+    def src_suffixes(self, env):
+        """Return a list of the src_suffix attributes for all
+        src_builders of this Builder.
+        """
         try:
-            s = kw['source']
-            if type(s) is types.StringType:
-                s = [s]
-            src = PathList(map(os.path.normpath, s))
-        except:
-            src = PathList()
-        cmd = scons_varrepl(self.command, tgt, src)
-       if print_actions:
-           self.show(cmd)
-       ret = 0
-       if execute_actions:
-           pid = os.fork()
-           if not pid:
-               # Child process.
-               args = string.split(cmd)
-               try:
-                   ENV = kw['ENV']
-               except:
-                   import SCons.Defaults
-                   ENV = SCons.Defaults.ENV
-               os.execvpe(args[0], args, ENV)
-           else:
-               # Parent process.
-               pid, stat = os.waitpid(pid, 0)
-               ret = stat >> 8
-       return ret
-
-
-
-class FunctionAction(ActionBase):
-    """Class for Python function actions."""
-    def __init__(self, function):
-       self.function = function
-
-    def execute(self, **kw):
-       # if print_actions:
-       # XXX:  WHAT SHOULD WE PRINT HERE?
-       if execute_actions:
-           return self.function(kw)
-
-class ListAction(ActionBase):
-    """Class for lists of other actions."""
-    def __init__(self, list):
-       self.list = map(lambda x: Action(x), list)
-
-    def execute(self, **kw):
-       for l in self.list:
-           r = apply(l.execute, (), kw)
-           if r != 0:
-               return r
-       return 0
+            return self.cached_src_suffixes[id(env)]
+        except KeyError:
+            suffixes = BuilderBase.src_suffixes(self, env)
+            for builder in self.get_src_builders(env):
+                suffixes.extend(builder.src_suffixes(env))
+            self.cached_src_suffixes[id(env)] = suffixes
+            return suffixes
+
+class CompositeBuilder(SCons.Util.Proxy):
+    """A Builder Proxy whose main purpose is to always have
+    a DictCmdGenerator as its action, and to provide access
+    to the DictCmdGenerator's add_action() method.
+    """
+
+    def __init__(self, builder, cmdgen):
+        SCons.Util.Proxy.__init__(self, builder)
+
+        # cmdgen should always be an instance of DictCmdGenerator.
+        self.cmdgen = cmdgen
+        self.builder = builder
+
+    def add_action(self, suffix, action):
+        self.cmdgen.add_action(suffix, action)
+        self.set_src_suffix(self.cmdgen.src_suffixes())
+        
+    def __cmp__(self, other):
+        return cmp(self.__dict__, other.__dict__)