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. |