Add warnings when we switch to BuildDir() targets when using -u or -U.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 26 Mar 2003 15:57:50 +0000 (15:57 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 26 Mar 2003 15:57:50 +0000 (15:57 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@622 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
test/option-u.py

index 57fd1c29f076e5e4a546716eb98c18150689de38..d6b7b532fdbe925dffe8fb4c8e8c96d963587427 100644 (file)
@@ -197,6 +197,7 @@ class ParentOfRoot:
         self.name=''
         self.duplicate=0
         self.srcdir=None
+        self.build_dirs=[]
         
     def is_under(self, dir):
         return 0
@@ -714,6 +715,24 @@ class FS:
     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
@@ -749,6 +768,7 @@ class Dir(Entry):
         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.
@@ -801,6 +821,7 @@ class Dir(Entry):
         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."""
@@ -850,6 +871,11 @@ class Dir(Entry):
         """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
@@ -1160,6 +1186,13 @@ class File(Entry):
         """
         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."""
 
index d460c1ed98c88fbad12dfd45566e490286ea1b19..11146eb5a73057c8629d0f6436908ad6d333e442 100644 (file)
@@ -282,6 +282,21 @@ class BuildDirTestCase(unittest.TestCase):
         assert f10.exists()
         assert f10.get_contents() == 'stuff', f10.get_contents()
 
+        f11 = fs.File('src/file11')
+        t, m = f11.alter_targets()
+        bdt = map(lambda n: n.path, t)
+        assert bdt == ['build/var1/file11', 'build/var2/file11'], bdt
+
+        f12 = fs.File('src/file12')
+        f12.builder = 1
+        bdt, m = f12.alter_targets()
+        assert bdt == [], map(lambda n: n.path, bdt)
+
+        d13 = fs.Dir('src/new_dir')
+        t, m = d13.alter_targets()
+        bdt = map(lambda n: n.path, t)
+        assert bdt == ['build/var1/new_dir', 'build/var2/new_dir'], bdt
+
         save_Mkdir = SCons.Node.FS.Mkdir
         dir_made = []
         def mkdir_func(target, source, env, dir_made=dir_made):
index 91b35d036a252e3bc30402fb730fbcee77d2f09f..f2828609255e4f70c23d96f5b2809f52ab43867b 100644 (file)
@@ -277,6 +277,14 @@ class NodeTestCase(unittest.TestCase):
         assert n2.is_derived() == 1
         assert n3.is_derived() == 1
 
+    def test_alter_targets(self):
+        """Test the alter_targets() method
+        """
+        n = SCons.Node.Node()
+        t, m = n.alter_targets()
+        assert t == [], t
+        assert m == None, m
+
     def test_builder_sig_adapter(self):
         """Test the node's adapter for builder signatures
         """
index 326aee396b51fbc822f899742d21845224369d7a..222cd1f1139b3b599a184045cf140ad0612fb420 100644 (file)
@@ -231,6 +231,11 @@ class Node:
     def is_derived(self):
         return self.has_builder() or self.side_effect
 
+    def alter_targets(self):
+        """Return a list of alternate targets for this Node.
+        """
+        return [], None
+
     def builder_sig_adapter(self):
         """Create an adapter for calculating a builder's signature.
 
index 60d299d22c9145374bd9cbedfa1d05eb4cc65f00..d2c60aa45bcf00b22e7a8f384e5c7ea58da31792 100644 (file)
@@ -73,6 +73,9 @@ import SCons.Warnings
 #
 class BuildTask(SCons.Taskmaster.Task):
     """An SCons build task."""
+    def display(self, message):
+        display('scons: ' + message)
+
     def execute(self):
         target = self.targets[0]
         if target.get_state() == SCons.Node.up_to_date:
index 93df5697521e7051a152e817a211ccf4a99c4245..bc53024d1be4d80340a3592ce30e4365b8619085 100644 (file)
@@ -58,6 +58,11 @@ class Task:
         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.
 
@@ -69,6 +74,10 @@ class Task:
         # 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()
@@ -197,6 +206,7 @@ class Taskmaster:
         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"""
@@ -243,24 +253,39 @@ class Taskmaster:
                 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
index c6543e3746223d1b9901b1ec6e43caaf75c1c40e..89c53c74a436cd0f0491533621f68ef9c71c723e 100644 (file)
@@ -50,6 +50,7 @@ class Node:
         self.parents = []
         self.side_effect = 0
         self.side_effects = []
+        self.alttargets = []
 
         for kid in kids:
             kid.parents.append(self)
@@ -64,6 +65,9 @@ class Node:
     def is_derived(self):
         return self.has_builder or self.side_effect
 
+    def alter_targets(self):
+        return self.alttargets, None
+
     def built(self):
         global built_text
         built_text = built_text + " really"
@@ -376,6 +380,18 @@ class TaskmasterTestCase(unittest.TestCase):
         assert t.get_target() == n4, t.get_target()
         t.executed()
 
+        n5 = Node("n5")
+        n6 = Node("n6")
+        n7 = Node("n7")
+        n6.alttargets = [n7]
+        tm = SCons.Taskmaster.Taskmaster([n5])
+        t = tm.next_task()
+        assert t.get_target() == n5
+        t.executed()
+        tm = SCons.Taskmaster.Taskmaster([n6])
+        t = tm.next_task()
+        assert t.get_target() == n7
+        t.executed()
 
 
     def test_make_ready_exception(self):
index 1f7df7ae57b24640da04f61ea03b355cdd440800..9cb00550145dad75020203ca85046b39d704d000 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+"""
+Test that the -u option only builds targets at or below
+the current directory.
+"""
+
 import os.path
 import sys
 
@@ -31,49 +36,104 @@ import TestSCons
 
 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()