From fc912e7ebd9f7e83de0b6b5399bd7bd6edf3fc24 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Thu, 8 Jul 2004 16:25:24 +0000 Subject: [PATCH] Fix errors when there are dangling symlinks. (Gary Oberbrunner) git-svn-id: http://scons.tigris.org/svn/scons/trunk@999 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- etc/TestCmd.py | 26 +++++++++--- etc/TestCommon.py | 19 ++++++++- src/CHANGES.txt | 2 + src/engine/SCons/Node/FS.py | 14 ++++++- src/engine/SCons/Node/FSTests.py | 7 ++++ test/symlink.py | 71 ++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 test/symlink.py diff --git a/etc/TestCmd.py b/etc/TestCmd.py index ca89ed94..48aa2048 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 " -__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 bf84ed39..8f4f22c3 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 " -__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 f2ee12dc..0ecb5269 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 22d69c05..e61878ba 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 bbc64ef9..231d7369 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 00000000..62ce75b5 --- /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() -- 2.26.2