Add a QT tool. (Christoph Wiedemann)
[scons.git] / src / engine / SCons / Builder.py
index a28a31d85dd1def440398c8fb9f932da9eefaf2c..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, 2002 Steven Knight
+# __COPYRIGHT__
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -32,14 +46,19 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 import os.path
-import string
-import copy
-from SCons.Errors import UserError
+from SCons.Errors import InternalError, UserError
 
 import SCons.Action
+import SCons.Executor
 import SCons.Node
 import SCons.Node.FS
 import SCons.Util
+import SCons.Warnings
+
+class _Null:
+    pass
+
+_null = _Null
 
 class DictCmdGenerator:
     """This is a callable class that can be used as a
@@ -54,34 +73,56 @@ class DictCmdGenerator:
     def src_suffixes(self):
         return self.action_dict.keys()
 
-    def __call__(self, source, target, env, **kw):
+    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 = os.path.splitext(src)[1]
+            my_ext = SCons.Util.splitext(src)[1]
             if ext and my_ext != ext:
-                raise UserError("Cannot build multiple sources with different extensions.")
+                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 ext is None:
-            raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
+        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:
-            # XXX Do we need to perform Environment substitution
-            # on the keys of action_dict before looking it up?
             return self.action_dict[ext]
         except KeyError:
-            raise UserError("Don't know how to build a file with suffix %s." % ext)
+            # 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']):
-        action_dict = kw['action']
-        kw['action'] = SCons.Action.CommandGenerator(DictCmdGenerator(action_dict))
-        kw['src_suffix'] = action_dict.keys()
+        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']):
@@ -93,47 +134,80 @@ def Builder(**kw):
         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'):
-        return apply(MultiStepBuilder, (), kw)
+        ret = apply(MultiStepBuilder, (), kw)
     else:
-        return apply(BuilderBase, (), kw)
+        ret = apply(BuilderBase, (), kw)
 
-def _init_nodes(builder, env, tlist, slist):
+    if composite:
+        ret = CompositeBuilder(ret, composite)
+
+    return ret
+
+def _init_nodes(builder, env, overrides, tlist, slist):
     """Initialize lists of target and source nodes with all of
     the proper Builder information.
     """
-    for s in slist:
-        src_key = s.scanner_key()        # the file suffix
-        scanner = env.get_scanner(src_key)
-        if scanner:
-            s.source_scanner = scanner
-            
+
+    # 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.cwd = SCons.Node.FS.default_fs.getcwd()       # XXX
+        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
-        
-class _callable_adaptor:
-    """When crteating a Builder, you can pass a string OR
-    a callable in for prefix, suffix, or src_suffix.
-    src_suffix even takes a list!
-
-    If a string or list is passed, we use this class to
-    adapt it to a callable."""
-    def __init__(self, static):
-        self.static = static
 
-    def __call__(self, **kw):
-        return self.static
+    # 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 __cmp__(self, other):
-        if isinstance(other, _callable_adaptor):
-            return cmp(self.static, other.static)
-        return -1
 
 def _adjust_suffix(suff):
     if suff and not suff[0] in [ '.', '$' ]:
@@ -150,10 +224,10 @@ class EmitterProxy:
     def __init__(self, var):
         self.var = SCons.Util.to_String(var)
 
-    def __call__(self, target, source, env, **kw):
+    def __call__(self, target, source, env):
         emitter = self.var
 
-        # Recursively substitue the variable.
+        # 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 \
@@ -161,19 +235,18 @@ class EmitterProxy:
             emitter = env[emitter]
         if not callable(emitter):
             return (target, source)
-        args = { 'target':target,
-                 'source':source,
-                 'env':env }
-        args.update(kw)
-        return apply(emitter, (), args)
+
+        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,
+    def __init__(self,  action = None,
                         prefix = '',
                         suffix = '',
                         src_suffix = '',
@@ -181,136 +254,155 @@ class BuilderBase:
                         target_factory = None,
                         source_factory = None,
                         scanner = None,
-                        emitter = None):
-        if name is None:
-            raise UserError, "You must specify a name for the builder."
-        self.name = name
+                        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
 
-        if callable(prefix):
-            self.prefix = prefix
-        else:
-            self.prefix = _callable_adaptor(str(prefix))
-
-        if callable(suffix):
-            self.suffix = suffix
-        else:
-            self.suffix = _callable_adaptor(str(suffix))
+        self.set_src_suffix(src_suffix)
 
-        if callable(src_suffix):
-            self.src_suffix = src_suffix
-        elif SCons.Util.is_String(src_suffix):
-            self.src_suffix = _callable_adaptor([ str(src_suffix) ])
-        else:
-            self.src_suffix = _callable_adaptor(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 _create_nodes(self, env, args, target = None, source = None):
+    def _create_nodes(self, env, overrides, target = None, source = None):
         """Create and return lists of target and source nodes.
         """
         def adjustixes(files, pre, suf):
+            if not files:
+                return []
             ret = []
-            # FOR RELEASE 0.08:
-            #if not SCons.Util.is_List(files):
-            #    files = [files]
-            files = SCons.Util.argmunge(files)
+            if not SCons.Util.is_List(files):
+                files = [files]
 
             for f in files:
                 if SCons.Util.is_String(f):
-                    if pre and f[:len(pre)] != pre:
+                    if pre:
                         path, fn = os.path.split(os.path.normpath(f))
-                        f = os.path.join(path, pre + fn)
+                        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 os.path.splitext(f)[1]:
-                       if f[-len(suf):] != suf:
-                           f = f + suf
-               ret.append(f)
-           return ret
-
-        pre = self.get_prefix(env, args)
-        suf = self.get_suffix(env, args)
-        tlist = SCons.Node.arg2nodes(adjustixes(target,
-                                                pre, suf),
-                                     self.target_factory)
-        src_suf = self.get_src_suffix(env, args)
-        slist = SCons.Node.arg2nodes(adjustixes(source,
-                                                None,
-                                                src_suf),
-                                     self.source_factory)
+                    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:
-            emit_args = { 'target' : tlist,
-                          'source' : slist,
-                          'env' : env }
-            emit_args.update(args)
-            target, source = apply(self.emitter, (), emit_args)
-
-            # Have to run it through again in case the
-            # function returns non-Node targets/sources.
-            tlist = SCons.Node.arg2nodes(adjustixes(target,
-                                                    pre, suf),
-                                         self.target_factory)
-            slist = SCons.Node.arg2nodes(adjustixes(source,
-                                                    None,
-                                                    src_suf),
-                                         self.source_factory)
-            
-        for t in tlist:
-            t.build_args = args
+            # 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)
+        
+            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 = None, **kw):
-        tlist, slist = self._create_nodes(env, kw, target, source)
+    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, tlist, slist)
+            _init_nodes(self, env, overrides, tlist, slist)
             tlist = tlist[0]
         else:
-            _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
+            _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
 
         return tlist
 
-
-    def execute(self, **kw):
-        """Execute a builder's action to create an output object.
-        """
-        return apply(self.action.execute, (), kw)
-
-    def get_raw_contents(self, **kw):
-        """Fetch the "contents" of the builder's action.
-        """
-        return apply(self.action.get_raw_contents, (), kw)
-
-    def get_contents(self, **kw):
-        """Fetch the "contents" of the builder's action
-        (for signature calculation).
-        """
-        return apply(self.action.get_contents, (), kw)
-
-    def src_suffixes(self, env, args):
+    def src_suffixes(self, env):
         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
-                   apply(self.src_suffix, (), args))
+                   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, args):
+    def get_src_suffix(self, env):
         """Get the first src_suffix in the list of src_suffixes."""
-        ret = self.src_suffixes(env, args)
+        ret = self.src_suffixes(env)
         if not ret:
             return ''
-        else:
-            return ret[0]
+        return ret[0]
 
-    def get_suffix(self, env, args):
-        return env.subst(_adjust_suffix(apply(self.suffix, (), args)))
+    def get_suffix(self, env):
+        return env.subst(_adjust_suffix(self.suffix))
 
-    def get_prefix(self, env, args):
-        return env.subst(apply(self.prefix, (), args))
+    def get_prefix(self, env):
+        return env.subst(self.prefix)
 
     def targets(self, node):
         """Return the list of targets for this builder instance.
@@ -319,47 +411,32 @@ class BuilderBase:
         """
         return [ node ]
 
-class ListBuilder:
-    """This is technically not a Builder object, but a wrapper
-    around another Builder object.  This is designed to look
-    like a Builder object, though, for purposes of building an
-    array of targets from a single Action execution.
+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
-
-    def execute(self, **kw):
-        if hasattr(self, 'status'):
-            return self.status
-        for t in self.tlist:
-            # unlink all targets and make all directories
-            # before building anything
-            t.prepare()
-        kw['target'] = self.tlist
-        self.status = apply(self.builder.execute, (), kw)
-        for t in self.tlist:
-            if not t is kw['target']:
-                t.build()
-        return self.status
-
-    def get_raw_contents(self, **kw):
-        return apply(self.builder.get_raw_contents, (), kw)
-
-    def get_contents(self, **kw):
-        return apply(self.builder.get_contents, (), kw)
-
-    def src_suffixes(self, env, args):
-        return self.builder.src_suffixes(env, args)
+        self.multi = builder.multi
 
     def targets(self, node):
         """Return the list of targets for this builder instance.
         """
         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.  The src_builder parameter to the constructor
@@ -372,7 +449,6 @@ class MultiStepBuilder(BuilderBase):
     src_suffix.
     """
     def __init__(self,  src_builder,
-                        name = None,
                         action = None,
                         prefix = '',
                         suffix = '',
@@ -382,59 +458,106 @@ class MultiStepBuilder(BuilderBase):
                         source_factory = None,
                         scanner=None,
                         emitter=None):
-        BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
+        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
 
-    def __call__(self, env, target = None, source = None, **kw):
         slist = SCons.Node.arg2nodes(source, self.source_factory)
         final_sources = []
 
-        r=repr(env)
         try:
-            sdict = self.sdict[r]
+            sdict = self.sdict[id(env)]
         except KeyError:
             sdict = {}
-            self.sdict[r] = sdict
+            self.sdict[id(env)] = sdict
             for bld in self.src_builder:
-                for suf in bld.src_suffixes(env, kw):
+                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.abspath)
+            path, ext = SCons.Util.splitext(snode.get_abspath())
             if sdict.has_key(ext):
                 src_bld = sdict[ext]
-
-                dictArgs = copy.copy(kw)
-                dictArgs['target'] = [path]
-                dictArgs['source'] = snode
-                dictArgs['env'] = env
-                tgt = apply(src_bld, (), dictArgs)
-                if not SCons.Util.is_List(tgt):
-                    tgt = [ tgt ]
-
+                tgt = apply(src_bld, (env, path, snode), kw)
                 # Only supply the builder with sources it is capable
                 # of building.
-                tgt = filter(lambda x,
-                             suf=self.src_suffixes(env, kw):
-                             os.path.splitext(SCons.Util.to_String(x))[1] in \
-                             suf, tgt)
-                final_sources.extend(tgt)
+                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)
-        dictKwArgs = kw
-        dictKwArgs['target'] = target
-        dictKwArgs['source'] = final_sources
+
         return apply(BuilderBase.__call__,
-                     (self, env), dictKwArgs)
-
-    def src_suffixes(self, env, args):
-        return BuilderBase.src_suffixes(self, env, args) + \
-               reduce(lambda x, y: x + y,
-                      map(lambda b, e=env, args=args: b.src_suffixes(e, args),
-                          self.src_builder),
-                      [])
+                     (self, env, target, final_sources), kw)
+
+    def get_src_builders(self, env):
+        """Return all the src_builders for this Builder.
+
+        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:
+            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__)