summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/TestCmd.py26
-rw-r--r--etc/TestCommon.py19
-rw-r--r--src/CHANGES.txt2
-rw-r--r--src/engine/SCons/Node/FS.py14
-rw-r--r--src/engine/SCons/Node/FSTests.py7
-rw-r--r--test/symlink.py71
6 files changed, 130 insertions, 9 deletions
diff --git a/etc/TestCmd.py b/etc/TestCmd.py
index ca89ed9..48aa204 100644
--- a/etc/TestCmd.py
+++ b/etc/TestCmd.py
@@ -88,6 +88,8 @@ things. Here is an overview of them:
test.stderr()
test.stderr(run)
+ test.symlink(target, link)
+
test.match(actual, expected)
test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n")
@@ -173,8 +175,8 @@ version.
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.6.D001 2004/03/20 17:39:42 knight"
-__version__ = "0.6"
+__revision__ = "TestCmd.py 0.7.D001 2004/07/08 10:02:13 knight"
+__version__ = "0.7"
import os
import os.path
@@ -494,6 +496,8 @@ class TestCmd:
"""
if not self._dirlist:
return
+ os.chdir(self._cwd)
+ self.workdir = None
if condition is None:
condition = self.condition
if self._preserve[condition]:
@@ -507,8 +511,6 @@ class TestCmd:
shutil.rmtree(dir, ignore_errors = 1)
self._dirlist = []
- self.workdir = None
- os.chdir(self._cwd)
try:
global _Cleanup
_Cleanup.remove(self)
@@ -752,7 +754,21 @@ class TestCmd:
count = count + 1
return count
- def unlink (self, file):
+ def symlink(self, target, link):
+ """Creates a symlink to the specified target.
+ The link name may be a list, in which case the elements are
+ concatenated with the os.path.join() method. The link is
+ assumed to be under the temporary working directory unless it
+ is an absolute path name. The target is *not* assumed to be
+ under the temporary working directory.
+ """
+ if is_List(link):
+ link = apply(os.path.join, tuple(link))
+ if not os.path.isabs(link):
+ link = os.path.join(self.workdir, link)
+ os.symlink(target, link)
+
+ def unlink(self, file):
"""Unlinks the specified file name.
The file name may be a list, in which case the elements are
concatenated with the os.path.join() method. The file is
diff --git a/etc/TestCommon.py b/etc/TestCommon.py
index bf84ed3..8f4f22c 100644
--- a/etc/TestCommon.py
+++ b/etc/TestCommon.py
@@ -32,6 +32,8 @@ TestCommon object; see the TestCmd documentation for details.
Here is an overview of the methods and keyword arguments that are
provided by the TestCommon class:
+ test.must_contain('file', 'required text\n')
+
test.must_exist('file1', ['file2', ...])
test.must_match('file', "expected contents\n")
@@ -73,8 +75,8 @@ The TestCommon module also provides the following variables
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.6.D002 2004/03/29 06:21:41 knight"
-__version__ = "0.6"
+__revision__ = "TestCommon.py 0.7.D001 2004/07/08 10:02:13 knight"
+__version__ = "0.7"
import os
import os.path
@@ -182,6 +184,19 @@ class TestCommon(TestCmd):
apply(TestCmd.__init__, [self], kw)
os.chdir(self.workdir)
+ def must_contain(self, file, required):
+ """Ensures that the specified file contains the required text.
+ """
+ file_contents = self.read(file)
+ contains = (string.find(file_contents, required) != -1)
+ if not contains:
+ print "File `%s' does not contain required string." % file
+ print "Required string ====="
+ print required
+ print "%s contents =====" % file
+ print file_contents
+ self.fail_test(not contains)
+
def must_exist(self, *files):
"""Ensures that the specified file(s) must exist. An individual
file be specified as a list of directory names, in which case the
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index f2ee12d..0ecb526 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -157,6 +157,8 @@ RELEASE 0.96 - XXX
- Add support for fetching command-line keyword=value arguments in
order from an ARGLIST list.
+ - Avoid stack traces when trying to read dangling symlinks.
+
From Simon Perkins:
- Fix a bug introduced in building shared libraries under MinGW.
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 22d69c0..e61878b 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -494,7 +494,7 @@ class Base(SCons.Node.Node):
try:
return self._exists
except AttributeError:
- self._exists = self.fs.exists_or_islink(self.abspath)
+ self._exists = self.fs.exists(self.abspath)
return self._exists
def rexists(self):
@@ -635,6 +635,8 @@ class Entry(Base):
self.__class__ = Dir
self._morph()
return Dir.get_contents(self)
+ if self.fs.islink(self.abspath):
+ return '' # avoid errors for dangling symlinks
raise AttributeError
def exists(self):
@@ -719,9 +721,13 @@ class LocalFS:
return os.unlink(path)
if hasattr(os, 'symlink'):
+ def islink(self, path):
+ return os.path.islink(path)
def exists_or_islink(self, path):
return os.path.exists(path) or os.path.islink(path)
else:
+ def islink(self, path):
+ return 0 # no symlinks
exists_or_islink = exists
#class RemoteFS:
@@ -1700,7 +1706,11 @@ class File(Base):
else:
old = BuildInfo()
- mtime = self.get_timestamp()
+ try:
+ mtime = self.get_timestamp()
+ except:
+ mtime = 0
+ raise SCons.Errors.UserError, "no such %s" % self
try:
if (old.timestamp and old.csig and old.timestamp == mtime):
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index bbc64ef..231d736 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -990,6 +990,13 @@ class FSTestCase(unittest.TestCase):
assert c == "", c
assert e.__class__ == SCons.Node.FS.Dir
+ if hasattr(os, 'symlink'):
+ os.symlink('nonexistent', test.workpath('dangling_symlink'))
+ e = fs.Entry('dangling_symlink')
+ c = e.get_contents()
+ assert e.__class__ == SCons.Node.FS.Entry
+ assert c == "", c
+
test.write("tstamp", "tstamp\n")
try:
# Okay, *this* manipulation accomodates Windows FAT file systems
diff --git a/test/symlink.py b/test/symlink.py
new file mode 100644
index 0000000..62ce75b
--- /dev/null
+++ b/test/symlink.py
@@ -0,0 +1,71 @@
+#!/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__"
+
+"""
+Test how we handle symlinks in end-cases.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+if not hasattr(os, 'symlink'):
+ print "No os.symlink() method, no symlinks to test."
+ test.no_result(1)
+
+foo_obj = 'foo' + TestSCons._obj
+
+test.write('SConstruct', """
+Program('foo.c')
+""")
+
+test.write('foo.c', """\
+#include "foo.h"
+""")
+
+test.symlink('nonexistent', 'foo.h')
+
+test.run(arguments = '.',
+ status = 2,
+ stderr = None)
+
+expect = "scons: *** [%s] Error 1\n" % foo_obj
+test.fail_test(string.find(test.stderr(), expect) == -1)
+
+test.write('SConstruct', """
+Command('file.out', 'file.in', Copy())
+""")
+
+test.symlink('nonexistent', 'file.in')
+
+test.run(arguments = '.',
+ status = 2,
+ stderr = "scons: *** Source `file.in' not found, needed by target `file.out'. Stop.\n")
+
+test.pass_test()