summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Node
diff options
context:
space:
mode:
authorGary Oberbrunner <garyo@oberbrunner.com>2010-11-18 14:59:10 (GMT)
committerGary Oberbrunner <garyo@oberbrunner.com>2010-11-18 14:59:10 (GMT)
commit2de14f9d4a0cf9f95dcb172c93f83015b7877546 (patch)
treea062063d12284c921b4603bb52eec8b6daa116b1 /src/engine/SCons/Node
parent3f478c8dbe11dbb4eabb77cf6443a10ed039af0d (diff)
downloadSCons-2de14f9d4a0cf9f95dcb172c93f83015b7877546.zip
SCons-2de14f9d4a0cf9f95dcb172c93f83015b7877546.tar.gz
SCons-2de14f9d4a0cf9f95dcb172c93f83015b7877546.tar.bz2
Fix for issue #1420, Windows UNC path handling. Applied patch from Benoit Belley after updating it to fit into trunk, and added a couple of extra tests from issues 1420, 1857 and 1948.
Diffstat (limited to 'src/engine/SCons/Node')
-rw-r--r--src/engine/SCons/Node/FS.py350
-rw-r--r--src/engine/SCons/Node/FSTests.py271
2 files changed, 494 insertions, 127 deletions
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 967da26..7f3bd8e 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -110,33 +110,85 @@ def save_strings(val):
#
do_splitdrive = None
+_my_splitdrive =None
def initialize_do_splitdrive():
global do_splitdrive
+ global has_unc
drive, path = os.path.splitdrive('X:/foo')
- do_splitdrive = not not drive
+ has_unc = hasattr(os.path, 'splitunc')
+
+ do_splitdrive = not not drive or has_unc
+
+ global _my_splitdrive
+ if has_unc:
+ def splitdrive(p):
+ if p[1:2] == ':':
+ return p[:2], p[2:]
+ if p[0:2] == '//':
+ # Note that we leave a leading slash in the path
+ # because UNC paths are always absolute.
+ return '//', p[1:]
+ return '', p
+ else:
+ def splitdrive(p):
+ if p[1:2] == ':':
+ return p[:2], p[2:]
+ return '', p
+ _my_splitdrive = splitdrive
+
+ # Keep some commonly used values in global variables to skip to
+ # module look-up costs.
+ global OS_SEP
+ global UNC_PREFIX
+ global os_sep_is_slash
+
+ OS_SEP = os.sep
+ UNC_PREFIX = OS_SEP + OS_SEP
+ os_sep_is_slash = OS_SEP == '/'
initialize_do_splitdrive()
-#
-
-needs_normpath_check = None
-
-def initialize_normpath_check():
- """
- Initialize the normpath_check regular expression.
-
- This function is used by the unit tests to re-initialize the pattern
- when testing for behavior with different values of os.sep.
- """
- global needs_normpath_check
- if os.sep == '/':
- pattern = r'.*/|\.$|\.\.$'
- else:
- pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
- needs_normpath_check = re.compile(pattern)
-
-initialize_normpath_check()
+# Used to avoid invoking os.path.normpath if not necessary.
+needs_normpath_check = re.compile(
+ r'''
+ # We need to renormalize the path if it contains any consecutive
+ # '/' characters.
+ .*// |
+
+ # We need to renormalize the path if it contains a '..' directory.
+ # Note that we check for all the following cases:
+ #
+ # a) The path is a single '..'
+ # b) The path starts with '..'. E.g. '../' or '../moredirs'
+ # but we not match '..abc/'.
+ # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
+ # d) The path contains a '..' in the middle.
+ # E.g. dirs/../moredirs
+
+ (.*/)?\.\.(?:/|$) |
+
+ # We need to renormalize the path if it contains a '.'
+ # directory, but NOT if it is a single '.' '/' characters. We
+ # do not want to match a single '.' because this case is checked
+ # for explicitely since this is common enough case.
+ #
+ # Note that we check for all the following cases:
+ #
+ # a) We don't match a single '.'
+ # b) We match if the path starts with '.'. E.g. './' or
+ # './moredirs' but we not match '.abc/'.
+ # c) We match if the path ends with '.'. E.g. '/.' or
+ # 'dirs/.'
+ # d) We match if the path contains a '.' in the middle.
+ # E.g. dirs/./moredirs
+
+ \./|.*/\.(?:/|$)
+
+ ''',
+ re.VERBOSE
+ )
+needs_normpath_match = needs_normpath_check.match
#
# SCons.Action objects for interacting with the outside world.
@@ -438,21 +490,21 @@ class EntryProxy(SCons.Util.Proxy):
def __get_posix_path(self):
"""Return the path with / as the path separator,
regardless of platform."""
- if os.sep == '/':
+ if os_sep_is_slash:
return self
else:
entry = self.get()
- r = entry.get_path().replace(os.sep, '/')
+ r = entry.get_path().replace(OS_SEP, '/')
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_windows_path(self):
"""Return the path with \ as the path separator,
regardless of platform."""
- if os.sep == '\\':
+ if OS_SEP == '\\':
return self
else:
entry = self.get()
- r = entry.get_path().replace(os.sep, '\\')
+ r = entry.get_path().replace(OS_SEP, '\\')
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
def __get_srcnode(self):
@@ -695,11 +747,15 @@ class Base(SCons.Node.Node):
if self == dir:
return '.'
path_elems = self.path_elements
+ pathname = ''
try: i = path_elems.index(dir)
- except ValueError: pass
- else: path_elems = path_elems[i+1:]
- path_elems = [n.name for n in path_elems]
- return os.sep.join(path_elems)
+ except ValueError:
+ for p in path_elems[:-1]:
+ pathname += p.dirname
+ else:
+ for p in path_elems[i+1:-1]:
+ pathname += p.dirname
+ return pathname + path_elems[-1].name
def set_src_builder(self, builder):
"""Set the source code builder for this node."""
@@ -1063,7 +1119,7 @@ class FS(LocalFS):
self.pathTop = os.getcwd()
else:
self.pathTop = path
- self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
+ self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
self.Top = self.Dir(self.pathTop)
self.Top.path = '.'
@@ -1150,54 +1206,110 @@ class FS(LocalFS):
# str(p) in case it's something like a proxy object
p = str(p)
- initial_hash = (p[0:1] == '#')
- if initial_hash:
+ if not os_sep_is_slash:
+ p = p.replace(OS_SEP, '/')
+
+ if p[0:1] == '#':
# There was an initial '#', so we strip it and override
# whatever directory they may have specified with the
# top-level SConstruct directory.
p = p[1:]
directory = self.Top
- if directory and not isinstance(directory, Dir):
- directory = self.Dir(directory)
+ # There might be a drive letter following the
+ # '#'. Although it is not described in the SCons man page,
+ # the regression test suite explicitly tests for that
+ # syntax. It seems to mean the following thing:
+ #
+ # Assuming the the SCons top dir is in C:/xxx/yyy,
+ # '#X:/toto' means X:/xxx/yyy/toto.
+ #
+ # i.e. it assumes that the X: drive has a directory
+ # structure similar to the one found on drive C:.
+ if do_splitdrive:
+ drive, p = _my_splitdrive(p)
+ if drive:
+ root = self.get_root(drive)
+ else:
+ root = directory.root
+ else:
+ root = directory.root
- if do_splitdrive:
- drive, p = os.path.splitdrive(p)
- else:
- drive = ''
- if drive and not p:
- # This causes a naked drive letter to be treated as a synonym
- # for the root directory on that drive.
- p = os.sep
- absolute = os.path.isabs(p)
-
- needs_normpath = needs_normpath_check.match(p)
-
- if initial_hash or not absolute:
- # This is a relative lookup, either to the top-level
- # SConstruct directory (because of the initial '#') or to
- # the current directory (the path name is not absolute).
- # Add the string to the appropriate directory lookup path,
- # after which the whole thing gets normalized.
- if not directory:
- directory = self._cwd
- if p:
+ # We can only strip trailing after splitting the drive
+ # since the drive might the UNC '//' prefix.
+ p = p.strip('/')
+
+ needs_normpath = needs_normpath_match(p)
+
+ # The path is relative to the top-level SCons directory.
+ if p in ('', '.'):
+ p = directory.labspath
+ else:
p = directory.labspath + '/' + p
+ else:
+ if do_splitdrive:
+ drive, p = _my_splitdrive(p)
+ if drive and not p:
+ # This causes a naked drive letter to be treated
+ # as a synonym for the root directory on that
+ # drive.
+ p = '/'
else:
- p = directory.labspath
+ drive = ''
- if needs_normpath:
- p = os.path.normpath(p)
+ # We can only strip trailing '/' since the drive might the
+ # UNC '//' prefix.
+ if p != '/':
+ p = p.rstrip('/')
- if drive or absolute:
- root = self.get_root(drive)
- else:
- if not directory:
- directory = self._cwd
- root = directory.root
+ needs_normpath = needs_normpath_match(p)
+
+ if p[0:1] == '/':
+ # Absolute path
+ root = self.get_root(drive)
+ else:
+ # This is a relative lookup or to the current directory
+ # (the path name is not absolute). Add the string to the
+ # appropriate directory lookup path, after which the whole
+ # thing gets normalized.
+ if directory:
+ if not isinstance(directory, Dir):
+ directory = self.Dir(directory)
+ else:
+ directory = self._cwd
+
+ if p in ('', '.'):
+ p = directory.labspath
+ else:
+ p = directory.labspath + '/' + p
+
+ if drive:
+ root = self.get_root(drive)
+ else:
+ root = directory.root
+
+ if needs_normpath is not None:
+ # Normalize a pathname. Will return the same result for
+ # equivalent paths.
+ #
+ # We take advantage of the fact that we have an absolute
+ # path here for sure. In addition, we know that the
+ # components of lookup path are separated by slashes at
+ # this point. Because of this, this code is about 2X
+ # faster than calling os.path.normpath() followed by
+ # replacing os.sep with '/' again.
+ ins = p.split('/')[1:]
+ outs = []
+ for d in ins:
+ if d == '..':
+ try:
+ outs.pop()
+ except IndexError:
+ pass
+ elif d not in ('', '.'):
+ outs.append(d)
+ p = '/' + '/'.join(outs)
- if os.sep != '/':
- p = p.replace(os.sep, '/')
return root._lookup_abs(p, fsclass, create)
def Entry(self, name, directory = None, create = 1):
@@ -1303,7 +1415,7 @@ class DirNodeInfo(SCons.Node.NodeInfoBase):
top = self.fs.Top
root = top.root
if do_splitdrive:
- drive, s = os.path.splitdrive(s)
+ drive, s = _my_splitdrive(s)
if drive:
root = self.fs.get_root(drive)
if not os.path.isabs(s):
@@ -1353,6 +1465,17 @@ class Dir(Base):
self.variant_dirs = []
self.root = self.dir.root
+ # For directories, we make a difference between the directory
+ # 'name' and the directory 'dirname'. The 'name' attribute is
+ # used when we need to print the 'name' of the directory or
+ # when we it is used as the last part of a path. The 'dirname'
+ # is used when the directory is not the last element of the
+ # path. The main reason for making that distinction is that
+ # for RoorDir's the dirname can not be easily inferred from
+ # the name. For example, we have to add a '/' after a drive
+ # letter but not after a UNC path prefix ('//').
+ self.dirname = self.name + OS_SEP
+
# Don't just reset the executor, replace its action list,
# because it might have some pre-or post-actions that need to
# be preserved.
@@ -1418,23 +1541,6 @@ class Dir(Base):
"""
return self.fs.File(name, self)
- def _lookup_rel(self, name, klass, create=1):
- """
- Looks up a *normalized* relative path name, relative to this
- directory.
-
- This method is intended for use by internal lookups with
- already-normalized path data. For general-purpose lookups,
- use the Entry(), Dir() and File() methods above.
-
- This method does *no* input checking and will die or give
- incorrect results if it's passed a non-normalized path name (e.g.,
- a path containing '..'), an absolute path name, a top-relative
- ('#foo') path name, or any kind of object.
- """
- name = self.entry_labspath(name)
- return self.root._lookup_abs(name, klass, create)
-
def link(self, srcdir, duplicate):
"""Set this directory as the variant directory for the
supplied source directory."""
@@ -1467,7 +1573,7 @@ class Dir(Base):
if fname == '.':
fname = dir.name
else:
- fname = dir.name + os.sep + fname
+ fname = dir.name + OS_SEP + fname
dir = dir.up()
self._memo['get_all_rdirs'] = list(result)
@@ -1481,7 +1587,7 @@ class Dir(Base):
self.__clearRepositoryCache()
def up(self):
- return self.entries['..']
+ return self.dir
def _rel_path_key(self, other):
return str(other)
@@ -1529,14 +1635,14 @@ class Dir(Base):
if dir_rel_path == '.':
result = other.name
else:
- result = dir_rel_path + os.sep + other.name
+ result = dir_rel_path + OS_SEP + other.name
else:
i = self.path_elements.index(other) + 1
path_elems = ['..'] * (len(self.path_elements) - i) \
+ [n.name for n in other.path_elements[i:]]
- result = os.sep.join(path_elems)
+ result = OS_SEP.join(path_elems)
memo_dict[other] = result
@@ -1706,16 +1812,16 @@ class Dir(Base):
return stamp
def entry_abspath(self, name):
- return self.abspath + os.sep + name
+ return self.abspath + OS_SEP + name
def entry_labspath(self, name):
return self.labspath + '/' + name
def entry_path(self, name):
- return self.path + os.sep + name
+ return self.path + OS_SEP + name
def entry_tpath(self, name):
- return self.tpath + os.sep + name
+ return self.tpath + OS_SEP + name
def entry_exists_on_disk(self, name):
try:
@@ -1736,7 +1842,7 @@ class Dir(Base):
if result is None:
# Belt-and-suspenders for Windows: check directly for
# 8.3 file names that don't show up in os.listdir().
- result = os.path.exists(self.abspath + os.sep + name)
+ result = os.path.exists(self.abspath + OS_SEP + name)
d[name] = result
return result
else:
@@ -1757,7 +1863,7 @@ class Dir(Base):
while dir:
if dir.srcdir:
result.append(dir.srcdir.Dir(dirname))
- dirname = dir.name + os.sep + dirname
+ dirname = dir.name + OS_SEP + dirname
dir = dir.up()
self._memo['srcdir_list'] = result
@@ -2001,7 +2107,7 @@ class RootDir(Dir):
add a separator when creating the path names of entries within
this directory.
"""
- def __init__(self, name, fs):
+ def __init__(self, drive, fs):
if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
# We're going to be our own parent directory (".." entry and .dir
# attribute) so we have to set up some values so Base.__init__()
@@ -2013,29 +2119,47 @@ class RootDir(Dir):
self.path_elements = []
self.duplicate = 0
self.root = self
+
+ # Handle all the types of drives:
+ if drive == '':
+ # No drive, regular UNIX root or Windows default drive.
+ name = OS_SEP
+ dirname = OS_SEP
+ elif drive == '//':
+ # UNC path
+ name = UNC_PREFIX
+ dirname = UNC_PREFIX
+ else:
+ # Windows drive letter
+ name = drive
+ dirname = drive + OS_SEP
+
Base.__init__(self, name, self, fs)
- # Now set our paths to what we really want them to be: the
- # initial drive letter (the name) plus the directory separator,
- # except for the "lookup abspath," which does not have the
- # drive letter.
- self.abspath = name + os.sep
+ # Now set our paths to what we really want them to be. The
+ # name should already contain any necessary separators, such
+ # as the initial drive letter (the name) plus the directory
+ # separator, except for the "lookup abspath," which does not
+ # have the drive letter.
+ self.abspath = dirname
self.labspath = ''
- self.path = name + os.sep
- self.tpath = name + os.sep
+ self.path = dirname
+ self.tpath = dirname
self._morph()
+ # Must be reset after Dir._morph() is invoked...
+ self.dirname = dirname
+
self._lookupDict = {}
- # The // and os.sep + os.sep entries are necessary because
- # os.path.normpath() seems to preserve double slashes at the
- # beginning of a path (presumably for UNC path names), but
- # collapses triple slashes to a single slash.
self._lookupDict[''] = self
self._lookupDict['/'] = self
- self._lookupDict['//'] = self
- self._lookupDict[os.sep] = self
- self._lookupDict[os.sep + os.sep] = self
+
+ # The // entry is necessary because os.path.normpath()
+ # preserves double slashes at the beginning of a path on Posix
+ # platforms.
+ if not has_unc:
+ self._lookupDict['//'] = self
def must_be_same(self, klass):
if klass is Dir:
@@ -2067,7 +2191,7 @@ class RootDir(Dir):
raise SCons.Errors.UserError(msg)
# There is no Node for this path name, and we're allowed
# to create it.
- dir_name, file_name = os.path.split(p)
+ dir_name, file_name = p.rsplit('/',1)
dir_node = self._lookup_abs(dir_name, Dir)
result = klass(file_name, dir_node, self.fs)
@@ -2126,7 +2250,7 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
top = self.fs.Top
root = top.root
if do_splitdrive:
- drive, s = os.path.splitdrive(s)
+ drive, s = _my_splitdrive(s)
if drive:
root = self.fs.get_root(drive)
if not os.path.isabs(s):
@@ -2144,7 +2268,7 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
usual string representation: relative to the top-level SConstruct
directory, or an absolute path if it's outside.
"""
- if os.sep == '/':
+ if os_sep_is_slash:
node_to_str = str
else:
def node_to_str(n):
@@ -2153,7 +2277,7 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
except AttributeError:
s = str(n)
else:
- s = s.replace(os.sep, '/')
+ s = s.replace(OS_SEP, '/')
return s
for attr in ['bsources', 'bdepends', 'bimplicit']:
try:
@@ -2997,8 +3121,8 @@ class FileFinder(object):
if fd is None:
fd = self.default_filedir
dir, name = os.path.split(fd)
- drive, d = os.path.splitdrive(dir)
- if not name and d[:1] in ('/', os.sep):
+ drive, d = _my_splitdrive(dir)
+ if not name and d[:1] in ('/', OS_SEP):
#return p.fs.get_root(drive).dir_on_disk(name)
return p.fs.get_root(drive)
if dir:
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index b5c1448..d4fb7df 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -817,6 +817,107 @@ class FileBuildInfoTestCase(_tempdirTestCase):
assert format == expect, (repr(expect), repr(format))
class FSTestCase(_tempdirTestCase):
+ def test_needs_normpath(self):
+ """Test the needs_normpath Regular expression
+
+ This test case verifies that the regular expression used to
+ determine whether a path needs normalization works as
+ expected.
+ """
+ needs_normpath_match = SCons.Node.FS.needs_normpath_match
+
+ do_not_need_normpath = [
+ ".",
+ "/",
+ "/a",
+ "/aa",
+ "/a/",
+ "/aa/",
+ "/a/b",
+ "/aa/bb",
+ "/a/b/",
+ "/aa/bb/",
+
+ "",
+ "a",
+ "aa",
+ "a/",
+ "aa/",
+ "a/b",
+ "aa/bb",
+ "a/b/",
+ "aa/bb/",
+
+ "a.",
+ "a..",
+ "/a.",
+ "/a..",
+ "a./",
+ "a../",
+ "/a./",
+ "/a../",
+
+
+ ".a",
+ "..a",
+ "/.a",
+ "/..a",
+ ".a/",
+ "..a/",
+ "/.a/",
+ "/..a/",
+ ]
+ for p in do_not_need_normpath:
+ assert needs_normpath_match(p) is None, p
+
+ needs_normpath = [
+ "//",
+ "//a",
+ "//aa",
+ "//a/",
+ "//a/",
+ "/aa//",
+
+ "//a/b",
+ "//aa/bb",
+ "//a/b/",
+ "//aa/bb/",
+
+ "/a//b",
+ "/aa//bb",
+ "/a/b//",
+ "/aa/bb//",
+
+ "/a/b//",
+ "/aa/bb//",
+
+ "a//",
+ "aa//",
+ "a//b",
+ "aa//bb",
+ "a//b/",
+ "aa//bb/",
+ "a/b//",
+ "aa/bb//",
+
+ "..",
+ "/.",
+ "/..",
+ "./",
+ "../",
+ "/./",
+ "/../",
+
+ "a/.",
+ "a/..",
+ "./a",
+ "../a",
+ "a/./a",
+ "a/../a",
+ ]
+ for p in needs_normpath:
+ assert needs_normpath_match(p) is not None, p
+
def test_runTest(self):
"""Test FS (file system) Node operations
@@ -932,8 +1033,20 @@ class FSTestCase(_tempdirTestCase):
path = strip_slash(path_)
abspath = strip_slash(abspath_)
up_path = strip_slash(up_path_)
+
name = abspath.split(os.sep)[-1]
+ if not name:
+ if drive:
+ name = drive
+ else:
+ name = os.sep
+
+ if dir.up() is None:
+ dir_up_path = dir.path
+ else:
+ dir_up_path = dir.up().path
+
assert dir.name == name, \
"dir.name %s != expected name %s" % \
(dir.name, name)
@@ -946,15 +1059,16 @@ class FSTestCase(_tempdirTestCase):
assert dir.get_abspath() == abspath, \
"dir.abspath %s != expected absolute path %s" % \
(dir.get_abspath(), abspath)
- assert dir.up().path == up_path, \
+ assert dir_up_path == up_path, \
"dir.up().path %s != expected parent path %s" % \
- (dir.up().path, up_path)
+ (dir_up_path, up_path)
for sep in seps:
def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test):
return func(lpath, path_, abspath_, up_path_, sep)
-
+
+ Dir_test('/', '/', '/', '/')
Dir_test('', './', sub_dir, sub)
Dir_test('foo', 'foo/', sub_dir_foo, './')
Dir_test('foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
@@ -1423,12 +1537,12 @@ class FSTestCase(_tempdirTestCase):
test.subdir('sub', ['sub', 'dir'])
- def drive_workpath(drive, dirs, test=test):
+ def drive_workpath(dirs, test=test):
x = test.workpath(*dirs)
drive, path = os.path.splitdrive(x)
return 'X:' + path
- wp = drive_workpath('X:', [''])
+ wp = drive_workpath([''])
if wp[-1] in (os.sep, '/'):
tmp = os.path.split(wp[:-1])[0]
@@ -1441,13 +1555,13 @@ class FSTestCase(_tempdirTestCase):
tmp_foo = os.path.join(tmp, 'foo')
- foo = drive_workpath('X:', ['foo'])
- foo_bar = drive_workpath('X:', ['foo', 'bar'])
- sub = drive_workpath('X:', ['sub', ''])
- sub_dir = drive_workpath('X:', ['sub', 'dir', ''])
- sub_dir_foo = drive_workpath('X:', ['sub', 'dir', 'foo', ''])
- sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', ''])
- sub_foo = drive_workpath('X:', ['sub', 'foo', ''])
+ foo = drive_workpath(['foo'])
+ foo_bar = drive_workpath(['foo', 'bar'])
+ sub = drive_workpath(['sub', ''])
+ sub_dir = drive_workpath(['sub', 'dir', ''])
+ sub_dir_foo = drive_workpath(['sub', 'dir', 'foo', ''])
+ sub_dir_foo_bar = drive_workpath(['sub', 'dir', 'foo', 'bar', ''])
+ sub_foo = drive_workpath(['sub', 'foo', ''])
fs = SCons.Node.FS.FS()
@@ -1490,7 +1604,6 @@ class FSTestCase(_tempdirTestCase):
os.path = ntpath
os.sep = '\\'
SCons.Node.FS.initialize_do_splitdrive()
- SCons.Node.FS.initialize_normpath_check()
for sep in seps:
@@ -1517,7 +1630,117 @@ class FSTestCase(_tempdirTestCase):
os.path = save_os_path
os.sep = save_os_sep
SCons.Node.FS.initialize_do_splitdrive()
- SCons.Node.FS.initialize_normpath_check()
+
+ def test_unc_path(self):
+ """Test UNC path look-ups"""
+
+ test = self.test
+
+ test.subdir('sub', ['sub', 'dir'])
+
+ def strip_slash(p):
+ if p[-1] == os.sep and len(p) > 3:
+ p = p[:-1]
+ return p
+
+ def unc_workpath(dirs, test=test):
+ import ntpath
+ x = apply(test.workpath, dirs)
+ drive, path = ntpath.splitdrive(x)
+ unc, path = ntpath.splitunc(path)
+ path = strip_slash(path)
+ return '//' + path[1:]
+
+ wp = unc_workpath([''])
+
+ if wp[-1] in (os.sep, '/'):
+ tmp = os.path.split(wp[:-1])[0]
+ else:
+ tmp = os.path.split(wp)[0]
+
+ parent_tmp = os.path.split(tmp)[0]
+
+ tmp_foo = os.path.join(tmp, 'foo')
+
+ foo = unc_workpath(['foo'])
+ foo_bar = unc_workpath(['foo', 'bar'])
+ sub = unc_workpath(['sub', ''])
+ sub_dir = unc_workpath(['sub', 'dir', ''])
+ sub_dir_foo = unc_workpath(['sub', 'dir', 'foo', ''])
+ sub_dir_foo_bar = unc_workpath(['sub', 'dir', 'foo', 'bar', ''])
+ sub_foo = unc_workpath(['sub', 'foo', ''])
+
+ fs = SCons.Node.FS.FS()
+
+ seps = [os.sep]
+ if os.sep != '/':
+ seps = seps + ['/']
+
+ def _do_Dir_test(lpath, path, up_path, sep, fileSys=fs):
+ dir = fileSys.Dir(lpath.replace('/', sep))
+
+ if os.sep != '/':
+ path = path.replace('/', os.sep)
+ up_path = up_path.replace('/', os.sep)
+
+ if path == os.sep + os.sep:
+ name = os.sep + os.sep
+ else:
+ name = path.split(os.sep)[-1]
+
+ if dir.up() is None:
+ dir_up_path = dir.path
+ else:
+ dir_up_path = dir.up().path
+
+ assert dir.name == name, \
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
+ assert dir.path == path, \
+ "dir.path %s != expected path %s" % \
+ (dir.path, path)
+ assert str(dir) == path, \
+ "str(dir) %s != expected path %s" % \
+ (str(dir), path)
+ assert dir_up_path == up_path, \
+ "dir.up().path %s != expected parent path %s" % \
+ (dir.up().path, up_path)
+
+ save_os_path = os.path
+ save_os_sep = os.sep
+ try:
+ import ntpath
+ os.path = ntpath
+ os.sep = '\\'
+ SCons.Node.FS.initialize_do_splitdrive()
+
+ for sep in seps:
+
+ def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
+ return func(lpath, path_, up_path_, sep)
+
+ Dir_test('//foo', '//foo', '//')
+ Dir_test('//foo/bar', '//foo/bar', '//foo')
+ Dir_test('//', '//', '//')
+ Dir_test('//..', '//', '//')
+ Dir_test('//foo/..', '//', '//')
+ Dir_test('//../foo', '//foo', '//')
+ Dir_test('//.', '//', '//')
+ Dir_test('//./.', '//', '//')
+ Dir_test('//foo/./bar', '//foo/bar', '//foo')
+ Dir_test('//foo/../bar', '//bar', '//')
+ Dir_test('//foo/../../bar', '//bar', '//')
+ Dir_test('//foo/bar/../..', '//', '//')
+ Dir_test('#//', wp, tmp)
+ Dir_test('#//../foo', tmp_foo, tmp)
+ Dir_test('#//../foo', tmp_foo, tmp)
+ Dir_test('#//foo/bar', foo_bar, foo)
+ Dir_test('#//foo/bar', foo_bar, foo)
+ Dir_test('#//', wp, tmp)
+ finally:
+ os.path = save_os_path
+ os.sep = save_os_sep
+ SCons.Node.FS.initialize_do_splitdrive()
def test_target_from_source(self):
"""Test the method for generating target nodes from sources"""
@@ -1571,6 +1794,26 @@ class FSTestCase(_tempdirTestCase):
above_path = os.path.join(*['..']*len(dirs) + ['above'])
above = d2.Dir(above_path)
+ def test_lookup_uncpath(self):
+ """Testing looking up a UNC path on Windows"""
+ if sys.platform not in ('win32',):
+ return
+ test = self.test
+ fs = self.fs
+ path='//servername/C$/foo'
+ f = self.fs._lookup('//servername/C$/foo', fs.Dir('#'), SCons.Node.FS.File)
+ # before the fix in this commit, this returned 'C:\servername\C$\foo'
+ # Should be a normalized Windows UNC path as below.
+ assert str(f) == r'\\servername\C$\foo', \
+ 'UNC path %s got looked up as %s'%(path, f)
+
+ def test_unc_drive_letter(self):
+ """Test drive-letter lookup for windows UNC-style directories"""
+ if sys.platform not in ('win32',):
+ return
+ share = self.fs.Dir(r'\\SERVER\SHARE\Directory')
+ assert str(share) == r'\\SERVER\SHARE\Directory', str(share)
+
def test_rel_path(self):
"""Test the rel_path() method"""
test = self.test