From 3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 17 Sep 2001 04:57:00 +0000 Subject: Run setup.py on the unpacked .tar.gz for testing. --- Construct | 181 ++++++---- doc/Conscript | 15 +- etc/Conscript | 4 +- etc/TestSCons.py | 5 + runtest.py | 16 +- src/MANIFEST | 17 - src/engine/MANIFEST | 16 + src/engine/SCons/.aeignore | 4 + src/engine/SCons/Builder.py | 100 ++++++ src/engine/SCons/BuilderTests.py | 113 +++++++ src/engine/SCons/Defaults.py | 22 ++ src/engine/SCons/Environment.py | 133 ++++++++ src/engine/SCons/EnvironmentTests.py | 144 ++++++++ src/engine/SCons/Errors.py | 18 + src/engine/SCons/ErrorsTests.py | 28 ++ src/engine/SCons/Job.py | 239 +++++++++++++ src/engine/SCons/JobTests.py | 228 +++++++++++++ src/engine/SCons/Node/.aeignore | 4 + src/engine/SCons/Node/FS.py | 139 ++++++++ src/engine/SCons/Node/FS/.aeignore | 4 + src/engine/SCons/Node/FSTests.py | 109 ++++++ src/engine/SCons/Node/NodeTests.py | 45 +++ src/engine/SCons/Node/__init__.py | 22 ++ src/engine/SCons/Scanner/.aeignore | 4 + src/engine/SCons/Scanner/C.py | 87 +++++ src/engine/SCons/Scanner/CTests.py | 131 ++++++++ src/engine/SCons/Scanner/ScannerTests.py | 84 +++++ src/engine/SCons/Scanner/__init__.py | 69 ++++ src/engine/SCons/Sig/.aeignore | 4 + src/engine/SCons/Sig/MD5.py | 70 ++++ src/engine/SCons/Sig/MD5Tests.py | 76 +++++ src/engine/SCons/Sig/TimeStamp.py | 49 +++ src/engine/SCons/Sig/TimeStampTests.py | 74 ++++ src/engine/SCons/Sig/__init__.py | 7 + src/engine/SCons/__init__.py | 9 + src/engine/SCons/exitfuncs.py | 48 +++ src/engine/setup.py | 16 + src/scons.py | 553 ------------------------------ src/scons/.aeignore | 4 - src/scons/Builder.py | 100 ------ src/scons/BuilderTests.py | 113 ------- src/scons/Defaults.py | 20 -- src/scons/Environment.py | 133 -------- src/scons/EnvironmentTests.py | 144 -------- src/scons/Errors.py | 18 - src/scons/ErrorsTests.py | 28 -- src/scons/Job.py | 239 ------------- src/scons/JobTests.py | 228 ------------- src/scons/Node/.aeignore | 4 - src/scons/Node/FS.py | 139 -------- src/scons/Node/FS/.aeignore | 4 - src/scons/Node/FSTests.py | 109 ------ src/scons/Node/NodeTests.py | 45 --- src/scons/Node/__init__.py | 22 -- src/scons/Scanner/.aeignore | 4 - src/scons/Scanner/C.py | 87 ----- src/scons/Scanner/CTests.py | 131 -------- src/scons/Scanner/ScannerTests.py | 84 ----- src/scons/Scanner/__init__.py | 69 ---- src/scons/Sig/.aeignore | 4 - src/scons/Sig/MD5.py | 70 ---- src/scons/Sig/MD5Tests.py | 76 ----- src/scons/Sig/TimeStamp.py | 49 --- src/scons/Sig/TimeStampTests.py | 74 ---- src/scons/Sig/__init__.py | 7 - src/scons/__init__.py | 9 - src/scons/exitfuncs.py | 48 --- src/script/MANIFEST | 3 + src/script/scons.py | 560 +++++++++++++++++++++++++++++++ src/script/setup.py | 13 + src/setup.py | 14 - test/exitfns.py | 2 +- 72 files changed, 2814 insertions(+), 2727 deletions(-) delete mode 100644 src/MANIFEST create mode 100644 src/engine/MANIFEST create mode 100644 src/engine/SCons/.aeignore create mode 100644 src/engine/SCons/Builder.py create mode 100644 src/engine/SCons/BuilderTests.py create mode 100644 src/engine/SCons/Defaults.py create mode 100644 src/engine/SCons/Environment.py create mode 100644 src/engine/SCons/EnvironmentTests.py create mode 100644 src/engine/SCons/Errors.py create mode 100644 src/engine/SCons/ErrorsTests.py create mode 100644 src/engine/SCons/Job.py create mode 100644 src/engine/SCons/JobTests.py create mode 100644 src/engine/SCons/Node/.aeignore create mode 100644 src/engine/SCons/Node/FS.py create mode 100644 src/engine/SCons/Node/FS/.aeignore create mode 100644 src/engine/SCons/Node/FSTests.py create mode 100644 src/engine/SCons/Node/NodeTests.py create mode 100644 src/engine/SCons/Node/__init__.py create mode 100644 src/engine/SCons/Scanner/.aeignore create mode 100644 src/engine/SCons/Scanner/C.py create mode 100644 src/engine/SCons/Scanner/CTests.py create mode 100644 src/engine/SCons/Scanner/ScannerTests.py create mode 100644 src/engine/SCons/Scanner/__init__.py create mode 100644 src/engine/SCons/Sig/.aeignore create mode 100644 src/engine/SCons/Sig/MD5.py create mode 100644 src/engine/SCons/Sig/MD5Tests.py create mode 100644 src/engine/SCons/Sig/TimeStamp.py create mode 100644 src/engine/SCons/Sig/TimeStampTests.py create mode 100644 src/engine/SCons/Sig/__init__.py create mode 100644 src/engine/SCons/__init__.py create mode 100644 src/engine/SCons/exitfuncs.py create mode 100644 src/engine/setup.py delete mode 100644 src/scons.py delete mode 100644 src/scons/.aeignore delete mode 100644 src/scons/Builder.py delete mode 100644 src/scons/BuilderTests.py delete mode 100644 src/scons/Defaults.py delete mode 100644 src/scons/Environment.py delete mode 100644 src/scons/EnvironmentTests.py delete mode 100644 src/scons/Errors.py delete mode 100644 src/scons/ErrorsTests.py delete mode 100644 src/scons/Job.py delete mode 100644 src/scons/JobTests.py delete mode 100644 src/scons/Node/.aeignore delete mode 100644 src/scons/Node/FS.py delete mode 100644 src/scons/Node/FS/.aeignore delete mode 100644 src/scons/Node/FSTests.py delete mode 100644 src/scons/Node/NodeTests.py delete mode 100644 src/scons/Node/__init__.py delete mode 100644 src/scons/Scanner/.aeignore delete mode 100644 src/scons/Scanner/C.py delete mode 100644 src/scons/Scanner/CTests.py delete mode 100644 src/scons/Scanner/ScannerTests.py delete mode 100644 src/scons/Scanner/__init__.py delete mode 100644 src/scons/Sig/.aeignore delete mode 100644 src/scons/Sig/MD5.py delete mode 100644 src/scons/Sig/MD5Tests.py delete mode 100644 src/scons/Sig/TimeStamp.py delete mode 100644 src/scons/Sig/TimeStampTests.py delete mode 100644 src/scons/Sig/__init__.py delete mode 100644 src/scons/__init__.py delete mode 100644 src/scons/exitfuncs.py create mode 100644 src/script/MANIFEST create mode 100644 src/script/scons.py create mode 100644 src/script/setup.py delete mode 100644 src/setup.py diff --git a/Construct b/Construct index d12673f..a7e6a77 100644 --- a/Construct +++ b/Construct @@ -5,11 +5,6 @@ # $project = 'scons'; -$env = new cons( ENV => { - AEGIS_PROJECT => $ENV{AEGIS_PROJECT}, - PATH => $ENV{PATH}, - } ); - Default qw( . ); # @@ -65,74 +60,140 @@ pop @arr if $#arr >= 2; map {s/^[CD]//, s/^0*(\d\d)$/$1/} @arr; $version = join('.', @arr); -# -# Use %(-%) around the date so date changes don't cause rebuilds. -# -$sed_cmd = "sed" . - " %( -e 's+__DATE__+$date+' %)" . - " -e 's+__DEVELOPER__+$developer+'" . - " -e 's+__REVISION__+$revision+'" . - " -e 's+__VERSION__+$version+'" . - " %< > %>"; +chomp($python_ver = `python -c 'import sys; print sys.version[0:3]'`); -# -# Run everything in the MANIFEST through the sed command we concocted. -# -chomp(@files = `cat src/MANIFEST`); -foreach $file (@files) { - Command $env "build/$file", "src/$file", $sed_cmd; -} +use Cwd; +$test_dir = File::Spec->catfile(cwd, "build", "test"); -# -# Use the Python distutils to generate the packages. -# -$tar_gz = "build/dist/$project-$version.tar.gz"; +%package_name = ( + 'script' => $project, + 'engine' => "$project-pylib", +); -@setup_args = ('bdist sdist'); +$test_bin_dir = File::Spec->catfile($test_dir, "bin"); +$test_lib_dir = File::Spec->catfile($test_dir, + "lib", + "python${python_ver}", + "site-packages"), -@targets = ( - "build/dist/$project-$version.linux-i686.tar.gz", - $tar_gz, +%install_dir = ( + 'script' => $test_bin_dir, + 'engine' => $test_lib_dir, ); -if ($rpm) { - push(@setup_args, 'bdist_rpm'); +$env = new cons( ENV => { + AEGIS_PROJECT => $ENV{AEGIS_PROJECT}, + PATH => $ENV{PATH}, + }, - push(@targets, - "build/build/bdist.linux-i686/rpm/SOURCES/$project-$version.tar.gz", - "build/build/bdist.linux-i686/rpm/SPECS/$project.spec", - "build/dist/$project-$version-1.src.rpm", - "build/dist/$project-$version-1.noarch.rpm", - ); -}; + TEST_BIN_DIR => $test_bin_dir, + TEST_LIB_DIR => $test_lib_dir, + + DATE => $date, + DEVELOPER => $developer, + REVISION => $revision, + VERSION => $version, + + SED => 'sed', + # Use %(-%) around the date so date + # changes don't cause rebuilds. + SEDFLAGS => " %( -e 's+__DATE__+%DATE+' %)" . + " -e 's+__DEVELOPER__+%DEVELOPER+'" . + " -e 's+__REVISION__+%REVISION+'" . + " -e 's+__VERSION__+%VERSION+'", + SEDCOM => "%SED %SEDFLAGS %< > %>", + ); + +my @src_deps; +for $dir ('script', 'engine') { -$env->Command([@targets], - map("build/$_", @files), - qq(rm -rf build/build build/dist/* - cd build && python setup.py @setup_args) + my $pkg = $package_name{$dir}; + my $install = $install_dir{$dir}; + + my $build = "build/$dir"; + my $src = "src/$dir"; + + my @files; + chomp(@files = `cat src/$dir/MANIFEST`); + + # + # Run everything in the MANIFEST through the sed command we concocted. + # + my $file; + foreach $file (@files) { + $env->Command("$build/$file", "$src/$file", "%SEDCOM"); + } + + # + # Use the Python distutils to generate the packages. + # + my $tar_gz = "$build/dist/$pkg-$version.tar.gz"; + + push(@src_deps, $tar_gz); + + my @setup_args = ('bdist sdist'); + + my @targets = ( + "$build/dist/$pkg-$version.linux-i686.tar.gz", + $tar_gz, + ); + + if ($rpm) { + push(@setup_args, 'bdist_rpm'); + + # XXX "$build/build/bdist.linux-i686/rpm/SOURCES/$pkg-$version.tar.gz", + # XXX "$build/build/bdist.linux-i686/rpm/SPECS/$pkg.spec", + push(@targets, + "$build/dist/$pkg-$version-1.src.rpm", + "$build/dist/$pkg-$version-1.noarch.rpm", + ); + }; + + $env->Command([@targets], + map("$build/$_", @files), + qq(rm -rf $build/build $build/dist/* + cd $build && python setup.py @setup_args) ); -$env->Depends([@targets], 'build/MANIFEST'); + $env->Depends([@targets], "$build/MANIFEST"); -# -# Unpack the .tar.gz created by the distutils into build/test, and -# add the TestCmd.py module. The runtest.py script will set PYTHONPATH -# so that the tests only look under build/test. This makes sure that -# our tests pass with what we really packaged, not because of something -# hanging around in the development directory. -# -$test_dir = "build/test"; + $env->Install("build/dist", @targets); -@test_files = map("$test_dir/$project-$version/$_", @files); + # + # Unpack the .tar.gz created by the distutils into build/unpack. + # + my $unpack = "build/unpack"; -Command $env [@test_files], $tar_gz, qq( - rm -rf $test_dir/$project-$version - tar zxf %< -C $test_dir -); + my @unpack_files = map("$unpack/$pkg-$version/$_", @files); -Export qw( env test_dir ); + Command $env [@unpack_files], $tar_gz, qq( + rm -rf $unpack/$pkg-$version + tar zxf %< -C $unpack + ); + + # + # Run setup.py in the unpacked subdirectory to "install" everything + # into our build/test subdirectory. Auxiliary modules that we need + # (TestCmd.py, TestSCons.py, unittest.py) will be copied in by + # etc/Conscript. The runtest.py script will set PYTHONPATH so that + # the tests only look under build/test. This makes sure that our + # tests pass with what we really packaged, not because of something + # hanging around in the development directory. + # + my %seen; + map($seen{$_}++, "MANIFEST", "setup.py"); + @test_files = map(File::Spec->catfile($install, $_), + grep($_ =~ /\.py$/ && ! $seen{$_}++, @files)); + + Command $env [@test_files], @unpack_files, qq( + rm -rf $install + cd $unpack/$pkg-$version && python setup.py install --prefix=$test_dir + ); +} + +Export qw( env ); Build "etc/Conscript"; @@ -142,8 +203,6 @@ Build "etc/Conscript"; if ($jw) { Link 'build/doc' => 'doc'; - Export qw( date env revision version ); - Build 'build/doc/Conscript'; } @@ -172,11 +231,11 @@ if ($change) { chomp(@src_files); foreach $file (@src_files) { - $env->Command("build/$project-src/$file", $file, $sed_cmd); + $env->Command("build/$project-src/$file", $file, "%SEDCOM"); } $env->Command("build/dist/$project-src-$version.tar.gz", - $tar_gz, + @src_deps, map("build/$project-src/$_", @src_files), qq( rm -rf build/$project-src-$version diff --git a/doc/Conscript b/doc/Conscript index 0ca7999..9e5f0d9 100644 --- a/doc/Conscript +++ b/doc/Conscript @@ -2,17 +2,12 @@ # Conscript file for building SCons documentation. # -Import qw( - date - env - revision - version -); +Import qw( env ); # # # -$doc_tar_gz = "#build/dist/scons-doc-$version.tar.gz"; +$doc_tar_gz = "#build/dist/scons-doc-${\$env->{VERSION}}.tar.gz"; # # We'll only try to build text files (for some documents) @@ -33,9 +28,9 @@ print FILE <<_EOF_; - - - +{DATE}}"> +{VERSION}}"> +{REVISION}}"> _EOF_ close(FILE); diff --git a/etc/Conscript b/etc/Conscript index 5c4efc5..c1a7942 100644 --- a/etc/Conscript +++ b/etc/Conscript @@ -4,8 +4,8 @@ # of stuff to work on SCons. # -Import qw( env test_dir ); +Import qw( env ); @modules = qw(TestCmd.py TestSCons.py unittest.py); -$env->Install("#$test_dir", @modules); +$env->Install($env->{TEST_LIB_DIR}, @modules); diff --git a/etc/TestSCons.py b/etc/TestSCons.py index b1eeae6..dcd462a 100644 --- a/etc/TestSCons.py +++ b/etc/TestSCons.py @@ -84,6 +84,11 @@ class TestSCons(TestCmd.TestCmd): print "STDERR ============" print self.stderr() raise + if self.status: + print self.progam + " returned " + (self.status >> 8) + print "STDERR ============" + print self.stderr() + raise TestFailed if stdout and not self.match(self.stdout(), stdout): print "Expected STDOUT ==========" print stdout diff --git a/runtest.py b/runtest.py index 965e24c..9542e78 100644 --- a/runtest.py +++ b/runtest.py @@ -100,18 +100,20 @@ if build == 'aegis': version = aegis_to_version(version) - build_test = os.path.join(cwd, "build", "test") - scons_dir = os.path.join(build_test, "scons-" + version) + scons_dir = os.path.join(cwd, 'build', 'test', 'bin') - os.environ['PYTHONPATH'] = string.join([scons_dir, - build_test], - os.pathsep) + os.environ['PYTHONPATH'] = os.path.join(cwd, + 'build', + 'test', + 'lib', + 'python' + sys.version[0:3], + 'site-packages') else: - scons_dir = os.path.join(cwd, 'src') + scons_dir = os.path.join(cwd, 'src', 'script') - os.environ['PYTHONPATH'] = string.join([os.path.join(cwd, 'src'), + os.environ['PYTHONPATH'] = string.join([os.path.join(cwd, 'src', 'engine'), os.path.join(cwd, 'etc')], os.pathsep) diff --git a/src/MANIFEST b/src/MANIFEST deleted file mode 100644 index 33e888c..0000000 --- a/src/MANIFEST +++ /dev/null @@ -1,17 +0,0 @@ -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 -scons.py -setup.py 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 - 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 + +int main() +{ + return 0; +} +""") + +test.write('f2.cpp',""" +#include \"d1/f1.h\" +#include +#include \"f1.h\" +#include + +int main() +{ + return 0; +} +""") + +test.write('f3.cpp',""" +#include \t "f1.h" + \t #include "f2.h" +# \t include "f3.h" + +#include \t + \t #include +# \t include + +// #include "never.h" + +const char* x = "#include " + +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"]) diff --git a/src/scons.py b/src/scons.py deleted file mode 100644 index 78cfece..0000000 --- a/src/scons.py +++ /dev/null @@ -1,553 +0,0 @@ -#! /usr/bin/env python -# -# SCons -# - -__revision__ = "scons.py __REVISION__ __DATE__ __DEVELOPER__" - -import getopt -import os.path -import string -import sys -import traceback - -from scons.Node.FS import init, Dir, File, lookup -from scons.Environment import Environment -import scons.Job -from scons.Builder import Builder -from scons.Errors import * - -class Task: - "XXX: this is here only until the build engine is implemented" - - def __init__(self, target): - self.target = target - - def execute(self): - self.target.build() - - - -class Taskmaster: - "XXX: this is here only until the build engine is implemented" - - def __init__(self, targets): - self.targets = targets - self.num_iterated = 0 - - - def next_task(self): - if self.num_iterated == len(self.targets): - return None - else: - current = self.num_iterated - self.num_iterated = self.num_iterated + 1 - return Task(self.targets[current]) - - def is_blocked(self): - return 0 - - def executed(self, task): - pass - - def failed(self, task): - pass - - -# Global variables - -local_help = None -num_jobs = 1 -Scripts = [] -include_dirs = [] - -# utility functions - -def _scons_syntax_error(e): - """Handle syntax errors. Print out a message and show where the error - occurred. - """ - etype, value, tb = sys.exc_info() - lines = traceback.format_exception_only(etype, value) - for line in lines: - sys.stderr.write(line+'\n') - -def _scons_user_error(e): - """Handle user errors. Print out a message and a description of the - error, along with the line number and routine where it occured. - """ - print 'user error' - etype, value, tb = sys.exc_info() - while tb.tb_next is not None: - tb = tb.tb_next - lineno = traceback.tb_lineno(tb) - filename = tb.tb_frame.f_code.co_filename - routine = tb.tb_frame.f_code.co_name - sys.stderr.write("\nSCons error: %s\n" % value) - sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) - -def _scons_other_errors(): - """Handle all errors but user errors. Print out a message telling - the user what to do in this case and print a normal trace. - """ - print 'other errors' - traceback.print_exc() - - - -def Conscript(filename): - global Scripts - Scripts.append(filename) - -def Help(text): - global local_help - if local_help: - print text - print "Use scons -H for help about command-line options." - sys.exit(0) - - - -# -# After options are initialized, the following variables are -# filled in: -# -option_list = [] # list of Option objects -short_opts = "" # string of short (single-character) options -long_opts = [] # array of long (--) options -opt_func = {} # mapping of option strings to functions - -def options_init(): - """Initialize command-line options processing. - - This is in a subroutine mainly so we can easily single-step over - it in the debugger. - """ - - class Option: - """Class for command-line option information. - - This exists to provide a central location for everything - describing a command-line option, so that we can change - options without having to update the code to handle the - option in one place, the -h help message in another place, - etc. There are no methods here, only attributes. - - You can initialize an Option with the following: - - func The function that will be called when this - option is processed on the command line. - Calling sequence is: - - func(opt, arg) - - If there is no func, then this Option probably - stores an optstring to be printed. - - helpline - The string to be printed in -h output. If no - helpline is specified but a help string is - specified (the usual case), a helpline will be - constructed automatically from the short, long, - arg, and help attributes. (In practice, then, - setting helpline without setting func allows you - to print arbitrary lines of text in the -h - output.) - - short The string for short, single-hyphen - command-line options. - Do not include the hyphen: - - 'a' for -a, 'xy' for -x and -y, etc. - - long An array of strings for long, double-hyphen - command-line options. Do not include - the hyphens: - - ['my-option', 'verbose'] - - arg If this option takes an argument, this string - specifies how you want it to appear in the - -h output ('DIRECTORY', 'FILE', etc.). - - help The help string that will be printed for - this option in the -h output. Must be - 49 characters or fewer. - - future If non-zero, this indicates that this feature - will be supported in a future release, not - the currently planned one. SCons will - recognize the option, but it won't show up - in the -h output. - - The following attribute is derived from the supplied attributes: - - optstring - A string, with hyphens, describing the flags - for this option, as constructed from the - specified short, long and arg attributes. - - All Option objects are stored in the global option_list list, - in the order in which they're created. This is the list - that's used to generate -h output, so the order in which the - objects are created is the order in which they're printed. - - The upshot is that specifying a command-line option and having - everything work correctly is a matter of defining a function to - process its command-line argument (set the right flag, update - the right value), and then creating an appropriate Option object - at the correct point in the code below. - """ - - def __init__(self, func = None, helpline = None, - short = None, long = None, arg = None, - help = None, future = None): - self.func = func - self.short = short - self.long = long - self.arg = arg - self.help = help - opts = [] - if self.short: - for c in self.short: - if arg: - c = c + " " + arg - opts = opts + ['-' + c] - if self.long: - l = self.long - if arg: - l = map(lambda x,a=arg: x + "=" + a, self.long) - opts = opts + map(lambda x: '--' + x, l) - self.optstring = string.join(opts, ', ') - if helpline: - self.helpline = helpline - elif help and not future: - if len(self.optstring) <= 26: - sep = " " * (28 - len(self.optstring)) - else: - sep = self.helpstring = "\n" + " " * 30 - self.helpline = " " + self.optstring + sep + self.help - else: - self.helpline = None - global option_list - option_list.append(self) - - # Generic routine for to-be-written options, used by multiple - # options below. - - def opt_not_yet(opt, arg): - sys.stderr.write("Warning: the %s option is not yet implemented\n" - % opt) - - # In the following instantiations, the help string should be no - # longer than 49 characters. Use the following as a guide: - # help = "1234567890123456789012345678901234567890123456789" - - def opt_ignore(opt, arg): - sys.stderr.write("Warning: ignoring %s option\n" % opt) - - Option(func = opt_ignore, - short = 'bmSt', long = ['no-keep-going', 'stop', 'touch'], - help = "Ignored for compatibility.") - - Option(func = opt_not_yet, - short = 'c', long = ['clean', 'remove'], - help = "Remove specified targets and dependencies.") - - Option(func = opt_not_yet, future = 1, - long = ['cache-disable', 'no-cache'], - help = "Do not retrieve built targets from Cache.") - - Option(func = opt_not_yet, future = 1, - long = ['cache-force', 'cache-populate'], - help = "Copy already-built targets into the Cache.") - - Option(func = opt_not_yet, future = 1, - long = ['cache-show'], - help = "Print what would have built Cached targets.") - - def opt_C(opt, arg): - try: - os.chdir(arg) - except: - sys.stderr.write("Could not change directory to 'arg'\n") - - Option(func = opt_C, - short = 'C', long = ['directory'], arg = 'DIRECTORY', - help = "Change to DIRECTORY before doing anything.") - - Option(func = opt_not_yet, - short = 'd', - help = "Print file dependency information.") - - Option(func = opt_not_yet, future = 1, - long = ['debug'], arg = 'FLAGS', - help = "Print various types of debugging information.") - - Option(func = opt_not_yet, future = 1, - short = 'e', long = ['environment-overrides'], - help = "Environment variables override makefiles.") - - def opt_f(opt, arg): - global Scripts - Scripts.append(arg) - - Option(func = opt_f, - short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE', - help = "Read FILE as the top-level SConstruct file.") - - def opt_help(opt, arg): - global local_help - local_help = 1 - - Option(func = opt_help, - short = 'h', long = ['help'], - help = "Print defined help message, or this one.") - - def opt_help_options(opt, arg): - PrintUsage() - sys.exit(0) - - Option(func = opt_help_options, - short = 'H', long = ['help-options'], - help = "Print this message and exit.") - - Option(func = opt_not_yet, - short = 'i', long = ['ignore-errors'], - help = "Ignore errors from build actions.") - - def opt_I(opt, arg): - global include_dirs - include_dirs = include_dirs + [arg] - - Option(func = opt_I, - short = 'I', long = ['include-dir'], arg = 'DIRECTORY', - help = "Search DIRECTORY for imported Python modules.") - - def opt_j(opt, arg): - global num_jobs - try: - num_jobs = int(arg) - except: - PrintUsage() - sys.exit(1) - - if num_jobs <= 0: - PrintUsage() - sys.exit(1) - - Option(func = opt_j, - short = 'j', long = ['jobs'], arg = 'N', - help = "Allow N jobs at once.") - - Option(func = opt_not_yet, - short = 'k', long = ['keep-going'], - help = "Keep going when a target can't be made.") - - Option(func = opt_not_yet, future = 1, - short = 'l', long = ['load-average', 'max-load'], arg = 'N', - help = "Don't start multiple jobs unless load is below N.") - - Option(func = opt_not_yet, future = 1, - long = ['list-derived'], - help = "Don't build; list files that would be built.") - - Option(func = opt_not_yet, future = 1, - long = ['list-actions'], - help = "Don't build; list files and build actions.") - - Option(func = opt_not_yet, future = 1, - long = ['list-where'], - help = "Don't build; list files and where defined.") - - def opt_n(opt, arg): - scons.Builder.execute_actions = None - - Option(func = opt_n, - short = 'n', long = ['no-exec', 'just-print', 'dry-run', 'recon'], - help = "Don't build; just print commands.") - - Option(func = opt_not_yet, future = 1, - short = 'o', long = ['old-file', 'assume-old'], arg = 'FILE', - help = "Consider FILE to be old; don't rebuild it.") - - Option(func = opt_not_yet, future = 1, - long = ['override'], arg = 'FILE', - help = "Override variables as specified in FILE.") - - Option(func = opt_not_yet, future = 1, - short = 'p', - help = "Print internal environments/objects.") - - Option(func = opt_not_yet, future = 1, - short = 'q', long = ['question'], - help = "Don't build; exit status says if up to date.") - - Option(func = opt_not_yet, future = 1, - short = 'rR', long = ['no-builtin-rules', 'no-builtin-variables'], - help = "Clear default environments and variables.") - - Option(func = opt_not_yet, future = 1, - long = ['random'], - help = "Build dependencies in random order.") - - def opt_s(opt, arg): - scons.Builder.print_actions = None - - Option(func = opt_s, - short = 's', long = ['silent', 'quiet'], - help = "Don't print commands.") - - Option(func = opt_not_yet, future = 1, - short = 'u', long = ['up', 'search-up'], - help = "Search up directory tree for SConstruct.") - - def option_v(opt, arg): - print "SCons version __VERSION__, by Steven Knight et al." - print "Copyright 2001 Steven Knight" - sys.exit(0) - - Option(func = option_v, - short = 'v', long = ['version'], - help = "Print the SCons version number and exit.") - - Option(func = opt_not_yet, future = 1, - short = 'w', long = ['print-directory'], - help = "Print the current directory.") - - Option(func = opt_not_yet, future = 1, - long = ['no-print-directory'], - help = "Turn off -w, even if it was turned on implicitly.") - - Option(func = opt_not_yet, future = 1, - long = ['write-filenames'], arg = 'FILE', - help = "Write all filenames examined into FILE.") - - Option(func = opt_not_yet, future = 1, - short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE', - help = "Consider FILE to be changed.") - - Option(func = opt_not_yet, future = 1, - long = ['warn-undefined-variables'], - help = "Warn when an undefined variable is referenced.") - - Option(func = opt_not_yet, future = 1, - short = 'Y', long = ['repository'], arg = 'REPOSITORY', - help = "Search REPOSITORY for source and target files.") - - global short_opts - global long_opts - global opt_func - for o in option_list: - if o.short: - if o.func: - for c in o.short: - opt_func['-' + c] = o.func - short_opts = short_opts + o.short - if o.arg: - short_opts = short_opts + ":" - if o.long: - if o.func: - for l in o.long: - opt_func['--' + l] = o.func - if o.arg: - long_opts = long_opts + map(lambda a: a + "=", o.long) - else: - long_opts = long_opts + o.long - -options_init() - - - -def PrintUsage(): - print "Usage: scons [OPTION] [TARGET] ..." - print "Options:" - for o in option_list: - if o.helpline: - print o.helpline - - - -def main(): - global Scripts, local_help, num_jobs - - try: - cmd_opts, targets = getopt.getopt(sys.argv[1:], short_opts, long_opts) -# except getopt.GetoptError, x: - except: - #print x - PrintUsage() - sys.exit(1) - - for opt, arg in cmd_opts: - opt_func[opt](opt, arg) - - if not Scripts: - for file in ['SConstruct', 'Sconstruct', 'sconstruct']: - if os.path.isfile(file): - Scripts.append(file) - break - - if local_help and not Scripts: - # They specified -h, but there's no SConstruct. Give them - # the options usage before we try to read it and fail. - PrintUsage() - sys.exit(0) - - # XXX The commented-out code here adds any "scons" subdirs in anything - # along sys.path to sys.path. This was an attempt at setting up things - # so we can import "node.FS" instead of "scons.Node.FS". This doesn't - # quite fit our testing methodology, though, so save it for now until - # the right solutions pops up. - # - #dirlist = [] - #for dir in sys.path: - # scons = os.path.join(dir, 'scons') - # if os.path.isdir(scons): - # dirlist = dirlist + [scons] - # dirlist = dirlist + [dir] - # - #sys.path = dirlist - - sys.path = include_dirs + sys.path - - # initialize node factory - init() - - while Scripts: - file, Scripts = Scripts[0], Scripts[1:] - if file == "-": - exec sys.stdin in globals() - else: - try: - f = open(file, "r") - except IOError, s: - sys.stderr.write("Ignoring missing script '%s'\n" % file) - else: - exec f in globals() - - if local_help: - # They specified -h, but there was no Help() inside the - # SConscript files. Give them the options usage. - PrintUsage() - sys.exit(0) - - taskmaster = Taskmaster(map(lambda x: lookup(File, x), targets)) - - jobs = scons.Job.Jobs(num_jobs, taskmaster) - jobs.start() - jobs.wait() - -if __name__ == "__main__": - try: - main() - except SystemExit: - pass - except KeyboardInterrupt: - print "Build interrupted." - except SyntaxError, e: - _scons_syntax_error(e) - except UserError, e: - _scons_user_error(e) - except: - _scons_other_errors() diff --git a/src/scons/.aeignore b/src/scons/.aeignore deleted file mode 100644 index 43fe851..0000000 --- a/src/scons/.aeignore +++ /dev/null @@ -1,4 +0,0 @@ -*,D -*.pyc -.*.swp -.consign diff --git a/src/scons/Builder.py b/src/scons/Builder.py deleted file mode 100644 index 8b19bd5..0000000 --- a/src/scons/Builder.py +++ /dev/null @@ -1,100 +0,0 @@ -"""scons.Builder - -XXX - -""" - -__revision__ = "Builder.py __REVISION__ __DATE__ __DEVELOPER__" - - - -import os -import types -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(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 = 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/scons/BuilderTests.py b/src/scons/BuilderTests.py deleted file mode 100644 index 822b64c..0000000 --- a/src/scons/BuilderTests.py +++ /dev/null @@ -1,113 +0,0 @@ -__revision__ = "BuilderTests.py __REVISION__ __DATE__ __DEVELOPER__" - -import sys -import unittest - -import TestCmd -from scons.Builder import Builder, CommandAction, FunctionAction - - -# 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 = Builder(action = "foo") - assert builder.action.command == "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 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 = 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 = 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 = Builder(input_suffix = '.c') - assert builder.insuffix == '.c' - builder = Builder(input_suffix = 'c') - assert builder.insuffix == '.c' - - def test_name(self): - """Test Builder creation with a specified name - """ - 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 = 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 = 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 deleted file mode 100644 index 0aa2b82..0000000 --- a/src/scons/Defaults.py +++ /dev/null @@ -1,20 +0,0 @@ -"""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 deleted file mode 100644 index 9b050d1..0000000 --- a/src/scons/Environment.py +++ /dev/null @@ -1,133 +0,0 @@ -"""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/scons/EnvironmentTests.py b/src/scons/EnvironmentTests.py deleted file mode 100644 index 2f13c81..0000000 --- a/src/scons/EnvironmentTests.py +++ /dev/null @@ -1,144 +0,0 @@ -__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 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/scons/Errors.py b/src/scons/Errors.py deleted file mode 100644 index 2709c19..0000000 --- a/src/scons/Errors.py +++ /dev/null @@ -1,18 +0,0 @@ -"""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/scons/ErrorsTests.py b/src/scons/ErrorsTests.py deleted file mode 100644 index 8d27332..0000000 --- a/src/scons/ErrorsTests.py +++ /dev/null @@ -1,28 +0,0 @@ -__revision__ = "ErrorsTests.py __REVISION__ __DATE__ __DEVELOPER__" - -import sys -import unittest -from scons.Errors import InternalError, UserError - - -class ErrorsTestCase(unittest.TestCase): - def test_InternalError(self): - """Test the InternalError exception.""" - try: - raise InternalError, "test internal error" - except InternalError, e: - assert e.args == "test internal error" - - def test_UserError(self): - """Test the UserError exception.""" - try: - raise UserError, "test user error" - except 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/scons/Job.py b/src/scons/Job.py deleted file mode 100644 index 09bfcb8..0000000 --- a/src/scons/Job.py +++ /dev/null @@ -1,239 +0,0 @@ -"""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/scons/JobTests.py b/src/scons/JobTests.py deleted file mode 100644 index 2d9eff0..0000000 --- a/src/scons/JobTests.py +++ /dev/null @@ -1,228 +0,0 @@ -__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/scons/Node/.aeignore b/src/scons/Node/.aeignore deleted file mode 100644 index 43fe851..0000000 --- a/src/scons/Node/.aeignore +++ /dev/null @@ -1,4 +0,0 @@ -*,D -*.pyc -.*.swp -.consign diff --git a/src/scons/Node/FS.py b/src/scons/Node/FS.py deleted file mode 100644 index 7640a7a..0000000 --- a/src/scons/Node/FS.py +++ /dev/null @@ -1,139 +0,0 @@ -"""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 deleted file mode 100644 index 43fe851..0000000 --- a/src/scons/Node/FS/.aeignore +++ /dev/null @@ -1,4 +0,0 @@ -*,D -*.pyc -.*.swp -.consign diff --git a/src/scons/Node/FSTests.py b/src/scons/Node/FSTests.py deleted file mode 100644 index 14b62c2..0000000 --- a/src/scons/Node/FSTests.py +++ /dev/null @@ -1,109 +0,0 @@ -__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): - """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) - - 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 deleted file mode 100644 index 46292bc..0000000 --- a/src/scons/Node/NodeTests.py +++ /dev/null @@ -1,45 +0,0 @@ -__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 building 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): - """Test setting a Node's Builder - """ - 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 deleted file mode 100644 index e4eb45a..0000000 --- a/src/scons/Node/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""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/scons/Scanner/.aeignore b/src/scons/Scanner/.aeignore deleted file mode 100644 index 43fe851..0000000 --- a/src/scons/Scanner/.aeignore +++ /dev/null @@ -1,4 +0,0 @@ -*,D -*.pyc -.*.swp -.consign diff --git a/src/scons/Scanner/C.py b/src/scons/Scanner/C.py deleted file mode 100644 index fcb3a46..0000000 --- a/src/scons/Scanner/C.py +++ /dev/null @@ -1,87 +0,0 @@ -"""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 - 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/scons/Scanner/CTests.py b/src/scons/Scanner/CTests.py deleted file mode 100644 index 326e9b8..0000000 --- a/src/scons/Scanner/CTests.py +++ /dev/null @@ -1,131 +0,0 @@ -__revision__ = "Scanner/CTests.py __REVISION__ __DATE__ __DEVELOPER__" - -from TestCmd import TestCmd -import scons.Scanner.C -import unittest -import sys - -test = TestCmd(workdir = '') - -# create some source files and headers: - -test.write('f1.cpp',""" -#include \"f1.h\" -#include - -int main() -{ - return 0; -} -""") - -test.write('f2.cpp',""" -#include \"d1/f1.h\" -#include -#include \"f1.h\" -#include - -int main() -{ - return 0; -} -""") - -test.write('f3.cpp',""" -#include \t "f1.h" - \t #include "f2.h" -# \t include "f3.h" - -#include \t - \t #include -# \t include - -// #include "never.h" - -const char* x = "#include " - -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/scons/Scanner/ScannerTests.py b/src/scons/Scanner/ScannerTests.py deleted file mode 100644 index 9761cd0..0000000 --- a/src/scons/Scanner/ScannerTests.py +++ /dev/null @@ -1,84 +0,0 @@ -__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/scons/Scanner/__init__.py b/src/scons/Scanner/__init__.py deleted file mode 100644 index 7ce602c..0000000 --- a/src/scons/Scanner/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -"""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/scons/Sig/.aeignore b/src/scons/Sig/.aeignore deleted file mode 100644 index 43fe851..0000000 --- a/src/scons/Sig/.aeignore +++ /dev/null @@ -1,4 +0,0 @@ -*,D -*.pyc -.*.swp -.consign diff --git a/src/scons/Sig/MD5.py b/src/scons/Sig/MD5.py deleted file mode 100644 index 36e4230..0000000 --- a/src/scons/Sig/MD5.py +++ /dev/null @@ -1,70 +0,0 @@ -"""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 deleted file mode 100644 index efcec7f..0000000 --- a/src/scons/Sig/MD5Tests.py +++ /dev/null @@ -1,76 +0,0 @@ -__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/scons/Sig/TimeStamp.py b/src/scons/Sig/TimeStamp.py deleted file mode 100644 index cab44bf..0000000 --- a/src/scons/Sig/TimeStamp.py +++ /dev/null @@ -1,49 +0,0 @@ -"""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 deleted file mode 100644 index 4fb1920..0000000 --- a/src/scons/Sig/TimeStampTests.py +++ /dev/null @@ -1,74 +0,0 @@ -__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/scons/Sig/__init__.py b/src/scons/Sig/__init__.py deleted file mode 100644 index 411a94b..0000000 --- a/src/scons/Sig/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""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 deleted file mode 100644 index 9e279c2..0000000 --- a/src/scons/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""scons - -The main package for the scons software construction utility. - -""" - -__revision__ = "__init__.py __REVISION__ __DATE__ __DEVELOPER__" - -__version__ = "__VERSION__" diff --git a/src/scons/exitfuncs.py b/src/scons/exitfuncs.py deleted file mode 100644 index e26a031..0000000 --- a/src/scons/exitfuncs.py +++ /dev/null @@ -1,48 +0,0 @@ -"""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/script/MANIFEST b/src/script/MANIFEST new file mode 100644 index 0000000..98e74a0 --- /dev/null +++ b/src/script/MANIFEST @@ -0,0 +1,3 @@ +MANIFEST +scons.py +setup.py diff --git a/src/script/scons.py b/src/script/scons.py new file mode 100644 index 0000000..d433773 --- /dev/null +++ b/src/script/scons.py @@ -0,0 +1,560 @@ +#! /usr/bin/env python +# +# SCons +# + +__revision__ = "scons.py __REVISION__ __DATE__ __DEVELOPER__" + +import getopt +import os.path +import string +import sys +import traceback + +import SCons.Node.FS +import SCons.Job +from SCons.Errors import * + +# +# Modules and classes that we don't use directly in this script, but +# which we want available for use in SConstruct and SConscript files. +# +from SCons.Environment import Environment +from SCons.Builder import Builder + +class Task: + "XXX: this is here only until the build engine is implemented" + + def __init__(self, target): + self.target = target + + def execute(self): + self.target.build() + + + +class Taskmaster: + "XXX: this is here only until the build engine is implemented" + + def __init__(self, targets): + self.targets = targets + self.num_iterated = 0 + + + def next_task(self): + if self.num_iterated == len(self.targets): + return None + else: + current = self.num_iterated + self.num_iterated = self.num_iterated + 1 + return Task(self.targets[current]) + + def is_blocked(self): + return 0 + + def executed(self, task): + pass + + def failed(self, task): + pass + + +# Global variables + +local_help = None +num_jobs = 1 +Scripts = [] +include_dirs = [] + +# utility functions + +def _scons_syntax_error(e): + """Handle syntax errors. Print out a message and show where the error + occurred. + """ + etype, value, tb = sys.exc_info() + lines = traceback.format_exception_only(etype, value) + for line in lines: + sys.stderr.write(line+'\n') + +def _scons_user_error(e): + """Handle user errors. Print out a message and a description of the + error, along with the line number and routine where it occured. + """ + print 'user error' + etype, value, tb = sys.exc_info() + while tb.tb_next is not None: + tb = tb.tb_next + lineno = traceback.tb_lineno(tb) + filename = tb.tb_frame.f_code.co_filename + routine = tb.tb_frame.f_code.co_name + sys.stderr.write("\nSCons error: %s\n" % value) + sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) + +def _scons_other_errors(): + """Handle all errors but user errors. Print out a message telling + the user what to do in this case and print a normal trace. + """ + print 'other errors' + traceback.print_exc() + + + +def Conscript(filename): + global Scripts + Scripts.append(filename) + +def Help(text): + global local_help + if local_help: + print text + print "Use scons -H for help about command-line options." + sys.exit(0) + + + +# +# After options are initialized, the following variables are +# filled in: +# +option_list = [] # list of Option objects +short_opts = "" # string of short (single-character) options +long_opts = [] # array of long (--) options +opt_func = {} # mapping of option strings to functions + +def options_init(): + """Initialize command-line options processing. + + This is in a subroutine mainly so we can easily single-step over + it in the debugger. + """ + + class Option: + """Class for command-line option information. + + This exists to provide a central location for everything + describing a command-line option, so that we can change + options without having to update the code to handle the + option in one place, the -h help message in another place, + etc. There are no methods here, only attributes. + + You can initialize an Option with the following: + + func The function that will be called when this + option is processed on the command line. + Calling sequence is: + + func(opt, arg) + + If there is no func, then this Option probably + stores an optstring to be printed. + + helpline + The string to be printed in -h output. If no + helpline is specified but a help string is + specified (the usual case), a helpline will be + constructed automatically from the short, long, + arg, and help attributes. (In practice, then, + setting helpline without setting func allows you + to print arbitrary lines of text in the -h + output.) + + short The string for short, single-hyphen + command-line options. + Do not include the hyphen: + + 'a' for -a, 'xy' for -x and -y, etc. + + long An array of strings for long, double-hyphen + command-line options. Do not include + the hyphens: + + ['my-option', 'verbose'] + + arg If this option takes an argument, this string + specifies how you want it to appear in the + -h output ('DIRECTORY', 'FILE', etc.). + + help The help string that will be printed for + this option in the -h output. Must be + 49 characters or fewer. + + future If non-zero, this indicates that this feature + will be supported in a future release, not + the currently planned one. SCons will + recognize the option, but it won't show up + in the -h output. + + The following attribute is derived from the supplied attributes: + + optstring + A string, with hyphens, describing the flags + for this option, as constructed from the + specified short, long and arg attributes. + + All Option objects are stored in the global option_list list, + in the order in which they're created. This is the list + that's used to generate -h output, so the order in which the + objects are created is the order in which they're printed. + + The upshot is that specifying a command-line option and having + everything work correctly is a matter of defining a function to + process its command-line argument (set the right flag, update + the right value), and then creating an appropriate Option object + at the correct point in the code below. + """ + + def __init__(self, func = None, helpline = None, + short = None, long = None, arg = None, + help = None, future = None): + self.func = func + self.short = short + self.long = long + self.arg = arg + self.help = help + opts = [] + if self.short: + for c in self.short: + if arg: + c = c + " " + arg + opts = opts + ['-' + c] + if self.long: + l = self.long + if arg: + l = map(lambda x,a=arg: x + "=" + a, self.long) + opts = opts + map(lambda x: '--' + x, l) + self.optstring = string.join(opts, ', ') + if helpline: + self.helpline = helpline + elif help and not future: + if len(self.optstring) <= 26: + sep = " " * (28 - len(self.optstring)) + else: + sep = self.helpstring = "\n" + " " * 30 + self.helpline = " " + self.optstring + sep + self.help + else: + self.helpline = None + global option_list + option_list.append(self) + + # Generic routine for to-be-written options, used by multiple + # options below. + + def opt_not_yet(opt, arg): + sys.stderr.write("Warning: the %s option is not yet implemented\n" + % opt) + + # In the following instantiations, the help string should be no + # longer than 49 characters. Use the following as a guide: + # help = "1234567890123456789012345678901234567890123456789" + + def opt_ignore(opt, arg): + sys.stderr.write("Warning: ignoring %s option\n" % opt) + + Option(func = opt_ignore, + short = 'bmSt', long = ['no-keep-going', 'stop', 'touch'], + help = "Ignored for compatibility.") + + Option(func = opt_not_yet, + short = 'c', long = ['clean', 'remove'], + help = "Remove specified targets and dependencies.") + + Option(func = opt_not_yet, future = 1, + long = ['cache-disable', 'no-cache'], + help = "Do not retrieve built targets from Cache.") + + Option(func = opt_not_yet, future = 1, + long = ['cache-force', 'cache-populate'], + help = "Copy already-built targets into the Cache.") + + Option(func = opt_not_yet, future = 1, + long = ['cache-show'], + help = "Print what would have built Cached targets.") + + def opt_C(opt, arg): + try: + os.chdir(arg) + except: + sys.stderr.write("Could not change directory to 'arg'\n") + + Option(func = opt_C, + short = 'C', long = ['directory'], arg = 'DIRECTORY', + help = "Change to DIRECTORY before doing anything.") + + Option(func = opt_not_yet, + short = 'd', + help = "Print file dependency information.") + + Option(func = opt_not_yet, future = 1, + long = ['debug'], arg = 'FLAGS', + help = "Print various types of debugging information.") + + Option(func = opt_not_yet, future = 1, + short = 'e', long = ['environment-overrides'], + help = "Environment variables override makefiles.") + + def opt_f(opt, arg): + global Scripts + Scripts.append(arg) + + Option(func = opt_f, + short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE', + help = "Read FILE as the top-level SConstruct file.") + + def opt_help(opt, arg): + global local_help + local_help = 1 + + Option(func = opt_help, + short = 'h', long = ['help'], + help = "Print defined help message, or this one.") + + def opt_help_options(opt, arg): + PrintUsage() + sys.exit(0) + + Option(func = opt_help_options, + short = 'H', long = ['help-options'], + help = "Print this message and exit.") + + Option(func = opt_not_yet, + short = 'i', long = ['ignore-errors'], + help = "Ignore errors from build actions.") + + def opt_I(opt, arg): + global include_dirs + include_dirs = include_dirs + [arg] + + Option(func = opt_I, + short = 'I', long = ['include-dir'], arg = 'DIRECTORY', + help = "Search DIRECTORY for imported Python modules.") + + def opt_j(opt, arg): + global num_jobs + try: + num_jobs = int(arg) + except: + PrintUsage() + sys.exit(1) + + if num_jobs <= 0: + PrintUsage() + sys.exit(1) + + Option(func = opt_j, + short = 'j', long = ['jobs'], arg = 'N', + help = "Allow N jobs at once.") + + Option(func = opt_not_yet, + short = 'k', long = ['keep-going'], + help = "Keep going when a target can't be made.") + + Option(func = opt_not_yet, future = 1, + short = 'l', long = ['load-average', 'max-load'], arg = 'N', + help = "Don't start multiple jobs unless load is below N.") + + Option(func = opt_not_yet, future = 1, + long = ['list-derived'], + help = "Don't build; list files that would be built.") + + Option(func = opt_not_yet, future = 1, + long = ['list-actions'], + help = "Don't build; list files and build actions.") + + Option(func = opt_not_yet, future = 1, + long = ['list-where'], + help = "Don't build; list files and where defined.") + + def opt_n(opt, arg): + SCons.Builder.execute_actions = None + + Option(func = opt_n, + short = 'n', long = ['no-exec', 'just-print', 'dry-run', 'recon'], + help = "Don't build; just print commands.") + + Option(func = opt_not_yet, future = 1, + short = 'o', long = ['old-file', 'assume-old'], arg = 'FILE', + help = "Consider FILE to be old; don't rebuild it.") + + Option(func = opt_not_yet, future = 1, + long = ['override'], arg = 'FILE', + help = "Override variables as specified in FILE.") + + Option(func = opt_not_yet, future = 1, + short = 'p', + help = "Print internal environments/objects.") + + Option(func = opt_not_yet, future = 1, + short = 'q', long = ['question'], + help = "Don't build; exit status says if up to date.") + + Option(func = opt_not_yet, future = 1, + short = 'rR', long = ['no-builtin-rules', 'no-builtin-variables'], + help = "Clear default environments and variables.") + + Option(func = opt_not_yet, future = 1, + long = ['random'], + help = "Build dependencies in random order.") + + def opt_s(opt, arg): + SCons.Builder.print_actions = None + + Option(func = opt_s, + short = 's', long = ['silent', 'quiet'], + help = "Don't print commands.") + + Option(func = opt_not_yet, future = 1, + short = 'u', long = ['up', 'search-up'], + help = "Search up directory tree for SConstruct.") + + def option_v(opt, arg): + print "SCons version __VERSION__, by Steven Knight et al." + print "Copyright 2001 Steven Knight" + sys.exit(0) + + Option(func = option_v, + short = 'v', long = ['version'], + help = "Print the SCons version number and exit.") + + Option(func = opt_not_yet, future = 1, + short = 'w', long = ['print-directory'], + help = "Print the current directory.") + + Option(func = opt_not_yet, future = 1, + long = ['no-print-directory'], + help = "Turn off -w, even if it was turned on implicitly.") + + Option(func = opt_not_yet, future = 1, + long = ['write-filenames'], arg = 'FILE', + help = "Write all filenames examined into FILE.") + + Option(func = opt_not_yet, future = 1, + short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE', + help = "Consider FILE to be changed.") + + Option(func = opt_not_yet, future = 1, + long = ['warn-undefined-variables'], + help = "Warn when an undefined variable is referenced.") + + Option(func = opt_not_yet, future = 1, + short = 'Y', long = ['repository'], arg = 'REPOSITORY', + help = "Search REPOSITORY for source and target files.") + + global short_opts + global long_opts + global opt_func + for o in option_list: + if o.short: + if o.func: + for c in o.short: + opt_func['-' + c] = o.func + short_opts = short_opts + o.short + if o.arg: + short_opts = short_opts + ":" + if o.long: + if o.func: + for l in o.long: + opt_func['--' + l] = o.func + if o.arg: + long_opts = long_opts + map(lambda a: a + "=", o.long) + else: + long_opts = long_opts + o.long + +options_init() + + + +def PrintUsage(): + print "Usage: scons [OPTION] [TARGET] ..." + print "Options:" + for o in option_list: + if o.helpline: + print o.helpline + + + +def main(): + global Scripts, local_help, num_jobs + + try: + cmd_opts, targets = getopt.getopt(sys.argv[1:], short_opts, long_opts) +# except getopt.GetoptError, x: + except: + #print x + PrintUsage() + sys.exit(1) + + for opt, arg in cmd_opts: + opt_func[opt](opt, arg) + + if not Scripts: + for file in ['SConstruct', 'Sconstruct', 'sconstruct']: + if os.path.isfile(file): + Scripts.append(file) + break + + if local_help and not Scripts: + # They specified -h, but there's no SConstruct. Give them + # the options usage before we try to read it and fail. + PrintUsage() + sys.exit(0) + + # XXX The commented-out code here adds any "scons" subdirs in anything + # along sys.path to sys.path. This was an attempt at setting up things + # so we can import "node.FS" instead of "SCons.Node.FS". This doesn't + # quite fit our testing methodology, though, so save it for now until + # the right solutions pops up. + # + #dirlist = [] + #for dir in sys.path: + # scons = os.path.join(dir, 'scons') + # if os.path.isdir(scons): + # dirlist = dirlist + [scons] + # dirlist = dirlist + [dir] + # + #sys.path = dirlist + + sys.path = include_dirs + sys.path + + # initialize node factory + SCons.Node.FS.init() + + while Scripts: + file, Scripts = Scripts[0], Scripts[1:] + if file == "-": + exec sys.stdin in globals() + else: + try: + f = open(file, "r") + except IOError, s: + sys.stderr.write("Ignoring missing script '%s'\n" % file) + else: + exec f in globals() + + if local_help: + # They specified -h, but there was no Help() inside the + # SConscript files. Give them the options usage. + PrintUsage() + sys.exit(0) + + taskmaster = Taskmaster(map( + lambda x: SCons.Node.FS.lookup(SCons.Node.FS.File, x), + targets)) + + jobs = SCons.Job.Jobs(num_jobs, taskmaster) + jobs.start() + jobs.wait() + +if __name__ == "__main__": + try: + main() + except SystemExit: + pass + except KeyboardInterrupt: + print "Build interrupted." + except SyntaxError, e: + _scons_syntax_error(e) + except UserError, e: + _scons_user_error(e) + except: + _scons_other_errors() diff --git a/src/script/setup.py b/src/script/setup.py new file mode 100644 index 0000000..8732e3b --- /dev/null +++ b/src/script/setup.py @@ -0,0 +1,13 @@ +__revision__ = "setup.py __REVISION__ __DATE__ __DEVELOPER__" + +from string import join, split + +from distutils.core import setup + +setup(name = "scons", + version = "__VERSION__", + description = "scons", + author = "Steven Knight", + author_email = "knight@baldmt.com", + url = "http://www.baldmt.com/scons", + scripts = ["scons.py"]) diff --git a/src/setup.py b/src/setup.py deleted file mode 100644 index ad93cac..0000000 --- a/src/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -__revision__ = "setup.py __REVISION__ __DATE__ __DEVELOPER__" - -from string import join, split - -from distutils.core import setup - -setup(name = "scons", - version = "__VERSION__", - description = "scons", - author = "Steven Knight", - author_email = "knight@baldmt.com", - url = "http://www.baldmt.com/scons", - packages = ["scons"], - scripts = ["scons.py"]) diff --git a/test/exitfns.py b/test/exitfns.py index a13dd9b..d9a1019 100644 --- a/test/exitfns.py +++ b/test/exitfns.py @@ -8,7 +8,7 @@ import TestSCons test = TestSCons.TestSCons(match = TestCmd.match_exact) sconstruct = """ -from scons.exitfuncs import * +from SCons.exitfuncs import * def x1(): print "running x1" -- cgit v0.12