self.name=''
self.duplicate=0
self.srcdir=None
+ self.build_dirs=[]
def is_under(self, dir):
return 0
def CacheDir(self, path):
self.CachePath = path
+ def build_dir_target_climb(self, dir, tail):
+ """Create targets in corresponding build directories
+
+ Climb the directory tree, and look up path names
+ relative to any linked build directories we find.
+ """
+ targets = []
+ message = None
+ while dir:
+ for bd in dir.build_dirs:
+ p = apply(os.path.join, [bd.path] + tail)
+ targets.append(self.Entry(p))
+ tail = [dir.name] + tail
+ dir = dir.up()
+ if targets:
+ message = "building associated BuildDir targets: %s" % string.join(map(str, targets))
+ return targets, message
+
# XXX TODO?
# Annotate with the creator
# rel_path
self.cwd = self
self.builder = 1
self._sconsign = None
+ self.build_dirs = []
def __clearRepositoryCache(self, duplicate=None):
"""Called when we change the repository(ies) for a directory.
self.srcdir = srcdir
self.duplicate = duplicate
self.__clearRepositoryCache(duplicate)
+ srcdir.build_dirs.append(self)
def getRepositories(self):
"""Returns a list of repositories for this directory."""
"""A null "builder" for directories."""
pass
+ def alter_targets(self):
+ """Return any corresponding targets in a build directory.
+ """
+ return self.fs.build_dir_target_climb(self, [])
+
def calc_signature(self, calc):
"""A directory has no signature."""
return None
"""
return self.has_builder() or self.side_effect or self.has_src_builder()
+ def alter_targets(self):
+ """Return any corresponding targets in a build directory.
+ """
+ if self.has_builder():
+ return [], None
+ return self.fs.build_dir_target_climb(self.dir, [self.name])
+
def prepare(self):
"""Prepare for this file to be created."""
self.top = top
self.node = node
+ def display(self, message):
+ """Allow the calling interface to display a message
+ """
+ pass
+
def prepare(self):
"""Called just before the task is executed.
# this task.
self.tm.exception_raise()
+ if self.tm.message:
+ self.display(self.tm.message)
+ self.tm.message = None
+
if self.targets[0].get_state() != SCons.Node.up_to_date:
for t in self.targets:
t.prepare()
self.calc = calc
self.order = order
self.exception_set(None, None)
+ self.message = None
def _find_next_ready_node(self):
"""Find the next node that is ready to be built"""
desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
raise SCons.Errors.UserError, desc
- # Add derived files that have not been built
- # to the candidates list:
- def derived(node):
- return node.is_derived() and node.get_state() == None
+ # Find all of the derived dependencies (that is,
+ # children who have builders or are side effects):
try:
- derived = filter(derived, children)
+ def derived_nodes(node): return node.is_derived()
+ derived = filter(derived_nodes, 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."
+ # We had a problem just trying to figure out if any of
+ # the kids are derived (like a child couldn't be linked
+ # from a repository). 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))
+
+ # If there aren't any children with builders and this
+ # was a top-level argument, then see if we can find any
+ # corresponding targets in linked build directories:
+ if not derived and node in self.targets:
+ alt, message = node.alter_targets()
+ if alt:
+ self.message = message
+ self.candidates.pop()
+ self.candidates.extend(alt)
+ continue
+
+ # Add derived files that have not been built
+ # to the candidates list:
+ def unbuilt_nodes(node): return node.get_state() == None
+ not_built = filter(unbuilt_nodes, derived)
+ if not_built:
+ not_built.reverse()
+ self.candidates.extend(self.order(not_built))
continue
# Skip this node if it has side-effects that are
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Test that the -u option only builds targets at or below
+the current directory.
+"""
+
import os.path
import sys
test = TestSCons.TestSCons()
-python = TestSCons.python
-
-test.subdir('sub1', 'sub2', 'sub3')
-
-test.write('build.py', r"""
-import sys
-contents = open(sys.argv[2], 'rb').read()
-file = open(sys.argv[1], 'wb')
-file.write(contents)
-file.close()
-""")
+test.subdir('sub1',
+ 'sub2', ['sub2', 'dir'],
+ 'sub3',
+ 'sub4', ['sub4', 'dir'])
test.write('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()
-env['BUILDERS']['B'] = Builder(action=r'%s build.py $TARGET $SOURCES')
-env.B(target = 'sub1/foo.out', source = 'sub1/foo.in')
+env.Append(BUILDERS = {'Cat' : Builder(action=cat)})
+env.Cat(target = 'sub1/f1a.out', source = 'sub1/f1a.in')
+env.Cat(target = 'sub1/f1b.out', source = 'sub1/f1b.in')
Export('env')
SConscript('sub2/SConscript')
-env.Alias('baz', env.B(target = 'sub3/baz.out', source = 'sub3/baz.in'))
-""" % python)
+f3 = env.Cat(target = 'sub3/f3.out', source = 'sub3/f3.in')
+env.Alias('my_alias', f3)
+BuildDir('build', 'sub4')
+SConscript('build/SConscript')
+""")
test.write(['sub2', 'SConscript'], """
Import('env')
-env.B(target = 'bar.out', source = 'bar.in')
+env.Cat(target = 'f2a.out', source = 'f2a.in')
+env.Cat(target = 'dir/f2b.out', source = 'dir/f2b.in')
""")
-test.write(['sub1', 'foo.in'], "sub1/foo.in")
-test.write(['sub2', 'bar.in'], "sub2/bar.in")
-test.write(['sub3', 'baz.in'], "sub3/baz.in")
-
-test.run(arguments = '-u foo.out', chdir = 'sub1')
-
-test.fail_test(test.read(['sub1', 'foo.out']) != "sub1/foo.in")
-test.fail_test(os.path.exists(test.workpath('sub2', 'bar.out')))
-test.fail_test(os.path.exists(test.workpath('sub3', 'baz.out')))
+test.write(['sub4', 'SConscript'], """
+Import('env')
+env.Cat(target = 'f4a.out', source = 'f4a.in')
+env.Cat(target = 'dir/f4b.out', source = 'dir/f4b.in')
+""")
+test.write(['sub1', 'f1a.in'], "sub1/f1a.in")
+test.write(['sub1', 'f1b.in'], "sub1/f1b.in")
+test.write(['sub2', 'f2a.in'], "sub2/f2a.in")
+test.write(['sub2', 'dir', 'f2b.in'], "sub2/dir/f2b.in")
+test.write(['sub3', 'f3.in'], "sub3/f3.in")
+test.write(['sub4', 'f4a.in'], "sub4/f4a.in")
+test.write(['sub4', 'dir', 'f4b.in'], "sub4/dir/f4b.in")
+
+# Verify that we only build the specified local argument.
+test.run(chdir = 'sub1', arguments = '-u f1a.out')
+
+test.fail_test(test.read(['sub1', 'f1a.out']) != "sub1/f1a.in")
+test.fail_test(os.path.exists(test.workpath('sub1', 'sub1/f1b.out')))
+test.fail_test(os.path.exists(test.workpath('sub2', 'f2a.out')))
+test.fail_test(os.path.exists(test.workpath('sub2', 'dir', 'f2b.out')))
+test.fail_test(os.path.exists(test.workpath('sub3', 'f3.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'dir', 'f4b.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'dir', 'f4b.out')))
+
+# Verify that we build everything at or below our current directory.
test.run(chdir = 'sub2', arguments = '-u')
-test.fail_test(test.read(['sub2', 'bar.out']) != "sub2/bar.in")
-test.fail_test(os.path.exists(test.workpath('sub3', 'baz.out')))
-
-test.run(chdir = 'sub2', arguments = '-u baz')
-test.fail_test(test.read(['sub3', 'baz.out']) != "sub3/baz.in")
+test.fail_test(os.path.exists(test.workpath('sub1', 'sub1/f1b.out')))
+test.fail_test(test.read(['sub2', 'f2a.out']) != "sub2/f2a.in")
+test.fail_test(test.read(['sub2', 'dir', 'f2b.out']) != "sub2/dir/f2b.in")
+test.fail_test(os.path.exists(test.workpath('sub3', 'f3.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'dir', 'f4b.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'dir', 'f4b.out')))
+
+# Verify that we build a specified alias, regardless of where.
+test.run(chdir = 'sub2', arguments = '-u my_alias')
+
+test.fail_test(os.path.exists(test.workpath('sub1', 'sub1/f1b.out')))
+test.fail_test(test.read(['sub3', 'f3.out']) != "sub3/f3.in")
+test.fail_test(os.path.exists(test.workpath('sub4', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'dir', 'f4b.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('build', 'dir', 'f4b.out')))
+
+# Verify that we build things in a linked BuildDir.
+f4a_in = os.path.join('build', 'f4a.in')
+f4a_out = os.path.join('build', 'f4a.out')
+f4b_in = os.path.join('build', 'dir', 'f4b.in')
+f4b_out = os.path.join('build', 'dir', 'f4b.out')
+test.run(chdir = 'sub4',
+ arguments = '-u',
+ stdout = "scons: Entering directory %s\n" % test.workpath() + \
+ test.wrap_stdout("""\
+scons: building associated BuildDir targets: build
+cat("%s", "%s")
+cat("%s", "%s")
+""" % (f4b_out, f4b_in, f4a_out, f4a_in)))
+
+test.fail_test(os.path.exists(test.workpath('sub1', 'sub1/f1b.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'f4a.out')))
+test.fail_test(os.path.exists(test.workpath('sub4', 'dir', 'f4b.out')))
+test.fail_test(test.read(['build', 'f4a.out']) != "sub4/f4a.in")
+test.fail_test(test.read(['build', 'dir', 'f4b.out']) != "sub4/dir/f4b.in")
test.pass_test()
-