summaryrefslogtreecommitdiff
path: root/src/engine/SCons/Node
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/SCons/Node')
-rw-r--r--src/engine/SCons/Node/Alias.py4
-rw-r--r--src/engine/SCons/Node/AliasTests.py17
-rw-r--r--src/engine/SCons/Node/FS.py389
-rw-r--r--src/engine/SCons/Node/FSTests.py845
-rw-r--r--src/engine/SCons/Node/NodeTests.py25
-rw-r--r--src/engine/SCons/Node/Python.py17
-rw-r--r--src/engine/SCons/Node/PythonTests.py23
-rw-r--r--src/engine/SCons/Node/__init__.py125
8 files changed, 953 insertions, 492 deletions
diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py
index 567f663..a9defb6 100644
--- a/src/engine/SCons/Node/Alias.py
+++ b/src/engine/SCons/Node/Alias.py
@@ -8,7 +8,7 @@ This creates a hash of global Aliases (dummy targets).
"""
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -30,7 +30,7 @@ This creates a hash of global Aliases (dummy targets).
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-__revision__ = "src/engine/SCons/Node/Alias.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/Alias.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import collections
diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py
index e1929f6..764ac67 100644
--- a/src/engine/SCons/Node/AliasTests.py
+++ b/src/engine/SCons/Node/AliasTests.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,13 +21,11 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-__revision__ = "src/engine/SCons/Node/AliasTests.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/AliasTests.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import sys
import unittest
-import TestUnit
-
import SCons.Errors
import SCons.Node.Alias
@@ -113,16 +111,7 @@ class AliasBuildInfoTestCase(unittest.TestCase):
bi = SCons.Node.Alias.AliasBuildInfo()
if __name__ == "__main__":
- suite = unittest.TestSuite()
- tclasses = [
- AliasTestCase,
- AliasBuildInfoTestCase,
- AliasNodeInfoTestCase,
- ]
- for tclass in tclasses:
- names = unittest.getTestCaseNames(tclass, 'test_')
- suite.addTests(list(map(tclass, names)))
- TestUnit.run(suite)
+ unittest.main()
# Local Variables:
# tab-width:4
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index a953890..21ccecc 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -11,7 +11,7 @@ that can be used by scripts or modules looking for the canonical default.
"""
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -33,7 +33,7 @@ that can be used by scripts or modules looking for the canonical default.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
-__revision__ = "src/engine/SCons/Node/FS.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/FS.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import fnmatch
import os
@@ -43,6 +43,7 @@ import stat
import sys
import time
import codecs
+from itertools import chain
import SCons.Action
import SCons.Debug
@@ -59,6 +60,8 @@ from SCons.Debug import Trace
print_duplicate = 0
+MD5_TIMESTAMP_DEBUG = False
+
def sconsign_none(node):
raise NotImplementedError
@@ -74,6 +77,9 @@ def sconsign_dir(node):
_sconsign_map = {0 : sconsign_none,
1 : sconsign_dir}
+class FileBuildInfoFileToCsigMappingError(Exception):
+ pass
+
class EntryProxyAttributeError(AttributeError):
"""
An AttributeError subclass for recording and displaying the name
@@ -132,7 +138,10 @@ def initialize_do_splitdrive():
global do_splitdrive
global has_unc
drive, path = os.path.splitdrive('X:/foo')
- has_unc = hasattr(os.path, 'splitunc')
+ # splitunc is removed from python 3.7 and newer
+ # so we can also just test if splitdrive works with UNC
+ has_unc = (hasattr(os.path, 'splitunc')
+ or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive')
do_splitdrive = not not drive or has_unc
@@ -272,7 +281,7 @@ def set_duplicate(duplicate):
'copy' : _copy_func
}
- if not duplicate in Valid_Duplicates:
+ if duplicate not in Valid_Duplicates:
raise SCons.Errors.InternalError("The argument of set_duplicate "
"should be in Valid_Duplicates")
global Link_Funcs
@@ -282,11 +291,13 @@ def set_duplicate(duplicate):
Link_Funcs.append(link_dict[func])
def LinkFunc(target, source, env):
- # Relative paths cause problems with symbolic links, so
- # we use absolute paths, which may be a problem for people
- # who want to move their soft-linked src-trees around. Those
- # people should use the 'hard-copy' mode, softlinks cannot be
- # used for that; at least I have no idea how ...
+ """
+ Relative paths cause problems with symbolic links, so
+ we use absolute paths, which may be a problem for people
+ who want to move their soft-linked src-trees around. Those
+ people should use the 'hard-copy' mode, softlinks cannot be
+ used for that; at least I have no idea how ...
+ """
src = source[0].get_abspath()
dest = target[0].get_abspath()
dir, file = os.path.split(dest)
@@ -328,7 +339,12 @@ Unlink = SCons.Action.Action(UnlinkFunc, None)
def MkdirFunc(target, source, env):
t = target[0]
- if not t.exists():
+ # This os.path.exists test looks redundant, but it's possible
+ # when using Install() to install multiple dirs outside the
+ # source tree to get a case where t.exists() is true but
+ # the path does already exist, so this prevents spurious
+ # build failures in that case. See test/Install/multi-dir.
+ if not t.exists() and not os.path.exists(t.get_abspath()):
t.fs.mkdir(t.get_abspath())
return 0
@@ -463,7 +479,7 @@ class EntryProxy(SCons.Util.Proxy):
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_windows_path(self):
- """Return the path with \ as the path separator,
+ r"""Return the path with \ as the path separator,
regardless of platform."""
if OS_SEP == '\\':
return self
@@ -514,7 +530,7 @@ class EntryProxy(SCons.Util.Proxy):
except KeyError:
try:
attr = SCons.Util.Proxy.__getattr__(self, name)
- except AttributeError as e:
+ except AttributeError:
# Raise our own AttributeError subclass with an
# overridden __str__() method that identifies the
# name of the entry that caused the exception.
@@ -682,10 +698,15 @@ class Base(SCons.Node.Node):
@SCons.Memoize.CountMethodCall
def stat(self):
- try: return self._memo['stat']
- except KeyError: pass
- try: result = self.fs.stat(self.get_abspath())
- except os.error: result = None
+ try:
+ return self._memo['stat']
+ except KeyError:
+ pass
+ try:
+ result = self.fs.stat(self.get_abspath())
+ except os.error:
+ result = None
+
self._memo['stat'] = result
return result
@@ -697,13 +718,17 @@ class Base(SCons.Node.Node):
def getmtime(self):
st = self.stat()
- if st: return st[stat.ST_MTIME]
- else: return None
+ if st:
+ return st[stat.ST_MTIME]
+ else:
+ return None
def getsize(self):
st = self.stat()
- if st: return st[stat.ST_SIZE]
- else: return None
+ if st:
+ return st[stat.ST_SIZE]
+ else:
+ return None
def isdir(self):
st = self.stat()
@@ -1048,21 +1073,22 @@ _classEntry = Entry
class LocalFS(object):
-
- # This class implements an abstraction layer for operations involving
- # a local file system. Essentially, this wraps any function in
- # the os, os.path or shutil modules that we use to actually go do
- # anything with or to the local file system.
- #
- # Note that there's a very good chance we'll refactor this part of
- # the architecture in some way as we really implement the interface(s)
- # for remote file system Nodes. For example, the right architecture
- # might be to have this be a subclass instead of a base class.
- # Nevertheless, we're using this as a first step in that direction.
- #
- # We're not using chdir() yet because the calling subclass method
- # needs to use os.chdir() directly to avoid recursion. Will we
- # really need this one?
+ """
+ This class implements an abstraction layer for operations involving
+ a local file system. Essentially, this wraps any function in
+ the os, os.path or shutil modules that we use to actually go do
+ anything with or to the local file system.
+
+ Note that there's a very good chance we'll refactor this part of
+ the architecture in some way as we really implement the interface(s)
+ for remote file system Nodes. For example, the right architecture
+ might be to have this be a subclass instead of a base class.
+ Nevertheless, we're using this as a first step in that direction.
+
+ We're not using chdir() yet because the calling subclass method
+ needs to use os.chdir() directly to avoid recursion. Will we
+ really need this one?
+ """
#def chdir(self, path):
# return os.chdir(path)
def chmod(self, path, mode):
@@ -1389,10 +1415,10 @@ class FS(LocalFS):
if not isinstance(d, SCons.Node.Node):
d = self.Dir(d)
self.Top.addRepository(d)
-
+
def PyPackageDir(self, modulename):
- """Locate the directory of a given python module name
-
+ r"""Locate the directory of a given python module name
+
For example scons might resolve to
Windows: C:\Python27\Lib\site-packages\scons-2.5.1
Linux: /usr/lib/scons
@@ -1662,7 +1688,7 @@ class Dir(Base):
return result
def addRepository(self, dir):
- if dir != self and not dir in self.repositories:
+ if dir != self and dir not in self.repositories:
self.repositories.append(dir)
dir._tpath = '.'
self.__clearRepositoryCache()
@@ -1702,7 +1728,7 @@ class Dir(Base):
if self is other:
result = '.'
- elif not other in self._path_elements:
+ elif other not in self._path_elements:
try:
other_dir = other.get_dir()
except AttributeError:
@@ -2234,7 +2260,7 @@ class RootDir(Dir):
this directory.
"""
- __slots__ = ['_lookupDict']
+ __slots__ = ('_lookupDict', )
def __init__(self, drive, fs):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
@@ -2440,7 +2466,7 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
"""
state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro():
- for name in getattr(obj,'__slots__',()):
+ for name in getattr(obj, '__slots__', ()):
if hasattr(self, name):
state[name] = getattr(self, name)
@@ -2462,11 +2488,42 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
if key not in ('__weakref__',):
setattr(self, key, value)
+ def __eq__(self, other):
+ return self.csig == other.csig and self.timestamp == other.timestamp and self.size == other.size
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
class FileBuildInfo(SCons.Node.BuildInfoBase):
- __slots__ = ()
+ """
+ This is info loaded from sconsign.
+
+ Attributes unique to FileBuildInfo:
+ dependency_map : Caches file->csig mapping
+ for all dependencies. Currently this is only used when using
+ MD5-timestamp decider.
+ It's used to ensure that we copy the correct
+ csig from previous build to be written to .sconsign when current build
+ is done. Previously the matching of csig to file was strictly by order
+ they appeared in bdepends, bsources, or bimplicit, and so a change in order
+ or count of any of these could yield writing wrong csig, and then false positive
+ rebuilds
+ """
+ __slots__ = ['dependency_map', ]
current_version_id = 2
+ def __setattr__(self, key, value):
+
+ # If any attributes are changed in FileBuildInfo, we need to
+ # invalidate the cached map of file name to content signature
+ # heald in dependency_map. Currently only used with
+ # MD5-timestamp decider
+ if key != 'dependency_map' and hasattr(self, 'dependency_map'):
+ del self.dependency_map
+
+ return super(FileBuildInfo, self).__setattr__(key, value)
+
def convert_to_sconsign(self):
"""
Converts this FileBuildInfo object for writing to a .sconsign file
@@ -3225,46 +3282,230 @@ class File(Base):
self._memo['changed'] = has_changed
return has_changed
- def changed_content(self, target, prev_ni):
+ def changed_content(self, target, prev_ni, repo_node=None):
cur_csig = self.get_csig()
try:
return cur_csig != prev_ni.csig
except AttributeError:
return 1
- def changed_state(self, target, prev_ni):
+ def changed_state(self, target, prev_ni, repo_node=None):
return self.state != SCons.Node.up_to_date
- def changed_timestamp_then_content(self, target, prev_ni):
- if not self.changed_timestamp_match(target, prev_ni):
+
+ # Caching node -> string mapping for the below method
+ __dmap_cache = {}
+ __dmap_sig_cache = {}
+
+
+ def _build_dependency_map(self, binfo):
+ """
+ Build mapping from file -> signature
+
+ Args:
+ self - self
+ binfo - buildinfo from node being considered
+
+ Returns:
+ dictionary of file->signature mappings
+ """
+
+ # For an "empty" binfo properties like bsources
+ # do not exist: check this to avoid exception.
+ if (len(binfo.bsourcesigs) + len(binfo.bdependsigs) + \
+ len(binfo.bimplicitsigs)) == 0:
+ return {}
+
+ binfo.dependency_map = { child:signature for child, signature in zip(chain(binfo.bsources, binfo.bdepends, binfo.bimplicit),
+ chain(binfo.bsourcesigs, binfo.bdependsigs, binfo.bimplicitsigs))}
+
+ return binfo.dependency_map
+
+ # @profile
+ def _add_strings_to_dependency_map(self, dmap):
+ """
+ In the case comparing node objects isn't sufficient, we'll add the strings for the nodes to the dependency map
+ :return:
+ """
+
+ first_string = str(next(iter(dmap)))
+
+ # print("DMAP:%s"%id(dmap))
+ if first_string not in dmap:
+ string_dict = {str(child): signature for child, signature in dmap.items()}
+ dmap.update(string_dict)
+ return dmap
+
+ def _get_previous_signatures(self, dmap):
+ """
+ Return a list of corresponding csigs from previous
+ build in order of the node/files in children.
+
+ Args:
+ self - self
+ dmap - Dictionary of file -> csig
+
+ Returns:
+ List of csigs for provided list of children
+ """
+ prev = []
+ # MD5_TIMESTAMP_DEBUG = False
+
+ if len(dmap) == 0:
+ if MD5_TIMESTAMP_DEBUG: print("Nothing dmap shortcutting")
+ return None
+ elif MD5_TIMESTAMP_DEBUG: print("len(dmap):%d"%len(dmap))
+
+
+ # First try retrieving via Node
+ if MD5_TIMESTAMP_DEBUG: print("Checking if self is in map:%s id:%s type:%s"%(str(self), id(self), type(self)))
+ df = dmap.get(self, False)
+ if df:
+ return df
+
+ # Now check if self's repository file is in map.
+ rf = self.rfile()
+ if MD5_TIMESTAMP_DEBUG: print("Checking if self.rfile is in map:%s id:%s type:%s"%(str(rf), id(rf), type(rf)))
+ rfm = dmap.get(rf, False)
+ if rfm:
+ return rfm
+
+ # get default string for node and then also string swapping os.altsep for os.sep (/ for \)
+ c_strs = [str(self)]
+
+ if os.altsep:
+ c_strs.append(c_strs[0].replace(os.sep, os.altsep))
+
+ # In some cases the dependency_maps' keys are already strings check.
+ # Check if either string is now in dmap.
+ for s in c_strs:
+ if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map :%s" % s)
+ df = dmap.get(s, False)
+ if df:
+ return df
+
+ # Strings don't exist in map, add them and try again
+ # If there are no strings in this dmap, then add them.
+ # This may not be necessary, we could walk the nodes in the dmap and check each string
+ # rather than adding ALL the strings to dmap. In theory that would be n/2 vs 2n str() calls on node
+ # if not dmap.has_strings:
+ dmap = self._add_strings_to_dependency_map(dmap)
+
+ # In some cases the dependency_maps' keys are already strings check.
+ # Check if either string is now in dmap.
+ for s in c_strs:
+ if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map (now with strings) :%s" % s)
+ df = dmap.get(s, False)
+ if df:
+ return df
+
+ # Lastly use nodes get_path() to generate string and see if that's in dmap
+ if not df:
try:
- self.get_ninfo().csig = prev_ni.csig
+ # this should yield a path which matches what's in the sconsign
+ c_str = self.get_path()
+ if os.altsep:
+ c_str = c_str.replace(os.sep, os.altsep)
+
+ if MD5_TIMESTAMP_DEBUG: print("Checking if self.get_path is in map (now with strings) :%s" % s)
+
+ df = dmap.get(c_str, None)
+
+ except AttributeError as e:
+ raise FileBuildInfoFileToCsigMappingError("No mapping from file name to content signature for :%s"%c_str)
+
+ return df
+
+ def changed_timestamp_then_content(self, target, prev_ni, node=None):
+ """
+ Used when decider for file is Timestamp-MD5
+
+ NOTE: If the timestamp hasn't changed this will skip md5'ing the
+ file and just copy the prev_ni provided. If the prev_ni
+ is wrong. It will propagate it.
+ See: https://github.com/SCons/scons/issues/2980
+
+ Args:
+ self - dependency
+ target - target
+ prev_ni - The NodeInfo object loaded from previous builds .sconsign
+ node - Node instance. Check this node for file existence/timestamp
+ if specified.
+
+ Returns:
+ Boolean - Indicates if node(File) has changed.
+ """
+
+ # Now get sconsign name -> csig map and then get proper prev_ni if possible
+ bi = node.get_stored_info().binfo
+ rebuilt = False
+ try:
+ dependency_map = bi.dependency_map
+ except AttributeError as e:
+ dependency_map = self._build_dependency_map(bi)
+ rebuilt = True
+
+ if len(dependency_map) == 0:
+ # If there's no dependency map, there's no need to find the
+ # prev_ni as there aren't any
+ # shortcut the rest of the logic
+ if MD5_TIMESTAMP_DEBUG: print("Skipping checks len(dmap)=0")
+
+ # We still need to get the current file's csig
+ # This should be slightly faster than calling self.changed_content(target, new_prev_ni)
+ self.get_csig()
+ return True
+
+ new_prev_ni = self._get_previous_signatures(dependency_map)
+ new = self.changed_timestamp_match(target, new_prev_ni)
+
+ if MD5_TIMESTAMP_DEBUG:
+ old = self.changed_timestamp_match(target, prev_ni)
+
+ if old != new:
+ print("Mismatch self.changed_timestamp_match(%s, prev_ni) old:%s new:%s"%(str(target), old, new))
+ new_prev_ni = self._get_previous_signatures(dependency_map)
+
+ if not new:
+ try:
+ # NOTE: We're modifying the current node's csig in a query.
+ self.get_ninfo().csig = new_prev_ni.csig
except AttributeError:
pass
return False
- return self.changed_content(target, prev_ni)
+ return self.changed_content(target, new_prev_ni)
- def changed_timestamp_newer(self, target, prev_ni):
+ def changed_timestamp_newer(self, target, prev_ni, repo_node=None):
try:
return self.get_timestamp() > target.get_timestamp()
except AttributeError:
return 1
- def changed_timestamp_match(self, target, prev_ni):
+ def changed_timestamp_match(self, target, prev_ni, repo_node=None):
+ """
+ Return True if the timestamps don't match or if there is no previous timestamp
+ :param target:
+ :param prev_ni: Information about the node from the previous build
+ :return:
+ """
try:
return self.get_timestamp() != prev_ni.timestamp
except AttributeError:
return 1
def is_up_to_date(self):
+ """Check for whether the Node is current
+ In all cases self is the target we're checking to see if it's up to date
+ """
+
T = 0
if T: Trace('is_up_to_date(%s):' % self)
if not self.exists():
if T: Trace(' not self.exists():')
- # The file doesn't exist locally...
+ # The file (always a target) doesn't exist locally...
r = self.rfile()
if r != self:
- # ...but there is one in a Repository...
+ # ...but there is one (always a target) in a Repository...
if not self.changed(r):
if T: Trace(' changed(%s):' % r)
# ...and it's even up-to-date...
@@ -3272,7 +3513,9 @@ class File(Base):
# ...and they'd like a local copy.
e = LocalCopy(self, r, None)
if isinstance(e, SCons.Errors.BuildError):
- raise
+ # Likely this should be re-raising exception e
+ # (which would be BuildError)
+ raise e
SCons.Node.store_info_map[self.store_info](self)
if T: Trace(' 1\n')
return 1
@@ -3293,11 +3536,14 @@ class File(Base):
result = self
if not self.exists():
norm_name = _my_normcase(self.name)
- for dir in self.dir.get_all_rdirs():
- try: node = dir.entries[norm_name]
- except KeyError: node = dir.file_on_disk(self.name)
+ for repo_dir in self.dir.get_all_rdirs():
+ try:
+ node = repo_dir.entries[norm_name]
+ except KeyError:
+ node = repo_dir.file_on_disk(self.name)
+
if node and node.exists() and \
- (isinstance(node, File) or isinstance(node, Entry) \
+ (isinstance(node, File) or isinstance(node, Entry)
or not node.is_derived()):
result = node
# Copy over our local attributes to the repository
@@ -3317,6 +3563,28 @@ class File(Base):
self._memo['rfile'] = result
return result
+ def find_repo_file(self):
+ """
+ For this node, find if there exists a corresponding file in one or more repositories
+ :return: list of corresponding files in repositories
+ """
+ retvals = []
+
+ norm_name = _my_normcase(self.name)
+ for repo_dir in self.dir.get_all_rdirs():
+ try:
+ node = repo_dir.entries[norm_name]
+ except KeyError:
+ node = repo_dir.file_on_disk(self.name)
+
+ if node and node.exists() and \
+ (isinstance(node, File) or isinstance(node, Entry) \
+ or not node.is_derived()):
+ retvals.append(node)
+
+ return retvals
+
+
def rstr(self):
return str(self.rfile())
@@ -3374,6 +3642,8 @@ class File(Base):
because multiple targets built by the same action will all
have the same build signature, and we have to differentiate
them somehow.
+
+ Signature should normally be string of hex digits.
"""
try:
return self.cachesig
@@ -3383,10 +3653,13 @@ class File(Base):
# Collect signatures for all children
children = self.children()
sigs = [n.get_cachedir_csig() for n in children]
+
# Append this node's signature...
sigs.append(self.get_contents_sig())
+
# ...and it's path
sigs.append(self.get_internal_path())
+
# Merge this all into a single signature
result = self.cachesig = SCons.Util.MD5collect(sigs)
return result
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 3a36894..2366a4d 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -22,7 +22,7 @@
#
from __future__ import division, print_function
-__revision__ = "src/engine/SCons/Node/FSTests.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/FSTests.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import SCons.compat
@@ -41,59 +41,79 @@ import SCons.Errors
import SCons.Node.FS
import SCons.Util
import SCons.Warnings
+import SCons.Environment
built_it = None
scanner_count = 0
+
class Scanner(object):
def __init__(self, node=None):
global scanner_count
scanner_count = scanner_count + 1
self.hash = scanner_count
self.node = node
+
def path(self, env, dir, target=None, source=None):
return ()
+
def __call__(self, node, env, path):
return [self.node]
+
def __hash__(self):
return self.hash
+
def select(self, node):
return self
+
def recurse_nodes(self, nodes):
return nodes
+
class Environment(object):
def __init__(self):
self.scanner = Scanner()
+
def Dictionary(self, *args):
return {}
+
def autogenerate(self, **kw):
return {}
+
def get_scanner(self, skey):
return self.scanner
+
def Override(self, overrides):
return self
+
def _update(self, dict):
pass
+
class Action(object):
def __call__(self, targets, sources, env, **kw):
global built_it
if kw.get('execute', 1):
built_it = 1
return 0
+
def show(self, string):
pass
+
def get_contents(self, target, source, env):
- return bytearray("",'utf-8')
+ return bytearray("", 'utf-8')
+
def genstring(self, target, source, env):
return ""
+
def strfunction(self, targets, sources, env):
return ""
+
def get_implicit_deps(self, target, source, env):
return []
+
class Builder(object):
def __init__(self, factory, action=Action()):
self.factory = factory
@@ -109,6 +129,7 @@ class Builder(object):
def source_factory(self, name):
return self.factory(name)
+
class _tempdirTestCase(unittest.TestCase):
def setUp(self):
self.save_cwd = os.getcwd()
@@ -120,10 +141,11 @@ class _tempdirTestCase(unittest.TestCase):
def tearDown(self):
os.chdir(self.save_cwd)
+
class VariantDirTestCase(unittest.TestCase):
def runTest(self):
"""Test variant dir functionality"""
- test=TestCmd(workdir='')
+ test = TestCmd(workdir='')
fs = SCons.Node.FS.FS()
f1 = fs.File('build/test1')
@@ -168,30 +190,30 @@ class VariantDirTestCase(unittest.TestCase):
test.subdir(['rep1', 'build', 'var2'])
# A source file in the source directory
- test.write([ 'work', 'src', 'test.in' ], 'test.in')
+ test.write(['work', 'src', 'test.in'], 'test.in')
# A source file in a subdir of the source directory
- test.subdir([ 'work', 'src', 'new_dir' ])
- test.write([ 'work', 'src', 'new_dir', 'test9.out' ], 'test9.out\n')
+ test.subdir(['work', 'src', 'new_dir'])
+ test.write(['work', 'src', 'new_dir', 'test9.out'], 'test9.out\n')
# A source file in the repository
- test.write([ 'rep1', 'src', 'test2.in' ], 'test2.in')
+ test.write(['rep1', 'src', 'test2.in'], 'test2.in')
# Some source files in the variant directory
- test.write([ 'work', 'build', 'var2', 'test.in' ], 'test.old')
- test.write([ 'work', 'build', 'var2', 'test2.in' ], 'test2.old')
+ test.write(['work', 'build', 'var2', 'test.in'], 'test.old')
+ test.write(['work', 'build', 'var2', 'test2.in'], 'test2.old')
# An old derived file in the variant directories
- test.write([ 'work', 'build', 'var1', 'test.out' ], 'test.old')
- test.write([ 'work', 'build', 'var2', 'test.out' ], 'test.old')
+ test.write(['work', 'build', 'var1', 'test.out'], 'test.old')
+ test.write(['work', 'build', 'var2', 'test.out'], 'test.old')
# And just in case we are weird, a derived file in the source
# dir.
- test.write([ 'work', 'src', 'test.out' ], 'test.out.src')
+ test.write(['work', 'src', 'test.out'], 'test.out.src')
# A derived file in the repository
- test.write([ 'rep1', 'build', 'var1', 'test2.out' ], 'test2.out_rep')
- test.write([ 'rep1', 'build', 'var2', 'test2.out' ], 'test2.out_rep')
+ test.write(['rep1', 'build', 'var1', 'test2.out'], 'test2.out_rep')
+ test.write(['rep1', 'build', 'var2', 'test2.out'], 'test2.out_rep')
os.chdir(test.workpath('work'))
@@ -210,8 +232,8 @@ class VariantDirTestCase(unittest.TestCase):
f2out_2.builder = 1
fs.Repository(test.workpath('rep1'))
- assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\
- f1.srcnode().get_internal_path()
+ assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'), \
+ f1.srcnode().get_internal_path()
# str(node) returns source path for duplicate = 0
assert str(f1) == os.path.normpath('src/test.in'), str(f1)
# Build path does not exist
@@ -221,26 +243,26 @@ class VariantDirTestCase(unittest.TestCase):
# And duplicate=0 should also work just like a Repository
assert f1.rexists()
# rfile() should point to the source path
- assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'),\
- f1.rfile().get_internal_path()
+ assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'), \
+ f1.rfile().get_internal_path()
- assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\
- f2.srcnode().get_internal_path()
+ assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'), \
+ f2.srcnode().get_internal_path()
# str(node) returns build path for duplicate = 1
assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2)
# Build path exists
assert f2.exists()
# ...and exists() should copy the file from src to build path
- assert test.read(['work', 'build', 'var2', 'test.in']) == bytearray('test.in','utf-8'),\
- test.read(['work', 'build', 'var2', 'test.in'])
+ assert test.read(['work', 'build', 'var2', 'test.in']) == bytearray('test.in', 'utf-8'), \
+ test.read(['work', 'build', 'var2', 'test.in'])
# Since exists() is true, so should rexists() be
assert f2.rexists()
f3 = fs.File('build/var1/test2.in')
f4 = fs.File('build/var2/test2.in')
- assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\
- f3.srcnode().get_internal_path()
+ assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'), \
+ f3.srcnode().get_internal_path()
# str(node) returns source path for duplicate = 0
assert str(f3) == os.path.normpath('src/test2.in'), str(f3)
# Build path does not exist
@@ -250,22 +272,22 @@ class VariantDirTestCase(unittest.TestCase):
# But we do have a file in the Repository
assert f3.rexists()
# rfile() should point to the source path
- assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')),\
- f3.rfile().get_internal_path()
+ assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')), \
+ f3.rfile().get_internal_path()
- assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\
- f4.srcnode().get_internal_path()
+ assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'), \
+ f4.srcnode().get_internal_path()
# str(node) returns build path for duplicate = 1
assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4)
# Build path should exist
assert f4.exists()
# ...and copy over the file into the local build path
- assert test.read(['work', 'build', 'var2', 'test2.in']) == bytearray('test2.in','utf-8')
+ assert test.read(['work', 'build', 'var2', 'test2.in']) == bytearray('test2.in', 'utf-8')
# should exist in repository, since exists() is true
assert f4.rexists()
# rfile() should point to ourselves
- assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'),\
- f4.rfile().get_internal_path()
+ assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'), \
+ f4.rfile().get_internal_path()
f5 = fs.File('build/var1/test.out')
f6 = fs.File('build/var2/test.out')
@@ -273,12 +295,12 @@ class VariantDirTestCase(unittest.TestCase):
assert f5.exists()
# We should not copy the file from the source dir, since this is
# a derived file.
- assert test.read(['work', 'build', 'var1', 'test.out']) == bytearray('test.old','utf-8')
+ assert test.read(['work', 'build', 'var1', 'test.out']) == bytearray('test.old', 'utf-8')
assert f6.exists()
# We should not copy the file from the source dir, since this is
# a derived file.
- assert test.read(['work', 'build', 'var2', 'test.out']) == bytearray('test.old','utf-8')
+ assert test.read(['work', 'build', 'var2', 'test.out']) == bytearray('test.old', 'utf-8')
f7 = fs.File('build/var1/test2.out')
f8 = fs.File('build/var2/test2.out')
@@ -291,8 +313,8 @@ class VariantDirTestCase(unittest.TestCase):
assert not f8.exists()
assert f8.rexists()
- assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
- f8.rfile().get_internal_path()
+ assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')), \
+ f8.rfile().get_internal_path()
# Verify the Mkdir and Link actions are called
d9 = fs.Dir('build/var2/new_dir')
@@ -301,6 +323,7 @@ class VariantDirTestCase(unittest.TestCase):
class MkdirAction(Action):
def __init__(self, dir_made):
self.dir_made = dir_made
+
def __call__(self, target, source, env, executor=None):
if executor:
target = executor.get_all_targets()
@@ -309,8 +332,10 @@ class VariantDirTestCase(unittest.TestCase):
save_Link = SCons.Node.FS.Link
link_made = []
+
def link_func(target, source, env, link_made=link_made):
link_made.append(target)
+
SCons.Node.FS.Link = link_func
try:
@@ -331,10 +356,10 @@ class VariantDirTestCase(unittest.TestCase):
# happen if you switch from duplicate=1 to duplicate=0, then
# delete a source file. At one time, this would cause exists()
# to return a 1 but get_contents() to throw.
- test.write([ 'work', 'build', 'var1', 'asourcefile' ], 'stuff')
+ test.write(['work', 'build', 'var1', 'asourcefile'], 'stuff')
f10 = fs.File('build/var1/asourcefile')
assert f10.exists()
- assert f10.get_contents() == bytearray('stuff','utf-8'), f10.get_contents()
+ assert f10.get_contents() == bytearray('stuff', 'utf-8'), f10.get_contents()
f11 = fs.File('src/file11')
t, m = f11.alter_targets()
@@ -360,8 +385,10 @@ class VariantDirTestCase(unittest.TestCase):
fIO = fs.File("build/var2/IOError")
save_Link = SCons.Node.FS.Link
+
def Link_IOError(target, source, env):
raise IOError(17, "Link_IOError")
+
SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
@@ -378,16 +405,16 @@ class VariantDirTestCase(unittest.TestCase):
SCons.Node.FS.Link = save_Link
# Test to see if Link() works...
- test.subdir('src','build')
+ test.subdir('src', 'build')
test.write('src/foo', 'src/foo\n')
os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
SCons.Node.FS.Link(fs.File(test.workpath('build/foo')),
fs.File(test.workpath('src/foo')),
None)
os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE)
- st=os.stat(test.workpath('build/foo'))
+ st = os.stat(test.workpath('build/foo'))
assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
- stat.S_IMODE(st[stat.ST_MODE])
+ stat.S_IMODE(st[stat.ST_MODE])
# This used to generate a UserError when we forbid the source
# directory from being outside the top-level SConstruct dir.
@@ -403,8 +430,8 @@ class VariantDirTestCase(unittest.TestCase):
exc_caught = 1
assert exc_caught, "Should have caught a UserError."
finally:
- test.unlink( "src/foo" )
- test.unlink( "build/foo" )
+ test.unlink("src/foo")
+ test.unlink("build/foo")
fs = SCons.Node.FS.FS()
fs.VariantDir('build', 'src1')
@@ -423,18 +450,20 @@ class VariantDirTestCase(unittest.TestCase):
# Test against a former bug. Make sure we can get a repository
# path for the variant directory itself!
- fs=SCons.Node.FS.FS(test.workpath('work'))
- test.subdir('work')
+ fs = SCons.Node.FS.FS(test.workpath('work'))
+
+ # not needed this subdir is created above near line 188
+ # test.subdir('work')
fs.VariantDir('build/var3', 'src', duplicate=0)
d1 = fs.Dir('build/var3')
r = d1.rdir()
assert r == d1, "%s != %s" % (r, d1)
# verify the link creation attempts in file_link()
- class LinkSimulator (object):
+ class LinkSimulator(object):
"""A class to intercept os.[sym]link() calls and track them."""
- def __init__( self, duplicate, link, symlink, copy ) :
+ def __init__(self, duplicate, link, symlink, copy):
self.duplicate = duplicate
self.have = {}
self.have['hard'] = link
@@ -446,27 +475,28 @@ class VariantDirTestCase(unittest.TestCase):
if self.have[link]:
self.links_to_be_called.append(link)
- def link_fail( self , src , dest ) :
+ def link_fail(self, src, dest):
next_link = self.links_to_be_called.pop(0)
assert next_link == "hard", \
- "Wrong link order: expected %s to be called "\
- "instead of hard" % next_link
- raise OSError( "Simulating hard link creation error." )
+ "Wrong link order: expected %s to be called " \
+ "instead of hard" % next_link
+ raise OSError("Simulating hard link creation error.")
- def symlink_fail( self , src , dest ) :
+ def symlink_fail(self, src, dest):
next_link = self.links_to_be_called.pop(0)
assert next_link == "soft", \
- "Wrong link order: expected %s to be called "\
- "instead of soft" % next_link
- raise OSError( "Simulating symlink creation error." )
+ "Wrong link order: expected %s to be called " \
+ "instead of soft" % next_link
+ raise OSError("Simulating symlink creation error.")
- def copy( self , src , dest ) :
+ def copy(self, src, dest):
next_link = self.links_to_be_called.pop(0)
assert next_link == "copy", \
- "Wrong link order: expected %s to be called "\
- "instead of copy" % next_link
+ "Wrong link order: expected %s to be called " \
+ "instead of copy" % next_link
# copy succeeds, but use the real copy
self.have['copy'](src, dest)
+
# end class LinkSimulator
try:
@@ -538,33 +568,33 @@ class VariantDirTestCase(unittest.TestCase):
fs.VariantDir('work/src/b1/b2', 'work/src')
dir_list = [
- 'work/src',
- 'work/src/b1',
- 'work/src/b1/b2',
- 'work/src/b1/b2/b1',
- 'work/src/b1/b2/b1/b2',
- 'work/src/b1/b2/b1/b2/b1',
- 'work/src/b1/b2/b1/b2/b1/b2',
+ 'work/src',
+ 'work/src/b1',
+ 'work/src/b1/b2',
+ 'work/src/b1/b2/b1',
+ 'work/src/b1/b2/b1/b2',
+ 'work/src/b1/b2/b1/b2/b1',
+ 'work/src/b1/b2/b1/b2/b1/b2',
]
srcnode_map = {
- 'work/src/b1/b2' : 'work/src',
- 'work/src/b1/b2/f' : 'work/src/f',
- 'work/src/b1/b2/b1' : 'work/src/b1/',
- 'work/src/b1/b2/b1/f' : 'work/src/b1/f',
- 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2',
- 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f',
- 'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1',
- 'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f',
- 'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2',
- 'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f',
+ 'work/src/b1/b2': 'work/src',
+ 'work/src/b1/b2/f': 'work/src/f',
+ 'work/src/b1/b2/b1': 'work/src/b1/',
+ 'work/src/b1/b2/b1/f': 'work/src/b1/f',
+ 'work/src/b1/b2/b1/b2': 'work/src/b1/b2',
+ 'work/src/b1/b2/b1/b2/f': 'work/src/b1/b2/f',
+ 'work/src/b1/b2/b1/b2/b1': 'work/src/b1/b2/b1',
+ 'work/src/b1/b2/b1/b2/b1/f': 'work/src/b1/b2/b1/f',
+ 'work/src/b1/b2/b1/b2/b1/b2': 'work/src/b1/b2/b1/b2',
+ 'work/src/b1/b2/b1/b2/b1/b2/f': 'work/src/b1/b2/b1/b2/f',
}
alter_map = {
- 'work/src' : 'work/src/b1/b2',
- 'work/src/f' : 'work/src/b1/b2/f',
- 'work/src/b1' : 'work/src/b1/b2/b1',
- 'work/src/b1/f' : 'work/src/b1/b2/b1/f',
+ 'work/src': 'work/src/b1/b2',
+ 'work/src/f': 'work/src/b1/b2/f',
+ 'work/src/b1': 'work/src/b1/b2/b1',
+ 'work/src/b1/f': 'work/src/b1/b2/b1/f',
}
errors = 0
@@ -605,7 +635,8 @@ class VariantDirTestCase(unittest.TestCase):
print("File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect))
errors = errors + 1
- self.failIf(errors)
+ self.assertFalse(errors)
+
class BaseTestCase(_tempdirTestCase):
def test_stat(self):
@@ -702,18 +733,21 @@ class BaseTestCase(_tempdirTestCase):
nonexistent = fs.Entry('nonexistent')
assert not nonexistent.islink()
+
class DirNodeInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test DirNodeInfo initialization"""
ddd = self.fs.Dir('ddd')
ni = SCons.Node.FS.DirNodeInfo()
+
class DirBuildInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test DirBuildInfo initialization"""
ddd = self.fs.Dir('ddd')
bi = SCons.Node.FS.DirBuildInfo()
+
class FileNodeInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test FileNodeInfo initialization"""
@@ -759,6 +793,7 @@ class FileNodeInfoTestCase(_tempdirTestCase):
size = st[stat.ST_SIZE]
assert ni.size != size, (ni.size, size)
+
class FileBuildInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test File.BuildInfo initialization"""
@@ -820,6 +855,7 @@ class FileBuildInfoTestCase(_tempdirTestCase):
format = bi1.format()
assert format == expect, (repr(expect), repr(format))
+
class FSTestCase(_tempdirTestCase):
def test_needs_normpath(self):
"""Test the needs_normpath Regular expression
@@ -861,7 +897,6 @@ class FSTestCase(_tempdirTestCase):
"/a./",
"/a../",
-
".a",
"..a",
"/.a",
@@ -870,7 +905,7 @@ class FSTestCase(_tempdirTestCase):
"..a/",
"/.a/",
"/..a/",
- ]
+ ]
for p in do_not_need_normpath:
assert needs_normpath_match(p) is None, p
@@ -918,7 +953,7 @@ class FSTestCase(_tempdirTestCase):
"../a",
"a/./a",
"a/../a",
- ]
+ ]
for p in needs_normpath:
assert needs_normpath_match(p) is not None, p
@@ -951,7 +986,7 @@ class FSTestCase(_tempdirTestCase):
assert isinstance(d1, SCons.Node.FS.Dir)
assert d1.cwd is d1, d1
- f1 = fs.File('f1', directory = d1)
+ f1 = fs.File('f1', directory=d1)
assert isinstance(f1, SCons.Node.FS.File)
d1_f1 = os.path.join('d1', 'f1')
@@ -1034,6 +1069,7 @@ class FSTestCase(_tempdirTestCase):
if p[0] == os.sep:
p = drive + p
return p
+
path = strip_slash(path_)
abspath = strip_slash(abspath_)
up_path = strip_slash(up_path_)
@@ -1047,51 +1083,51 @@ class FSTestCase(_tempdirTestCase):
name = os.sep
if dir.up() is None:
- dir_up_path = dir.get_internal_path()
+ dir_up_path = dir.get_internal_path()
else:
- dir_up_path = dir.up().get_internal_path()
+ dir_up_path = dir.up().get_internal_path()
assert dir.name == name, \
- "dir.name %s != expected name %s" % \
- (dir.name, name)
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
assert dir.get_internal_path() == path, \
- "dir.path %s != expected path %s" % \
- (dir.get_internal_path(), path)
+ "dir.path %s != expected path %s" % \
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
- "str(dir) %s != expected path %s" % \
- (str(dir), path)
+ "str(dir) %s != expected path %s" % \
+ (str(dir), path)
assert dir.get_abspath() == abspath, \
- "dir.abspath %s != expected absolute path %s" % \
- (dir.get_abspath(), abspath)
+ "dir.abspath %s != expected absolute path %s" % \
+ (dir.get_abspath(), abspath)
assert dir_up_path == up_path, \
- "dir.up().path %s != expected parent path %s" % \
- (dir_up_path, up_path)
+ "dir.up().path %s != expected parent path %s" % \
+ (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/')
- Dir_test('/foo', '/foo/', '/foo/', '/')
- Dir_test('/foo/bar', '/foo/bar/', '/foo/bar/', '/foo/')
- Dir_test('..', sub, sub, wp)
- Dir_test('foo/..', './', sub_dir, sub)
- Dir_test('../foo', sub_foo, sub_foo, sub)
- Dir_test('.', './', sub_dir, sub)
- Dir_test('./.', './', sub_dir, sub)
- Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
- Dir_test('#../foo', sub_foo, sub_foo, sub)
- Dir_test('#/../foo', sub_foo, sub_foo, sub)
- Dir_test('#foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
- Dir_test('#/foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
- Dir_test('#', './', sub_dir, sub)
+
+ 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/')
+ Dir_test('/foo', '/foo/', '/foo/', '/')
+ Dir_test('/foo/bar', '/foo/bar/', '/foo/bar/', '/foo/')
+ Dir_test('..', sub, sub, wp)
+ Dir_test('foo/..', './', sub_dir, sub)
+ Dir_test('../foo', sub_foo, sub_foo, sub)
+ Dir_test('.', './', sub_dir, sub)
+ Dir_test('./.', './', sub_dir, sub)
+ Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('#../foo', sub_foo, sub_foo, sub)
+ Dir_test('#/../foo', sub_foo, sub_foo, sub)
+ Dir_test('#foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('#/foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('#', './', sub_dir, sub)
try:
- f2 = fs.File(sep.join(['f1', 'f2']), directory = d1)
+ f2 = fs.File(sep.join(['f1', 'f2']), directory=d1)
except TypeError as x:
assert str(x) == ("Tried to lookup File '%s' as a Dir." %
d1_f1), x
@@ -1133,13 +1169,15 @@ class FSTestCase(_tempdirTestCase):
e1 = fs.Entry(p)
e2 = fs.Entry(path)
assert e1 is e2, (e1, e2)
- assert str(e1) is str(e2), (str(e1), str(e2))
+ a = str(e1)
+ b = str(e2)
+ assert a == b, ("Strings should match for same file/node\n%s\n%s" % (a, b))
# Test for a bug in 0.04 that did not like looking up
# dirs with a trailing slash on Windows.
- d=fs.Dir('./')
+ d = fs.Dir('./')
assert d.get_internal_path() == '.', d.get_abspath()
- d=fs.Dir('foo/')
+ d = fs.Dir('foo/')
assert d.get_internal_path() == 'foo', d.get_abspath()
# Test for sub-classing of node building.
@@ -1147,7 +1185,7 @@ class FSTestCase(_tempdirTestCase):
built_it = None
assert not built_it
- d1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
+ d1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
d1.builder_set(Builder(fs.File))
d1.reset_executor()
d1.env_set(Environment())
@@ -1156,7 +1194,7 @@ class FSTestCase(_tempdirTestCase):
built_it = None
assert not built_it
- f1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
+ f1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
f1.builder_set(Builder(fs.File))
f1.reset_executor()
f1.env_set(Environment())
@@ -1240,9 +1278,11 @@ class FSTestCase(_tempdirTestCase):
class MyScanner(Scanner):
call_count = 0
+
def __call__(self, node, env, path):
self.call_count = self.call_count + 1
return Scanner.__call__(self, node, env, path)
+
s = MyScanner(xyz)
deps = f12.get_found_includes(env, s, t1)
@@ -1261,8 +1301,6 @@ class FSTestCase(_tempdirTestCase):
assert deps == [xyz], deps
assert s.call_count == 3, s.call_count
-
-
# Make sure we can scan this file even if the target isn't
# a file that has a scanner (it might be an Alias, e.g.).
class DummyNode(object):
@@ -1292,7 +1330,7 @@ class FSTestCase(_tempdirTestCase):
f1 = fs.File(test.workpath("do_i_exist"))
assert not f1.exists()
- test.write("do_i_exist","\n")
+ test.write("do_i_exist", "\n")
assert not f1.exists(), "exists() call not cached"
f1.built()
assert f1.exists(), "exists() call caching not reset"
@@ -1306,14 +1344,14 @@ class FSTestCase(_tempdirTestCase):
# get_contents() returns the binary contents.
test.write("binary_file", "Foo\x1aBar")
f1 = fs.File(test.workpath("binary_file"))
- assert f1.get_contents() == bytearray("Foo\x1aBar",'utf-8'), f1.get_contents()
+ assert f1.get_contents() == bytearray("Foo\x1aBar", 'utf-8'), f1.get_contents()
# This tests to make sure we can decode UTF-8 text files.
test_string = u"Foo\x1aBar"
test.write("utf8_file", test_string.encode('utf-8'))
f1 = fs.File(test.workpath("utf8_file"))
assert eval('f1.get_text_contents() == u"Foo\x1aBar"'), \
- f1.get_text_contents()
+ f1.get_text_contents()
# Check for string which doesn't have BOM and isn't valid
# ASCII
@@ -1321,11 +1359,11 @@ class FSTestCase(_tempdirTestCase):
test.write('latin1_file', test_string)
f1 = fs.File(test.workpath("latin1_file"))
assert f1.get_text_contents() == test_string.decode('latin-1'), \
- f1.get_text_contents()
+ f1.get_text_contents()
def nonexistent(method, s):
try:
- x = method(s, create = 0)
+ x = method(s, create=0)
except SCons.Errors.UserError:
pass
else:
@@ -1363,15 +1401,15 @@ class FSTestCase(_tempdirTestCase):
f = fs.File('f_local')
assert f._local == 0
- #XXX test_is_up_to_date() for directories
+ # XXX test_is_up_to_date() for directories
- #XXX test_sconsign() for directories
+ # XXX test_sconsign() for directories
- #XXX test_set_signature() for directories
+ # XXX test_set_signature() for directories
- #XXX test_build() for directories
+ # XXX test_build() for directories
- #XXX test_root()
+ # XXX test_root()
# test Entry.get_contents()
e = fs.Entry('does_not_exist')
@@ -1383,7 +1421,7 @@ class FSTestCase(_tempdirTestCase):
try:
e = fs.Entry('file')
c = e.get_contents()
- assert c == bytearray("file\n",'utf-8'), c
+ assert c == bytearray("file\n", 'utf-8'), c
assert e.__class__ == SCons.Node.FS.File
finally:
test.unlink("file")
@@ -1537,7 +1575,7 @@ class FSTestCase(_tempdirTestCase):
f = fs.Entry('foo/bar/baz')
assert f.for_signature() == 'baz', f.for_signature()
assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \
- f.get_string(0)
+ f.get_string(0)
assert f.get_string(1) == 'baz', f.get_string(1)
def test_drive_letters(self):
@@ -1552,26 +1590,26 @@ class FSTestCase(_tempdirTestCase):
drive, path = os.path.splitdrive(x)
return 'X:' + path
- wp = drive_workpath([''])
+ wp = drive_workpath([''])
if wp[-1] in (os.sep, '/'):
- tmp = os.path.split(wp[:-1])[0]
+ tmp = os.path.split(wp[:-1])[0]
else:
- tmp = os.path.split(wp)[0]
+ tmp = os.path.split(wp)[0]
- parent_tmp = os.path.split(tmp)[0]
+ parent_tmp = os.path.split(tmp)[0]
if parent_tmp == 'X:':
parent_tmp = 'X:' + os.sep
- tmp_foo = os.path.join(tmp, 'foo')
+ tmp_foo = os.path.join(tmp, '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', ''])
+ 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', ''])
+ sub_foo = drive_workpath(['sub', 'foo', ''])
fs = SCons.Node.FS.FS()
@@ -1590,22 +1628,23 @@ class FSTestCase(_tempdirTestCase):
if p[-1] == os.sep and len(p) > 3:
p = p[:-1]
return p
+
path = strip_slash(path_)
up_path = strip_slash(up_path_)
name = path.split(os.sep)[-1]
assert dir.name == name, \
- "dir.name %s != expected name %s" % \
- (dir.name, name)
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
assert dir.get_internal_path() == path, \
- "dir.path %s != expected path %s" % \
- (dir.get_internal_path(), path)
+ "dir.path %s != expected path %s" % \
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
- "str(dir) %s != expected path %s" % \
- (str(dir), path)
+ "str(dir) %s != expected path %s" % \
+ (str(dir), path)
assert dir.up().get_internal_path() == up_path, \
- "dir.up().path %s != expected parent path %s" % \
- (dir.up().get_internal_path(), up_path)
+ "dir.up().path %s != expected parent path %s" % \
+ (dir.up().get_internal_path(), up_path)
save_os_path = os.path
save_os_sep = os.sep
@@ -1616,26 +1655,25 @@ class FSTestCase(_tempdirTestCase):
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('#X:', wp, tmp)
- Dir_test('X:foo', foo, wp)
- Dir_test('X:foo/bar', foo_bar, foo)
- Dir_test('X:/foo', 'X:/foo', 'X:/')
- Dir_test('X:/foo/bar', 'X:/foo/bar/', 'X:/foo/')
- Dir_test('X:..', tmp, parent_tmp)
- Dir_test('X:foo/..', wp, tmp)
- Dir_test('X:../foo', tmp_foo, tmp)
- Dir_test('X:.', wp, tmp)
- Dir_test('X:./.', wp, tmp)
- Dir_test('X:foo/./bar', foo_bar, foo)
- Dir_test('#X:../foo', tmp_foo, tmp)
- Dir_test('#X:/../foo', tmp_foo, tmp)
- Dir_test('#X:foo/bar', foo_bar, foo)
- Dir_test('#X:/foo/bar', foo_bar, foo)
- Dir_test('#X:/', wp, tmp)
+ Dir_test('#X:', wp, tmp)
+ Dir_test('X:foo', foo, wp)
+ Dir_test('X:foo/bar', foo_bar, foo)
+ Dir_test('X:/foo', 'X:/foo', 'X:/')
+ Dir_test('X:/foo/bar', 'X:/foo/bar/', 'X:/foo/')
+ Dir_test('X:..', tmp, parent_tmp)
+ Dir_test('X:foo/..', wp, tmp)
+ Dir_test('X:../foo', tmp_foo, tmp)
+ Dir_test('X:.', wp, tmp)
+ Dir_test('X:./.', wp, tmp)
+ Dir_test('X:foo/./bar', foo_bar, foo)
+ Dir_test('#X:../foo', tmp_foo, tmp)
+ Dir_test('#X:/../foo', tmp_foo, tmp)
+ Dir_test('#X:foo/bar', foo_bar, foo)
+ Dir_test('#X:/foo/bar', foo_bar, foo)
+ Dir_test('#X:/', wp, tmp)
finally:
os.path = save_os_path
os.sep = save_os_sep
@@ -1657,28 +1695,33 @@ class FSTestCase(_tempdirTestCase):
import ntpath
x = test.workpath(*dirs)
drive, path = ntpath.splitdrive(x)
- unc, path = ntpath.splitunc(path)
+ try:
+ unc, path = ntpath.splitunc(path)
+ except AttributeError:
+ # could be python 3.7 or newer, make sure splitdrive can do UNC
+ assert ntpath.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive'
+ pass
path = strip_slash(path)
return '//' + path[1:]
- wp = unc_workpath([''])
+ wp = unc_workpath([''])
if wp[-1] in (os.sep, '/'):
- tmp = os.path.split(wp[:-1])[0]
+ tmp = os.path.split(wp[:-1])[0]
else:
- tmp = os.path.split(wp)[0]
+ tmp = os.path.split(wp)[0]
- parent_tmp = os.path.split(tmp)[0]
+ parent_tmp = os.path.split(tmp)[0]
- tmp_foo = os.path.join(tmp, 'foo')
+ 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', ''])
+ 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', ''])
+ sub_foo = unc_workpath(['sub', 'foo', ''])
fs = SCons.Node.FS.FS()
@@ -1699,22 +1742,22 @@ class FSTestCase(_tempdirTestCase):
name = path.split(os.sep)[-1]
if dir.up() is None:
- dir_up_path = dir.get_internal_path()
+ dir_up_path = dir.get_internal_path()
else:
- dir_up_path = dir.up().get_internal_path()
+ dir_up_path = dir.up().get_internal_path()
assert dir.name == name, \
- "dir.name %s != expected name %s" % \
- (dir.name, name)
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
assert dir.get_internal_path() == path, \
- "dir.path %s != expected path %s" % \
- (dir.get_internal_path(), path)
+ "dir.path %s != expected path %s" % \
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
- "str(dir) %s != expected path %s" % \
- (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().get_internal_path(), up_path)
+ "dir.up().path %s != expected parent path %s" % \
+ (dir.up().get_internal_path(), up_path)
save_os_path = os.path
save_os_sep = os.sep
@@ -1725,28 +1768,27 @@ class FSTestCase(_tempdirTestCase):
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)
+ 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
@@ -1801,7 +1843,7 @@ class FSTestCase(_tempdirTestCase):
d1 = fs.Dir('d1')
d2 = d1.Dir('d2')
dirs = os.path.normpath(d2.get_abspath()).split(os.sep)
- above_path = os.path.join(*['..']*len(dirs) + ['above'])
+ above_path = os.path.join(*['..'] * len(dirs) + ['above'])
above = d2.Dir(above_path)
def test_lookup_abs(self):
@@ -1819,13 +1861,13 @@ class FSTestCase(_tempdirTestCase):
return
test = self.test
fs = self.fs
- path='//servername/C$/foo'
+ 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)
-
+ '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',):
@@ -1860,56 +1902,56 @@ class FSTestCase(_tempdirTestCase):
d3_d4_f = d3_d4.File('f')
cases = [
- d1, d1, '.',
- d1, d1_f, 'f',
- d1, d1_d2, 'd2',
- d1, d1_d2_f, 'd2/f',
- d1, d3, '../d3',
- d1, d3_f, '../d3/f',
- d1, d3_d4, '../d3/d4',
- d1, d3_d4_f, '../d3/d4/f',
-
- d1_f, d1, '.',
- d1_f, d1_f, 'f',
- d1_f, d1_d2, 'd2',
- d1_f, d1_d2_f, 'd2/f',
- d1_f, d3, '../d3',
- d1_f, d3_f, '../d3/f',
- d1_f, d3_d4, '../d3/d4',
- d1_f, d3_d4_f, '../d3/d4/f',
-
- d1_d2, d1, '..',
- d1_d2, d1_f, '../f',
- d1_d2, d1_d2, '.',
- d1_d2, d1_d2_f, 'f',
- d1_d2, d3, '../../d3',
- d1_d2, d3_f, '../../d3/f',
- d1_d2, d3_d4, '../../d3/d4',
- d1_d2, d3_d4_f, '../../d3/d4/f',
-
- d1_d2_f, d1, '..',
- d1_d2_f, d1_f, '../f',
- d1_d2_f, d1_d2, '.',
- d1_d2_f, d1_d2_f, 'f',
- d1_d2_f, d3, '../../d3',
- d1_d2_f, d3_f, '../../d3/f',
- d1_d2_f, d3_d4, '../../d3/d4',
- d1_d2_f, d3_d4_f, '../../d3/d4/f',
+ d1, d1, '.',
+ d1, d1_f, 'f',
+ d1, d1_d2, 'd2',
+ d1, d1_d2_f, 'd2/f',
+ d1, d3, '../d3',
+ d1, d3_f, '../d3/f',
+ d1, d3_d4, '../d3/d4',
+ d1, d3_d4_f, '../d3/d4/f',
+
+ d1_f, d1, '.',
+ d1_f, d1_f, 'f',
+ d1_f, d1_d2, 'd2',
+ d1_f, d1_d2_f, 'd2/f',
+ d1_f, d3, '../d3',
+ d1_f, d3_f, '../d3/f',
+ d1_f, d3_d4, '../d3/d4',
+ d1_f, d3_d4_f, '../d3/d4/f',
+
+ d1_d2, d1, '..',
+ d1_d2, d1_f, '../f',
+ d1_d2, d1_d2, '.',
+ d1_d2, d1_d2_f, 'f',
+ d1_d2, d3, '../../d3',
+ d1_d2, d3_f, '../../d3/f',
+ d1_d2, d3_d4, '../../d3/d4',
+ d1_d2, d3_d4_f, '../../d3/d4/f',
+
+ d1_d2_f, d1, '..',
+ d1_d2_f, d1_f, '../f',
+ d1_d2_f, d1_d2, '.',
+ d1_d2_f, d1_d2_f, 'f',
+ d1_d2_f, d3, '../../d3',
+ d1_d2_f, d3_f, '../../d3/f',
+ d1_d2_f, d3_d4, '../../d3/d4',
+ d1_d2_f, d3_d4_f, '../../d3/d4/f',
]
if sys.platform in ('win32',):
- x_d1 = fs.Dir(r'X:\d1')
- x_d1_d2 = x_d1.Dir('d2')
- y_d1 = fs.Dir(r'Y:\d1')
- y_d1_d2 = y_d1.Dir('d2')
- y_d2 = fs.Dir(r'Y:\d2')
+ x_d1 = fs.Dir(r'X:\d1')
+ x_d1_d2 = x_d1.Dir('d2')
+ y_d1 = fs.Dir(r'Y:\d1')
+ y_d1_d2 = y_d1.Dir('d2')
+ y_d2 = fs.Dir(r'Y:\d2')
win32_cases = [
- x_d1, x_d1, '.',
- x_d1, x_d1_d2, 'd2',
- x_d1, y_d1, r'Y:\d1',
- x_d1, y_d1_d2, r'Y:\d1\d2',
- x_d1, y_d2, r'Y:\d2',
+ x_d1, x_d1, '.',
+ x_d1, x_d1_d2, 'd2',
+ x_d1, y_d1, r'Y:\d1',
+ x_d1, y_d1_d2, r'Y:\d1\d2',
+ x_d1, y_d2, r'Y:\d2',
]
cases.extend(win32_cases)
@@ -1930,14 +1972,15 @@ class FSTestCase(_tempdirTestCase):
def test_proxy(self):
"""Test a Node.FS object wrapped in a proxy instance"""
f1 = self.fs.File('fff')
+
class MyProxy(SCons.Util.Proxy):
__str__ = SCons.Util.Delegate('__str__')
+
p = MyProxy(f1)
f2 = self.fs.Entry(p)
assert f1 is f2, (f1, str(f1), f2, str(f2))
-
class DirTestCase(_tempdirTestCase):
def test__morph(self):
@@ -1954,8 +1997,10 @@ class DirTestCase(_tempdirTestCase):
def test_subclass(self):
"""Test looking up subclass of Dir nodes"""
+
class DirSubclass(SCons.Node.FS.Dir):
pass
+
sd = self.fs._lookup('special_dir', None, DirSubclass, create=1)
sd.must_be_same(SCons.Node.FS.Dir)
@@ -2002,9 +2047,9 @@ class DirTestCase(_tempdirTestCase):
test.subdir('d')
test.write(['d', 'g'], "67890\n")
test.write(['d', 'f'], "12345\n")
- test.subdir(['d','sub'])
- test.write(['d', 'sub','h'], "abcdef\n")
- test.subdir(['d','empty'])
+ test.subdir(['d', 'sub'])
+ test.write(['d', 'sub', 'h'], "abcdef\n")
+ test.subdir(['d', 'empty'])
d = self.fs.Dir('d')
g = self.fs.File(os.path.join('d', 'g'))
@@ -2017,10 +2062,10 @@ class DirTestCase(_tempdirTestCase):
assert e.get_contents() == '', e.get_contents()
assert e.get_text_contents() == '', e.get_text_contents()
- assert e.get_csig()+" empty" == files[0], files
- assert f.get_csig()+" f" == files[1], files
- assert g.get_csig()+" g" == files[2], files
- assert s.get_csig()+" sub" == files[3], files
+ assert e.get_csig() + " empty" == files[0], files
+ assert f.get_csig() + " f" == files[1], files
+ assert g.get_csig() + " g" == files[2], files
+ assert s.get_csig() + " sub" == files[3], files
def test_implicit_re_scans(self):
"""Test that adding entries causes a directory to be re-scanned
@@ -2078,7 +2123,7 @@ class DirTestCase(_tempdirTestCase):
d = self.fs.Dir('d')
r = self.fs.Dir('r')
d.addRepository(r)
-
+
assert d.rentry_exists_on_disk('exists')
assert d.rentry_exists_on_disk('rexists')
assert not d.rentry_exists_on_disk('does_not_exist')
@@ -2184,7 +2229,7 @@ class DirTestCase(_tempdirTestCase):
SCons.Node._is_derived_map[2] = return_true
SCons.Node._exists_map[5] = return_true
-
+
test.subdir('src0')
test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n")
test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n")
@@ -2332,6 +2377,7 @@ class DirTestCase(_tempdirTestCase):
r = sub.file_on_disk('dir')
assert not r, r
+
class EntryTestCase(_tempdirTestCase):
def test_runTest(self):
"""Test methods specific to the Entry sub-class.
@@ -2382,16 +2428,20 @@ class EntryTestCase(_tempdirTestCase):
class MyCalc(object):
def __init__(self, val):
self.max_drift = 0
+
class M(object):
def __init__(self, val):
self.val = val
+
def collect(self, args):
result = 0
for a in args:
result += a
return result
+
def signature(self, executor):
return self.val + 222
+
self.module = M(val)
test.subdir('e5d')
@@ -2403,13 +2453,14 @@ class EntryTestCase(_tempdirTestCase):
self.fs.Entry('#topdir/a/b/c')
-
class FileTestCase(_tempdirTestCase):
def test_subclass(self):
"""Test looking up subclass of File nodes"""
+
class FileSubclass(SCons.Node.FS.File):
pass
+
sd = self.fs._lookup('special_file', None, FileSubclass, create=1)
sd.must_be_same(SCons.Node.FS.File)
@@ -2453,8 +2504,144 @@ class FileTestCase(_tempdirTestCase):
build_f1.clear()
build_f1.linked = None
assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1)
- assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (build_f1, src_f1)
+ assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (
+ build_f1, src_f1)
+
+ def test_changed(self):
+ """
+ Verify that changes between BuildInfo's list of souces, depends, and implicit
+ dependencies do not corrupt content signature values written to .SConsign
+ when using CacheDir and Timestamp-MD5 decider.
+ This is for issue #2980
+ """
+
+ # node should have
+ # 1 source (for example main.cpp)
+ # 0 depends
+ # N implicits (for example ['alpha.h', 'beta.h', 'gamma.h', '/usr/bin/g++'])
+
+ class ChangedNode(SCons.Node.FS.File):
+ def __init__(self, name, directory=None, fs=None):
+ SCons.Node.FS.File.__init__(self, name, directory, fs)
+ self.name = name
+ self.Tag('found_includes', [])
+ self.stored_info = None
+ self.build_env = None
+ self.changed_since_last_build = 4
+ self.timestamp = 1
+
+ def get_stored_info(self):
+ return self.stored_info
+
+ def get_build_env(self):
+ return self.build_env
+
+ def get_timestamp(self):
+ """ Fake timestamp so they always match"""
+ return self.timestamp
+ def get_contents(self):
+ return self.name
+
+ def get_ninfo(self):
+ """ mocked to ensure csig will equal the filename"""
+ try:
+ return self.ninfo
+ except AttributeError:
+ self.ninfo = FakeNodeInfo(self.name, self.timestamp)
+ return self.ninfo
+
+ def get_csig(self):
+ ninfo = self.get_ninfo()
+ try:
+ return ninfo.csig
+ except AttributeError:
+ pass
+
+ return "Should Never Happen"
+
+ class ChangedEnvironment(SCons.Environment.Base):
+
+ def __init__(self):
+ SCons.Environment.Base.__init__(self)
+ self.decide_source = self._changed_timestamp_then_content
+
+ class FakeNodeInfo(object):
+ def __init__(self, csig, timestamp):
+ self.csig = csig
+ self.timestamp = timestamp
+
+ # Create nodes
+ fs = SCons.Node.FS.FS()
+ d = self.fs.Dir('.')
+
+ node = ChangedNode('main.o', d, fs) # main.o
+ s1 = ChangedNode('main.cpp', d, fs) # main.cpp
+ s1.timestamp = 2 # this changed
+ i1 = ChangedNode('alpha.h', d, fs) # alpha.h - The bug is caused because the second build adds this file
+ i1.timestamp = 2 # This is the new file.
+ i2 = ChangedNode('beta.h', d, fs) # beta.h
+ i3 = ChangedNode('gamma.h', d, fs) # gamma.h - In the bug beta.h's csig from binfo overwrites this ones
+ i4 = ChangedNode('/usr/bin/g++', d, fs) # /usr/bin/g++
+
+ node.add_source([s1])
+ node.add_dependency([])
+ node.implicit = [i1, i2, i3, i4]
+ node.implicit_set = set()
+ # node._add_child(node.implicit, node.implicit_set, [n7, n8, n9])
+ # node._add_child(node.implicit, node.implicit_set, [n10, n11, n12])
+
+ # Mock out node's scan method
+ # node.scan = lambda *args: None
+
+ # Mock out nodes' children() ?
+ # Should return Node's.
+ # All those nodes should have changed_since_last_build set to match Timestamp-MD5's
+ # decider method...
+
+ # Generate sconsign entry needed
+ sconsign_entry = SCons.SConsign.SConsignEntry()
+ sconsign_entry.binfo = node.new_binfo()
+ sconsign_entry.ninfo = node.new_ninfo()
+
+ # mock out loading info from sconsign
+ # This will cause node.get_stored_info() to return our freshly created sconsign_entry
+ node.stored_info = sconsign_entry
+
+ # binfo = information from previous build (from sconsign)
+ # We'll set the following attributes (which are lists): "bsources", "bsourcesigs",
+ # "bdepends","bdependsigs", "bimplicit", "bimplicitsigs"
+ bi = sconsign_entry.binfo
+ bi.bsources = ['main.cpp']
+ bi.bsourcesigs = [FakeNodeInfo('main.cpp', 1), ]
+
+ bi.bdepends = []
+ bi.bdependsigs = []
+
+ bi.bimplicit = ['beta.h', 'gamma.h']
+ bi.bimplicitsigs = [FakeNodeInfo('beta.h', 1), FakeNodeInfo('gamma.h', 1)]
+
+ ni = sconsign_entry.ninfo
+ # We'll set the following attributes (which are lists): sources, depends, implicit lists
+
+ # Set timestamp-md5
+ # Call changed
+ # Check results
+ node.build_env = ChangedEnvironment()
+
+ changed = node.changed()
+
+ # change to true to debug
+ if False:
+ print("Changed:%s" % changed)
+ print("%15s -> csig:%s" % (s1.name, s1.ninfo.csig))
+ print("%15s -> csig:%s" % (i1.name, i1.ninfo.csig))
+ print("%15s -> csig:%s" % (i2.name, i2.ninfo.csig))
+ print("%15s -> csig:%s" % (i3.name, i3.ninfo.csig))
+ print("%15s -> csig:%s" % (i4.name, i4.ninfo.csig))
+
+ self.assertEqual(i2.name, i2.ninfo.csig,
+ "gamma.h's fake csig should equal gamma.h but equals:%s" % i2.ninfo.csig)
class GlobTestCase(_tempdirTestCase):
@@ -2523,7 +2710,6 @@ class GlobTestCase(_tempdirTestCase):
self.sub_dir3_jjj = self.sub_dir3.File('jjj')
self.sub_dir3_lll = self.sub_dir3.File('lll')
-
def do_cases(self, cases, **kwargs):
# First, execute all of the cases with string=True and verify
@@ -2571,17 +2757,17 @@ class GlobTestCase(_tempdirTestCase):
join = os.path.join
cases = (
- ('ggg', ['ggg'], [self.ggg]),
+ ('ggg', ['ggg'], [self.ggg]),
- ('subdir1', ['subdir1'], [self.subdir1]),
+ ('subdir1', ['subdir1'], [self.subdir1]),
- ('subdir1/jjj', [join('subdir1', 'jjj')], [self.subdir1_jjj]),
+ ('subdir1/jjj', [join('subdir1', 'jjj')], [self.subdir1_jjj]),
- ('disk-aaa', ['disk-aaa'], None),
+ ('disk-aaa', ['disk-aaa'], None),
- ('disk-sub', ['disk-sub'], None),
+ ('disk-sub', ['disk-sub'], None),
- ('both-aaa', ['both-aaa'], []),
+ ('both-aaa', ['both-aaa'], []),
)
self.do_cases(cases)
@@ -2676,8 +2862,8 @@ class GlobTestCase(_tempdirTestCase):
"""Test globbing for things that don't exist"""
cases = (
- ('does_not_exist', [], []),
- ('no_subdir/*', [], []),
+ ('does_not_exist', [], []),
+ ('no_subdir/*', [], []),
('subdir?/no_file', [], []),
)
@@ -2785,7 +2971,7 @@ class GlobTestCase(_tempdirTestCase):
assert g == expect, str(g) + " is not sorted, but should be!"
g = self.fs.Glob('disk-*', strings=True)
- expect = [ 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub' ]
+ expect = ['disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub']
assert g == expect, str(g) + " is not sorted, but should be!"
@@ -2918,10 +3104,13 @@ class RepositoryTestCase(_tempdirTestCase):
def test_rdir(self):
"""Test the Dir.rdir() method"""
+
def return_true(obj):
return 1
+
def return_false(obj):
return 0
+
SCons.Node._exists_map[5] = return_true
SCons.Node._exists_map[6] = return_false
SCons.Node._is_derived_map[2] = return_true
@@ -2971,10 +3160,13 @@ class RepositoryTestCase(_tempdirTestCase):
def test_rfile(self):
"""Test the File.rfile() method"""
+
def return_true(obj):
return 1
+
def return_false(obj):
return 0
+
SCons.Node._exists_map[5] = return_true
SCons.Node._exists_map[6] = return_false
SCons.Node._is_derived_map[2] = return_true
@@ -3113,7 +3305,7 @@ class RepositoryTestCase(_tempdirTestCase):
test.write(["rep3", "contents"], "Con\x1aTents\n")
try:
c = fs.File("contents").get_contents()
- assert c == bytearray("Con\x1aTents\n",'utf-8'), "got '%s'" % c
+ assert c == bytearray("Con\x1aTents\n", 'utf-8'), "got '%s'" % c
finally:
test.unlink(["rep3", "contents"])
@@ -3132,8 +3324,8 @@ class RepositoryTestCase(_tempdirTestCase):
class FakeUnicodeString(collections.UserString):
def encode(self, encoding):
return str(self)
- test_string = FakeUnicodeString("Con\x1aTents\n")
+ test_string = FakeUnicodeString("Con\x1aTents\n")
# Test with ASCII.
test.write(["rep3", "contents"], test_string.encode('ascii'))
@@ -3159,14 +3351,13 @@ class RepositoryTestCase(_tempdirTestCase):
finally:
test.unlink(["rep3", "contents"])
- #def test_is_up_to_date(self):
-
+ # def test_is_up_to_date(self):
class find_fileTestCase(unittest.TestCase):
def runTest(self):
"""Testing find_file function"""
- test = TestCmd(workdir = '')
+ test = TestCmd(workdir='')
test.write('./foo', 'Some file\n')
test.write('./foo2', 'Another file\n')
test.subdir('same')
@@ -3179,9 +3370,9 @@ class find_fileTestCase(unittest.TestCase):
os.chdir(test.workpath(""))
node_derived = fs.File(test.workpath('bar/baz'))
- node_derived.builder_set(1) # Any non-zero value.
+ node_derived.builder_set(1) # Any non-zero value.
node_pseudo = fs.File(test.workpath('pseudo'))
- node_pseudo.set_src_builder(1) # Any non-zero value.
+ node_pseudo.set_src_builder(1) # Any non-zero value.
paths = tuple(map(fs.Dir, ['.', 'same', './bar']))
nodes = [SCons.Node.FS.find_file('foo', paths)]
@@ -3234,12 +3425,13 @@ class find_fileTestCase(unittest.TestCase):
finally:
sys.stdout = save_sys_stdout
+
class StringDirTestCase(unittest.TestCase):
def runTest(self):
"""Test using a string as the second argument of
File() and Dir()"""
- test = TestCmd(workdir = '')
+ test = TestCmd(workdir='')
test.subdir('sub')
fs = SCons.Node.FS.FS(test.workpath(''))
@@ -3250,10 +3442,11 @@ class StringDirTestCase(unittest.TestCase):
assert str(f) == os.path.join('sub', 'file')
assert not f.exists()
+
class stored_infoTestCase(unittest.TestCase):
def runTest(self):
"""Test how we store build information"""
- test = TestCmd(workdir = '')
+ test = TestCmd(workdir='')
test.subdir('sub')
fs = SCons.Node.FS.FS(test.workpath(''))
@@ -3266,9 +3459,10 @@ class stored_infoTestCase(unittest.TestCase):
class Null(object):
def __init__(self):
self.xyzzy = 7
+
def get_entry(self, name):
return self.Null()
-
+
def test_sconsign(node):
return MySConsign()
@@ -3278,10 +3472,11 @@ class stored_infoTestCase(unittest.TestCase):
bi = f.get_stored_info()
assert bi.xyzzy == 7, bi
+
class has_src_builderTestCase(unittest.TestCase):
def runTest(self):
"""Test the has_src_builder() method"""
- test = TestCmd(workdir = '')
+ test = TestCmd(workdir='')
fs = SCons.Node.FS.FS(test.workpath(''))
os.chdir(test.workpath(''))
test.subdir('sub1')
@@ -3300,9 +3495,9 @@ class has_src_builderTestCase(unittest.TestCase):
sub1.set_src_builder(b1)
test.write(['sub1', 'f2'], "sub1/f2\n")
- h = f1.has_src_builder() # cached from previous call
+ h = f1.has_src_builder() # cached from previous call
assert not h, h
- h = f1.has_builder() # cached from previous call
+ h = f1.has_builder() # cached from previous call
assert not h, h
h = f2.has_src_builder()
assert not h, h
@@ -3314,6 +3509,7 @@ class has_src_builderTestCase(unittest.TestCase):
assert h, h
assert f3.builder is b1, f3.builder
+
class prepareTestCase(unittest.TestCase):
def runTest(self):
"""Test the prepare() method"""
@@ -3321,6 +3517,7 @@ class prepareTestCase(unittest.TestCase):
class MyFile(SCons.Node.FS.File):
def _createDir(self, update=None):
raise SCons.Errors.StopError
+
def exists(self):
return None
@@ -3337,6 +3534,7 @@ class prepareTestCase(unittest.TestCase):
class MkdirAction(Action):
def __init__(self, dir_made):
self.dir_made = dir_made
+
def __call__(self, target, source, env, executor=None):
if executor:
target = executor.get_all_targets()
@@ -3361,7 +3559,6 @@ class prepareTestCase(unittest.TestCase):
dir.prepare()
-
class SConstruct_dirTestCase(unittest.TestCase):
def runTest(self):
"""Test setting the SConstruct directory"""
@@ -3371,7 +3568,6 @@ class SConstruct_dirTestCase(unittest.TestCase):
assert fs.SConstruct_dir.get_internal_path() == 'xxx'
-
class CacheDirTestCase(unittest.TestCase):
def test_get_cachedir_csig(self):
@@ -3382,7 +3578,6 @@ class CacheDirTestCase(unittest.TestCase):
assert r == 'd41d8cd98f00b204e9800998ecf8427e', r
-
class clearTestCase(unittest.TestCase):
def runTest(self):
"""Test clearing FS nodes of cached data."""
@@ -3392,11 +3587,11 @@ class clearTestCase(unittest.TestCase):
e = fs.Entry('e')
assert not e.exists()
assert not e.rexists()
- assert str(e) == 'e', str(d)
+ assert str(e) == 'e', str(e)
e.clear()
assert not e.exists()
assert not e.rexists()
- assert str(e) == 'e', str(d)
+ assert str(e) == 'e', str(e)
d = fs.Dir(test.workpath('d'))
test.subdir('d')
@@ -3410,10 +3605,10 @@ class clearTestCase(unittest.TestCase):
assert str(d) == test.workpath('d'), str(d)
# Now verify clear() resets the cache
d.clear()
- assert not d.exists()
+ assert not d.exists()
assert not d.rexists()
assert str(d) == test.workpath('d'), str(d)
-
+
f = fs.File(test.workpath('f'))
test.write(test.workpath('f'), 'file f')
assert f.exists()
@@ -3431,7 +3626,6 @@ class clearTestCase(unittest.TestCase):
assert str(f) == test.workpath('f'), str(f)
-
class disambiguateTestCase(unittest.TestCase):
def runTest(self):
"""Test calling the disambiguate() method."""
@@ -3493,6 +3687,7 @@ class disambiguateTestCase(unittest.TestCase):
f = build_nonexistant.disambiguate()
assert f.__class__ is fff.__class__, f.__class__
+
class postprocessTestCase(unittest.TestCase):
def runTest(self):
"""Test calling the postprocess() method."""
@@ -3508,11 +3703,10 @@ class postprocessTestCase(unittest.TestCase):
f.postprocess()
-
class SpecialAttrTestCase(unittest.TestCase):
def runTest(self):
"""Test special attributes of file nodes."""
- test=TestCmd(workdir='')
+ test = TestCmd(workdir='')
fs = SCons.Node.FS.FS(test.workpath('work'))
f = fs.Entry('foo/bar/baz.blat').get_subst_proxy()
@@ -3667,11 +3861,10 @@ class SpecialAttrTestCase(unittest.TestCase):
assert caught, "did not catch expected AttributeError"
-
class SaveStringsTestCase(unittest.TestCase):
def runTest(self):
"""Test caching string values of nodes."""
- test=TestCmd(workdir='')
+ test = TestCmd(workdir='')
def setup(fs):
fs.Dir('src')
@@ -3725,13 +3918,13 @@ class SaveStringsTestCase(unittest.TestCase):
s = list(map(str, nodes))
expect = list(map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']))
- assert s == expect, 'node str() not cached: %s'%s
+ assert s == expect, 'node str() not cached: %s' % s
class AbsolutePathTestCase(unittest.TestCase):
def test_root_lookup_equivalence(self):
"""Test looking up /fff vs. fff in the / directory"""
- test=TestCmd(workdir='')
+ test = TestCmd(workdir='')
fs = SCons.Node.FS.FS('/')
@@ -3745,40 +3938,8 @@ class AbsolutePathTestCase(unittest.TestCase):
os.chdir(save_cwd)
-
if __name__ == "__main__":
- suite = unittest.TestSuite()
- suite.addTest(VariantDirTestCase())
- suite.addTest(find_fileTestCase())
- suite.addTest(StringDirTestCase())
- suite.addTest(stored_infoTestCase())
- suite.addTest(has_src_builderTestCase())
- suite.addTest(prepareTestCase())
- suite.addTest(SConstruct_dirTestCase())
- suite.addTest(clearTestCase())
- suite.addTest(disambiguateTestCase())
- suite.addTest(postprocessTestCase())
- suite.addTest(SpecialAttrTestCase())
- suite.addTest(SaveStringsTestCase())
- tclasses = [
- AbsolutePathTestCase,
- BaseTestCase,
- CacheDirTestCase,
- DirTestCase,
- DirBuildInfoTestCase,
- DirNodeInfoTestCase,
- EntryTestCase,
- FileTestCase,
- FileBuildInfoTestCase,
- FileNodeInfoTestCase,
- FSTestCase,
- GlobTestCase,
- RepositoryTestCase,
- ]
- for tclass in tclasses:
- names = unittest.getTestCaseNames(tclass, 'test_')
- suite.addTests(list(map(tclass, names)))
- TestUnit.run(suite)
+ unittest.main()
# Local Variables:
# tab-width:4
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index e50616a..ce44b7d 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -20,7 +20,7 @@
# 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__ = "src/engine/SCons/Node/NodeTests.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/NodeTests.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import SCons.compat
@@ -30,8 +30,6 @@ import re
import sys
import unittest
-import TestUnit
-
import SCons.Errors
import SCons.Node
import SCons.Util
@@ -273,7 +271,7 @@ class NodeInfoBaseTestCase(unittest.TestCase):
f = ni1.format()
assert f == ['x', 'y', 'z'], f
-
+
field_list = ['xxx', 'zzz', 'aaa']
f = ni1.format(field_list)
@@ -753,7 +751,7 @@ class NodeTestCase(unittest.TestCase):
e = node.exists()
assert e == 1, e
- def test_exists(self):
+ def test_exists_repo(self):
"""Test evaluating whether a Node exists locally or in a repository.
"""
node = SCons.Node.Node()
@@ -1337,9 +1335,9 @@ class NodeListTestCase(unittest.TestCase):
assert s == "['n3', 'n2', 'n1']", s
r = repr(nl)
- r = re.sub('at (0[xX])?[0-9a-fA-F]+', 'at 0x', r)
+ r = re.sub(r'at (0[xX])?[0-9a-fA-F]+', 'at 0x', r)
# Don't care about ancestry: just leaf value of MyNode
- r = re.sub('<.*?\.MyNode', '<MyNode', r)
+ r = re.sub(r'<.*?\.MyNode', '<MyNode', r)
# New-style classes report as "object"; classic classes report
# as "instance"...
r = re.sub("object", "instance", r)
@@ -1349,15 +1347,8 @@ class NodeListTestCase(unittest.TestCase):
if __name__ == "__main__":
- suite = unittest.TestSuite()
- tclasses = [ BuildInfoBaseTestCase,
- NodeInfoBaseTestCase,
- NodeTestCase,
- NodeListTestCase ]
- for tclass in tclasses:
- names = unittest.getTestCaseNames(tclass, 'test_')
- suite.addTests(list(map(tclass, names)))
- TestUnit.run(suite)
+ unittest.main()
+
# Local Variables:
# tab-width:4
diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py
index 06cb93a..d338051 100644
--- a/src/engine/SCons/Node/Python.py
+++ b/src/engine/SCons/Node/Python.py
@@ -5,7 +5,7 @@ Python nodes.
"""
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -27,7 +27,7 @@ Python nodes.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-__revision__ = "src/engine/SCons/Node/Python.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/Python.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import SCons.Node
@@ -137,6 +137,10 @@ class Value(SCons.Node.Node):
return contents
def get_contents(self):
+ """
+ Get contents for signature calculations.
+ :return: bytes
+ """
text_contents = self.get_text_contents()
try:
return text_contents.encode()
@@ -155,12 +159,17 @@ class Value(SCons.Node.Node):
def get_csig(self, calc=None):
"""Because we're a Python value node and don't have a real
timestamp, we get to ignore the calculator and just use the
- value contents."""
+ value contents.
+
+ Returns string. Ideally string of hex digits. (Not bytes)
+ """
try:
return self.ninfo.csig
except AttributeError:
pass
- contents = self.get_contents()
+
+ contents = self.get_text_contents()
+
self.get_ninfo().csig = contents
return contents
diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py
index aab7e85..98137c7 100644
--- a/src/engine/SCons/Node/PythonTests.py
+++ b/src/engine/SCons/Node/PythonTests.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,13 +21,11 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-__revision__ = "src/engine/SCons/Node/PythonTests.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/PythonTests.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
import sys
import unittest
-import TestUnit
-
import SCons.Errors
import SCons.Node.Python
@@ -90,15 +88,15 @@ class ValueTestCase(unittest.TestCase):
"""
v1 = SCons.Node.Python.Value('aaa')
csig = v1.get_csig(None)
- assert csig.decode() == 'aaa', csig
+ assert csig == 'aaa', csig
v2 = SCons.Node.Python.Value(7)
csig = v2.get_csig(None)
- assert csig.decode() == '7', csig
+ assert csig == '7', csig
v3 = SCons.Node.Python.Value(None)
csig = v3.get_csig(None)
- assert csig.decode() == 'None', csig
+ assert csig == 'None', csig
class ValueNodeInfoTestCase(unittest.TestCase):
def test___init__(self):
@@ -113,16 +111,7 @@ class ValueBuildInfoTestCase(unittest.TestCase):
bi = SCons.Node.Python.ValueBuildInfo()
if __name__ == "__main__":
- suite = unittest.TestSuite()
- tclasses = [
- ValueTestCase,
- ValueBuildInfoTestCase,
- ValueNodeInfoTestCase,
- ]
- for tclass in tclasses:
- names = unittest.getTestCaseNames(tclass, 'test_')
- suite.addTests(list(map(tclass, names)))
- TestUnit.run(suite)
+ unittest.main()
# Local Variables:
# tab-width:4
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 793e101..5455bd6 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -22,7 +22,7 @@ be able to depend on any other type of "thing."
from __future__ import print_function
#
-# Copyright (c) 2001 - 2017 The SCons Foundation
+# Copyright (c) 2001 - 2019 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -43,12 +43,18 @@ from __future__ import print_function
# 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__ = "src/engine/SCons/Node/__init__.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
+__revision__ = "src/engine/SCons/Node/__init__.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
+import os
import collections
import copy
from itertools import chain
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
+
import SCons.Debug
from SCons.Debug import logInstanceCreation
import SCons.Executor
@@ -102,9 +108,9 @@ implicit_deps_changed = 0
# A variable that can be set to an interface-specific function be called
# to annotate a Node with information about its creation.
-def do_nothing(node): pass
+def do_nothing_node(node): pass
-Annotate = do_nothing
+Annotate = do_nothing_node
# Gets set to 'True' if we're running in interactive mode. Is
# currently used to release parts of a target's info during
@@ -139,6 +145,7 @@ def exists_entry(node):
node.disambiguate()
return _exists_map[node._func_exists](node)
+
def exists_file(node):
# Duplicate from source path if we are set up to do this.
if node.duplicate and not node.is_derived() and not node.linked:
@@ -155,7 +162,7 @@ def exists_file(node):
# The source file does not exist. Make sure no old
# copy remains in the variant directory.
if print_duplicate:
- print("dup: no src for %s, unlinking old variant copy"%self)
+ print("dup: no src for %s, unlinking old variant copy" % node)
if exists_base(node) or node.islink():
node.fs.unlink(node.get_internal_path())
# Return None explicitly because the Base.exists() call
@@ -249,7 +256,7 @@ _target_from_source_map = {0 : target_from_source_none,
#
# First, the single decider functions
#
-def changed_since_last_build_node(node, target, prev_ni):
+def changed_since_last_build_node(node, target, prev_ni, repo_node=None):
"""
Must be overridden in a specific subclass to return True if this
@@ -269,27 +276,33 @@ def changed_since_last_build_node(node, target, prev_ni):
"""
raise NotImplementedError
-def changed_since_last_build_alias(node, target, prev_ni):
+
+def changed_since_last_build_alias(node, target, prev_ni, repo_node=None):
cur_csig = node.get_csig()
try:
return cur_csig != prev_ni.csig
except AttributeError:
return 1
-def changed_since_last_build_entry(node, target, prev_ni):
+
+def changed_since_last_build_entry(node, target, prev_ni, repo_node=None):
node.disambiguate()
- return _decider_map[node.changed_since_last_build](node, target, prev_ni)
+ return _decider_map[node.changed_since_last_build](node, target, prev_ni, repo_node)
+
-def changed_since_last_build_state_changed(node, target, prev_ni):
- return (node.state != SCons.Node.up_to_date)
+def changed_since_last_build_state_changed(node, target, prev_ni, repo_node=None):
+ return node.state != SCons.Node.up_to_date
-def decide_source(node, target, prev_ni):
- return target.get_build_env().decide_source(node, target, prev_ni)
-def decide_target(node, target, prev_ni):
- return target.get_build_env().decide_target(node, target, prev_ni)
+def decide_source(node, target, prev_ni, repo_node=None):
+ return target.get_build_env().decide_source(node, target, prev_ni, repo_node)
-def changed_since_last_build_python(node, target, prev_ni):
+
+def decide_target(node, target, prev_ni, repo_node=None):
+ return target.get_build_env().decide_target(node, target, prev_ni, repo_node)
+
+
+def changed_since_last_build_python(node, target, prev_ni, repo_node=None):
cur_csig = node.get_csig()
try:
return cur_csig != prev_ni.csig
@@ -380,6 +393,7 @@ class NodeInfoBase(object):
"""
state = other.__getstate__()
self.__setstate__(state)
+
def format(self, field_list=None, names=0):
if field_list is None:
try:
@@ -505,6 +519,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
__slots__ = ['sources',
'sources_set',
+ 'target_peers',
'_specific_sources',
'depends',
'depends_set',
@@ -665,7 +680,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
executor.cleanup()
def reset_executor(self):
- "Remove cached executor; forces recompute when needed."
+ """Remove cached executor; forces recompute when needed."""
try:
delattr(self, 'executor')
except AttributeError:
@@ -760,6 +775,25 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
for parent in self.waiting_parents:
parent.implicit = None
+ # Handle issue where builder emits more than one target and
+ # the source file for the builder is generated.
+ # in that case only the first target was getting it's .implicit
+ # cleared when the source file is built (second scan).
+ # leaving only partial implicits from scan before source file is generated
+ # typically the compiler only. Then scanned files are appended
+ # This is persisted to sconsign and rebuild causes false rebuilds
+ # because the ordering of the implicit list then changes to what it
+ # should have been.
+ # This is at least the following bugs
+ # https://github.com/SCons/scons/issues/2811
+ # https://jira.mongodb.org/browse/SERVER-33111
+ try:
+ for peer in parent.target_peers:
+ peer.implicit = None
+ except AttributeError:
+ pass
+
+
self.clear()
if self.pseudo:
@@ -1136,7 +1170,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
if self._specific_sources:
- sources = [ s for s in self.sources if not s in ignore_set]
+ sources = [s for s in self.sources if s not in ignore_set]
else:
sources = executor.get_unignored_sources(self, self.ignore)
@@ -1145,13 +1179,17 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
binfo.bsources = [s for s in sources if s not in seen and not seen.add(s)]
binfo.bsourcesigs = [s.get_ninfo() for s in binfo.bsources]
+ binfo.bdepends = [d for d in self.depends if d not in ignore_set]
+ binfo.bdependsigs = [d.get_ninfo() for d in self.depends]
- binfo.bdepends = self.depends
- binfo.bdependsigs = [d.get_ninfo() for d in self.depends if d not in ignore_set]
-
- binfo.bimplicit = self.implicit or []
- binfo.bimplicitsigs = [i.get_ninfo() for i in binfo.bimplicit if i not in ignore_set]
-
+ # Because self.implicit is initialized to None (and not empty list [])
+ # we have to handle this case
+ if not self.implicit:
+ binfo.bimplicit = []
+ binfo.bimplicitsigs = []
+ else:
+ binfo.bimplicit = [i for i in self.implicit if i not in ignore_set]
+ binfo.bimplicitsigs = [i.get_ninfo() for i in binfo.bimplicit]
return binfo
@@ -1213,7 +1251,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return _exists_map[self._func_exists](self)
def rexists(self):
- """Does this node exist locally or in a repositiory?"""
+ """Does this node exist locally or in a repository?"""
# There are no repositories by default:
return _rexists_map[self._func_rexists](self)
@@ -1452,13 +1490,12 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
result = True
for child, prev_ni in zip(children, then):
- if _decider_map[child.changed_since_last_build](child, self, prev_ni):
+ if _decider_map[child.changed_since_last_build](child, self, prev_ni, node):
if t: Trace(': %s changed' % child)
result = True
- contents = self.get_executor().get_contents()
if self.has_builder():
- import SCons.Util
+ contents = self.get_executor().get_contents()
newsig = SCons.Util.MD5signature(contents)
if bi.bactsig != newsig:
if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
@@ -1607,29 +1644,42 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
# so we only print them after running them through this lambda
# to turn them into the right relative Node and then return
# its string.
- def stringify( s, E=self.dir.Entry ) :
+ def stringify( s, E=self.dir.Entry):
if hasattr( s, 'dir' ) :
return str(E(s))
return str(s)
lines = []
- removed = [x for x in old_bkids if not x in new_bkids]
+ removed = [x for x in old_bkids if x not in new_bkids]
if removed:
- removed = list(map(stringify, removed))
+ removed = [stringify(r) for r in removed]
fmt = "`%s' is no longer a dependency\n"
lines.extend([fmt % s for s in removed])
for k in new_bkids:
- if not k in old_bkids:
+ if k not in old_bkids:
lines.append("`%s' is a new dependency\n" % stringify(k))
- elif _decider_map[k.changed_since_last_build](k, self, osig[k]):
- lines.append("`%s' changed\n" % stringify(k))
+ else:
+ try:
+ changed = _decider_map[k.changed_since_last_build](k, self, osig[k])
+ except DeciderNeedsNode as e:
+ changed = e.decider(self, osig[k], node=self)
+
+ if changed:
+ lines.append("`%s' changed\n" % stringify(k))
if len(lines) == 0 and old_bkids != new_bkids:
- lines.append("the dependency order changed:\n" +
- "%sold: %s\n" % (' '*15, list(map(stringify, old_bkids))) +
- "%snew: %s\n" % (' '*15, list(map(stringify, new_bkids))))
+ lines.append("the dependency order changed:\n")
+ lines.append("->Sources\n")
+ for (o,n) in zip_longest(old.bsources, new.bsources, fillvalue=None):
+ lines.append("Old:%s\tNew:%s\n"%(o,n))
+ lines.append("->Depends\n")
+ for (o,n) in zip_longest(old.bdepends, new.bdepends, fillvalue=None):
+ lines.append("Old:%s\tNew:%s\n"%(o,n))
+ lines.append("->Implicit\n")
+ for (o,n) in zip_longest(old.bimplicit, new.bimplicit, fillvalue=None):
+ lines.append("Old:%s\tNew:%s\n"%(o,n))
if len(lines) == 0:
def fmt_with_title(title, strlines):
@@ -1672,7 +1722,6 @@ class Walker(object):
This is depth-first, children are visited before the parent.
The Walker object can be initialized with any node, and
returns the next node on the descent with each get_next() call.
- 'kids_func' is an optional function that will be called to
get the children of a node instead of calling 'children'.
'cycle_func' is an optional function that will be called
when a cycle is detected.