- Fix adding a prefix to a file when the target isn't specified.
(Bug reported by Esa Ilari Vuokko.)
+ - Clean up error messages from problems duplicating into read-only
+ BuildDir directories or into read-only files.
+
From Steve Leblanc:
- Fix the output of -c -n when directories are involved, so it
Unlink(self, None, None)
except OSError:
pass
- Link(self, src, None)
+ try:
+ Link(self, src, None)
+ except IOError, e:
+ desc = "Cannot duplicate `%s' in `%s': %s." % (src, self.dir, e.strerror)
+ raise SCons.Errors.StopError, desc
self.linked = 1
# The Link() action may or may not have actually
# created the file, depending on whether the -n
finally:
SCons.Node.FS.Mkdir = save_Mkdir
SCons.Node.FS.Link = save_Link
+
+ # Test that an IOError trying to Link a src file
+ # into a BuildDir ends up throwing a StopError.
+ fIO = fs.File("build/var2/IOError")
+
+ save_Link = SCons.Node.FS.Link
+ def Link_IOError(target, source, env):
+ raise IOError, "Link_IOError"
+ SCons.Node.FS.Link = Link_IOError
+
+ test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
+
+ try:
+ exc_caught = 0
+ try:
+ fIO.exists()
+ except SCons.Errors.StopError:
+ exc_caught = 1
+ assert exc_caught, "Should have caught a StopError"
+
+ finally:
+ SCons.Node.FS.Link = save_Link
# Test to see if Link() works...
test.subdir('src','build')
from SCons.Util import display
import SCons.Warnings
-
#
# Task control.
#
def _main():
- import SCons.Node
-
targets = []
# Enable deprecated warnings by default.
display("scons: Reading SConscript files ...")
try:
start_time = time.time()
- for script in scripts:
- SCons.Script.SConscript.SConscript(script)
+ try:
+ for script in scripts:
+ SCons.Script.SConscript.SConscript(script)
+ except SCons.Errors.StopError, e:
+ # We had problems reading an SConscript file, such as it
+ # couldn't be copied in to the BuildDir. Since we're just
+ # reading SConscript files and haven't started building
+ # things yet, stop regardless of whether they used -i or -k
+ # or anything else, but don't say "Stop." on the message.
+ global exit_status
+ sys.stderr.write("scons: *** %s\n" % e)
+ exit_status = 2
+ sys.exit(exit_status)
global sconscript_time
sconscript_time = time.time() - start_time
except PrintHelp, text:
This method is called from multiple threads in a parallel build,
so only do thread safe stuff here. Do thread unsafe stuff in
prepare(), executed() or failed()."""
+ try:
+ # We recorded an exception while getting this Task ready
+ # for execution. Raise it now.
+ raise self.node.exc_type, self.node.exc_value
+ except AttributeError:
+ # The normal case: no exception to raise.
+ pass
try:
self.targets[0].build()
except KeyboardInterrupt:
# keep track of which nodes are in the execution stack:
node.set_state(SCons.Node.stack)
- children = node.children()
+ try:
+ children = node.children()
+ except:
+ # We had a problem just trying to figure out the
+ # children (like a child couldn't be linked in to a
+ # BuildDir). Arrange to raise the exception when the
+ # Task is "executed."
+ node.exc_type = sys.exc_type
+ node.exc_value = sys.exc_value
+ self.candidates.pop()
+ self.ready = node
+ break
# detect dependency cycles:
def in_stack(node): return node.get_state() == SCons.Node.stack
node.set_state(SCons.Node.pending)
self.candidates.pop()
continue
- else:
- self.candidates.pop()
- self.ready = node
- break
+
+ # The default when we've gotten through all of the checks above.
+ self.candidates.pop()
+ self.ready = node
+ break
def next_task(self):
"""Return the next task to be executed."""
self.executing.extend(node.side_effects)
task = self.tasker(self, tlist, node in self.targets, node)
- task.make_ready()
+ try:
+ task.make_ready()
+ except:
+ # We had a problem just trying to get this task ready (like
+ # a child couldn't be linked in to a BuildDir when deciding
+ # whether this node is current). Arrange to raise the
+ # exception when the Task is "executed."
+ node.exc_type = sys.exc_type
+ node.exc_value = sys.exc_value
self.ready = None
return task
class OtherError(Exception):
pass
+class MyException(Exception):
+ pass
+
class TaskmasterTestCase(unittest.TestCase):
assert not tm.next_task()
t.executed()
+ def test_make_ready_exception(self):
+ """Test handling exceptions from Task.make_ready()
+ """
+ class MyTask(SCons.Taskmaster.Task):
+ def make_ready(self):
+ raise MyException, "from make_ready()"
+
+ n1 = Node("n1")
+ tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask)
+ t = tm.next_task()
+ assert n1.exc_type == MyException, n1.exc_type
+ assert str(n1.exc_value) == "from make_ready()", n1.exc_value
+
+
+ def test_children_errors(self):
+ """Test errors when fetching the children of a node.
+ """
+ class MyNode(Node):
+ def children(self):
+ raise SCons.Errors.StopError, "stop!"
+ n1 = MyNode("n1")
+ tm = SCons.Taskmaster.Taskmaster([n1])
+ t = tm.next_task()
+ assert n1.exc_type == SCons.Errors.StopError, "Did not record StopError on node"
+ assert str(n1.exc_value) == "stop!", "Unexpected exc_value `%s'" % n1.exc_value
+
def test_cycle_detection(self):
"""Test detecting dependency cycles
else:
raise TestFailed, "did not catch expected BuildError"
+ # On a generic (non-BuildError) exception from a Builder,
+ # the target should throw a BuildError exception with the
+ # args set to the exception value, instance, and traceback.
def raise_OtherError():
raise OtherError
n4 = Node("n4")
try:
t.execute()
except SCons.Errors.BuildError, e:
- # On a generic (non-BuildError) exception from a Builder,
- # the target should throw a BuildError exception with the
- # args set to the exception value, instance, and traceback.
assert e.node == n4, e.node
assert e.errstr == "Exception", e.errstr
assert len(e.args) == 3, `e.args`
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")
+ n5.exc_type = MyException
+ n5.exc_value = "exception value"
+ tm = SCons.Taskmaster.Taskmaster([n5])
+ 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
--- /dev/null
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Validate successful handling of errors when duplicating things in
+BuildDirs. This is generally when the BuildDir, or something in it,
+is read-only.
+"""
+
+import os
+import os.path
+import stat
+import sys
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+for dir in ['normal', 'ro-dir', 'ro-SConscript', 'ro-src']:
+ test.subdir(dir, [dir, 'src'])
+
+ test.write([dir, 'SConstruct'], """\
+import os.path
+BuildDir('build', 'src')
+SConscript(os.path.join('build', 'SConscript'))
+""")
+
+ test.write([dir, 'src', 'SConscript'], """\
+def fake_scan(node, env, target):
+ # We fetch the contents here, even though we don't examine
+ # them, because get_contents() will cause the engine to
+ # try to link the source file into the build directory,
+ # potentially triggering a different failure case.
+ contents = node.get_contents()
+ return []
+
+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={'Build':Builder(action=cat)},
+ SCANNERS=[Scanner(fake_scan, skeys = ['.in'])])
+env.Build('file.out', 'file.in')
+""")
+
+ test.write([dir, 'src', 'file.in'], dir + "/src/file.in\n")
+
+# Just verify that the normal case works fine.
+test.run(chdir = 'normal', arguments = ".")
+
+test.fail_test(test.read(['normal', 'build', 'file.out']) != "normal/src/file.in\n")
+
+# Verify the error when the BuildDir itself is read-only.
+dir = os.path.join('ro-dir', 'build')
+test.subdir(dir)
+os.chmod(dir, os.stat(dir)[stat.ST_MODE] & ~stat.S_IWUSR)
+
+test.run(chdir = 'ro-dir',
+ arguments = ".",
+ status = 2,
+ stderr = "scons: *** Cannot duplicate `%s' in `build': Permission denied.\n" % os.path.join('src', 'SConscript'))
+
+# Verify the error when the SConscript file within the BuildDir is
+# read-only. Note that we have to make the directory read-only too,
+# because otherwise our duplication logic will be able to unlink
+# the read-only SConscript and duplicate the new one.
+dir = os.path.join('ro-SConscript', 'build')
+test.subdir(dir)
+SConscript = test.workpath(dir, 'SConscript')
+test.write(SConscript, '')
+os.chmod(SConscript, os.stat(SConscript)[stat.ST_MODE] & ~stat.S_IWUSR)
+f = open(SConscript, 'r')
+os.chmod(dir, os.stat(dir)[stat.ST_MODE] & ~stat.S_IWUSR)
+
+test.run(chdir = 'ro-SConscript',
+ arguments = ".",
+ status = 2,
+ stderr = "scons: *** Cannot duplicate `%s' in `build': Permission denied.\n" % os.path.join('src', 'SConscript'))
+
+os.chmod('ro-SConscript', os.stat('ro-SConscript')[stat.ST_MODE] | stat.S_IWUSR)
+f.close()
+
+test.run(chdir = 'ro-SConscript',
+ arguments = ".",
+ status = 2,
+ stderr = "scons: *** Cannot duplicate `%s' in `build': Permission denied.\n" % os.path.join('src', 'SConscript'))
+
+# Verify the error when the source file within the BuildDir is
+# read-only. Note that we have to make the directory read-only too,
+# because otherwise our duplication logic will be able to unlink the
+# read-only source file and duplicate the new one. But because we've
+# made the BuildDir read-only, we must also create a writable SConscript
+# file there so it can be duplicated from the source directory.
+dir = os.path.join('ro-src', 'build')
+test.subdir(dir)
+test.write([dir, 'SConscript'], '')
+file_in = test.workpath(dir, 'file.in')
+test.write(file_in, '')
+os.chmod(file_in, os.stat(file_in)[stat.ST_MODE] & ~stat.S_IWUSR)
+f = open(file_in, 'r')
+os.chmod(dir, os.stat(dir)[stat.ST_MODE] & ~stat.S_IWUSR)
+
+test.run(chdir = 'ro-src',
+ arguments = ".",
+ status = 2,
+ stderr = "scons: *** Cannot duplicate `%s' in `build': Permission denied. Stop.\n" % os.path.join('src', 'file.in'))
+
+test.run(chdir = 'ro-src',
+ arguments = "-k .",
+ status = 2,
+ stderr = "scons: *** Cannot duplicate `%s' in `build': Permission denied.\n" % os.path.join('src', 'file.in'))
+
+f.close()
+
+#
+test.pass_test()