summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-12-15 00:23:46 (GMT)
committerSteven Knight <knight@baldmt.com>2001-12-15 00:23:46 (GMT)
commitaf3987ad4663708a4f9d10b661971fd7b29a9f0d (patch)
tree9de48645f9d229266078ede22a78fcd740f705b6 /src
parent0d14a2ff1774d42dc855aa9aab94791df3f55157 (diff)
downloadSCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.zip
SCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.tar.gz
SCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.tar.bz2
Add BuildDir(), Export(), and Install() functionality (courtesy Charles Crain).
Diffstat (limited to 'src')
-rw-r--r--src/CHANGES.txt6
-rw-r--r--src/engine/SCons/Environment.py48
-rw-r--r--src/engine/SCons/EnvironmentTests.py12
-rw-r--r--src/engine/SCons/Node/FS.py85
-rw-r--r--src/engine/SCons/Node/FSTests.py54
-rw-r--r--src/engine/SCons/Node/NodeTests.py28
-rw-r--r--src/engine/SCons/Node/__init__.py22
-rw-r--r--src/engine/SCons/Scanner/C.py6
-rw-r--r--src/engine/SCons/Scanner/CTests.py6
-rw-r--r--src/engine/SCons/Script.py34
10 files changed, 263 insertions, 38 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 3d47e84..2aa1525 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -13,6 +13,12 @@ RELEASE 0.02 -
- Fixed the version comment in the scons.bat (the UNIX geek used
# instead of @rem).
+ - Added support for the Install method (courtesy Charles Crain).
+
+ - Added support for the BuildDir method (courtesy Charles Crain).
+
+ - Added the Export method (courtesy Charles Crain).
+
RELEASE 0.01 - Thu Dec 13 19:25:23 CST 2001
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 9f78a65..ad92dfd 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -30,7 +30,7 @@ XXX
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
+import os
import copy
import os.path
import re
@@ -40,12 +40,32 @@ import SCons.Builder
import SCons.Defaults
from SCons.Errors import UserError
from UserList import UserList
-
-def Command():
- pass # XXX
-
-def Install():
- pass # XXX
+import SCons.Node.FS
+import sys
+import shutil
+
+def installFunc(env, target, source):
+ try:
+ os.unlink(target)
+ except OSError:
+ pass
+
+ try:
+ SCons.Node.FS.file_link(source[0], target)
+ print 'Install file: "%s" as "%s"' % \
+ (source[0], target)
+ return 0
+ except IOError, e:
+ sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \
+ (source[0], target, str(e)))
+ return -1
+ except OSError, e:
+ sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \
+ (source[0], target, str(e)))
+ return -1
+
+InstallBuilder = SCons.Builder.Builder(name='Install',
+ action=installFunc)
def InstallAs():
pass # XXX
@@ -171,6 +191,20 @@ class Environment:
bld = SCons.Builder.Builder(name="Command", action=action)
return bld(self, target, source)
+ def Install(self, dir, source):
+ """Install specified files in the given directory."""
+ sources = SCons.Util.scons_str2nodes(source)
+ dnodes = SCons.Util.scons_str2nodes(dir,
+ SCons.Node.FS.default_fs.Dir)
+ tgt = []
+ for dnode in dnodes:
+ for src in sources:
+ target = SCons.Node.FS.default_fs.File(src.name, dnode)
+ tgt.append(InstallBuilder(self, target, src))
+ if len(tgt) == 1:
+ tgt = tgt[0]
+ return tgt
+
def subst(self, string):
"""Recursively interpolates construction variables from the
Environment into the specified string, returning the expanded
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index 65eb510..c2faa35 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -139,9 +139,6 @@ class EnvironmentTestCase(unittest.TestCase):
s = env3.get_scanner(".cxx")
assert s == None, s
- def test_Command(self):
- pass # XXX
-
def test_Copy(self):
"""Test construction Environment copying
@@ -218,7 +215,14 @@ class EnvironmentTestCase(unittest.TestCase):
assert env1 == env2
def test_Install(self):
- pass # XXX
+ """Test Install method"""
+ env=Environment()
+ tgt = env.Install('export', [ 'build/foo1', 'build/foo2' ])
+ paths = map(str, tgt)
+ paths.sort()
+ assert paths == [ 'export/foo1', 'export/foo2' ], paths
+ for tnode in tgt:
+ assert tnode.builder == InstallBuilder
def test_InstallAs(self):
pass # XXX
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 648e1f9..303db61 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -39,6 +39,18 @@ import types
import SCons.Node
from UserDict import UserDict
import sys
+from SCons.Errors import UserError
+
+try:
+ import os
+ file_link = os.link
+except AttributeError:
+ import shutil
+ import stat
+ def file_link(src, dest):
+ shutil.copyfile(src, dest)
+ st=os.stat(src)
+ os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]))
class PathName:
"""This is a string like object with limited capabilities (i.e.,
@@ -119,6 +131,7 @@ class FS:
self.Root = PathDict()
self.Top = self.__doLookup(Dir, path)
self.Top.path = '.'
+ self.Top.srcpath = '.'
self.Top.path_ = os.path.join('.', '')
self.cwd = self.Top
@@ -242,7 +255,17 @@ class FS:
name, directory = self.__transformPath(name, directory)
return self.__doLookup(Dir, name, directory)
-
+ def BuildDir(self, build_dir, src_dir):
+ """Link the supplied build directory to the source directory
+ for purposes of building files."""
+ dirSrc = self.Dir(src_dir)
+ dirBuild = self.Dir(build_dir)
+ if not dirSrc.is_under(self.Top) or not dirBuild.is_under(self.Top):
+ raise UserError, "Both source and build directories must be under top of build tree."
+ if dirSrc.is_under(dirBuild):
+ raise UserError, "Source directory cannot be under build directory."
+ dirBuild.link(dirSrc)
+
class Entry(SCons.Node.Node):
"""A generic class for file system entries. This class if for
@@ -272,6 +295,19 @@ class Entry(SCons.Node.Node):
self.abspath_ = self.abspath
self.dir = directory
self.use_signature = 1
+ self.__doSrcpath()
+
+ def adjust_srcpath(self):
+ self.__doSrcpath()
+
+ def __doSrcpath(self):
+ if self.dir:
+ if str(self.dir.srcpath) == '.':
+ self.srcpath = self.name
+ else:
+ self.srcpath = os.path.join(self.dir.srcpath, self.name)
+ else:
+ self.srcpath = self.name
def __str__(self):
"""A FS node's string representation is its path name."""
@@ -301,6 +337,13 @@ class Entry(SCons.Node.Node):
return 0
return None
+ def is_under(self, dir):
+ if self is dir:
+ return 1
+ if not self.dir:
+ return 0
+ return self.dir.is_under(dir)
+
# XXX TODO?
@@ -343,6 +386,21 @@ class Dir(Entry):
self.builder = 1
self._sconsign = None
+ def __doReparent(self):
+ for ent in self.entries.values():
+ if not ent is self and not ent is self.dir:
+ ent.adjust_srcpath()
+
+ def adjust_srcpath(self):
+ Entry.adjust_srcpath(self)
+ self.__doReparent()
+
+ def link(self, srcdir):
+ """Set this directory as the build directory for the
+ supplied source directory."""
+ self.srcpath = srcdir.path
+ self.__doReparent()
+
def up(self):
return self.entries['..']
@@ -425,17 +483,21 @@ class Dir(Entry):
class File(Entry):
"""A class for files in a file system.
"""
+ def __init__(self, name, directory = None):
+ Entry.__init__(self, name, directory)
+ self._morph()
+
def _morph(self):
- """Turn a file system node into a File object. Nothing
- to be done, actually, because all of the info we need
- is handled by our base Entry class initialization."""
- pass
+ """Turn a file system node into a File object."""
+ self.created = 0
def root(self):
return self.dir.root()
def get_contents(self):
- return open(self.path, "r").read()
+ if not self.exists():
+ return ''
+ return open(str(self), "r").read()
def get_timestamp(self):
if self.exists():
@@ -472,6 +534,17 @@ class File(Entry):
self.add_implicit(scn.scan(self.path, self.env),
scn)
self.scanned[scn] = 1
+
+ def exists(self):
+ if not self.created:
+ self.created = 1
+ if self.srcpath != self.path and \
+ os.path.exists(self.srcpath):
+ if os.path.exists(self.path):
+ os.unlink(self.path)
+ self.__createDir()
+ file_link(self.srcpath, self.path)
+ return Entry.exists(self)
def __createDir(self):
# ensure that the directories for this node are
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 45f8e64..0f6b8b7 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -29,7 +29,8 @@ import string
import sys
import unittest
import SCons.Node.FS
-
+from TestCmd import TestCmd
+from SCons.Errors import UserError
built_it = None
@@ -59,6 +60,54 @@ class Environment:
def get_scanner(self, skey):
return self.scanner
+class BuildDirTestCase(unittest.TestCase):
+ def runTest(self):
+ """Test build dir functionality"""
+ fs = SCons.Node.FS.FS()
+ f1 = fs.File('build/test1')
+ fs.BuildDir('build', 'src')
+ f2 = fs.File('build/test2')
+ assert f1.srcpath == 'src/test1', f1.srcpath
+ assert f2.srcpath == 'src/test2', f2.srcpath
+
+ fs = SCons.Node.FS.FS()
+ f1 = fs.File('build/test1')
+ fs.BuildDir('build', '.')
+ f2 = fs.File('build/test2')
+ assert f1.srcpath == 'test1', f1.srcpath
+ assert f2.srcpath == 'test2', f2.srcpath
+
+ fs = SCons.Node.FS.FS()
+ fs.BuildDir('build/var1', 'src')
+ fs.BuildDir('build/var2', 'src')
+ f1 = fs.File('build/var1/test1')
+ f2 = fs.File('build/var2/test1')
+ assert f1.srcpath == 'src/test1', f1.srcpath
+ assert f2.srcpath == 'src/test1', f2.srcpath
+
+ exc_caught = 0
+ try:
+ fs = SCons.Node.FS.FS()
+ fs.BuildDir('/test/foo', '.')
+ except UserError:
+ exc_caught = 1
+ assert exc_caught, "Should have caught a UserError."
+
+ exc_caught = 0
+ try:
+ fs = SCons.Node.FS.FS()
+ fs.BuildDir('build', '/test/foo')
+ except UserError:
+ exc_caught = 1
+ assert exc_caught, "Should have caught a UserError."
+
+ exc_caught = 0
+ try:
+ fs = SCons.Node.FS.FS()
+ fs.BuildDir('build', 'build/src')
+ except UserError:
+ exc_caught = 1
+ assert exc_caught, "Should have caught a UserError."
class FSTestCase(unittest.TestCase):
def runTest(self):
@@ -68,8 +117,6 @@ class FSTestCase(unittest.TestCase):
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'])
@@ -393,5 +440,6 @@ class FSTestCase(unittest.TestCase):
if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(FSTestCase())
+ suite.addTest(BuildDirTestCase())
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
index 673ab25..f55659e 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -415,8 +415,32 @@ class NodeTestCase(unittest.TestCase):
assert n2.children_are_executed()
assert n1.children_are_executed()
-
-
+ def test_rescan(self):
+ """Test that built nodes are rescanned."""
+ class DummyScanner:
+ pass
+
+ class TestNode(SCons.Node.Node):
+ def scan(self):
+ for scn in self.scanners:
+ if not self.scanned.has_key(scn):
+ n=SCons.Node.Node()
+ n.scanner_set(scn)
+ self.add_implicit([ n ], scn)
+ self.scanned[scn] = 1
+ tn=TestNode()
+ tn.builder_set(Builder())
+ tn.env_set(Environment())
+ ds = DummyScanner()
+ tn.scanner_set(ds)
+ tn.scan()
+ map(lambda x: x.scan(), tn.depends)
+ assert tn.scanned[ds]
+ assert len(tn.implicit[ds]) == 1, tn.implicit
+ tn.build()
+ assert len(tn.implicit[ds]) == 2, tn.implicit
+ for dep in tn.implicit[ds]:
+ assert dep.scanned[ds] == 1
if __name__ == "__main__":
suite = unittest.makeSuite(NodeTestCase, 'test_')
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index ddcddfb..0682c7f 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -76,6 +76,28 @@ class Node:
target = self, source = self.sources)
if stat != 0:
raise BuildError(node = self, stat = stat)
+
+ # If we succesfully build a node, then we need to rescan for
+ # implicit dependencies, since it might have changed on us.
+
+ # XXX Modify this so we only rescan using the scanner(s) relevant
+ # to this build.
+ for scn in self.scanners:
+ try:
+ del self.scanned[scn]
+ except KeyError:
+ pass
+
+ self.scan()
+
+ for scn in self.scanners:
+ try:
+ for dep in self.implicit[scn]:
+ w=Walker(dep)
+ while not w.is_done():
+ w.next().scan()
+ except KeyError:
+ pass
return stat
def builder_set(self, builder):
diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py
index 8ad7e00..a5aa02d 100644
--- a/src/engine/SCons/Scanner/C.py
+++ b/src/engine/SCons/Scanner/C.py
@@ -96,7 +96,7 @@ def scan(filename, env, args = [SCons.Node.FS.default_fs, ()]):
fs, cpppath = args
- try:
+ if fs.File(filename, fs.Top).exists():
file = open(filename)
contents = file.read()
file.close()
@@ -109,9 +109,9 @@ def scan(filename, env, args = [SCons.Node.FS.default_fs, ()]):
source_dir = (fs.Dir(dir, fs.Top),)
else:
source_dir = ( fs.Top, )
+
return (SCons.Util.find_files(angle_includes, cpppath + source_dir,
fs.File)
+ SCons.Util.find_files(quote_includes, source_dir + cpppath,
fs.File))
- except (IOError, OSError):
- return []
+ return []
diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py
index bc30fa4..c9cc5ea 100644
--- a/src/engine/SCons/Scanner/CTests.py
+++ b/src/engine/SCons/Scanner/CTests.py
@@ -29,6 +29,7 @@ import unittest
import sys
import os
import os.path
+import SCons.Node.FS
test = TestCmd.TestCmd(workdir = '')
@@ -176,6 +177,11 @@ class CScannerTestCase5(unittest.TestCase):
env = DummyEnvironment([])
s = SCons.Scanner.C.CScan()
deps = s.instance(env).scan(test.workpath('f3.cpp'), env)
+
+ # Make sure exists() gets called on the file node being
+ # scanned, essential for cooperation with BuildDir functionality.
+ assert SCons.Node.FS.default_fs.File(test.workpath('f3.cpp')).created
+
headers = ['f1.h', 'f2.h', 'f3.h', 'fi.h', 'fj.h',
'd1/f1.h', 'd1/f2.h', 'd1/f3.h']
deps_match(self, deps, map(test.workpath, headers))
diff --git a/src/engine/SCons/Script.py b/src/engine/SCons/Script.py
index 836fb1a..086e470 100644
--- a/src/engine/SCons/Script.py
+++ b/src/engine/SCons/Script.py
@@ -42,6 +42,7 @@ import os.path
import string
import sys
import traceback
+import copy
# Strip the script directory from sys.path() so on case-insensitive
# (WIN32) systems Python doesn't think that the "scons" script is the
@@ -159,9 +160,9 @@ def _scons_other_errors():
-def SConscript(filename):
+def SConscript(sconscript, export={}):
global scripts
- scripts.append(SCons.Node.FS.default_fs.File(filename))
+ scripts.append( (SCons.Node.FS.default_fs.File(sconscript), export) )
def Default(*targets):
for t in targets:
@@ -175,7 +176,12 @@ def Help(text):
print "Use scons -H for help about command-line options."
sys.exit(0)
+def BuildDir(build_dir, src_dir):
+ SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir)
+def Export(**kw):
+ # A convenient shorthand to pass exports to the SConscript function.
+ return kw
#
# After options are initialized, the following variables are
@@ -374,9 +380,9 @@ def options_init():
def opt_f(opt, arg):
global scripts
if arg == '-':
- scripts.append(arg)
+ scripts.append( ( arg, {} ) )
else:
- scripts.append(SCons.Node.FS.default_fs.File(arg))
+ scripts.append( (SCons.Node.FS.default_fs.File(arg), {}) )
Option(func = opt_f,
short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE',
@@ -599,7 +605,7 @@ def _main():
if not scripts:
for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
if os.path.isfile(file):
- scripts.append(SCons.Node.FS.default_fs.File(file))
+ scripts.append( (SCons.Node.FS.default_fs.File(file), {}) )
break
if help_option == 'H':
@@ -619,17 +625,19 @@ def _main():
sys.path = include_dirs + sys.path
while scripts:
- f, scripts = scripts[0], scripts[1:]
+ f, exports = scripts.pop(0)
+ script_env = copy.copy(globals())
+ script_env.update(exports)
if f == "-":
- exec sys.stdin in globals()
+ exec sys.stdin in script_env
else:
- try:
- file = open(f.path, "r")
- except IOError, s:
- sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path)
- else:
+ if f.exists():
+ file = open(str(f), "r")
SCons.Node.FS.default_fs.chdir(f.dir)
- exec file in globals()
+ exec file in script_env
+ else:
+ sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path)
+
SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top)
if help_option == 'h':