supports the
ability to define new scanners for unknown input file types.
+.B scons
+knows how to fetch files automatically from
+SCCS or RCS subdirectories
+using SCCS, RCS or BitKeeper.
+
.B scons
is normally executed in a top-level directory containing a
.I SConstruct
.ES
env.SourceCode('.', env.RCS())
.EE
+Note that
+.B scons
+will fetch source files
+from RCS subdirectories automatically,
+so configuring RCS
+as demonstrated in the above example
+should only be necessary if
+you are fetching from
+RCS,v
+files in the same
+directory as the source files,
+or if you need to explicitly specify RCS
+for a specific subdirectory.
.TP
.RI Replace( key = val ", [...])"
.ES
env.SourceCode('.', env.SCCS())
.EE
+Note that
+.B scons
+will fetch source files
+from SCCS subdirectories automatically,
+so configuring SCCS
+as demonstrated in the above example
+should only be necessary if
+you are fetching from
+.I s.SCCS
+files in the same
+directory as the source files,
+or if you need to explicitly specify SCCS
+for a specific subdirectory.
.TP
.RI SideEffect( side_effect , target )
.TP
.RI SourceCode( entries , builder )
Arrange for non-existent source files to
-be fetched from a source code system
+be fetched from a source code management system
using the specified
.IR builder .
The specified
and may represent either individual
source files or directories in which
source files can be found.
+
For any non-existent source files,
.B scons
will search up the directory tree
.B SourceCode
builder has been specified
for a directory higher up the tree.
+
+.B scons
+will, by default,
+fetch files from SCCS or RCS subdirectories
+without explicit configuration.
+This takes some extra processing time
+to search for the necessary
+source code management files on disk.
+You can avoid these extra searches
+and speed up your build a little
+by disabling these searches as follows:
+.ES
+env.SourceCode('.', None)
+.EE
+
Note that if the specified
.I builder
is one you create by hand,
it must have an associated
construction environment to use
when fetching a source file.
+
.B scons
provides a set of canned factory
functions that return appropriate
- Add an Environment.SourceCode() method to support fetching files
from source code systems. Add factory methods that create Builders
- to support BitKeeper, CVS, RCS, SCCS and Subversion.
+ to support BitKeeper, CVS, RCS, SCCS and Subversion. Add support
+ for fetching files from RCS or SCCS transparently (like GNU Make).
- Make the internal to_String() function more efficient.
for t in tlist:
if t.side_effect:
raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
- if t.has_builder(fetch = 0):
+ if t.has_builder():
if t.env != env:
raise UserError, "Two different environments were specified for the same target: %s"%str(t)
elif t.overrides != overrides:
return self.name
def builder_set(self, builder):
self.builder = builder
- def has_builder(self, fetch=1):
+ def has_builder(self):
return not self.builder is None
def env_set(self, env, safe=0):
self.env = env
import SCons.Scanner.Fortran
import SCons.Scanner.Prog
+# A placeholder for a default Environment (for fetching source files
+# from source code management systems and the like). This must be
+# initialized later, after the top-level directory is set by the calling
+# interface.
+_default_env = None
+
def alias_builder(env, target, source):
pass
CachePush = SCons.Action.Action(CachePushFunc, None)
+class _Null:
+ pass
+
+_null = _Null()
+
+DefaultSCCSBuilder = None
+DefaultRCSBuilder = None
+
+def get_DefaultSCCSBuilder():
+ global DefaultSCCSBuilder
+ if DefaultSCCSBuilder is None:
+ import SCons.Builder
+ import SCons.Defaults
+ DefaultSCCSBuilder = SCons.Builder.Builder(action = '$SCCSCOM',
+ env = SCons.Defaults._default_env)
+ return DefaultSCCSBuilder
+
+def get_DefaultRCSBuilder():
+ global DefaultRCSBuilder
+ if DefaultRCSBuilder is None:
+ import SCons.Builder
+ import SCons.Defaults
+ DefaultRCSBuilder = SCons.Builder.Builder(action = '$RCSCOM',
+ env = SCons.Defaults._default_env)
+ return DefaultRCSBuilder
+
#
class ParentOfRoot:
"""
return path_elems
def src_builder(self):
- return None
+ return _null
if os.path.normcase("TeSt") == os.path.normpath("TeSt"):
def _my_normcase(x):
try:
return self._exists
except AttributeError:
- self._exists = os.path.exists(self.abspath)
+ self._exists = _existsp(self.abspath)
return self._exists
def rexists(self):
self.pathTop = path
self.Root = {}
self.Top = None
- self.SConstruct = None
+ self.SConstruct_dir = None
self.CachePath = None
self.cache_force = None
self.cache_show = None
assert not self.Top, "You can only set the top-level path on an FS object that has not had its File, Dir, or Entry methods called yet."
self.pathTop = path
- def set_SConstruct(self, path):
- self.SConstruct = self.File(path)
+ def set_SConstruct_dir(self, dir):
+ self.SConstruct_dir = dir
def __setTopLevelDir(self):
if not self.Top:
# Go up one directory
d = d.get_dir()
return None
-
def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
"""Search for a list of somethings in the Repository list."""
d = n.get_dir()
name = n.name
- # Search repositories of all directories that this file is under.
+ # Search repositories of all directories that this file
+ # is under.
while d:
for rep in d.getRepositories():
try:
rnode = self.__doLookup(clazz, name, rep)
- # Only find the node if it exists (or must_exist is zero)
- # and it is not a derived file. If for some reason, we
- # are explicitly building a file IN a Repository, we don't
- # want it to show up in the build tree. This is usually the
- # case with BuildDir(). We only want to find pre-existing files.
+ # Only find the node if it exists (or
+ # must_exist is zero) and it is not a
+ # derived file. If for some reason, we
+ # are explicitly building a file IN a
+ # Repository, we don't want it to show up in
+ # the build tree. This is usually the case
+ # with BuildDir(). We only want to find
+ # pre-existing files.
if (not must_exist or rnode.exists()) and \
(not rnode.has_builder() or isinstance(rnode, Dir)):
ret.append(rnode)
else:
return 0
+ def rdir(self):
+ try:
+ return self._rdir
+ except AttributeError:
+ self._rdir = self
+ if not self.exists():
+ n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
+ if n:
+ self._rdir = n
+ return self._rdir
+
def sconsign(self):
"""Return the .sconsign file info for this directory,
creating it first if necessary."""
so only do thread safe stuff here. Do thread unsafe stuff in
built().
"""
- if not self.has_builder():
+ b = self.has_builder()
+ if not b and not self.has_src_builder():
return
- if self.fs.CachePath:
+ if b and self.fs.CachePath:
if self.fs.cache_show:
if CacheRetrieveSilent(self, None, None) == 0:
def do_print(action, targets, sources, env, self=self):
if self.fs.CachePath and self.fs.cache_force and os.path.exists(self.path):
CachePush(self, None, None)
- def has_builder(self, fetch = 1):
- """Return whether this Node has a builder or not.
+ def has_src_builder(self):
+ """Return whether this Node has a source builder or not.
+
+ If this Node doesn't have an explicit source code builder, this
+ is where we figure out, on the fly, if there's a transparent
+ source code builder for it.
- If this Node doesn't have an explicit builder, this is where we
- figure out, on the fly, if there's a source code builder for it.
+ Note that if we found a source builder, we also set the
+ self.builder attribute, so that all of the methods that actually
+ *build* this file don't have to do anything different.
"""
try:
- b = self.builder
+ scb = self.sbuilder
except AttributeError:
- if fetch and not os.path.exists(self.path):
- b = self.src_builder()
+ if self.rexists():
+ scb = None
else:
- b = None
- self.builder = b
- return not b is None
+ scb = self.dir.src_builder()
+ if scb is _null:
+ scb = None
+ dir = self.dir.path
+ sccspath = os.path.join('SCCS', 's.' + self.name)
+ if dir != '.':
+ sccspath = os.path.join(dir, sccspath)
+ if os.path.exists(sccspath):
+ scb = get_DefaultSCCSBuilder()
+ else:
+ rcspath = os.path.join('RCS', self.name + ',v')
+ if dir != '.':
+ rcspath = os.path.join(dir, rcspath)
+ if os.path.exists(rcspath):
+ scb = get_DefaultRCSBuilder()
+ self.builder = scb
+ self.sbuilder = scb
+ return not scb is None
+
+ def is_derived(self):
+ """Return whether this file is a derived file or not.
+
+ This overrides the base class method to account for the fact
+ that a file may be derived transparently from a source code
+ builder.
+ """
+ return self.has_builder() or self.side_effect or self.has_src_builder()
def prepare(self):
"""Prepare for this file to be created."""
def missing(node):
- return not node.has_builder() and not node.linked and not node.rexists()
+ return not node.has_builder() and not node.linked and not node.rexists() and not node.has_src_builder()
missing_sources = filter(missing, self.children())
if missing_sources:
desc = "No Builder for target `%s', needed by `%s'." % (missing_sources[0], self)
assert str(f) == os.path.join('sub', 'file')
assert not f.exists()
-class has_builderTestCase(unittest.TestCase):
+class has_src_builderTestCase(unittest.TestCase):
def runTest(self):
- """Test the has_builder() method"""
+ """Test the has_src_builder() method"""
test = TestCmd(workdir = '')
fs = SCons.Node.FS.FS(test.workpath(''))
os.chdir(test.workpath(''))
- test.subdir('sub')
-
- d = fs.Dir('sub', '.')
- f1 = fs.File('f1', d)
- f2 = fs.File('f2', d)
- f3 = fs.File('f3', d)
- f4 = fs.File('f4', d)
- f5 = fs.File('f5', d)
- f6 = fs.File('f6', d)
- f7 = fs.File('f7', d)
-
- h = f1.has_builder()
+ test.subdir('sub1')
+ test.subdir('sub2', ['sub2', 'SCCS'], ['sub2', 'RCS'])
+
+ sub1 = fs.Dir('sub1', '.')
+ f1 = fs.File('f1', sub1)
+ f2 = fs.File('f2', sub1)
+ f3 = fs.File('f3', sub1)
+ sub2 = fs.Dir('sub2', '.')
+ f4 = fs.File('f4', sub2)
+ f5 = fs.File('f5', sub2)
+ f6 = fs.File('f6', sub2)
+
+ h = f1.has_src_builder()
assert not h, h
b1 = Builder(fs.File)
- d.set_src_builder(b1)
+ sub1.set_src_builder(b1)
- test.write(['sub', 'f2'], "sub/f2\n")
- h = f1.has_builder() # cached from previous has_builder() call
+ test.write(['sub1', 'f2'], "sub1/f2\n")
+ h = f1.has_src_builder() # cached from previous call
assert not h, h
- h = f2.has_builder()
+ h = f2.has_src_builder()
assert not h, h
- h = f3.has_builder()
+ h = f3.has_src_builder()
assert h, h
assert f3.builder is b1, f3.builder
- test.write(['sub', 'f4'], "sub/f4\n")
- test.write(['sub', 'f6'], "sub/f6\n")
- h = f4.has_builder(fetch = 0)
+ test.write(['sub2', 'SCCS', 's.f5'], "sub2/SCCS/s.f5\n")
+ test.write(['sub2', 'RCS', 'f6,v'], "sub2/RCS/f6,v\n")
+ h = f4.has_src_builder()
assert not h, h
- h = f5.has_builder(fetch = 0)
- assert not h, h
- h = f6.has_builder(fetch = 1)
- assert not h, h
- h = f7.has_builder(fetch = 1)
+ h = f5.has_src_builder()
+ assert h, h
+ h = f6.has_src_builder()
assert h, h
class prepareTestCase(unittest.TestCase):
a = dir.get_actions()
assert a == [], a
-class SConstructTestCase(unittest.TestCase):
+class SConstruct_dirTestCase(unittest.TestCase):
def runTest(self):
- """Test setting the SConstruct file"""
+ """Test setting the SConstruct directory"""
fs = SCons.Node.FS.FS()
- fs.set_SConstruct('xxx')
- assert fs.SConstruct.path == 'xxx'
+ fs.set_SConstruct_dir(fs.Dir('xxx'))
+ assert fs.SConstruct_dir.path == 'xxx'
class CacheDirTestCase(unittest.TestCase):
def runTest(self):
suite.addTest(RepositoryTestCase())
suite.addTest(find_fileTestCase())
suite.addTest(StringDirTestCase())
- suite.addTest(has_builderTestCase())
+ suite.addTest(has_src_builderTestCase())
suite.addTest(prepareTestCase())
suite.addTest(get_actionsTestCase())
- suite.addTest(SConstructTestCase())
+ suite.addTest(SConstruct_dirTestCase())
suite.addTest(CacheDirTestCase())
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
"""Test the has_builder() method
"""
n1 = SCons.Node.Node()
+ assert n1.has_builder() == 0
+ n1.builder_set(Builder())
+ assert n1.has_builder() == 1
+
+ def test_is_derived(self):
+ """Test the is_derived() method
+ """
+ n1 = SCons.Node.Node()
n2 = SCons.Node.Node()
n3 = SCons.Node.Node()
- assert n1.has_builder() == 0
- assert n2.has_builder(fetch = 0) == 0
- assert n3.has_builder(fetch = 1) == 0
-
- n1.builder_set(Builder())
n2.builder_set(Builder())
- n3.builder_set(Builder())
+ n3.side_effect = 1
- assert n1.has_builder() == 1
- assert n2.has_builder(fetch = 0) == 1
- assert n3.has_builder(fetch = 1) == 1
+ assert n1.is_derived() == 0
+ assert n2.is_derived() == 1
+ assert n3.is_derived() == 1
def test_builder_sig_adapter(self):
"""Test the node's adapter for builder signatures
def builder_set(self, builder):
self.builder = builder
- def has_builder(self, fetch = 1):
+ def has_builder(self):
"""Return whether this Node has a builder or not.
In Boolean tests, this turns out to be a *lot* more efficient
b = self.builder
return not b is None
+ def is_derived(self):
+ return self.has_builder() or self.side_effect
+
def builder_sig_adapter(self):
"""Create an adapter for calculating a builder's signature.
import SCons.Node
import SCons.Node.FS
import SCons.Platform
+import SCons.Script
import SCons.Tool
import SCons.Util
import SCons.Options
default_fs = SCons.Node.FS.default_fs
top = default_fs.Top
- sd = default_fs.SConstruct.rfile().dir
+ sd = default_fs.SConstruct_dir.rdir()
# evaluate each SConscript file
results = []
default_fs.chdir(top, change_os_dir=1)
if f.rexists():
_file_ = open(f.rstr(), "r")
- elif f.has_builder():
+ elif f.has_src_builder():
# The SConscript file apparently exists in a source
# code management system. Build it, but then clear
# the builder so that it doesn't get built *again*
raise SCons.Errors.UserError, "Unknown build signature type '%s'"%type
def SetContentSignatureType(type):
- import SCons.Script
if type == 'MD5':
import SCons.Sig.MD5
SCons.Script.sig_module = SCons.Sig.MD5
def BuildDefaultGlobals():
"""
Create a dictionary containing all the default globals for
- SConscruct and SConscript files.
+ SConstruct and SConscript files.
"""
globals = {}
+ globals['_default_env'] = SCons.Defaults._default_env
globals['Action'] = SCons.Action.Action
globals['AddPostAction'] = AddPostAction
globals['AddPreAction'] = AddPreAction
# 'lib',
# 'scons-%d' % SCons.__version__)] + sys.path[1:]
+import SCons.Defaults
+import SCons.Environment
import SCons.Errors
import SCons.Job
import SCons.Node
SCons.Node.FS.default_fs.set_toplevel_dir(os.getcwd())
+ # Now that the top-level directory has been set,
+ # we can initialize the default Environment.
+ SCons.Defaults._default_env = SCons.Environment.Environment()
+
scripts = []
if options.file:
scripts.extend(options.file)
if not scripts:
raise SCons.Errors.UserError, "No SConstruct file found."
- SCons.Node.FS.default_fs.set_SConstruct(scripts[0])
+ if scripts[0] == "-":
+ d = SCons.Node.FS.default_fs.getcwd()
+ else:
+ d = SCons.Node.FS.default_fs.File(scripts[0]).dir
+ SCons.Node.FS.default_fs.set_SConstruct_dir(d)
class Unbuffered:
def __init__(self, file):
This unlinks all targets and makes all directories before
building anything."""
+
+ # Now that it's the appropriate time, give the TaskMaster a
+ # chance to raise any exceptions it encountered while preparing
+ # this task.
+ self.tm.exception_raise()
+
if self.targets[0].get_state() != SCons.Node.up_to_date:
for t in self.targets:
t.prepare()
so only do thread safe stuff here. Do thread unsafe stuff in
prepare(), executed() or failed()."""
- # Now that it's the appropriate time, give the TaskMaster a
- # chance to raise any exceptions it encountered while preparing
- # this task.
- self.tm.exception_raise()
-
try:
self.targets[0].build()
except KeyboardInterrupt:
# Add derived files that have not been built
# to the candidates list:
def derived(node):
- return (node.has_builder() or node.side_effect) and node.get_state() == None
- derived = filter(derived, children)
+ return node.is_derived() and node.get_state() == None
+ try:
+ derived = filter(derived, children)
+ except:
+ # We had a problem just trying to figure out the
+ # children (like a child couldn't be linked in to a
+ # BuildDir, or a Scanner threw something). Arrange to
+ # raise the exception when the Task is "executed."
+ self.exception_set(sys.exc_type, sys.exc_value)
+ self.candidates.pop()
+ self.ready = node
+ break
if derived:
derived.reverse()
self.candidates.extend(self.order(derived))
def has_builder(self):
return not self.builder is None
+ def is_derived(self):
+ return self.has_builder or self.side_effect
+
def built(self):
global built_text
built_text = built_text + " really"
assert n1.prepared
assert n2.prepared
+ # If the Node has had an exception recorded while it was getting
+ # prepared, then prepare() should raise that exception.
+ class MyException(Exception):
+ pass
+
+ built_text = None
+ n5 = Node("n5")
+ tm = SCons.Taskmaster.Taskmaster([n5])
+ tm.exc_type = MyException
+ tm.exc_value = "exception value"
+ t = tm.next_task()
+ exc_caught = None
+ try:
+ t.prepare()
+ except MyException, v:
+ assert str(v) == "exception value", v
+ exc_caught = 1
+ assert exc_caught, "did not catch expected MyException"
+ assert built_text is None, built_text
+
def test_execute(self):
"""Test executing a task
else:
raise TestFailed, "did not catch expected BuildError"
- # If the Node has had an exception recorded (during
- # preparation), then execute() should raise that exception,
- # not build the Node.
- class MyException(Exception):
- pass
-
- built_text = None
- n5 = Node("n5")
- tm = SCons.Taskmaster.Taskmaster([n5])
- tm.exc_type = MyException
- tm.exc_value = "exception value"
- t = tm.next_task()
- exc_caught = None
- try:
- t.execute()
- except MyException, v:
- assert str(v) == "exception value", v
- exc_caught = 1
- assert exc_caught, "did not catch expected MyException"
- assert built_text is None, built_text
-
def test_exception(self):
"""Test generic Taskmaster exception handling
print "Could not find `ci' command, skipping test(s)."
test.pass_test(1)
-# Test checkouts from local RCS files
+# Test explicit checkouts from local RCS files
test.subdir('work1', ['work1', 'sub'])
for file in ['aaa.in', 'bbb.in', 'ccc.in']:
for src in source:
f.write(open(src, "rb").read())
f.close()
-env = Environment(BUILDERS={'Cat':Builder(action=cat)})
+env = Environment(BUILDERS={'Cat':Builder(action=cat)},
+ RCSFLAGS='-q')
env.Cat('aaa.out', 'aaa.in')
env.Cat('bbb.out', 'bbb.in')
env.Cat('ccc.out', 'ccc.in')
test.run(chdir = 'work1',
arguments = '.',
stdout = test.wrap_stdout(read_str = """\
-co sub/SConscript
+co -q sub/SConscript
""",
build_str = """\
-co aaa.in
+co -q aaa.in
cat("aaa.out", "aaa.in")
cat("bbb.out", "bbb.in")
-co ccc.in
+co -q ccc.in
cat("ccc.out", "ccc.in")
cat("all", ["aaa.out", "bbb.out", "ccc.out"])
-co sub/ddd.in
+co -q sub/ddd.in
cat("sub/ddd.out", "sub/ddd.in")
cat("sub/eee.out", "sub/eee.in")
-co sub/fff.in
+co -q sub/fff.in
cat("sub/fff.out", "sub/fff.in")
cat("sub/all", ["sub/ddd.out", "sub/eee.out", "sub/fff.out"])
-"""),
- stderr = """\
-sub/SConscript,v --> sub/SConscript
-revision 1.1
-done
-aaa.in,v --> aaa.in
-revision 1.1
-done
-ccc.in,v --> ccc.in
-revision 1.1
-done
-sub/ddd.in,v --> sub/ddd.in
-revision 1.1
-done
-sub/fff.in,v --> sub/fff.in
-revision 1.1
-done
-""")
+"""))
test.fail_test(test.read(['work1', 'all']) != "work1/aaa.in\nchecked-out work1/bbb.in\nwork1/ccc.in\n")
test.fail_test(test.read(['work1', 'sub', 'all']) != "work1/sub/ddd.in\nchecked-out work1/sub/eee.in\nwork1/sub/fff.in\n")
-# Test RCS checkouts from an RCS subdirectory.
+# Test transparent RCS checkouts from an RCS subdirectory.
test.subdir('work2', ['work2', 'RCS'],
['work2', 'sub'], ['work2', 'sub', 'RCS'])
env.Cat('bbb.out', 'bbb.in')
env.Cat('ccc.out', 'ccc.in')
env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
-env.SourceCode('.', env.RCS())
SConscript('sub/SConscript', "env")
""")
test.run(chdir = 'work2',
arguments = '.',
stdout = test.wrap_stdout(read_str = """\
-co -q sub/SConscript
+co sub/SConscript
""",
build_str = """\
-co -q aaa.in
+co aaa.in
cat("aaa.out", "aaa.in")
cat("bbb.out", "bbb.in")
-co -q ccc.in
+co ccc.in
cat("ccc.out", "ccc.in")
cat("all", ["aaa.out", "bbb.out", "ccc.out"])
-co -q sub/ddd.in
+co sub/ddd.in
cat("sub/ddd.out", "sub/ddd.in")
cat("sub/eee.out", "sub/eee.in")
-co -q sub/fff.in
+co sub/fff.in
cat("sub/fff.out", "sub/fff.in")
cat("sub/all", ["sub/ddd.out", "sub/eee.out", "sub/fff.out"])
-"""))
+"""),
+ stderr = """\
+sub/RCS/SConscript,v --> sub/SConscript
+revision 1.1
+done
+RCS/aaa.in,v --> aaa.in
+revision 1.1
+done
+RCS/ccc.in,v --> ccc.in
+revision 1.1
+done
+sub/RCS/ddd.in,v --> sub/ddd.in
+revision 1.1
+done
+sub/RCS/fff.in,v --> sub/fff.in
+revision 1.1
+done
+""")
test.fail_test(test.read(['work2', 'all']) != "work2/aaa.in\nchecked-out work2/bbb.in\nwork2/ccc.in\n")
print "Could not find SCCS, skipping test(s)."
test.pass_test(1)
-# Test checkouts from local SCCS files.
+# Test explicit checkouts from local SCCS files.
test.subdir('work1', ['work1', 'sub'])
-test.preserve()
-
for file in ['aaa.in', 'bbb.in', 'ccc.in']:
test.write(['work1', file], "work1/%s\n" % file)
args = "create %s" % file
sccs get sub/fff.in
cat("sub/fff.out", "sub/fff.in")
cat("sub/all", ["sub/ddd.out", "sub/eee.out", "sub/fff.out"])
-"""), stderr = """\
+"""),
+ stderr = """\
sub/SConscript 1.1: 5 lines
aaa.in 1.1: 1 lines
ccc.in 1.1: 1 lines
test.fail_test(test.read(['work1', 'all']) != "work1/aaa.in\nchecked-out work1/bbb.in\nwork1/ccc.in\n")
+# Test transparent checkouts from SCCS files in an SCCS subdirectory.
+test.subdir('work2', ['work2', 'SCCS'],
+ ['work2', 'sub'], ['work2', 'sub', 'SCCS'])
+
+for file in ['aaa.in', 'bbb.in', 'ccc.in']:
+ test.write(['work2', file], "work2/%s\n" % file)
+ args = "create %s" % file
+ test.run(chdir = 'work2', program = sccs, arguments = args, stderr = None)
+ test.unlink(['work2', file])
+ test.unlink(['work2', ','+file])
+
+test.write(['work2', 'sub', 'SConscript'], """\
+Import("env")
+env.Cat('ddd.out', 'ddd.in')
+env.Cat('eee.out', 'eee.in')
+env.Cat('fff.out', 'fff.in')
+env.Cat('all', ['ddd.out', 'eee.out', 'fff.out'])
+""")
+args = "create SConscript"
+test.run(chdir = 'work2/sub', program = sccs, arguments = args, stderr = None)
+test.unlink(['work2', 'sub', 'SConscript'])
+test.unlink(['work2', 'sub', ',SConscript'])
+
+for file in ['ddd.in', 'eee.in', 'fff.in']:
+ test.write(['work2', 'sub', file], "work2/sub/%s\n" % file)
+ args = "create %s" % file
+ test.run(chdir = 'work2/sub', program = sccs, arguments = args, stderr = None)
+ test.unlink(['work2', 'sub', file])
+ test.unlink(['work2', 'sub', ','+file])
+
+test.write(['work2', 'SConstruct'], """
+def cat(env, source, target):
+ target = str(target[0])
+ source = map(str, source)
+ f = open(target, "wb")
+ for src in source:
+ f.write(open(src, "rb").read())
+ f.close()
+env = Environment(BUILDERS={'Cat':Builder(action=cat)})
+env.Cat('aaa.out', 'aaa.in')
+env.Cat('bbb.out', 'bbb.in')
+env.Cat('ccc.out', 'ccc.in')
+env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
+SConscript('sub/SConscript', "env")
+""")
+
+test.write(['work2', 'bbb.in'], "checked-out work2/bbb.in\n")
+
+test.write(['work2', 'sub', 'eee.in'], "checked-out work2/sub/eee.in\n")
+
+test.run(chdir = 'work2',
+ arguments = '.',
+ stdout = test.wrap_stdout(read_str = """\
+sccs get sub/SConscript
+""",
+ build_str = """\
+sccs get aaa.in
+cat("aaa.out", "aaa.in")
+cat("bbb.out", "bbb.in")
+sccs get ccc.in
+cat("ccc.out", "ccc.in")
+cat("all", ["aaa.out", "bbb.out", "ccc.out"])
+sccs get sub/ddd.in
+cat("sub/ddd.out", "sub/ddd.in")
+cat("sub/eee.out", "sub/eee.in")
+sccs get sub/fff.in
+cat("sub/fff.out", "sub/fff.in")
+cat("sub/all", ["sub/ddd.out", "sub/eee.out", "sub/fff.out"])
+"""),
+ stderr = """\
+sub/SConscript 1.1: 5 lines
+aaa.in 1.1: 1 lines
+ccc.in 1.1: 1 lines
+sub/ddd.in 1.1: 1 lines
+sub/fff.in 1.1: 1 lines
+""")
+
+test.fail_test(test.read(['work2', 'all']) != "work2/aaa.in\nchecked-out work2/bbb.in\nwork2/ccc.in\n")
+
test.pass_test()