diff options
Diffstat (limited to 'src/engine/SCons/Node')
| -rw-r--r-- | src/engine/SCons/Node/Alias.py | 4 | ||||
| -rw-r--r-- | src/engine/SCons/Node/AliasTests.py | 17 | ||||
| -rw-r--r-- | src/engine/SCons/Node/FS.py | 389 | ||||
| -rw-r--r-- | src/engine/SCons/Node/FSTests.py | 845 | ||||
| -rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 25 | ||||
| -rw-r--r-- | src/engine/SCons/Node/Python.py | 17 | ||||
| -rw-r--r-- | src/engine/SCons/Node/PythonTests.py | 23 | ||||
| -rw-r--r-- | src/engine/SCons/Node/__init__.py | 125 | 
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.  | 
