summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
committerSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
commit3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab (patch)
tree1a08c189644909cdadc489cce0eaa487e2c6f578 /src/engine
parente2faf9c21bc7712fcdc547b7df0f12a6b2177601 (diff)
downloadSCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.zip
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.gz
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.bz2
Run setup.py on the unpacked .tar.gz for testing.
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/MANIFEST16
-rw-r--r--src/engine/SCons/.aeignore4
-rw-r--r--src/engine/SCons/Builder.py100
-rw-r--r--src/engine/SCons/BuilderTests.py113
-rw-r--r--src/engine/SCons/Defaults.py22
-rw-r--r--src/engine/SCons/Environment.py133
-rw-r--r--src/engine/SCons/EnvironmentTests.py144
-rw-r--r--src/engine/SCons/Errors.py18
-rw-r--r--src/engine/SCons/ErrorsTests.py28
-rw-r--r--src/engine/SCons/Job.py239
-rw-r--r--src/engine/SCons/JobTests.py228
-rw-r--r--src/engine/SCons/Node/.aeignore4
-rw-r--r--src/engine/SCons/Node/FS.py139
-rw-r--r--src/engine/SCons/Node/FS/.aeignore4
-rw-r--r--src/engine/SCons/Node/FSTests.py109
-rw-r--r--src/engine/SCons/Node/NodeTests.py45
-rw-r--r--src/engine/SCons/Node/__init__.py22
-rw-r--r--src/engine/SCons/Scanner/.aeignore4
-rw-r--r--src/engine/SCons/Scanner/C.py87
-rw-r--r--src/engine/SCons/Scanner/CTests.py131
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py84
-rw-r--r--src/engine/SCons/Scanner/__init__.py69
-rw-r--r--src/engine/SCons/Sig/.aeignore4
-rw-r--r--src/engine/SCons/Sig/MD5.py70
-rw-r--r--src/engine/SCons/Sig/MD5Tests.py76
-rw-r--r--src/engine/SCons/Sig/TimeStamp.py49
-rw-r--r--src/engine/SCons/Sig/TimeStampTests.py74
-rw-r--r--src/engine/SCons/Sig/__init__.py7
-rw-r--r--src/engine/SCons/__init__.py9
-rw-r--r--src/engine/SCons/exitfuncs.py48
-rw-r--r--src/engine/setup.py16
31 files changed, 2096 insertions, 0 deletions
diff --git a/src/engine/MANIFEST b/src/engine/MANIFEST
new file mode 100644
index 0000000..51c6822
--- /dev/null
+++ b/src/engine/MANIFEST
@@ -0,0 +1,16 @@
+MANIFEST
+SCons/__init__.py
+SCons/Builder.py
+SCons/Defaults.py
+SCons/Environment.py
+SCons/Errors.py
+SCons/Job.py
+SCons/exitfuncs.py
+SCons/Node/__init__.py
+SCons/Node/FS.py
+SCons/Scanner/__init__.py
+SCons/Scanner/C.py
+SCons/Sig/__init__.py
+SCons/Sig/MD5.py
+SCons/Sig/TimeStamp.py
+setup.py
diff --git a/src/engine/SCons/.aeignore b/src/engine/SCons/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/engine/SCons/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
new file mode 100644
index 0000000..f1ab100
--- /dev/null
+++ b/src/engine/SCons/Builder.py
@@ -0,0 +1,100 @@
+"""SCons.Builder
+
+XXX
+
+"""
+
+__revision__ = "Builder.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+import os
+import SCons.Node.FS
+import types
+
+
+
+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 = SCons.Node.FS.File):
+ self.name = name
+ self.action = 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, env, target = None, source = None):
+ node = SCons.Node.FS.lookup(self.node_class, target)
+ node.builder_set(self)
+ node.env_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.
+ """
+ apply(self.action.execute, (), kw)
+
+
+
+print_actions = 1;
+execute_actions = 1;
+
+
+
+def Action(act):
+ """A factory for action objects."""
+ if type(act) == types.FunctionType:
+ return FunctionAction(act)
+ elif type(act) == types.StringType:
+ return CommandAction(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__)
+
+ def show(self, string):
+ print string
+
+class CommandAction(ActionBase):
+ """Class for command-execution actions."""
+ def __init__(self, string):
+ self.command = string
+
+ def execute(self, **kw):
+ cmd = self.command % kw
+ if print_actions:
+ self.show(cmd)
+ if execute_actions:
+ os.system(cmd)
+
+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:
+ self.function(kw)
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
new file mode 100644
index 0000000..67435bc
--- /dev/null
+++ b/src/engine/SCons/BuilderTests.py
@@ -0,0 +1,113 @@
+__revision__ = "BuilderTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+
+import TestCmd
+import SCons.Builder
+
+
+# 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.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 Builder creation
+
+ Verify that we can retrieve the supplied action attribute.
+ """
+ builder = SCons.Builder.Builder(action = "foo")
+ assert builder.action.command == "foo"
+
+ def test_cmp(self):
+ """Test simple comparisons of Builder objects
+ """
+ b1 = SCons.Builder.Builder(input_suffix = '.o')
+ b2 = SCons.Builder.Builder(input_suffix = '.o')
+ assert b1 == b2
+ b3 = SCons.Builder.Builder(input_suffix = '.x')
+ assert b1 != b3
+ assert b2 != b3
+
+ def test_execute(self):
+ """Test execution of simple Builder objects
+
+ One Builder is a string that executes an external command,
+ and one is an internal Python function.
+ """
+ cmd = "python %s %s xyzzy" % (act_py, outfile)
+ builder = SCons.Builder.Builder(action = cmd)
+ builder.execute()
+ assert test.read(outfile, 'r') == "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 = SCons.Builder.Builder(action = function)
+ builder.execute(out = outfile)
+ assert test.read(outfile, 'r') == "function\n"
+
+ def test_insuffix(self):
+ """Test Builder creation with a specified input suffix
+
+ Make sure that the '.' separator is appended to the
+ beginning if it isn't already present.
+ """
+ builder = SCons.Builder.Builder(input_suffix = '.c')
+ assert builder.insuffix == '.c'
+ builder = SCons.Builder.Builder(input_suffix = 'c')
+ assert builder.insuffix == '.c'
+
+ def test_name(self):
+ """Test Builder creation with a specified name
+ """
+ builder = SCons.Builder.Builder(name = 'foo')
+ assert builder.name == 'foo'
+
+ def test_node_class(self):
+ """Test a Builder that creates nodes of a specified class
+ """
+ class Foo:
+ pass
+ builder = SCons.Builder.Builder(node_class = Foo)
+ assert builder.node_class is Foo
+
+ def test_outsuffix(self):
+ """Test Builder creation with a specified output suffix
+
+ Make sure that the '.' separator is appended to the
+ beginning if it isn't already present.
+ """
+ builder = SCons.Builder.Builder(input_suffix = '.o')
+ assert builder.insuffix == '.o'
+ builder = SCons.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/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
new file mode 100644
index 0000000..56a15f7
--- /dev/null
+++ b/src/engine/SCons/Defaults.py
@@ -0,0 +1,22 @@
+"""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__"
+
+
+
+import SCons.Builder
+
+
+
+Object = SCons.Builder.Builder(name = 'Object',
+ action = 'cc -c -o %(target)s %(source)s')
+Program = SCons.Builder.Builder(name = 'Program',
+ action = 'cc -o %(target)s %(source)s')
+
+Builders = [Object, Program]
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
new file mode 100644
index 0000000..a3fe27a
--- /dev/null
+++ b/src/engine/SCons/Environment.py
@@ -0,0 +1,133 @@
+"""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))
+
+ class BuilderWrapper:
+ """Wrapper class that allows an environment to
+ be associated with a Builder at instantiation.
+ """
+ def __init__(self, env, builder):
+ self.env = env
+ self.builder = builder
+
+ def __call__(self, target = None, source = None):
+ return self.builder(self.env, target, source)
+
+ def execute(self, **kw):
+ apply(self.builder.execute, (), kw)
+
+ for b in kw['BUILDERS']:
+ setattr(self, b.name, BuilderWrapper(self, 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.
+ """
+ clone = copy.deepcopy(self)
+ apply(clone.Update, (), kw)
+ return clone
+
+ 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/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
new file mode 100644
index 0000000..6ac4cb7
--- /dev/null
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -0,0 +1,144 @@
+__revision__ = "EnvironmentTests.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 Builder execution through different environments
+
+ One environment is 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 construction Environment copying
+
+ Update the copy independently afterwards and check that
+ the original remains intact (that is, no dangling
+ references point to objects in the copied environment).
+ Copy the original with some construction variable
+ updates and check that the original remains intact
+ and the copy has the updated values.
+ """
+ env1 = Environment(XXX = 'x', YYY = 'y')
+ env2 = env1.Copy()
+ env1copy = env1.Copy()
+ env2.Update(YYY = 'yyy')
+ assert env1 != env2
+ assert env1 == env1copy
+
+ env3 = env1.Copy(XXX = 'x3', ZZZ = 'z3')
+ assert env3.Dictionary['XXX'] == 'x3'
+ assert env3.Dictionary['YYY'] == 'y'
+ assert env3.Dictionary['ZZZ'] == 'z3'
+ assert env1 == env1copy
+
+ def test_Dictionary(self):
+ """Test retrieval of known construction variables
+
+ Fetch them 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 construction Environments creation
+
+ 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 updating an Environment with new construction variables
+
+ After creation of the Environment, of course.
+ """
+ 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 substituting construction variables within strings
+
+ 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/engine/SCons/Errors.py b/src/engine/SCons/Errors.py
new file mode 100644
index 0000000..52facf7
--- /dev/null
+++ b/src/engine/SCons/Errors.py
@@ -0,0 +1,18 @@
+"""SCons.Errors
+
+This file contains the exception classes used to handle internal
+and user errors in SCons.
+
+"""
+
+__revision__ = "Errors.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+class InternalError(Exception):
+ def __init__(self, args=None):
+ self.args = args
+
+class UserError(Exception):
+ def __init__(self, args=None):
+ self.args = args
diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py
new file mode 100644
index 0000000..911b11b
--- /dev/null
+++ b/src/engine/SCons/ErrorsTests.py
@@ -0,0 +1,28 @@
+__revision__ = "ErrorsTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import sys
+import unittest
+import SCons.Errors
+
+
+class ErrorsTestCase(unittest.TestCase):
+ def test_InternalError(self):
+ """Test the InternalError exception."""
+ try:
+ raise SCons.Errors.InternalError, "test internal error"
+ except SCons.Errors.InternalError, e:
+ assert e.args == "test internal error"
+
+ def test_UserError(self):
+ """Test the UserError exception."""
+ try:
+ raise SCons.Errors.UserError, "test user error"
+ except SCons.Errors.UserError, e:
+ assert e.args == "test user error"
+
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(ErrorsTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py
new file mode 100644
index 0000000..aa9d92a
--- /dev/null
+++ b/src/engine/SCons/Job.py
@@ -0,0 +1,239 @@
+"""SCons.Job
+
+This module defines the Serial and Parallel classes that execute tasks to
+complete a build. The Jobs class provides a higher level interface to start,
+stop, and wait on jobs.
+
+"""
+
+__revision__ = "Job.py __REVISION__ __DATE__ __DEVELOPER__"
+
+class Jobs:
+ """An instance of this class initializes N jobs, and provides
+ methods for starting, stopping, and waiting on all N jobs.
+ """
+
+ def __init__(self, num, taskmaster):
+ """
+ create 'num' jobs using the given taskmaster.
+
+ If 'num' is equal to 0, then a serial job will be used,
+ otherwise 'num' parallel jobs will be used.
+ """
+
+ if num > 1:
+ self.jobs = []
+ for i in range(num):
+ self.jobs.append(Parallel(taskmaster, self))
+ else:
+ self.jobs = [Serial(taskmaster)]
+
+ def start(self):
+ """start the jobs"""
+
+ for job in self.jobs:
+ job.start()
+
+ def wait(self):
+ """ wait for the jobs started with start() to finish"""
+
+ for job in self.jobs:
+ job.wait()
+
+ def stop(self):
+ """
+ stop the jobs started with start()
+
+ This function does not wait for the jobs to finish.
+ """
+
+ for job in self.jobs:
+ job.stop()
+
+class Serial:
+ """This class is used to execute tasks in series, and is more efficient
+ than Parallel, but is only appropriate for non-parallel builds. Only
+ one instance of this class should be in existence at a time.
+
+ This class is not thread safe.
+ """
+
+ def __init__(self, taskmaster):
+ """Create a new serial job given a taskmaster.
+
+ The taskmaster's next_task() method should return the next task
+ that needs to be executed, or None if there are no more tasks. The
+ taskmaster's executed() method will be called for each task when it
+ is successfully executed or failed() will be called if it failed to
+ execute (e.g. execute() raised an exception). The taskmaster's
+ is_blocked() method will not be called. """
+
+ self.taskmaster = taskmaster
+
+ def start(self):
+
+ """Start the job. This will begin pulling tasks from the taskmaster
+ and executing them, and return when there are no more tasks. If a task
+ fails to execute (i.e. execute() raises an exception), then the job will
+ stop."""
+
+ while 1:
+ task = self.taskmaster.next_task()
+
+ if task is None:
+ break
+
+ try:
+ task.execute()
+ except:
+ self.taskmaster.failed(task)
+ return
+ else:
+ self.taskmaster.executed(task)
+
+ def stop(self):
+ """Serial jobs are always finished when start() returns, so there
+ is nothing to do here"""
+
+ pass
+
+ def wait(self):
+ """Serial jobs are always finished when start() returns, so there
+ is nothing to do here"""
+ pass
+
+
+# The will hold a condition variable once the first parallel task
+# is created.
+cv = None
+
+class Parallel:
+ """This class is used to execute tasks in parallel, and is less
+ efficient than Serial, but is appropriate for parallel builds. Create
+ an instance of this class for each job or thread you want.
+
+ This class is thread safe.
+ """
+
+
+ def __init__(self, taskmaster, jobs):
+ """Create a new parallel job given a taskmaster, and a Jobs instance.
+ Multiple jobs will be using the taskmaster in parallel, but all
+ method calls to taskmaster methods are serialized by the jobs
+ themselves.
+
+ The taskmaster's next_task() method should return the next task
+ that needs to be executed, or None if there are no more tasks. The
+ taskmaster's executed() method will be called for each task when it
+ is successfully executed or failed() will be called if the task
+ failed to execute (i.e. execute() raised an exception). The
+ taskmaster's is_blocked() method should return true iff there are
+ more tasks, but they can't be executed until one or more other
+ tasks have been executed. next_task() will be called iff
+ is_blocked() returned false.
+
+ Note: calls to taskmaster are serialized, but calls to execute() on
+ distinct tasks are not serialized, because that is the whole point
+ of parallel jobs: they can execute multiple tasks
+ simultaneously. """
+
+ global cv
+
+ # import threading here so that everything in the Job module
+ # but the Parallel class will work if the interpreter doesn't
+ # support threads
+ import threading
+
+ self.taskmaster = taskmaster
+ self.jobs = jobs
+ self.thread = threading.Thread(None, self.__run)
+ self.stop_running = 0
+
+ if cv is None:
+ cv = threading.Condition()
+
+ def start(self):
+ """Start the job. This will spawn a thread that will begin pulling
+ tasks from the task master and executing them. This method returns
+ immediately and doesn't wait for the jobs to be executed.
+
+ If a task fails to execute (i.e. execute() raises an exception),
+ all jobs will be stopped.
+
+ To stop the job, call stop().
+ To wait for the job to finish, call wait().
+ """
+ self.thread.start()
+
+ def stop(self):
+ """Stop the job. This will cause the job to finish after the
+ currently executing task is done. A job that has been stopped can
+ not be restarted.
+
+ To wait for the job to finish, call wait().
+ """
+
+ cv.acquire()
+ self.stop_running = 1
+ # wake up the sleeping jobs so this job will end as soon as possible:
+ cv.notifyAll()
+ cv.release()
+
+ def wait(self):
+ """Wait for the job to finish. A job is finished when either there
+ are no more tasks or the job has been stopped and it is no longer
+ executing a task.
+
+ This method should only be called after start() has been called.
+
+ To stop the job, call stop().
+ """
+ self.thread.join()
+
+ def __run(self):
+ """private method that actually executes the tasks"""
+
+ cv.acquire()
+
+ try:
+
+ while 1:
+ while self.taskmaster.is_blocked() and not self.stop_running:
+ cv.wait(None)
+
+ # check this before calling next_task(), because
+ # this job may have been stopped because of a build
+ # failure:
+ if self.stop_running:
+ break
+
+ task = self.taskmaster.next_task()
+
+ if task == None:
+ break
+
+ cv.release()
+ try:
+ try:
+ task.execute()
+ finally:
+ cv.acquire()
+ except:
+ self.taskmaster.failed(task)
+ # stop all jobs since there was a failure:
+ # (this will wake up any waiting jobs, so
+ # it isn't necessary to explicitly wake them
+ # here)
+ self.jobs.stop()
+ else:
+ self.taskmaster.executed(task)
+
+ if not self.taskmaster.is_blocked():
+ cv.notifyAll()
+
+ finally:
+ cv.release()
+
+
+
+
diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py
new file mode 100644
index 0000000..ec4c46d
--- /dev/null
+++ b/src/engine/SCons/JobTests.py
@@ -0,0 +1,228 @@
+__revision__ = "JobTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import unittest
+import random
+import math
+import SCons.Job
+import sys
+
+# a large number
+num_sines = 10000
+
+# how many parallel jobs to perform for the test
+num_jobs = 11
+
+# how many tasks to perform for the test
+num_tasks = num_jobs*5
+
+class DummyLock:
+ "fake lock class to use if threads are not supported"
+ def acquire(self):
+ pass
+
+ def release(self):
+ pass
+
+class NoThreadsException:
+ "raised by the ParallelTestCase if threads are not supported"
+
+ def __str__(self):
+ return "the interpreter doesn't support threads"
+
+class Task:
+ """A dummy task class for testing purposes."""
+
+ def __init__(self, i, taskmaster):
+ self.i = i
+ self.taskmaster = taskmaster
+ self.was_executed = 0
+
+ def execute(self):
+ self.taskmaster.guard.acquire()
+ self.taskmaster.begin_list.append(self.i)
+ self.taskmaster.guard.release()
+
+ # do something that will take some random amount of time:
+ for i in range(random.randrange(0, num_sines, 1)):
+ x = math.sin(i)
+
+ self.was_executed = 1
+
+ self.taskmaster.guard.acquire()
+ self.taskmaster.end_list.append(self.i)
+ self.taskmaster.guard.release()
+
+class ExceptionTask:
+ """A dummy task class for testing purposes."""
+
+ def __init__(self, i, taskmaster):
+ pass
+
+ def execute(self):
+ raise "exception"
+
+class Taskmaster:
+ """A dummy taskmaster class for testing the job classes."""
+
+ def __init__(self, n, test_case, Task):
+ """n is the number of dummy tasks to perform."""
+
+ self.test_case = test_case
+ self.num_tasks = n
+ self.num_iterated = 0
+ self.num_executed = 0
+ self.num_failed = 0
+ self.Task = Task
+ # 'guard' guards 'task_begin_list' and 'task_end_list'
+ try:
+ import threading
+ self.guard = threading.Lock()
+ except:
+ self.guard = DummyLock()
+
+ # keep track of the order tasks are begun in
+ self.begin_list = []
+
+ # keep track of the order tasks are completed in
+ self.end_list = []
+
+
+ def next_task(self):
+ if self.all_tasks_are_iterated():
+ return None
+ else:
+ self.num_iterated = self.num_iterated + 1
+ return self.Task(self.num_iterated, self)
+
+ def all_tasks_are_executed(self):
+ return self.num_executed == self.num_tasks
+
+ def all_tasks_are_iterated(self):
+ return self.num_iterated == self.num_tasks
+
+ def executed(self, task):
+ self.num_executed = self.num_executed + 1
+
+ self.test_case.failUnless(task.was_executed,
+ "the task wasn't really executed")
+ self.test_case.failUnless(task.__class__ is Task,
+ "the task wasn't really a Task instance")
+
+ def failed(self, task):
+ self.num_failed = self.num_failed + 1
+
+ def is_blocked(self):
+ # simulate blocking tasks
+ return self.num_iterated - self.num_executed >= max(num_jobs/2, 2)
+
+ def tasks_were_serial(self):
+ "analyze the task order to see if they were serial"
+ serial = 1 # assume the tasks were serial
+ for i in range(num_tasks):
+ serial = serial and (self.begin_list[i]
+ == self.end_list[i]
+ == (i + 1))
+ return serial
+
+class ParallelTestCase(unittest.TestCase):
+ def runTest(self):
+ "test parallel jobs"
+
+ try:
+ import threading
+ except:
+ raise NoThreadsException()
+
+ taskmaster = Taskmaster(num_tasks, self, Task)
+ jobs = SCons.Job.Jobs(num_jobs, taskmaster)
+ jobs.start()
+ jobs.wait()
+
+ self.failUnless(not taskmaster.tasks_were_serial(),
+ "the tasks were not executed in parallel")
+ self.failUnless(taskmaster.all_tasks_are_executed(),
+ "all the tests were not executed")
+ self.failUnless(taskmaster.all_tasks_are_iterated(),
+ "all the tests were not iterated over")
+ self.failIf(taskmaster.num_failed,
+ "some task(s) failed to execute")
+
+class SerialTestCase(unittest.TestCase):
+ def runTest(self):
+ "test a serial job"
+
+ taskmaster = Taskmaster(num_tasks, self, Task)
+ jobs = SCons.Job.Jobs(1, taskmaster)
+ jobs.start()
+ jobs.wait()
+
+ self.failUnless(taskmaster.tasks_were_serial(),
+ "the tasks were not executed in series")
+ self.failUnless(taskmaster.all_tasks_are_executed(),
+ "all the tests were not executed")
+ self.failUnless(taskmaster.all_tasks_are_iterated(),
+ "all the tests were not iterated over")
+ self.failIf(taskmaster.num_failed,
+ "some task(s) failed to execute")
+
+class SerialExceptionTestCase(unittest.TestCase):
+ def runTest(self):
+ "test a serial job with tasks that raise exceptions"
+
+ taskmaster = Taskmaster(num_tasks, self, ExceptionTask)
+ jobs = SCons.Job.Jobs(1, taskmaster)
+ jobs.start()
+ jobs.wait()
+
+ self.failIf(taskmaster.num_executed,
+ "a task was executed")
+ self.failUnless(taskmaster.num_iterated == 1,
+ "exactly one task should have been iterated")
+ self.failUnless(taskmaster.num_failed == 1,
+ "exactly one task should have failed")
+
+class ParallelExceptionTestCase(unittest.TestCase):
+ def runTest(self):
+ "test parallel jobs with tasks that raise exceptions"
+
+ taskmaster = Taskmaster(num_tasks, self, ExceptionTask)
+ jobs = SCons.Job.Jobs(num_jobs, taskmaster)
+ jobs.start()
+ jobs.wait()
+
+ self.failIf(taskmaster.num_executed,
+ "a task was executed")
+ self.failUnless(taskmaster.num_iterated >= 1,
+ "one or more task should have been iterated")
+ self.failUnless(taskmaster.num_failed >= 1,
+ "one or more tasks should have failed")
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(ParallelTestCase())
+ suite.addTest(SerialTestCase())
+ suite.addTest(SerialExceptionTestCase())
+ suite.addTest(ParallelExceptionTestCase())
+ return suite
+
+if __name__ == "__main__":
+ runner = unittest.TextTestRunner()
+ result = runner.run(suite())
+ if (len(result.failures) == 0
+ and len(result.errors) == 1
+ and type(result.errors[0][0]) == SerialTestCase
+ and type(result.errors[0][1][0]) == NoThreadsException):
+ sys.exit(2)
+ elif not result.wasSuccessful():
+ sys.exit(1)
+
+
+
+
+
+
+
+
+
+
diff --git a/src/engine/SCons/Node/.aeignore b/src/engine/SCons/Node/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/engine/SCons/Node/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
new file mode 100644
index 0000000..e7956c2
--- /dev/null
+++ b/src/engine/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
+import SCons.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(SCons.Node.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(SCons.Node.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/engine/SCons/Node/FS/.aeignore b/src/engine/SCons/Node/FS/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/engine/SCons/Node/FS/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
new file mode 100644
index 0000000..aa8cf2a
--- /dev/null
+++ b/src/engine/SCons/Node/FSTests.py
@@ -0,0 +1,109 @@
+__revision__ = "Node/FSTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import unittest
+
+import SCons.Node.FS
+
+
+
+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):
+ """Test FS (file system) Node operations
+
+ 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)
+
+ SCons.Node.FS.init()
+
+ def Dir_test(lpath, path, abspath, up_path):
+ dir = SCons.Node.FS.lookup(SCons.Node.FS.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 = SCons.Node.FS.lookup(SCons.Node.FS.Dir, 'd1')
+
+ f1 = SCons.Node.FS.lookup(SCons.Node.FS.File, 'f1', directory = d1)
+
+ assert(f1.path == 'd1/f1')
+
+ try:
+ f2 = SCons.Node.FS.lookup(SCons.Node.FS.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 = SCons.Node.FS.lookup(SCons.Node.FS.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/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
new file mode 100644
index 0000000..2249a96
--- /dev/null
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -0,0 +1,45 @@
+__revision__ = "Node/NodeTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import unittest
+
+import SCons.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 building a node
+ """
+ node = SCons.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):
+ """Test setting a Node's Builder
+ """
+ node = SCons.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/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
new file mode 100644
index 0000000..fb77c65
--- /dev/null
+++ b/src/engine/SCons/Node/__init__.py
@@ -0,0 +1,22 @@
+"""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
+
+ def env_set(self, env):
+ self.env = env
diff --git a/src/engine/SCons/Scanner/.aeignore b/src/engine/SCons/Scanner/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/engine/SCons/Scanner/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py
new file mode 100644
index 0000000..ce6f9c6
--- /dev/null
+++ b/src/engine/SCons/Scanner/C.py
@@ -0,0 +1,87 @@
+"""SCons.Scanner.C
+
+This module implements the depenency scanner for C/C++ code.
+
+"""
+
+__revision__ = "Scanner/C.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+import SCons.Scanner
+import re
+import os.path
+
+angle_re = re.compile('^[ \t]*#[ \t]*include[ \t]+<([\\w./\\\\]+)>', re.M)
+quote_re = re.compile('^[ \t]*#[ \t]*include[ \t]+"([\\w./\\\\]+)"', re.M)
+
+def CScan():
+ "Return a Scanner instance for scanning C/C++ source files"
+ return SCons.Scanner.Scanner(scan)
+
+def find_files(filenames, paths):
+ """
+ find_files([str], [str]) -> [str]
+
+ filenames - a list of filenames to find
+ paths - a list of paths to search in
+
+ returns - the fullnames of the files
+
+ Only the first fullname found is returned for each filename, and any
+ file that aren't found are ignored.
+ """
+ fullnames = []
+ for filename in filenames:
+ for path in paths:
+ fullname = os.path.join(path, filename)
+ if os.path.exists(fullname):
+ fullnames.append(fullname)
+ break
+
+ return fullnames
+
+def scan(filename, env):
+ """
+ scan(str, Environment) -> [str]
+
+ the C/C++ dependency scanner function
+
+ This function is intentionally simple. There are two rules it
+ follows:
+
+ 1) #include <foo.h> - search for foo.h in CPPPATH followed by the
+ directory 'filename' is in
+ 2) #include \"foo.h\" - search for foo.h in the directory 'filename' is
+ in followed by CPPPATH
+
+ These rules approximate the behaviour of most C/C++ compilers.
+
+ This scanner also ignores #ifdef and other preprocessor conditionals, so
+ it may find more depencies than there really are, but it never misses
+ dependencies.
+ """
+
+ if hasattr(env, "CPPPATH"):
+ paths = env.CPPPATH
+ else:
+ paths = []
+
+ file = open(filename)
+ contents = file.read()
+ file.close()
+
+ angle_includes = angle_re.findall(contents)
+ quote_includes = quote_re.findall(contents)
+
+ source_dir = os.path.dirname(filename)
+
+ deps = (find_files(angle_includes, paths + [source_dir])
+ + find_files(quote_includes, [source_dir] + paths))
+
+ return deps
+
+
+
+
+
+
diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py
new file mode 100644
index 0000000..6a85120
--- /dev/null
+++ b/src/engine/SCons/Scanner/CTests.py
@@ -0,0 +1,131 @@
+__revision__ = "Scanner/CTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestCmd
+import SCons.Scanner.C
+import unittest
+import sys
+
+test = TestCmd.TestCmd(workdir = '')
+
+# create some source files and headers:
+
+test.write('f1.cpp',"""
+#include \"f1.h\"
+#include <f2.h>
+
+int main()
+{
+ return 0;
+}
+""")
+
+test.write('f2.cpp',"""
+#include \"d1/f1.h\"
+#include <d2/f1.h>
+#include \"f1.h\"
+#include <f4.h>
+
+int main()
+{
+ return 0;
+}
+""")
+
+test.write('f3.cpp',"""
+#include \t "f1.h"
+ \t #include "f2.h"
+# \t include "f3.h"
+
+#include \t <d1/f1.h>
+ \t #include <d1/f2.h>
+# \t include <d1/f3.h>
+
+// #include "never.h"
+
+const char* x = "#include <never.h>"
+
+int main()
+{
+ return 0;
+}
+""")
+
+
+# for Emacs -> "
+
+test.subdir('d1', ['d1', 'd2'])
+
+headers = ['f1.h','f2.h', 'f3.h', 'never.h',
+ 'd1/f1.h', 'd1/f2.h', 'd1/f3.h',
+ 'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3.h', 'd1/d2/f4.h']
+
+for h in headers:
+ test.write(h, " ")
+
+# define some helpers:
+
+class DummyEnvironment:
+ pass
+
+def deps_match(deps, headers):
+ return deps.sort() == map(test.workpath, headers).sort()
+
+# define some tests:
+
+class CScannerTestCase1(unittest.TestCase):
+ def runTest(self):
+ env = DummyEnvironment
+ s = SCons.Scanner.C.CScan()
+ deps = s.scan(test.workpath('f1.cpp'), env)
+ self.failUnless(deps_match(deps, ['f1.h', 'f2.h']))
+
+class CScannerTestCase2(unittest.TestCase):
+ def runTest(self):
+ env = DummyEnvironment
+ env.CPPPATH = [test.workpath("d1")]
+ s = SCons.Scanner.C.CScan()
+ deps = s.scan(test.workpath('f1.cpp'), env)
+ headers = ['f1.h', 'd1/f2.h']
+ self.failUnless(deps_match(deps, headers))
+
+class CScannerTestCase3(unittest.TestCase):
+ def runTest(self):
+ env = DummyEnvironment
+ env.CPPPATH = [test.workpath("d1")]
+ s = SCons.Scanner.C.CScan()
+ deps = s.scan(test.workpath('f2.cpp'), env)
+ headers = ['f1.h', 'd1/f2.h', 'd1/d2/f1.h']
+ self.failUnless(deps_match(deps, headers))
+
+
+class CScannerTestCase4(unittest.TestCase):
+ def runTest(self):
+ env = DummyEnvironment
+ env.CPPPATH = [test.workpath("d1"), test.workpath("d1/d2")]
+ s = SCons.Scanner.C.CScan()
+ deps = s.scan(test.workpath('f2.cpp'), env)
+ headers = ['f1.h', 'd1/f2.h', 'd1/d2/f1.h', 'd1/d2/f4.h']
+ self.failUnless(deps_match(deps, headers))
+
+class CScannerTestCase5(unittest.TestCase):
+ def runTest(self):
+ env = DummyEnvironment
+ s = SCons.Scanner.C.CScan()
+ deps = s.scan(test.workpath('f3.cpp'), env)
+ headers = ['f1.h', 'f2.h', 'f3.h', 'd1/f1.h', 'd1/f2.h', 'd1/f3.h']
+ self.failUnless(deps_match(deps, headers))
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(CScannerTestCase1())
+ suite.addTest(CScannerTestCase2())
+ suite.addTest(CScannerTestCase3())
+ suite.addTest(CScannerTestCase4())
+ suite.addTest(CScannerTestCase5())
+ return suite
+
+if __name__ == "__main__":
+ runner = unittest.TextTestRunner()
+ result = runner.run(suite())
+ if not result.wasSuccessful():
+ sys.exit(1)
diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py
new file mode 100644
index 0000000..9c7163d
--- /dev/null
+++ b/src/engine/SCons/Scanner/ScannerTests.py
@@ -0,0 +1,84 @@
+__revision__ = "Scanner/ScannerTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import unittest
+import SCons.Scanner
+import sys
+
+class ScannerTestBase:
+
+ def func(self, filename, env, *args):
+ self.filename = filename
+ self.env = env
+
+ if len(args) > 0:
+ self.arg = args[0]
+
+ return self.deps
+
+
+ def test(self, scanner, env, filename, deps, *args):
+ self.deps = deps
+ deps = scanner.scan(filename, env)
+
+ self.failUnless(self.filename == filename, "the filename was passed incorrectly")
+ self.failUnless(self.env == env, "the environment was passed incorrectly")
+ self.failUnless(self.deps == deps, "the dependencies were returned incorrectly")
+
+ if len(args) > 0:
+ self.failUnless(self.arg == args[0], "the argument was passed incorrectly")
+ else:
+ self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been")
+
+class DummyEnvironment:
+ pass
+
+
+class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase):
+ "Test the Scanner class using the position argument"
+ def runTest(self):
+ s = SCons.Scanner.Scanner(self.func)
+ env = DummyEnvironment()
+ env.VARIABLE = "var1"
+ self.test(s, env, 'f1.cpp', ['f1.h', 'f1.hpp'])
+
+class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase):
+ "Test the Scanner class using the keyword argument"
+ def runTest(self):
+ s = SCons.Scanner.Scanner(function = self.func)
+ env = DummyEnvironment()
+ env.VARIABLE = "var2"
+ self.test(s, env, 'f2.cpp', ['f2.h', 'f2.hpp'])
+
+class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase):
+ "Test the Scanner class using the position argument and optional argument"
+ def runTest(self):
+ arg = "this is the argument"
+ s = SCons.Scanner.Scanner(self.func, arg)
+ env = DummyEnvironment()
+ env.VARIABLE = "var3"
+ self.test(s, env, 'f3.cpp', ['f3.h', 'f3.hpp'], arg)
+
+class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase):
+ "Test the Scanner class using the keyword argument and optional argument"
+ def runTest(self):
+ arg = "this is another argument"
+ s = SCons.Scanner.Scanner(function = self.func, argument = arg)
+ env = DummyEnvironment()
+ env.VARIABLE = "var4"
+ self.test(s, env, 'f4.cpp', ['f4.h', 'f4.hpp'], arg)
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(ScannerPositionalTestCase())
+ suite.addTest(ScannerKeywordTestCase())
+ suite.addTest(ScannerPositionalArgumentTestCase())
+ suite.addTest(ScannerKeywordArgumentTestCase())
+ return suite
+
+if __name__ == "__main__":
+ runner = unittest.TextTestRunner()
+ result = runner.run(suite())
+ if not result.wasSuccessful():
+ sys.exit(1)
+
+
diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py
new file mode 100644
index 0000000..225c7f3
--- /dev/null
+++ b/src/engine/SCons/Scanner/__init__.py
@@ -0,0 +1,69 @@
+"""SCons.Scanner
+
+The Scanner package for the SCons software construction utility.
+
+"""
+
+__revision__ = "Scanner/__init__.py __REVISION__ __DATE__ __DEVELOPER__"
+
+__version__ = "__VERSION__"
+
+class _Null:
+ pass
+
+# This is used instead of None as a default argument value so None can be
+# used as an actual argument value.
+_null = _Null
+
+class Scanner:
+
+ def __init__(self, function, argument=_null):
+ """
+ Construct a new scanner object given a scanner function.
+
+ 'function' - a scanner function taking two or three arguments and
+ returning a list of strings.
+
+ 'argument' - an optional argument that will be passed to the
+ scanner function if it is given.
+
+ The scanner function's first argument will be the name of a file
+ that should be scanned for dependencies, the second argument will
+ be an Environment object, the third argument will be the value
+ passed into 'argument', and the returned list should contain the
+ file names of all the direct dependencies of the file.
+
+ Examples:
+
+ s = Scanner(my_scanner_function)
+
+ s = Scanner(function = my_scanner_function)
+
+ s = Scanner(function = my_scanner_function, argument = 'foo')
+
+ """
+
+ # Note: this class could easily work with scanner functions that take
+ # something other than a filename as an argument (e.g. a database
+ # node) and a dependencies list that aren't file names. All that
+ # would need to be changed is the documentation.
+
+ self.function = function
+ self.argument = argument
+
+ def scan(self, filename, env):
+ """
+ This method does the actually scanning. 'filename' is the filename
+ that will be passed to the scanner function, and 'env' is the
+ environment that will be passed to the scanner function. A list of
+ dependencies will be returned.
+ """
+
+ if not self.argument is _null:
+ return self.function(filename, env, self.argument)
+ else:
+ return self.function(filename, env)
+
+
+
+
diff --git a/src/engine/SCons/Sig/.aeignore b/src/engine/SCons/Sig/.aeignore
new file mode 100644
index 0000000..43fe851
--- /dev/null
+++ b/src/engine/SCons/Sig/.aeignore
@@ -0,0 +1,4 @@
+*,D
+*.pyc
+.*.swp
+.consign
diff --git a/src/engine/SCons/Sig/MD5.py b/src/engine/SCons/Sig/MD5.py
new file mode 100644
index 0000000..9d461d9
--- /dev/null
+++ b/src/engine/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/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py
new file mode 100644
index 0000000..ac67f1d
--- /dev/null
+++ b/src/engine/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 deciding if an object is up-to-date
+
+ Simple comparison of 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 collecting a list of 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/engine/SCons/Sig/TimeStamp.py b/src/engine/SCons/Sig/TimeStamp.py
new file mode 100644
index 0000000..b821742
--- /dev/null
+++ b/src/engine/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/engine/SCons/Sig/TimeStampTests.py b/src/engine/SCons/Sig/TimeStampTests.py
new file mode 100644
index 0000000..4cc72e4
--- /dev/null
+++ b/src/engine/SCons/Sig/TimeStampTests.py
@@ -0,0 +1,74 @@
+__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 deciding if an object is up-to-date
+
+ Simple comparison of 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 collecting a list of signatures into a new signature value
+ 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/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py
new file mode 100644
index 0000000..c3b06ed
--- /dev/null
+++ b/src/engine/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/engine/SCons/__init__.py b/src/engine/SCons/__init__.py
new file mode 100644
index 0000000..56c5fad
--- /dev/null
+++ b/src/engine/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__"
diff --git a/src/engine/SCons/exitfuncs.py b/src/engine/SCons/exitfuncs.py
new file mode 100644
index 0000000..86d1990
--- /dev/null
+++ b/src/engine/SCons/exitfuncs.py
@@ -0,0 +1,48 @@
+"""SCons.exitfuncs
+
+Register functions which are executed when SCons exits for any reason.
+
+"""
+
+__revision__ = "exitfuncs.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+
+_exithandlers = []
+def _run_exitfuncs():
+ """run any registered exit functions
+
+ _exithandlers is traversed in reverse order so functions are executed
+ last in, first out.
+ """
+
+ while _exithandlers:
+ func, targs, kargs = _exithandlers.pop()
+ apply(func, targs, kargs)
+
+def register(func, *targs, **kargs):
+ """register a function to be executed upon normal program termination
+
+ func - function to be called at exit
+ targs - optional arguments to pass to func
+ kargs - optional keyword arguments to pass to func
+ """
+ _exithandlers.append((func, targs, kargs))
+
+import sys
+
+try:
+ x = sys.exitfunc
+
+ # if x isn't our own exit func executive, assume it's another
+ # registered exit function - append it to our list...
+ if x != _run_exitfuncs:
+ register(x)
+
+except AttributeError:
+ pass
+
+# make our exit function get run by python when it exits:
+sys.exitfunc = _run_exitfuncs
+
+del sys
diff --git a/src/engine/setup.py b/src/engine/setup.py
new file mode 100644
index 0000000..10fbef5
--- /dev/null
+++ b/src/engine/setup.py
@@ -0,0 +1,16 @@
+__revision__ = "setup.py __REVISION__ __DATE__ __DEVELOPER__"
+
+from string import join, split
+
+from distutils.core import setup
+
+setup(name = "scons-pylib",
+ version = "__VERSION__",
+ description = "scons",
+ author = "Steven Knight",
+ author_email = "knight@baldmt.com",
+ url = "http://www.scons.org/",
+ packages = ["SCons",
+ "SCons.Node",
+ "SCons.Scanner",
+ "SCons.Sig"])