summaryrefslogtreecommitdiffstats
path: root/src/scons
diff options
context:
space:
mode:
Diffstat (limited to 'src/scons')
-rw-r--r--src/scons/.aeignore4
-rw-r--r--src/scons/Builder.py58
-rw-r--r--src/scons/BuilderTests.py111
-rw-r--r--src/scons/Defaults.py20
-rw-r--r--src/scons/Environment.py114
-rw-r--r--src/scons/EnvironmentTests.py129
-rw-r--r--src/scons/Node/.aeignore4
-rw-r--r--src/scons/Node/FS.py139
-rw-r--r--src/scons/Node/FS/.aeignore4
-rw-r--r--src/scons/Node/FSTests.py107
-rw-r--r--src/scons/Node/NodeTests.py43
-rw-r--r--src/scons/Node/__init__.py19
-rw-r--r--src/scons/Sig/.aeignore4
-rw-r--r--src/scons/Sig/MD5.py70
-rw-r--r--src/scons/Sig/MD5Tests.py76
-rw-r--r--src/scons/Sig/TimeStamp.py49
-rw-r--r--src/scons/Sig/TimeStampTests.py73
-rw-r--r--src/scons/Sig/__init__.py7
-rw-r--r--src/scons/__init__.py9
19 files changed, 1040 insertions, 0 deletions
diff --git a/src/scons/.aeignore b/src/scons/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/scons/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/scons/Builder.py b/src/scons/Builder.py
new file mode 100644
index 0000000..76c5512
--- /dev/null
+++ b/src/scons/Builder.py
@@ -0,0 +1,58 @@
+"""scons.Builder
+
+XXX
+
+"""
+
+__revision__ = "Builder.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+import os
+from types import *
+from scons.Node.FS import Dir, File, lookup
+
+
+
+class Builder:
+ """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_class = File):
+ self.name = name
+ self.action = action
+ self.insuffix = input_suffix
+ self.outsuffix = output_suffix
+ self.node_class = node_class
+ 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 __cmp__(self, other):
+ return cmp(self.__dict__, other.__dict__)
+
+ def __call__(self, target = None, source = None):
+ node = lookup(self.node_class, target)
+ node.builder_set(self)
+ node.sources = source # XXX REACHING INTO ANOTHER OBJECT
+ return node
+
+ def execute(self, **kw):
+ """Execute a builder's action to create an output object.
+ """
+ # XXX THIS SHOULD BE DONE BY TURNING Builder INTO A FACTORY
+ # FOR SUBCLASSES FOR StringType AND FunctionType
+ t = type(self.action)
+ if t == StringType:
+ cmd = self.action % kw
+ print cmd
+ os.system(cmd)
+ elif t == FunctionType:
+ # XXX WHAT SHOULD WE PRINT HERE
+ self.action(kw)
diff --git a/src/scons/BuilderTests.py b/src/scons/BuilderTests.py
new file mode 100644
index 0000000..a749bf2
--- /dev/null
+++ b/src/scons/BuilderTests.py
@@ -0,0 +1,111 @@
+__revision__ = "BuilderTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+
+from scons.Builder import Builder
+from TestCmd import TestCmd
+
+
+# Initial setup of the common environment for all tests,
+# a temporary working directory containing a
+# script for writing arguments to an output file.
+#
+# We don't do this as a setUp() method because it's
+# unnecessary to create a separate directory and script
+# for each test, they can just use the one.
+test = TestCmd(workdir = '')
+
+test.write('act.py', """import os, string, sys
+f = open(sys.argv[1], 'w')
+f.write("act.py: " + string.join(sys.argv[2:]) + "\\n")
+f.close()
+sys.exit(0)
+""")
+
+act_py = test.workpath('act.py')
+outfile = test.workpath('outfile')
+
+
+class BuilderTestCase(unittest.TestCase):
+
+ def test_action(self):
+ """Test the simple ability to create a Builder
+ and retrieve the supplied action attribute.
+ """
+ builder = Builder(action = "foo")
+ assert builder.action == "foo"
+
+ def test_cmp(self):
+ """Test simple comparisons of Builder objects.
+ """
+ b1 = Builder(input_suffix = '.o')
+ b2 = Builder(input_suffix = '.o')
+ assert b1 == b2
+ b3 = Builder(input_suffix = '.x')
+ assert b1 != b3
+ assert b2 != b3
+
+ def test_execute(self):
+ """Test the ability to execute simple Builders, one
+ a string that executes an external command, and one an
+ internal function.
+ """
+ cmd = "python %s %s xyzzy" % (act_py, outfile)
+ builder = Builder(action = cmd)
+ builder.execute()
+ assert test.read(outfile) == "act.py: xyzzy\n"
+
+ def function(kw):
+ import os, string, sys
+ f = open(kw['out'], 'w')
+ f.write("function\n")
+ f.close()
+ return not None
+
+ builder = Builder(action = function)
+ builder.execute(out = outfile)
+ assert test.read(outfile) == "function\n"
+
+ def test_insuffix(self):
+ """Test the ability to create a Builder with a specified
+ input suffix, making sure that the '.' separator is
+ appended to the beginning if it isn't already present.
+ """
+ builder = Builder(input_suffix = '.c')
+ assert builder.insuffix == '.c'
+ builder = Builder(input_suffix = 'c')
+ assert builder.insuffix == '.c'
+
+ def test_name(self):
+ """Test the ability to create a Builder with a specified
+ name.
+ """
+ builder = Builder(name = 'foo')
+ assert builder.name == 'foo'
+
+ def test_node_class(self):
+ """Test the ability to create a Builder that creates nodes
+ of the specified class.
+ """
+ class Foo:
+ pass
+ builder = Builder(node_class = Foo)
+ assert builder.node_class is Foo
+
+ def test_outsuffix(self):
+ """Test the ability to create a Builder with a specified
+ output suffix, making sure that the '.' separator is
+ appended to the beginning if it isn't already present.
+ """
+ builder = Builder(input_suffix = '.o')
+ assert builder.insuffix == '.o'
+ builder = Builder(input_suffix = 'o')
+ assert builder.insuffix == '.o'
+
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(BuilderTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Defaults.py b/src/scons/Defaults.py
new file mode 100644
index 0000000..0aa2b82
--- /dev/null
+++ b/src/scons/Defaults.py
@@ -0,0 +1,20 @@
+"""scons.Defaults
+
+Builders and other things for the local site. Here's where we'll
+duplicate the functionality of autoconf until we move it into the
+installation procedure or use something like qmconf.
+
+"""
+
+__revision__ = "local.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+from scons.Builder import Builder
+
+
+
+Object = Builder(name = 'Object', action = 'cc -c -o %(target)s %(source)s')
+Program = Builder(name = 'Program', action = 'cc -o %(target)s %(source)s')
+
+Builders = [Object, Program]
diff --git a/src/scons/Environment.py b/src/scons/Environment.py
new file mode 100644
index 0000000..c410162
--- /dev/null
+++ b/src/scons/Environment.py
@@ -0,0 +1,114 @@
+"""scons.Environment
+
+XXX
+
+"""
+
+__revision__ = "Environment.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+import copy
+import re
+import types
+
+
+
+def Command():
+ pass # XXX
+
+def Install():
+ pass # XXX
+
+def InstallAs():
+ pass # XXX
+
+
+
+_cv = re.compile(r'%([_a-zA-Z]\w*|{[_a-zA-Z]\w*})')
+_self = None
+
+
+
+def _deepcopy_atomic(x, memo):
+ return x
+copy._deepcopy_dispatch[types.ModuleType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.ClassType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.FunctionType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.MethodType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.TracebackType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.FrameType] = _deepcopy_atomic
+copy._deepcopy_dispatch[types.FileType] = _deepcopy_atomic
+
+
+
+class Environment:
+ """Base class for construction Environments. These are
+ the primary objects used to communicate dependency and
+ construction information to the build engine.
+
+ Keyword arguments supplied when the construction Environment
+ is created are construction variables used to initialize the
+ Environment.
+ """
+
+ def __init__(self, **kw):
+ self.Dictionary = {}
+ if kw.has_key('BUILDERS'):
+ builders = kw['BUILDERS']
+ if not type(builders) is types.ListType:
+ kw['BUILDERS'] = [builders]
+ else:
+ import scons.Defaults
+ kw['BUILDERS'] = scons.Defaults.Builders[:]
+ self.Dictionary.update(copy.deepcopy(kw))
+ for b in kw['BUILDERS']:
+ setattr(self, b.name, b)
+
+ def __cmp__(self, other):
+ return cmp(self.Dictionary, other.Dictionary)
+
+ def Builders(self):
+ pass # XXX
+
+ def Copy(self, **kw):
+ """Return a copy of a construction Environment. The
+ copy is like a Python "deep copy"--that is, independent
+ copies are made recursively of each objects--except that
+ a reference is copied when an object is not deep-copyable
+ (like a function). There are no references to any mutable
+ objects in the original Environment.
+ """
+ return copy.deepcopy(self)
+
+ def Scanners(self):
+ pass # XXX
+
+ def Update(self, **kw):
+ """Update an existing construction Environment with new
+ construction variables and/or values.
+ """
+ self.Dictionary.update(copy.deepcopy(kw))
+
+ def subst(self, string):
+ """Recursively interpolates construction variables from the
+ Environment into the specified string, returning the expanded
+ result. Construction variables are specified by a % prefix
+ in the string and begin with an initial underscore or
+ alphabetic character followed by any number of underscores
+ or alphanumeric characters. The construction variable names
+ may be surrounded by curly braces to separate the name from
+ trailing characters.
+ """
+ global _self
+ _self = self # XXX NOT THREAD SAFE, BUT HOW ELSE DO WE DO THIS?
+ def repl(m):
+ key = m.group(1)
+ if key[:1] == '{' and key[-1:] == '}':
+ key = key[1:-1]
+ if _self.Dictionary.has_key(key): return _self.Dictionary[key]
+ else: return ''
+ n = 1
+ while n != 0:
+ string, n = _cv.subn(repl, string)
+ return string
diff --git a/src/scons/EnvironmentTests.py b/src/scons/EnvironmentTests.py
new file mode 100644
index 0000000..5c6c151
--- /dev/null
+++ b/src/scons/EnvironmentTests.py
@@ -0,0 +1,129 @@
+__revision__ = "EnivronmentTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+
+from scons.Environment import *
+
+
+
+built_it = {}
+
+class Builder:
+ """A dummy Builder class for testing purposes. "Building"
+ a target is simply setting a value in the dictionary.
+ """
+ def __init__(self, name = None):
+ self.name = name
+
+ def execute(self, target = None, source = None):
+ built_it[target] = 1
+
+
+
+class EnvironmentTestCase(unittest.TestCase):
+
+ def test_Builders(self):
+ """Test the ability to execute simple builders through
+ different environment, one initialized with a single
+ Builder object, one with a list of a single Builder
+ object, and one with a list of two Builder objects.
+ """
+ global built_it
+
+ b1 = Builder(name = 'builder1')
+ b2 = Builder(name = 'builder2')
+
+ built_it = {}
+ env1 = Environment(BUILDERS = b1)
+ env1.builder1.execute(target = 'out1')
+ assert built_it['out1']
+
+ built_it = {}
+ env2 = Environment(BUILDERS = [b1])
+ env1.builder1.execute(target = 'out1')
+ assert built_it['out1']
+
+ built_it = {}
+ env3 = Environment(BUILDERS = [b1, b2])
+ env3.builder1.execute(target = 'out1')
+ env3.builder2.execute(target = 'out2')
+ env3.builder1.execute(target = 'out3')
+ assert built_it['out1']
+ assert built_it['out2']
+ assert built_it['out3']
+
+ def test_Command(self):
+ pass # XXX
+
+ def test_Copy(self):
+ """Test the ability to copy a construction Environment.
+ Update the copy independently afterwards and check that
+ the original remains intact (that is, no dangling
+ references point to objects in the copied environment).
+ """
+ env1 = Environment(XXX = 'x', YYY = 'y')
+ env2 = env1.Copy()
+ env1copy = env1.Copy()
+ env2.Update(YYY = 'yyy')
+ assert env1 != env2
+ assert env1 == env1copy
+
+ def test_Dictionary(self):
+ """Test the simple ability to retrieve known construction
+ variables from the Dictionary and check for well-known
+ defaults that get inserted.
+ """
+ env = Environment(XXX = 'x', YYY = 'y')
+ assert env.Dictionary['XXX'] == 'x'
+ assert env.Dictionary['YYY'] == 'y'
+ assert env.Dictionary.has_key('BUILDERS')
+
+ def test_Environment(self):
+ """Test the simple ability to create construction
+ Environments. Create two with identical arguments
+ and check that they compare the same.
+ """
+ env1 = Environment(XXX = 'x', YYY = 'y')
+ env2 = Environment(XXX = 'x', YYY = 'y')
+ assert env1 == env2
+
+ def test_Install(self):
+ pass # XXX
+
+ def test_InstallAs(self):
+ pass # XXX
+
+ def test_Scanners(self):
+ pass # XXX
+
+ def test_Update(self):
+ """Test the ability to update a construction Environment
+ with new construction variables after it was first created.
+ """
+ env1 = Environment(AAA = 'a', BBB = 'b')
+ env1.Update(BBB = 'bbb', CCC = 'ccc')
+ env2 = Environment(AAA = 'a', BBB = 'bbb', CCC = 'c')
+ assert env1 != env2
+
+ def test_subst(self):
+ """Test the ability to substitute construction variables
+ into a string. Check various combinations, including
+ recursive expansion of variables into other variables.
+ """
+ env = Environment(AAA = 'a', BBB = 'b')
+ str = env.subst("%AAA %{AAA}A %BBBB %BBB")
+ assert str == "a aA b", str
+ env = Environment(AAA = '%BBB', BBB = 'b', BBBA = 'foo')
+ str = env.subst("%AAA %{AAA}A %{AAA}B %BBB")
+ assert str == "b foo b", str
+ env = Environment(AAA = '%BBB', BBB = '%CCC', CCC = 'c')
+ str = env.subst("%AAA %{AAA}A %{AAA}B %BBB")
+ assert str == "c c", str
+
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(EnvironmentTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Node/.aeignore b/src/scons/Node/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/scons/Node/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/scons/Node/FS.py b/src/scons/Node/FS.py
new file mode 100644
index 0000000..7640a7a
--- /dev/null
+++ b/src/scons/Node/FS.py
@@ -0,0 +1,139 @@
+"""scons.Node.FS
+
+File system nodes.
+
+"""
+
+__revision__ = "Node/FS.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+import os
+import os.path
+from scons.Node import Node
+
+
+
+Top = None
+Root = {}
+
+
+
+def init(path = None):
+ """Initialize the Node.FS subsystem.
+
+ The supplied path is the top of the source tree, where we
+ expect to find the top-level build file. If no path is
+ supplied, the current directory is the default.
+ """
+ global Top
+ if path == None:
+ path = os.getcwd()
+ Top = lookup(Dir, path, directory = None)
+ Top.path = '.'
+
+def lookup(fsclass, name, directory = Top):
+ """Look up a file system node for a path name. If the path
+ name is relative, it will be looked up relative to the
+ specified directory node, or to the top-level directory
+ if no node was specified. An initial '#' specifies that
+ the name will be looked up relative to the top-level directory,
+ regardless of the specified directory argument. Returns the
+ existing or newly-created node for the specified path name.
+ The node returned will be of the specified fsclass (Dir or
+ File).
+ """
+ global Top
+ head, tail = os.path.split(name)
+ if not tail:
+ drive, path = os.path.splitdrive(head)
+ if not Root.has_key(drive):
+ Root[drive] = Dir(head, None)
+ Root[drive].abspath = head
+ Root[drive].path = head
+ return Root[drive]
+ if tail[0] == '#':
+ directory = Top
+ tail = tail[1:]
+ elif directory is None:
+ directory = Top
+ if head:
+ directory = lookup(Dir, head, directory)
+ try:
+ self = directory.entries[tail]
+ except AttributeError:
+ # There was no "entries" attribute on the directory,
+ # which essentially implies that it was a file.
+ # Return it as a more descriptive exception.
+ raise TypeError, directory
+ except KeyError:
+ # There was to entry for "tail," so create the new
+ # node and link it in to the existing structure.
+ self = fsclass(tail, directory)
+ self.name = tail
+ if self.path[0:2] == "./":
+ self.path = self.path[2:]
+ directory.entries[tail] = self
+ except:
+ raise
+ if self.__class__.__name__ != fsclass.__name__:
+ # Here, we found an existing node for this path,
+ # but it was the wrong type (a File when we were
+ # looking for a Dir, or vice versa).
+ raise TypeError, self
+ return self
+
+
+
+# XXX TODO?
+# Annotate with the creator
+# is_under
+# rel_path
+# srcpath / srcdir
+# link / is_linked
+# linked_targets
+# is_accessible
+
+class Dir(Node):
+ """A class for directories in a file system.
+ """
+
+ def __init__(self, name, directory):
+ self.entries = {}
+ self.entries['.'] = self
+ self.entries['..'] = directory
+ if not directory is None:
+ self.abspath = os.path.join(directory.abspath, name, '')
+ self.path = os.path.join(directory.path, name, '')
+
+ def up(self):
+ return self.entries['..']
+
+
+# XXX TODO?
+# rfile
+# precious
+# no_rfile
+# rpath
+# rsrcpath
+# source_exists
+# derived_exists
+# is_on_rpath
+# local
+# base_suf
+# suffix
+# addsuffix
+# accessible
+# ignore
+# build
+# bind
+# is_under
+# relpath
+
+class File(Node):
+ """A class for files in a file system.
+ """
+
+ def __init__(self, name, directory):
+ self.abspath = os.path.join(directory.abspath, name)
+ self.path = os.path.join(directory.path, name)
diff --git a/src/scons/Node/FS/.aeignore b/src/scons/Node/FS/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/scons/Node/FS/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/scons/Node/FSTests.py b/src/scons/Node/FSTests.py
new file mode 100644
index 0000000..afa4340
--- /dev/null
+++ b/src/scons/Node/FSTests.py
@@ -0,0 +1,107 @@
+__revision__ = "Node/FSTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import unittest
+
+from scons.Node.FS import init, lookup, Dir, File
+
+
+
+built_it = None
+
+class Builder:
+ def execute(self, target = None, source = None):
+ global built_it
+ built_it = 1
+
+
+
+class FSTestCase(unittest.TestCase):
+ def runTest(self):
+ """This test case handles all of the file system node
+ tests in one environment, so we don't have to set up a
+ complicated directory structure for each test individually.
+ """
+ from TestCmd import TestCmd
+
+ test = TestCmd(workdir = '')
+ test.subdir('sub', ['sub', 'dir'])
+
+ wp = test.workpath('')
+ sub = test.workpath('sub', '')
+ sub_dir = test.workpath('sub', 'dir', '')
+ sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
+ sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
+ sub_foo = test.workpath('sub', 'foo', '')
+
+ os.chdir(sub_dir)
+
+ init()
+
+ def Dir_test(lpath, path, abspath, up_path):
+ dir = lookup(Dir, lpath)
+ assert(dir.path == path)
+ assert(dir.abspath == abspath)
+ assert(dir.up().path == up_path)
+
+ Dir_test('foo', 'foo/', sub_dir_foo, '.')
+ Dir_test('foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('/foo', '/foo/', '/foo/', '/')
+ Dir_test('/foo/bar', '/foo/bar/', '/foo/bar/', '/foo/')
+ Dir_test('..', sub, sub, wp)
+ Dir_test('foo/..', '.', sub_dir, sub)
+ Dir_test('../foo', sub_foo, sub_foo, sub)
+ Dir_test('.', '.', sub_dir, sub)
+ Dir_test('./.', '.', sub_dir, sub)
+ Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+
+ d1 = lookup(Dir, 'd1')
+
+ f1 = lookup(File, 'f1', directory = d1)
+
+ assert(f1.path == 'd1/f1')
+
+ try:
+ f2 = lookup(File, 'f1/f2', directory = d1)
+ except TypeError, x:
+ node = x.args[0]
+ assert(node.path == 'd1/f1')
+ assert(node.__class__.__name__ == 'File')
+ except:
+ raise
+
+ try:
+ dir = lookup(Dir, 'd1/f1')
+ except TypeError, x:
+ node = x.args[0]
+ assert(node.path == 'd1/f1')
+ assert(node.__class__.__name__ == 'File')
+ except:
+ raise
+
+ # Test for sub-classing of node building.
+ global built_it
+
+ built_it = None
+ assert not built_it
+ d1.path = "d" # XXX FAKE SUBCLASS ATTRIBUTE
+ d1.sources = "d" # XXX FAKE SUBCLASS ATTRIBUTE
+ d1.builder_set(Builder())
+ d1.build()
+ assert built_it
+
+ built_it = None
+ assert not built_it
+ f1.path = "f" # XXX FAKE SUBCLASS ATTRIBUTE
+ f1.sources = "f" # XXX FAKE SUBCLASS ATTRIBUTE
+ f1.builder_set(Builder())
+ f1.build()
+ assert built_it
+
+
+if __name__ == "__main__":
+ suite = unittest.TestSuite()
+ suite.addTest(FSTestCase())
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Node/NodeTests.py b/src/scons/Node/NodeTests.py
new file mode 100644
index 0000000..92bc195
--- /dev/null
+++ b/src/scons/Node/NodeTests.py
@@ -0,0 +1,43 @@
+__revision__ = "Node/NodeTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import unittest
+
+from scons.Node import Node
+
+
+
+built_it = None
+
+class Builder:
+ def execute(self, target = None, source = None):
+ global built_it
+ built_it = 1
+
+
+
+class NodeTestCase(unittest.TestCase):
+
+ def test_build(self):
+ """Test the ability to build a node.
+ """
+ node = Node()
+ node.builder_set(Builder())
+ node.path = "xxx" # XXX FAKE SUBCLASS ATTRIBUTE
+ node.sources = "yyy" # XXX FAKE SUBCLASS ATTRIBUTE
+ node.build()
+ assert built_it
+
+ def test_builder_set(self):
+ node = Node()
+ b = Builder()
+ node.builder_set(b)
+ assert node.builder == b
+
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(NodeTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Node/__init__.py b/src/scons/Node/__init__.py
new file mode 100644
index 0000000..767f297
--- /dev/null
+++ b/src/scons/Node/__init__.py
@@ -0,0 +1,19 @@
+"""scons.Node
+
+The Node package for the scons software construction utility.
+
+"""
+
+__revision__ = "Node/__init__.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+class Node:
+ """The base Node class, for entities that we know how to
+ build, or use to build other Nodes.
+ """
+ def build(self):
+ self.builder.execute(target = self.path, source = self.sources)
+
+ def builder_set(self, builder):
+ self.builder = builder
diff --git a/src/scons/Sig/.aeignore b/src/scons/Sig/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/scons/Sig/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/scons/Sig/MD5.py b/src/scons/Sig/MD5.py
new file mode 100644
index 0000000..36e4230
--- /dev/null
+++ b/src/scons/Sig/MD5.py
@@ -0,0 +1,70 @@
+"""scons.Sig.MD5
+
+The MD5 signature package for the scons software construction
+utility.
+
+"""
+
+__revision__ = "Sig/MD5.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import md5
+import string
+
+
+
+def hexdigest(s):
+ """Return a signature as a string of hex characters.
+ """
+ # NOTE: This routine is a method in the Python 2.0 interface
+ # of the native md5 module, but we want scons to operate all
+ # the way back to at least Python 1.5.2, which doesn't have it.
+ h = string.hexdigits
+ r = ''
+ for c in s:
+ i = ord(c)
+ r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
+ return r
+
+
+
+def _init():
+ pass # XXX
+
+def _end():
+ pass # XXX
+
+def current(obj, sig):
+ """Return whether a given object is up-to-date with the
+ specified signature.
+ """
+ return obj.signature() == sig
+
+def set():
+ pass # XXX
+
+def invalidate():
+ pass # XXX
+
+def collect(*objects):
+ """Collect signatures from a list of objects, returning the
+ aggregate signature of the list.
+ """
+ if len(objects) == 1:
+ sig = objects[0].signature()
+ else:
+ contents = string.join(map(lambda o: o.signature(), objects), ', ')
+ sig = signature(contents)
+# if debug:
+# pass
+ return sig
+
+def signature(contents):
+ """Generate a signature for a byte string.
+ """
+ return hexdigest(md5.new(contents).digest())
+
+def cmdsig():
+ pass # XXX
+
+def srcsig():
+ pass # XXX
diff --git a/src/scons/Sig/MD5Tests.py b/src/scons/Sig/MD5Tests.py
new file mode 100644
index 0000000..ac43f1b
--- /dev/null
+++ b/src/scons/Sig/MD5Tests.py
@@ -0,0 +1,76 @@
+__revision__ = "Sig/MD5Tests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+
+import scons.Sig.MD5
+
+
+
+class my_obj:
+ """A dummy object class that satisfies the interface
+ requirements of the MD5 class.
+ """
+
+ def __init__(self, value = ""):
+ self.value = value
+ self.sig = None
+
+ def signature(self):
+ if not self.sig:
+ self.sig = scons.Sig.MD5.signature(self.value)
+ return self.sig
+
+ def current(self, sig):
+ return scons.Sig.MD5.current(self, sig)
+
+
+
+class MD5TestCase(unittest.TestCase):
+
+ def test__init(self):
+ pass # XXX
+
+ def test__end(self):
+ pass # XXX
+
+ def test_current(self):
+ """Test the ability to decide if an object is up-to-date
+ with different signature values.
+ """
+ o111 = my_obj(value = '111')
+ assert not o111.current(scons.Sig.MD5.signature('110'))
+ assert o111.current(scons.Sig.MD5.signature('111'))
+ assert not o111.current(scons.Sig.MD5.signature('112'))
+
+ def test_set(self):
+ pass # XXX
+
+ def test_invalidate(self):
+ pass # XXX
+
+ def test_collect(self):
+ """Test the ability to collect a sequence of object signatures
+ into a new signature value.
+ """
+ o1 = my_obj(value = '111')
+ o2 = my_obj(value = '222')
+ o3 = my_obj(value = '333')
+ assert '698d51a19d8a121ce581499d7b701668' == scons.Sig.MD5.collect(o1)
+ assert '8980c988edc2c78cc43ccb718c06efd5' == scons.Sig.MD5.collect(o1, o2)
+ assert '53fd88c84ff8a285eb6e0a687e55b8c7' == scons.Sig.MD5.collect(o1, o2, o3)
+
+ def test_signature(self):
+ pass # XXX
+
+ def test_cmdsig(self):
+ pass # XXX
+
+ def test_srcsig(self):
+ pass # XXX
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(MD5TestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Sig/TimeStamp.py b/src/scons/Sig/TimeStamp.py
new file mode 100644
index 0000000..cab44bf
--- /dev/null
+++ b/src/scons/Sig/TimeStamp.py
@@ -0,0 +1,49 @@
+"""scons.Sig.TimeStamp
+
+The TimeStamp signature package for the scons software construction
+utility.
+
+"""
+
+__revision__ = "Sig/TimeStamp.py __REVISION__ __DATE__ __DEVELOPER__"
+
+def _init():
+ pass # XXX
+
+def _end():
+ pass # XXX
+
+def current(obj, sig):
+ """Return whether the object's timestamp is up-to-date.
+ """
+ return obj.signature() >= sig
+
+def set():
+ pass # XXX
+
+def invalidate():
+ pass # XXX
+
+def collect(*objects):
+ """Collect timestamps from a list of objects, returning
+ the most-recent timestamp from the list.
+ """
+ r = 0
+ for obj in objects:
+ s = obj.signature()
+ if s > r:
+ r = s
+ return r
+
+def signature(contents):
+ """Generate a timestamp.
+ """
+ pass # XXX
+# return md5.new(contents).hexdigest() # 2.0
+ return hexdigest(md5.new(contents).digest())
+
+def cmdsig():
+ pass # XXX
+
+def srcsig():
+ pass # XXX
diff --git a/src/scons/Sig/TimeStampTests.py b/src/scons/Sig/TimeStampTests.py
new file mode 100644
index 0000000..aa61af8
--- /dev/null
+++ b/src/scons/Sig/TimeStampTests.py
@@ -0,0 +1,73 @@
+__revision__ = "Sig/TimeStampTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+
+import scons.Sig.TimeStamp
+
+
+
+class my_obj:
+ """A dummy object class that satisfies the interface
+ requirements of the TimeStamp class.
+ """
+
+ def __init__(self, value = ""):
+ self.value = value
+
+ def signature(self):
+ return self.value
+
+
+
+class TimeStampTestCase(unittest.TestCase):
+
+ def test__init(self):
+ pass # XXX
+
+ def test__init(self):
+ pass # XXX
+
+ def test__end(self):
+ pass # XXX
+
+ def test_current(self):
+ """Test the ability to decide if an object is up-to-date
+ with different timestamp values.
+ """
+ o1 = my_obj(value = 111)
+ assert scons.Sig.TimeStamp.current(o1, 110)
+ assert scons.Sig.TimeStamp.current(o1, 111)
+ assert not scons.Sig.TimeStamp.current(o1, 112)
+
+ def test_set(self):
+ pass # XXX
+
+ def test_invalidate(self):
+ pass # XXX
+
+ def test_collect(self):
+ """Test the ability to collect a sequence of object timestamps
+ into a new timestamp value.
+ """
+ o1 = my_obj(value = 111)
+ o2 = my_obj(value = 222)
+ o3 = my_obj(value = 333)
+ assert 111 == scons.Sig.TimeStamp.collect(o1)
+ assert 222 == scons.Sig.TimeStamp.collect(o1, o2)
+ assert 333 == scons.Sig.TimeStamp.collect(o1, o2, o3)
+
+ def test_signature(self):
+ pass # XXX
+
+ def test_cmdsig(self):
+ pass # XXX
+
+ def test_srcsig(self):
+ pass # XXX
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(TimeStampTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/scons/Sig/__init__.py b/src/scons/Sig/__init__.py
new file mode 100644
index 0000000..411a94b
--- /dev/null
+++ b/src/scons/Sig/__init__.py
@@ -0,0 +1,7 @@
+"""scons.Sig
+
+The Signature package for the scons software construction utility.
+
+"""
+
+__revision__ = "Sig/__init__.py __REVISION__ __DATE__ __DEVELOPER__"
diff --git a/src/scons/__init__.py b/src/scons/__init__.py
new file mode 100644
index 0000000..9e279c2
--- /dev/null
+++ b/src/scons/__init__.py
@@ -0,0 +1,9 @@
+"""scons
+
+The main package for the scons software construction utility.
+
+"""
+
+__revision__ = "__init__.py __REVISION__ __DATE__ __DEVELOPER__"
+
+__version__ = "__VERSION__"