diff options
Diffstat (limited to 'src/scons')
-rw-r--r-- | src/scons/.aeignore | 4 | ||||
-rw-r--r-- | src/scons/Builder.py | 58 | ||||
-rw-r--r-- | src/scons/BuilderTests.py | 111 | ||||
-rw-r--r-- | src/scons/Defaults.py | 20 | ||||
-rw-r--r-- | src/scons/Environment.py | 114 | ||||
-rw-r--r-- | src/scons/EnvironmentTests.py | 129 | ||||
-rw-r--r-- | src/scons/Node/.aeignore | 4 | ||||
-rw-r--r-- | src/scons/Node/FS.py | 139 | ||||
-rw-r--r-- | src/scons/Node/FS/.aeignore | 4 | ||||
-rw-r--r-- | src/scons/Node/FSTests.py | 107 | ||||
-rw-r--r-- | src/scons/Node/NodeTests.py | 43 | ||||
-rw-r--r-- | src/scons/Node/__init__.py | 19 | ||||
-rw-r--r-- | src/scons/Sig/.aeignore | 4 | ||||
-rw-r--r-- | src/scons/Sig/MD5.py | 70 | ||||
-rw-r--r-- | src/scons/Sig/MD5Tests.py | 76 | ||||
-rw-r--r-- | src/scons/Sig/TimeStamp.py | 49 | ||||
-rw-r--r-- | src/scons/Sig/TimeStampTests.py | 73 | ||||
-rw-r--r-- | src/scons/Sig/__init__.py | 7 | ||||
-rw-r--r-- | src/scons/__init__.py | 9 |
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__" |