summaryrefslogtreecommitdiff
path: root/engine/SCons/Node
diff options
context:
space:
mode:
Diffstat (limited to 'engine/SCons/Node')
-rw-r--r--engine/SCons/Node/Alias.py152
-rw-r--r--engine/SCons/Node/FS.py3428
-rw-r--r--engine/SCons/Node/Python.py128
-rw-r--r--engine/SCons/Node/__init__.py1374
4 files changed, 5082 insertions, 0 deletions
diff --git a/engine/SCons/Node/Alias.py b/engine/SCons/Node/Alias.py
new file mode 100644
index 0000000..a2a22cc
--- /dev/null
+++ b/engine/SCons/Node/Alias.py
@@ -0,0 +1,152 @@
+
+"""scons.Node.Alias
+
+Alias nodes.
+
+This creates a hash of global Aliases (dummy targets).
+
+"""
+
+#
+# Copyright (c) 2001 - 2014 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/Node/Alias.py 2014/09/27 12:51:43 garyo"
+
+import collections
+
+import SCons.Errors
+import SCons.Node
+import SCons.Util
+
+class AliasNameSpace(collections.UserDict):
+ def Alias(self, name, **kw):
+ if isinstance(name, SCons.Node.Alias.Alias):
+ return name
+ try:
+ a = self[name]
+ except KeyError:
+ a = SCons.Node.Alias.Alias(name, **kw)
+ self[name] = a
+ return a
+
+ def lookup(self, name, **kw):
+ try:
+ return self[name]
+ except KeyError:
+ return None
+
+class AliasNodeInfo(SCons.Node.NodeInfoBase):
+ current_version_id = 1
+ field_list = ['csig']
+ def str_to_node(self, s):
+ return default_ans.Alias(s)
+
+class AliasBuildInfo(SCons.Node.BuildInfoBase):
+ current_version_id = 1
+
+class Alias(SCons.Node.Node):
+
+ NodeInfo = AliasNodeInfo
+ BuildInfo = AliasBuildInfo
+
+ def __init__(self, name):
+ SCons.Node.Node.__init__(self)
+ self.name = name
+
+ def str_for_display(self):
+ return '"' + self.__str__() + '"'
+
+ def __str__(self):
+ return self.name
+
+ def make_ready(self):
+ self.get_csig()
+
+ really_build = SCons.Node.Node.build
+ is_up_to_date = SCons.Node.Node.children_are_up_to_date
+
+ def is_under(self, dir):
+ # Make Alias nodes get built regardless of
+ # what directory scons was run from. Alias nodes
+ # are outside the filesystem:
+ return 1
+
+ def get_contents(self):
+ """The contents of an alias is the concatenation
+ of the content signatures of all its sources."""
+ childsigs = [n.get_csig() for n in self.children()]
+ return ''.join(childsigs)
+
+ def sconsign(self):
+ """An Alias is not recorded in .sconsign files"""
+ pass
+
+ #
+ #
+ #
+
+ def changed_since_last_build(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+ def build(self):
+ """A "builder" for aliases."""
+ pass
+
+ def convert(self):
+ try: del self.builder
+ except AttributeError: pass
+ self.reset_executor()
+ self.build = self.really_build
+
+ def get_csig(self):
+ """
+ Generate a node's content signature, the digested signature
+ of its content.
+
+ node - the node
+ cache - alternate node to use for the signature cache
+ returns - the content signature
+ """
+ try:
+ return self.ninfo.csig
+ except AttributeError:
+ pass
+
+ contents = self.get_contents()
+ csig = SCons.Util.MD5signature(contents)
+ self.get_ninfo().csig = csig
+ return csig
+
+default_ans = AliasNameSpace()
+
+SCons.Node.arg2nodes_lookups.append(default_ans.lookup)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/engine/SCons/Node/FS.py b/engine/SCons/Node/FS.py
new file mode 100644
index 0000000..4b95613
--- /dev/null
+++ b/engine/SCons/Node/FS.py
@@ -0,0 +1,3428 @@
+"""scons.Node.FS
+
+File system nodes.
+
+These Nodes represent the canonical external objects that people think
+of when they think of building software: files and directories.
+
+This holds a "default_fs" variable that should be initialized with an FS
+that can be used by scripts or modules looking for the canonical default.
+
+"""
+
+#
+# Copyright (c) 2001 - 2014 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "src/engine/SCons/Node/FS.py 2014/09/27 12:51:43 garyo"
+
+import fnmatch
+import os
+import re
+import shutil
+import stat
+import sys
+import time
+import codecs
+
+import SCons.Action
+import SCons.Debug
+from SCons.Debug import logInstanceCreation
+import SCons.Errors
+import SCons.Memoize
+import SCons.Node
+import SCons.Node.Alias
+import SCons.Subst
+import SCons.Util
+import SCons.Warnings
+
+from SCons.Debug import Trace
+
+do_store_info = True
+print_duplicate = 0
+
+
+class EntryProxyAttributeError(AttributeError):
+ """
+ An AttributeError subclass for recording and displaying the name
+ of the underlying Entry involved in an AttributeError exception.
+ """
+ def __init__(self, entry_proxy, attribute):
+ AttributeError.__init__(self)
+ self.entry_proxy = entry_proxy
+ self.attribute = attribute
+ def __str__(self):
+ entry = self.entry_proxy.get()
+ fmt = "%s instance %s has no attribute %s"
+ return fmt % (entry.__class__.__name__,
+ repr(entry.name),
+ repr(self.attribute))
+
+# The max_drift value: by default, use a cached signature value for
+# any file that's been untouched for more than two days.
+default_max_drift = 2*24*60*60
+
+#
+# We stringify these file system Nodes a lot. Turning a file system Node
+# into a string is non-trivial, because the final string representation
+# can depend on a lot of factors: whether it's a derived target or not,
+# whether it's linked to a repository or source directory, and whether
+# there's duplication going on. The normal technique for optimizing
+# calculations like this is to memoize (cache) the string value, so you
+# only have to do the calculation once.
+#
+# A number of the above factors, however, can be set after we've already
+# been asked to return a string for a Node, because a Repository() or
+# VariantDir() call or the like may not occur until later in SConscript
+# files. So this variable controls whether we bother trying to save
+# string values for Nodes. The wrapper interface can set this whenever
+# they're done mucking with Repository and VariantDir and the other stuff,
+# to let this module know it can start returning saved string values
+# for Nodes.
+#
+Save_Strings = None
+
+def save_strings(val):
+ global Save_Strings
+ Save_Strings = val
+
+#
+# Avoid unnecessary function calls by recording a Boolean value that
+# tells us whether or not os.path.splitdrive() actually does anything
+# on this system, and therefore whether we need to bother calling it
+# when looking up path names in various methods below.
+#
+
+do_splitdrive = None
+_my_splitdrive =None
+
+def initialize_do_splitdrive():
+ global do_splitdrive
+ global has_unc
+ drive, path = os.path.splitdrive('X:/foo')
+ has_unc = hasattr(os.path, 'splitunc')
+
+ do_splitdrive = not not drive or has_unc
+
+ global _my_splitdrive
+ if has_unc:
+ def splitdrive(p):
+ if p[1:2] == ':':
+ return p[:2], p[2:]
+ if p[0:2] == '//':
+ # Note that we leave a leading slash in the path
+ # because UNC paths are always absolute.
+ return '//', p[1:]
+ return '', p
+ else:
+ def splitdrive(p):
+ if p[1:2] == ':':
+ return p[:2], p[2:]
+ return '', p
+ _my_splitdrive = splitdrive
+
+ # Keep some commonly used values in global variables to skip to
+ # module look-up costs.
+ global OS_SEP
+ global UNC_PREFIX
+ global os_sep_is_slash
+
+ OS_SEP = os.sep
+ UNC_PREFIX = OS_SEP + OS_SEP
+ os_sep_is_slash = OS_SEP == '/'
+
+initialize_do_splitdrive()
+
+# Used to avoid invoking os.path.normpath if not necessary.
+needs_normpath_check = re.compile(
+ r'''
+ # We need to renormalize the path if it contains any consecutive
+ # '/' characters.
+ .*// |
+
+ # We need to renormalize the path if it contains a '..' directory.
+ # Note that we check for all the following cases:
+ #
+ # a) The path is a single '..'
+ # b) The path starts with '..'. E.g. '../' or '../moredirs'
+ # but we not match '..abc/'.
+ # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
+ # d) The path contains a '..' in the middle.
+ # E.g. dirs/../moredirs
+
+ (.*/)?\.\.(?:/|$) |
+
+ # We need to renormalize the path if it contains a '.'
+ # directory, but NOT if it is a single '.' '/' characters. We
+ # do not want to match a single '.' because this case is checked
+ # for explicitely since this is common enough case.
+ #
+ # Note that we check for all the following cases:
+ #
+ # a) We don't match a single '.'
+ # b) We match if the path starts with '.'. E.g. './' or
+ # './moredirs' but we not match '.abc/'.
+ # c) We match if the path ends with '.'. E.g. '/.' or
+ # 'dirs/.'
+ # d) We match if the path contains a '.' in the middle.
+ # E.g. dirs/./moredirs
+
+ \./|.*/\.(?:/|$)
+
+ ''',
+ re.VERBOSE
+ )
+needs_normpath_match = needs_normpath_check.match
+
+#
+# SCons.Action objects for interacting with the outside world.
+#
+# The Node.FS methods in this module should use these actions to
+# create and/or remove files and directories; they should *not* use
+# os.{link,symlink,unlink,mkdir}(), etc., directly.
+#
+# Using these SCons.Action objects ensures that descriptions of these
+# external activities are properly displayed, that the displays are
+# suppressed when the -s (silent) option is used, and (most importantly)
+# the actions are disabled when the the -n option is used, in which case
+# there should be *no* changes to the external file system(s)...
+#
+
+if hasattr(os, 'link'):
+ def _hardlink_func(fs, src, dst):
+ # If the source is a symlink, we can't just hard-link to it
+ # because a relative symlink may point somewhere completely
+ # different. We must disambiguate the symlink and then
+ # hard-link the final destination file.
+ while fs.islink(src):
+ link = fs.readlink(src)
+ if not os.path.isabs(link):
+ src = link
+ else:
+ src = os.path.join(os.path.dirname(src), link)
+ fs.link(src, dst)
+else:
+ _hardlink_func = None
+
+if hasattr(os, 'symlink'):
+ def _softlink_func(fs, src, dst):
+ fs.symlink(src, dst)
+else:
+ _softlink_func = None
+
+def _copy_func(fs, src, dest):
+ shutil.copy2(src, dest)
+ st = fs.stat(src)
+ fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+
+
+Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
+ 'hard-copy', 'soft-copy', 'copy']
+
+Link_Funcs = [] # contains the callables of the specified duplication style
+
+def set_duplicate(duplicate):
+ # Fill in the Link_Funcs list according to the argument
+ # (discarding those not available on the platform).
+
+ # Set up the dictionary that maps the argument names to the
+ # underlying implementations. We do this inside this function,
+ # not in the top-level module code, so that we can remap os.link
+ # and os.symlink for testing purposes.
+ link_dict = {
+ 'hard' : _hardlink_func,
+ 'soft' : _softlink_func,
+ 'copy' : _copy_func
+ }
+
+ if not duplicate in Valid_Duplicates:
+ raise SCons.Errors.InternalError("The argument of set_duplicate "
+ "should be in Valid_Duplicates")
+ global Link_Funcs
+ Link_Funcs = []
+ for func in duplicate.split('-'):
+ if link_dict[func]:
+ 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 ...
+ src = source[0].abspath
+ dest = target[0].abspath
+ dir, file = os.path.split(dest)
+ if dir and not target[0].fs.isdir(dir):
+ os.makedirs(dir)
+ if not Link_Funcs:
+ # Set a default order of link functions.
+ set_duplicate('hard-soft-copy')
+ fs = source[0].fs
+ # Now link the files with the previously specified order.
+ for func in Link_Funcs:
+ try:
+ func(fs, src, dest)
+ break
+ except (IOError, OSError):
+ # An OSError indicates something happened like a permissions
+ # problem or an attempt to symlink across file-system
+ # boundaries. An IOError indicates something like the file
+ # not existing. In either case, keeping trying additional
+ # functions in the list and only raise an error if the last
+ # one failed.
+ if func == Link_Funcs[-1]:
+ # exception of the last link method (copy) are fatal
+ raise
+ return 0
+
+Link = SCons.Action.Action(LinkFunc, None)
+def LocalString(target, source, env):
+ return 'Local copy of %s from %s' % (target[0], source[0])
+
+LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
+
+def UnlinkFunc(target, source, env):
+ t = target[0]
+ t.fs.unlink(t.abspath)
+ return 0
+
+Unlink = SCons.Action.Action(UnlinkFunc, None)
+
+def MkdirFunc(target, source, env):
+ t = target[0]
+ if not t.exists():
+ t.fs.mkdir(t.abspath)
+ return 0
+
+Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
+
+MkdirBuilder = None
+
+def get_MkdirBuilder():
+ global MkdirBuilder
+ if MkdirBuilder is None:
+ import SCons.Builder
+ import SCons.Defaults
+ # "env" will get filled in by Executor.get_build_env()
+ # calling SCons.Defaults.DefaultEnvironment() when necessary.
+ MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
+ env = None,
+ explain = None,
+ is_explicit = None,
+ target_scanner = SCons.Defaults.DirEntryScanner,
+ name = "MkdirBuilder")
+ return MkdirBuilder
+
+class _Null(object):
+ pass
+
+_null = _Null()
+
+DefaultSCCSBuilder = None
+DefaultRCSBuilder = None
+
+def get_DefaultSCCSBuilder():
+ global DefaultSCCSBuilder
+ if DefaultSCCSBuilder is None:
+ import SCons.Builder
+ # "env" will get filled in by Executor.get_build_env()
+ # calling SCons.Defaults.DefaultEnvironment() when necessary.
+ act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
+ DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
+ env = None,
+ name = "DefaultSCCSBuilder")
+ return DefaultSCCSBuilder
+
+def get_DefaultRCSBuilder():
+ global DefaultRCSBuilder
+ if DefaultRCSBuilder is None:
+ import SCons.Builder
+ # "env" will get filled in by Executor.get_build_env()
+ # calling SCons.Defaults.DefaultEnvironment() when necessary.
+ act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
+ DefaultRCSBuilder = SCons.Builder.Builder(action = act,
+ env = None,
+ name = "DefaultRCSBuilder")
+ return DefaultRCSBuilder
+
+# Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
+_is_cygwin = sys.platform == "cygwin"
+if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
+ def _my_normcase(x):
+ return x
+else:
+ def _my_normcase(x):
+ return x.upper()
+
+
+
+class DiskChecker(object):
+ def __init__(self, type, do, ignore):
+ self.type = type
+ self.do = do
+ self.ignore = ignore
+ self.func = do
+ def __call__(self, *args, **kw):
+ return self.func(*args, **kw)
+ def set(self, list):
+ if self.type in list:
+ self.func = self.do
+ else:
+ self.func = self.ignore
+
+def do_diskcheck_match(node, predicate, errorfmt):
+ result = predicate()
+ try:
+ # If calling the predicate() cached a None value from stat(),
+ # remove it so it doesn't interfere with later attempts to
+ # build this Node as we walk the DAG. (This isn't a great way
+ # to do this, we're reaching into an interface that doesn't
+ # really belong to us, but it's all about performance, so
+ # for now we'll just document the dependency...)
+ if node._memo['stat'] is None:
+ del node._memo['stat']
+ except (AttributeError, KeyError):
+ pass
+ if result:
+ raise TypeError(errorfmt % node.abspath)
+
+def ignore_diskcheck_match(node, predicate, errorfmt):
+ pass
+
+def do_diskcheck_rcs(node, name):
+ try:
+ rcs_dir = node.rcs_dir
+ except AttributeError:
+ if node.entry_exists_on_disk('RCS'):
+ rcs_dir = node.Dir('RCS')
+ else:
+ rcs_dir = None
+ node.rcs_dir = rcs_dir
+ if rcs_dir:
+ return rcs_dir.entry_exists_on_disk(name+',v')
+ return None
+
+def ignore_diskcheck_rcs(node, name):
+ return None
+
+def do_diskcheck_sccs(node, name):
+ try:
+ sccs_dir = node.sccs_dir
+ except AttributeError:
+ if node.entry_exists_on_disk('SCCS'):
+ sccs_dir = node.Dir('SCCS')
+ else:
+ sccs_dir = None
+ node.sccs_dir = sccs_dir
+ if sccs_dir:
+ return sccs_dir.entry_exists_on_disk('s.'+name)
+ return None
+
+def ignore_diskcheck_sccs(node, name):
+ return None
+
+diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
+diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
+diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
+
+diskcheckers = [
+ diskcheck_match,
+ diskcheck_rcs,
+ diskcheck_sccs,
+]
+
+def set_diskcheck(list):
+ for dc in diskcheckers:
+ dc.set(list)
+
+def diskcheck_types():
+ return [dc.type for dc in diskcheckers]
+
+
+
+class EntryProxy(SCons.Util.Proxy):
+
+ __str__ = SCons.Util.Delegate('__str__')
+
+ def __get_abspath(self):
+ entry = self.get()
+ return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
+ entry.name + "_abspath")
+
+ def __get_filebase(self):
+ name = self.get().name
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
+ name + "_filebase")
+
+ def __get_suffix(self):
+ name = self.get().name
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
+ name + "_suffix")
+
+ def __get_file(self):
+ name = self.get().name
+ return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
+
+ def __get_base_path(self):
+ """Return the file's directory and file name, with the
+ suffix stripped."""
+ entry = self.get()
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
+ entry.name + "_base")
+
+ def __get_posix_path(self):
+ """Return the path with / as the path separator,
+ regardless of platform."""
+ if os_sep_is_slash:
+ return self
+ else:
+ entry = self.get()
+ r = entry.get_path().replace(OS_SEP, '/')
+ return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
+
+ def __get_windows_path(self):
+ """Return the path with \ as the path separator,
+ regardless of platform."""
+ if OS_SEP == '\\':
+ return self
+ else:
+ entry = self.get()
+ r = entry.get_path().replace(OS_SEP, '\\')
+ return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
+
+ def __get_srcnode(self):
+ return EntryProxy(self.get().srcnode())
+
+ def __get_srcdir(self):
+ """Returns the directory containing the source node linked to this
+ node via VariantDir(), or the directory of this node if not linked."""
+ return EntryProxy(self.get().srcnode().dir)
+
+ def __get_rsrcnode(self):
+ return EntryProxy(self.get().srcnode().rfile())
+
+ def __get_rsrcdir(self):
+ """Returns the directory containing the source node linked to this
+ node via VariantDir(), or the directory of this node if not linked."""
+ return EntryProxy(self.get().srcnode().rfile().dir)
+
+ def __get_dir(self):
+ return EntryProxy(self.get().dir)
+
+ dictSpecialAttrs = { "base" : __get_base_path,
+ "posix" : __get_posix_path,
+ "windows" : __get_windows_path,
+ "win32" : __get_windows_path,
+ "srcpath" : __get_srcnode,
+ "srcdir" : __get_srcdir,
+ "dir" : __get_dir,
+ "abspath" : __get_abspath,
+ "filebase" : __get_filebase,
+ "suffix" : __get_suffix,
+ "file" : __get_file,
+ "rsrcpath" : __get_rsrcnode,
+ "rsrcdir" : __get_rsrcdir,
+ }
+
+ def __getattr__(self, name):
+ # This is how we implement the "special" attributes
+ # such as base, posix, srcdir, etc.
+ try:
+ attr_function = self.dictSpecialAttrs[name]
+ except KeyError:
+ try:
+ attr = SCons.Util.Proxy.__getattr__(self, name)
+ except AttributeError, e:
+ # Raise our own AttributeError subclass with an
+ # overridden __str__() method that identifies the
+ # name of the entry that caused the exception.
+ raise EntryProxyAttributeError(self, name)
+ return attr
+ else:
+ return attr_function(self)
+
+class Base(SCons.Node.Node):
+ """A generic class for file system entries. This class is for
+ when we don't know yet whether the entry being looked up is a file
+ or a directory. Instances of this class can morph into either
+ Dir or File objects by a later, more precise lookup.
+
+ Note: this class does not define __cmp__ and __hash__ for
+ efficiency reasons. SCons does a lot of comparing of
+ Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
+ as fast as possible, which means we want to use Python's built-in
+ object identity comparisons.
+ """
+
+ memoizer_counters = []
+
+ def __init__(self, name, directory, fs):
+ """Initialize a generic Node.FS.Base object.
+
+ Call the superclass initialization, take care of setting up
+ our relative and absolute paths, identify our parent
+ directory, and indicate that this node should use
+ signatures."""
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base')
+ SCons.Node.Node.__init__(self)
+
+ # Filenames and paths are probably reused and are intern'ed to
+ # save some memory.
+
+ #: Filename with extension as it was specified when the object was
+ #: created; to obtain filesystem path, use Python str() function
+ self.name = SCons.Util.silent_intern(name)
+ #: Cached filename extension
+ self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
+ self.fs = fs #: Reference to parent Node.FS object
+
+ assert directory, "A directory must be provided"
+
+ self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
+ self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
+ if directory.path == '.':
+ self.path = SCons.Util.silent_intern(name)
+ else:
+ self.path = SCons.Util.silent_intern(directory.entry_path(name))
+ if directory.tpath == '.':
+ self.tpath = SCons.Util.silent_intern(name)
+ else:
+ self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
+ self.path_elements = directory.path_elements + [self]
+
+ self.dir = directory
+ self.cwd = None # will hold the SConscript directory for target nodes
+ self.duplicate = directory.duplicate
+
+ def str_for_display(self):
+ return '"' + self.__str__() + '"'
+
+ def must_be_same(self, klass):
+ """
+ This node, which already existed, is being looked up as the
+ specified klass. Raise an exception if it isn't.
+ """
+ if isinstance(self, klass) or klass is Entry:
+ return
+ raise TypeError("Tried to lookup %s '%s' as a %s." %\
+ (self.__class__.__name__, self.path, klass.__name__))
+
+ def get_dir(self):
+ return self.dir
+
+ def get_suffix(self):
+ return self.suffix
+
+ def rfile(self):
+ return self
+
+ def __str__(self):
+ """A Node.FS.Base object's string representation is its path
+ name."""
+ global Save_Strings
+ if Save_Strings:
+ return self._save_str()
+ return self._get_str()
+
+ memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
+
+ def _save_str(self):
+ try:
+ return self._memo['_save_str']
+ except KeyError:
+ pass
+ result = sys.intern(self._get_str())
+ self._memo['_save_str'] = result
+ return result
+
+ def _get_str(self):
+ global Save_Strings
+ if self.duplicate or self.is_derived():
+ return self.get_path()
+ srcnode = self.srcnode()
+ if srcnode.stat() is None and self.stat() is not None:
+ result = self.get_path()
+ else:
+ result = srcnode.get_path()
+ if not Save_Strings:
+ # We're not at the point where we're saving the string
+ # representations of FS Nodes (because we haven't finished
+ # reading the SConscript files and need to have str() return
+ # things relative to them). That also means we can't yet
+ # cache values returned (or not returned) by stat(), since
+ # Python code in the SConscript files might still create
+ # or otherwise affect the on-disk file. So get rid of the
+ # values that the underlying stat() method saved.
+ try: del self._memo['stat']
+ except KeyError: pass
+ if self is not srcnode:
+ try: del srcnode._memo['stat']
+ except KeyError: pass
+ return result
+
+ rstr = __str__
+
+ memoizer_counters.append(SCons.Memoize.CountValue('stat'))
+
+ def stat(self):
+ try: return self._memo['stat']
+ except KeyError: pass
+ try: result = self.fs.stat(self.abspath)
+ except os.error: result = None
+ self._memo['stat'] = result
+ return result
+
+ def exists(self):
+ return self.stat() is not None
+
+ def rexists(self):
+ return self.rfile().exists()
+
+ def getmtime(self):
+ st = self.stat()
+ 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
+
+ def isdir(self):
+ st = self.stat()
+ return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
+
+ def isfile(self):
+ st = self.stat()
+ return st is not None and stat.S_ISREG(st[stat.ST_MODE])
+
+ if hasattr(os, 'symlink'):
+ def islink(self):
+ try: st = self.fs.lstat(self.abspath)
+ except os.error: return 0
+ return stat.S_ISLNK(st[stat.ST_MODE])
+ else:
+ def islink(self):
+ return 0 # no symlinks
+
+ def is_under(self, dir):
+ if self is dir:
+ return 1
+ else:
+ return self.dir.is_under(dir)
+
+ def set_local(self):
+ self._local = 1
+
+ def srcnode(self):
+ """If this node is in a build path, return the node
+ corresponding to its source file. Otherwise, return
+ ourself.
+ """
+ srcdir_list = self.dir.srcdir_list()
+ if srcdir_list:
+ srcnode = srcdir_list[0].Entry(self.name)
+ srcnode.must_be_same(self.__class__)
+ return srcnode
+ return self
+
+ def get_path(self, dir=None):
+ """Return path relative to the current working directory of the
+ Node.FS.Base object that owns us."""
+ if not dir:
+ dir = self.fs.getcwd()
+ if self == dir:
+ return '.'
+ path_elems = self.path_elements
+ pathname = ''
+ try: i = path_elems.index(dir)
+ except ValueError:
+ for p in path_elems[:-1]:
+ pathname += p.dirname
+ else:
+ for p in path_elems[i+1:-1]:
+ pathname += p.dirname
+ return pathname + path_elems[-1].name
+
+ def set_src_builder(self, builder):
+ """Set the source code builder for this node."""
+ self.sbuilder = builder
+ if not self.has_builder():
+ self.builder_set(builder)
+
+ def src_builder(self):
+ """Fetch the source code builder for this node.
+
+ If there isn't one, we cache the source code builder specified
+ for the directory (which in turn will cache the value from its
+ parent directory, and so on up to the file system root).
+ """
+ try:
+ scb = self.sbuilder
+ except AttributeError:
+ scb = self.dir.src_builder()
+ self.sbuilder = scb
+ return scb
+
+ def get_abspath(self):
+ """Get the absolute path of the file."""
+ return self.abspath
+
+ def for_signature(self):
+ # Return just our name. Even an absolute path would not work,
+ # because that can change thanks to symlinks or remapped network
+ # paths.
+ return self.name
+
+ def get_subst_proxy(self):
+ try:
+ return self._proxy
+ except AttributeError:
+ ret = EntryProxy(self)
+ self._proxy = ret
+ return ret
+
+ def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
+ """
+
+ Generates a target entry that corresponds to this entry (usually
+ a source file) with the specified prefix and suffix.
+
+ Note that this method can be overridden dynamically for generated
+ files that need different behavior. See Tool/swig.py for
+ an example.
+ """
+ return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
+
+ def _Rfindalldirs_key(self, pathlist):
+ return pathlist
+
+ memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
+
+ def Rfindalldirs(self, pathlist):
+ """
+ Return all of the directories for a given path list, including
+ corresponding "backing" directories in any repositories.
+
+ The Node lookups are relative to this Node (typically a
+ directory), so memoizing result saves cycles from looking
+ up the same path for each target in a given directory.
+ """
+ try:
+ memo_dict = self._memo['Rfindalldirs']
+ except KeyError:
+ memo_dict = {}
+ self._memo['Rfindalldirs'] = memo_dict
+ else:
+ try:
+ return memo_dict[pathlist]
+ except KeyError:
+ pass
+
+ create_dir_relative_to_self = self.Dir
+ result = []
+ for path in pathlist:
+ if isinstance(path, SCons.Node.Node):
+ result.append(path)
+ else:
+ dir = create_dir_relative_to_self(path)
+ result.extend(dir.get_all_rdirs())
+
+ memo_dict[pathlist] = result
+
+ return result
+
+ def RDirs(self, pathlist):
+ """Search for a list of directories in the Repository list."""
+ cwd = self.cwd or self.fs._cwd
+ return cwd.Rfindalldirs(pathlist)
+
+ memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
+
+ def rentry(self):
+ try:
+ return self._memo['rentry']
+ except KeyError:
+ pass
+ 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:
+ if dir.entry_exists_on_disk(self.name):
+ result = dir.Entry(self.name)
+ break
+ self._memo['rentry'] = result
+ return result
+
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ return []
+
+class Entry(Base):
+ """This is the class for generic Node.FS entries--that is, things
+ that could be a File or a Dir, but we're just not sure yet.
+ Consequently, the methods in this class really exist just to
+ transform their associated object into the right class when the
+ time comes, and then call the same-named method in the transformed
+ class."""
+
+ def diskcheck_match(self):
+ pass
+
+ def disambiguate(self, must_exist=None):
+ """
+ """
+ if self.isdir():
+ self.__class__ = Dir
+ self._morph()
+ elif self.isfile():
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ else:
+ # There was nothing on-disk at this location, so look in
+ # the src directory.
+ #
+ # We can't just use self.srcnode() straight away because
+ # that would create an actual Node for this file in the src
+ # directory, and there might not be one. Instead, use the
+ # dir_on_disk() method to see if there's something on-disk
+ # with that name, in which case we can go ahead and call
+ # self.srcnode() to create the right type of entry.
+ srcdir = self.dir.srcnode()
+ if srcdir != self.dir and \
+ srcdir.entry_exists_on_disk(self.name) and \
+ self.srcnode().isdir():
+ self.__class__ = Dir
+ self._morph()
+ elif must_exist:
+ msg = "No such file or directory: '%s'" % self.abspath
+ raise SCons.Errors.UserError(msg)
+ else:
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ return self
+
+ def rfile(self):
+ """We're a generic Entry, but the caller is actually looking for
+ a File at this point, so morph into one."""
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ return File.rfile(self)
+
+ def scanner_key(self):
+ return self.get_suffix()
+
+ def get_contents(self):
+ """Fetch the contents of the entry. Returns the exact binary
+ contents of the file."""
+ try:
+ self = self.disambiguate(must_exist=1)
+ except SCons.Errors.UserError:
+ # There was nothing on disk with which to disambiguate
+ # this entry. Leave it as an Entry, but return a null
+ # string so calls to get_contents() in emitters and the
+ # like (e.g. in qt.py) don't have to disambiguate by hand
+ # or catch the exception.
+ return ''
+ else:
+ return self.get_contents()
+
+ def get_text_contents(self):
+ """Fetch the decoded text contents of a Unicode encoded Entry.
+
+ Since this should return the text contents from the file
+ system, we check to see into what sort of subclass we should
+ morph this Entry."""
+ try:
+ self = self.disambiguate(must_exist=1)
+ except SCons.Errors.UserError:
+ # There was nothing on disk with which to disambiguate
+ # this entry. Leave it as an Entry, but return a null
+ # string so calls to get_text_contents() in emitters and
+ # the like (e.g. in qt.py) don't have to disambiguate by
+ # hand or catch the exception.
+ return ''
+ else:
+ return self.get_text_contents()
+
+ def must_be_same(self, klass):
+ """Called to make sure a Node is a Dir. Since we're an
+ Entry, we can morph into one."""
+ if self.__class__ is not klass:
+ self.__class__ = klass
+ self._morph()
+ self.clear()
+
+ # The following methods can get called before the Taskmaster has
+ # had a chance to call disambiguate() directly to see if this Entry
+ # should really be a Dir or a File. We therefore use these to call
+ # disambiguate() transparently (from our caller's point of view).
+ #
+ # Right now, this minimal set of methods has been derived by just
+ # looking at some of the methods that will obviously be called early
+ # in any of the various Taskmasters' calling sequences, and then
+ # empirically figuring out which additional methods are necessary
+ # to make various tests pass.
+
+ def exists(self):
+ """Return if the Entry exists. Check the file system to see
+ what we should turn into first. Assume a file if there's no
+ directory."""
+ return self.disambiguate().exists()
+
+ def rel_path(self, other):
+ d = self.disambiguate()
+ if d.__class__ is Entry:
+ raise Exception("rel_path() could not disambiguate File/Dir")
+ return d.rel_path(other)
+
+ def new_ninfo(self):
+ return self.disambiguate().new_ninfo()
+
+ def changed_since_last_build(self, target, prev_ni):
+ return self.disambiguate().changed_since_last_build(target, prev_ni)
+
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ return self.disambiguate()._glob1(pattern, ondisk, source, strings)
+
+ def get_subst_proxy(self):
+ return self.disambiguate().get_subst_proxy()
+
+# This is for later so we can differentiate between Entry the class and Entry
+# the method of the FS class.
+_classEntry = Entry
+
+
+class LocalFS(object):
+
+ if SCons.Memoize.use_memoizer:
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+ # 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):
+ return os.chmod(path, mode)
+ def copy(self, src, dst):
+ return shutil.copy(src, dst)
+ def copy2(self, src, dst):
+ return shutil.copy2(src, dst)
+ def exists(self, path):
+ return os.path.exists(path)
+ def getmtime(self, path):
+ return os.path.getmtime(path)
+ def getsize(self, path):
+ return os.path.getsize(path)
+ def isdir(self, path):
+ return os.path.isdir(path)
+ def isfile(self, path):
+ return os.path.isfile(path)
+ def link(self, src, dst):
+ return os.link(src, dst)
+ def lstat(self, path):
+ return os.lstat(path)
+ def listdir(self, path):
+ return os.listdir(path)
+ def makedirs(self, path):
+ return os.makedirs(path)
+ def mkdir(self, path):
+ return os.mkdir(path)
+ def rename(self, old, new):
+ return os.rename(old, new)
+ def stat(self, path):
+ return os.stat(path)
+ def symlink(self, src, dst):
+ return os.symlink(src, dst)
+ def open(self, path):
+ return open(path)
+ def unlink(self, path):
+ return os.unlink(path)
+
+ if hasattr(os, 'symlink'):
+ def islink(self, path):
+ return os.path.islink(path)
+ else:
+ def islink(self, path):
+ return 0 # no symlinks
+
+ if hasattr(os, 'readlink'):
+ def readlink(self, file):
+ return os.readlink(file)
+ else:
+ def readlink(self, file):
+ return ''
+
+
+#class RemoteFS:
+# # Skeleton for the obvious methods we might need from the
+# # abstraction layer for a remote filesystem.
+# def upload(self, local_src, remote_dst):
+# pass
+# def download(self, remote_src, local_dst):
+# pass
+
+
+class FS(LocalFS):
+
+ memoizer_counters = []
+
+ def __init__(self, path = None):
+ """Initialize the Node.FS subsystem.
+
+ The supplied path is the top of the source tree, where we
+ expect to find the top-level build file. If no path is
+ supplied, the current directory is the default.
+
+ The path argument must be a valid absolute path.
+ """
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
+
+ self._memo = {}
+
+ self.Root = {}
+ self.SConstruct_dir = None
+ self.max_drift = default_max_drift
+
+ self.Top = None
+ if path is None:
+ self.pathTop = os.getcwd()
+ else:
+ self.pathTop = path
+ self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
+
+ self.Top = self.Dir(self.pathTop)
+ self.Top.path = '.'
+ self.Top.tpath = '.'
+ self._cwd = self.Top
+
+ DirNodeInfo.fs = self
+ FileNodeInfo.fs = self
+
+ def set_SConstruct_dir(self, dir):
+ self.SConstruct_dir = dir
+
+ def get_max_drift(self):
+ return self.max_drift
+
+ def set_max_drift(self, max_drift):
+ self.max_drift = max_drift
+
+ def getcwd(self):
+ if hasattr(self, "_cwd"):
+ return self._cwd
+ else:
+ return "<no cwd>"
+
+ def chdir(self, dir, change_os_dir=0):
+ """Change the current working directory for lookups.
+ If change_os_dir is true, we will also change the "real" cwd
+ to match.
+ """
+ curr=self._cwd
+ try:
+ if dir is not None:
+ self._cwd = dir
+ if change_os_dir:
+ os.chdir(dir.abspath)
+ except OSError:
+ self._cwd = curr
+ raise
+
+ def get_root(self, drive):
+ """
+ Returns the root directory for the specified drive, creating
+ it if necessary.
+ """
+ drive = _my_normcase(drive)
+ try:
+ return self.Root[drive]
+ except KeyError:
+ root = RootDir(drive, self)
+ self.Root[drive] = root
+ if not drive:
+ self.Root[self.defaultDrive] = root
+ elif drive == self.defaultDrive:
+ self.Root[''] = root
+ return root
+
+ def _lookup(self, p, directory, fsclass, create=1):
+ """
+ The generic entry point for Node lookup with user-supplied data.
+
+ This translates arbitrary input into a canonical Node.FS object
+ of the specified fsclass. The general approach for strings is
+ to turn it into a fully normalized absolute path and then call
+ the root directory's lookup_abs() method for the heavy lifting.
+
+ If the path name begins with '#', it is unconditionally
+ interpreted relative to the top-level directory of this FS. '#'
+ is treated as a synonym for the top-level SConstruct directory,
+ much like '~' is treated as a synonym for the user's home
+ directory in a UNIX shell. So both '#foo' and '#/foo' refer
+ to the 'foo' subdirectory underneath the top-level SConstruct
+ directory.
+
+ If the path name is relative, then the path is looked up relative
+ to the specified directory, or the current directory (self._cwd,
+ typically the SConscript directory) if the specified directory
+ is None.
+ """
+ if isinstance(p, Base):
+ # It's already a Node.FS object. Make sure it's the right
+ # class and return.
+ p.must_be_same(fsclass)
+ return p
+ # str(p) in case it's something like a proxy object
+ p = str(p)
+
+ if not os_sep_is_slash:
+ p = p.replace(OS_SEP, '/')
+
+ if p[0:1] == '#':
+ # There was an initial '#', so we strip it and override
+ # whatever directory they may have specified with the
+ # top-level SConstruct directory.
+ p = p[1:]
+ directory = self.Top
+
+ # There might be a drive letter following the
+ # '#'. Although it is not described in the SCons man page,
+ # the regression test suite explicitly tests for that
+ # syntax. It seems to mean the following thing:
+ #
+ # Assuming the the SCons top dir is in C:/xxx/yyy,
+ # '#X:/toto' means X:/xxx/yyy/toto.
+ #
+ # i.e. it assumes that the X: drive has a directory
+ # structure similar to the one found on drive C:.
+ if do_splitdrive:
+ drive, p = _my_splitdrive(p)
+ if drive:
+ root = self.get_root(drive)
+ else:
+ root = directory.root
+ else:
+ root = directory.root
+
+ # We can only strip trailing after splitting the drive
+ # since the drive might the UNC '//' prefix.
+ p = p.strip('/')
+
+ needs_normpath = needs_normpath_match(p)
+
+ # The path is relative to the top-level SCons directory.
+ if p in ('', '.'):
+ p = directory.labspath
+ else:
+ p = directory.labspath + '/' + p
+ else:
+ if do_splitdrive:
+ drive, p = _my_splitdrive(p)
+ if drive and not p:
+ # This causes a naked drive letter to be treated
+ # as a synonym for the root directory on that
+ # drive.
+ p = '/'
+ else:
+ drive = ''
+
+ # We can only strip trailing '/' since the drive might the
+ # UNC '//' prefix.
+ if p != '/':
+ p = p.rstrip('/')
+
+ needs_normpath = needs_normpath_match(p)
+
+ if p[0:1] == '/':
+ # Absolute path
+ root = self.get_root(drive)
+ else:
+ # This is a relative lookup or to the current directory
+ # (the path name is not absolute). Add the string to the
+ # appropriate directory lookup path, after which the whole
+ # thing gets normalized.
+ if directory:
+ if not isinstance(directory, Dir):
+ directory = self.Dir(directory)
+ else:
+ directory = self._cwd
+
+ if p in ('', '.'):
+ p = directory.labspath
+ else:
+ p = directory.labspath + '/' + p
+
+ if drive:
+ root = self.get_root(drive)
+ else:
+ root = directory.root
+
+ if needs_normpath is not None:
+ # Normalize a pathname. Will return the same result for
+ # equivalent paths.
+ #
+ # We take advantage of the fact that we have an absolute
+ # path here for sure. In addition, we know that the
+ # components of lookup path are separated by slashes at
+ # this point. Because of this, this code is about 2X
+ # faster than calling os.path.normpath() followed by
+ # replacing os.sep with '/' again.
+ ins = p.split('/')[1:]
+ outs = []
+ for d in ins:
+ if d == '..':
+ try:
+ outs.pop()
+ except IndexError:
+ pass
+ elif d not in ('', '.'):
+ outs.append(d)
+ p = '/' + '/'.join(outs)
+
+ return root._lookup_abs(p, fsclass, create)
+
+ def Entry(self, name, directory = None, create = 1):
+ """Look up or create a generic Entry node with the specified name.
+ If the name is a relative path (begins with ./, ../, or a file
+ name), then it is looked up relative to the supplied directory
+ node, or to the top level directory of the FS (supplied at
+ construction time) if no directory is supplied.
+ """
+ return self._lookup(name, directory, Entry, create)
+
+ def File(self, name, directory = None, create = 1):
+ """Look up or create a File node with the specified name. If
+ the name is a relative path (begins with ./, ../, or a file name),
+ then it is looked up relative to the supplied directory node,
+ or to the top level directory of the FS (supplied at construction
+ time) if no directory is supplied.
+
+ This method will raise TypeError if a directory is found at the
+ specified path.
+ """
+ return self._lookup(name, directory, File, create)
+
+ def Dir(self, name, directory = None, create = True):
+ """Look up or create a Dir node with the specified name. If
+ the name is a relative path (begins with ./, ../, or a file name),
+ then it is looked up relative to the supplied directory node,
+ or to the top level directory of the FS (supplied at construction
+ time) if no directory is supplied.
+
+ This method will raise TypeError if a normal file is found at the
+ specified path.
+ """
+ return self._lookup(name, directory, Dir, create)
+
+ def VariantDir(self, variant_dir, src_dir, duplicate=1):
+ """Link the supplied variant directory to the source directory
+ for purposes of building files."""
+
+ if not isinstance(src_dir, SCons.Node.Node):
+ src_dir = self.Dir(src_dir)
+ if not isinstance(variant_dir, SCons.Node.Node):
+ variant_dir = self.Dir(variant_dir)
+ if src_dir.is_under(variant_dir):
+ raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
+ if variant_dir.srcdir:
+ if variant_dir.srcdir == src_dir:
+ return # We already did this.
+ raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
+ variant_dir.link(src_dir, duplicate)
+
+ def Repository(self, *dirs):
+ """Specify Repository directories to search."""
+ for d in dirs:
+ if not isinstance(d, SCons.Node.Node):
+ d = self.Dir(d)
+ self.Top.addRepository(d)
+
+ def variant_dir_target_climb(self, orig, dir, tail):
+ """Create targets in corresponding variant directories
+
+ Climb the directory tree, and look up path names
+ relative to any linked variant directories we find.
+
+ Even though this loops and walks up the tree, we don't memoize
+ the return value because this is really only used to process
+ the command-line targets.
+ """
+ targets = []
+ message = None
+ fmt = "building associated VariantDir targets: %s"
+ start_dir = dir
+ while dir:
+ for bd in dir.variant_dirs:
+ if start_dir.is_under(bd):
+ # If already in the build-dir location, don't reflect
+ return [orig], fmt % str(orig)
+ p = os.path.join(bd.path, *tail)
+ targets.append(self.Entry(p))
+ tail = [dir.name] + tail
+ dir = dir.up()
+ if targets:
+ message = fmt % ' '.join(map(str, targets))
+ return targets, message
+
+ def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
+ """
+ Globs
+
+ This is mainly a shim layer
+ """
+ if cwd is None:
+ cwd = self.getcwd()
+ return cwd.glob(pathname, ondisk, source, strings)
+
+class DirNodeInfo(SCons.Node.NodeInfoBase):
+ # This should get reset by the FS initialization.
+ current_version_id = 1
+
+ fs = None
+
+ def str_to_node(self, s):
+ top = self.fs.Top
+ root = top.root
+ if do_splitdrive:
+ drive, s = _my_splitdrive(s)
+ if drive:
+ root = self.fs.get_root(drive)
+ if not os.path.isabs(s):
+ s = top.labspath + '/' + s
+ return root._lookup_abs(s, Entry)
+
+class DirBuildInfo(SCons.Node.BuildInfoBase):
+ current_version_id = 1
+
+glob_magic_check = re.compile('[*?[]')
+
+def has_glob_magic(s):
+ return glob_magic_check.search(s) is not None
+
+class Dir(Base):
+ """A class for directories in a file system.
+ """
+
+ memoizer_counters = []
+
+ NodeInfo = DirNodeInfo
+ BuildInfo = DirBuildInfo
+
+ def __init__(self, name, directory, fs):
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir')
+ Base.__init__(self, name, directory, fs)
+ self._morph()
+
+ def _morph(self):
+ """Turn a file system Node (either a freshly initialized directory
+ object or a separate Entry object) into a proper directory object.
+
+ Set up this directory's entries and hook it into the file
+ system tree. Specify that directories (this Node) don't use
+ signatures for calculating whether they're current.
+ """
+
+ self.repositories = []
+ self.srcdir = None
+
+ self.entries = {}
+ self.entries['.'] = self
+ self.entries['..'] = self.dir
+ self.cwd = self
+ self.searched = 0
+ self._sconsign = None
+ self.variant_dirs = []
+ self.root = self.dir.root
+
+ # For directories, we make a difference between the directory
+ # 'name' and the directory 'dirname'. The 'name' attribute is
+ # used when we need to print the 'name' of the directory or
+ # when we it is used as the last part of a path. The 'dirname'
+ # is used when the directory is not the last element of the
+ # path. The main reason for making that distinction is that
+ # for RoorDir's the dirname can not be easily inferred from
+ # the name. For example, we have to add a '/' after a drive
+ # letter but not after a UNC path prefix ('//').
+ self.dirname = self.name + OS_SEP
+
+ # Don't just reset the executor, replace its action list,
+ # because it might have some pre-or post-actions that need to
+ # be preserved.
+ #
+ # But don't reset the executor if there is a non-null executor
+ # attached already. The existing executor might have other
+ # targets, in which case replacing the action list with a
+ # Mkdir action is a big mistake.
+ if not hasattr(self, 'executor'):
+ self.builder = get_MkdirBuilder()
+ self.get_executor().set_action_list(self.builder.action)
+ else:
+ # Prepend MkdirBuilder action to existing action list
+ l = self.get_executor().action_list
+ a = get_MkdirBuilder().action
+ l.insert(0, a)
+ self.get_executor().set_action_list(l)
+
+ def diskcheck_match(self):
+ diskcheck_match(self, self.isfile,
+ "File %s found where directory expected.")
+
+ def __clearRepositoryCache(self, duplicate=None):
+ """Called when we change the repository(ies) for a directory.
+ This clears any cached information that is invalidated by changing
+ the repository."""
+
+ for node in self.entries.values():
+ if node != self.dir:
+ if node != self and isinstance(node, Dir):
+ node.__clearRepositoryCache(duplicate)
+ else:
+ node.clear()
+ try:
+ del node._srcreps
+ except AttributeError:
+ pass
+ if duplicate is not None:
+ node.duplicate=duplicate
+
+ def __resetDuplicate(self, node):
+ if node != self:
+ node.duplicate = node.get_dir().duplicate
+
+ def Entry(self, name):
+ """
+ Looks up or creates an entry node named 'name' relative to
+ this directory.
+ """
+ return self.fs.Entry(name, self)
+
+ def Dir(self, name, create=True):
+ """
+ Looks up or creates a directory node named 'name' relative to
+ this directory.
+ """
+ return self.fs.Dir(name, self, create)
+
+ def File(self, name):
+ """
+ Looks up or creates a file node named 'name' relative to
+ this directory.
+ """
+ return self.fs.File(name, self)
+
+ def link(self, srcdir, duplicate):
+ """Set this directory as the variant directory for the
+ supplied source directory."""
+ self.srcdir = srcdir
+ self.duplicate = duplicate
+ self.__clearRepositoryCache(duplicate)
+ srcdir.variant_dirs.append(self)
+
+ def getRepositories(self):
+ """Returns a list of repositories for this directory.
+ """
+ if self.srcdir and not self.duplicate:
+ return self.srcdir.get_all_rdirs() + self.repositories
+ return self.repositories
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
+
+ def get_all_rdirs(self):
+ try:
+ return list(self._memo['get_all_rdirs'])
+ except KeyError:
+ pass
+
+ result = [self]
+ fname = '.'
+ dir = self
+ while dir:
+ for rep in dir.getRepositories():
+ result.append(rep.Dir(fname))
+ if fname == '.':
+ fname = dir.name
+ else:
+ fname = dir.name + OS_SEP + fname
+ dir = dir.up()
+
+ self._memo['get_all_rdirs'] = list(result)
+
+ return result
+
+ def addRepository(self, dir):
+ if dir != self and not dir in self.repositories:
+ self.repositories.append(dir)
+ dir.tpath = '.'
+ self.__clearRepositoryCache()
+
+ def up(self):
+ return self.dir
+
+ def _rel_path_key(self, other):
+ return str(other)
+
+ memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
+
+ def rel_path(self, other):
+ """Return a path to "other" relative to this directory.
+ """
+
+ # This complicated and expensive method, which constructs relative
+ # paths between arbitrary Node.FS objects, is no longer used
+ # by SCons itself. It was introduced to store dependency paths
+ # in .sconsign files relative to the target, but that ended up
+ # being significantly inefficient.
+ #
+ # We're continuing to support the method because some SConstruct
+ # files out there started using it when it was available, and
+ # we're all about backwards compatibility..
+
+ try:
+ memo_dict = self._memo['rel_path']
+ except KeyError:
+ memo_dict = {}
+ self._memo['rel_path'] = memo_dict
+ else:
+ try:
+ return memo_dict[other]
+ except KeyError:
+ pass
+
+ if self is other:
+ result = '.'
+
+ elif not other in self.path_elements:
+ try:
+ other_dir = other.get_dir()
+ except AttributeError:
+ result = str(other)
+ else:
+ if other_dir is None:
+ result = other.name
+ else:
+ dir_rel_path = self.rel_path(other_dir)
+ if dir_rel_path == '.':
+ result = other.name
+ else:
+ result = dir_rel_path + OS_SEP + other.name
+ else:
+ i = self.path_elements.index(other) + 1
+
+ path_elems = ['..'] * (len(self.path_elements) - i) \
+ + [n.name for n in other.path_elements[i:]]
+
+ result = OS_SEP.join(path_elems)
+
+ memo_dict[other] = result
+
+ return result
+
+ def get_env_scanner(self, env, kw={}):
+ import SCons.Defaults
+ return SCons.Defaults.DirEntryScanner
+
+ def get_target_scanner(self):
+ import SCons.Defaults
+ return SCons.Defaults.DirEntryScanner
+
+ def get_found_includes(self, env, scanner, path):
+ """Return this directory's implicit dependencies.
+
+ We don't bother caching the results because the scan typically
+ shouldn't be requested more than once (as opposed to scanning
+ .h file contents, which can be requested as many times as the
+ files is #included by other files).
+ """
+ if not scanner:
+ return []
+ # Clear cached info for this Dir. If we already visited this
+ # directory on our walk down the tree (because we didn't know at
+ # that point it was being used as the source for another Node)
+ # then we may have calculated build signature before realizing
+ # we had to scan the disk. Now that we have to, though, we need
+ # to invalidate the old calculated signature so that any node
+ # dependent on our directory structure gets one that includes
+ # info about everything on disk.
+ self.clear()
+ return scanner(self, env, path)
+
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def prepare(self):
+ pass
+
+ def build(self, **kw):
+ """A null "builder" for directories."""
+ global MkdirBuilder
+ if self.builder is not MkdirBuilder:
+ SCons.Node.Node.build(self, **kw)
+
+ #
+ #
+ #
+
+ def _create(self):
+ """Create this directory, silently and without worrying about
+ whether the builder is the default or not."""
+ listDirs = []
+ parent = self
+ while parent:
+ if parent.exists():
+ break
+ listDirs.append(parent)
+ p = parent.up()
+ if p is None:
+ # Don't use while: - else: for this condition because
+ # if so, then parent is None and has no .path attribute.
+ raise SCons.Errors.StopError(parent.path)
+ parent = p
+ listDirs.reverse()
+ for dirnode in listDirs:
+ try:
+ # Don't call dirnode.build(), call the base Node method
+ # directly because we definitely *must* create this
+ # directory. The dirnode.build() method will suppress
+ # the build if it's the default builder.
+ SCons.Node.Node.build(dirnode)
+ dirnode.get_executor().nullify()
+ # The build() action may or may not have actually
+ # created the directory, depending on whether the -n
+ # option was used or not. Delete the _exists and
+ # _rexists attributes so they can be reevaluated.
+ dirnode.clear()
+ except OSError:
+ pass
+
+ def multiple_side_effect_has_builder(self):
+ global MkdirBuilder
+ return self.builder is not MkdirBuilder and self.has_builder()
+
+ def alter_targets(self):
+ """Return any corresponding targets in a variant directory.
+ """
+ return self.fs.variant_dir_target_climb(self, self, [])
+
+ def scanner_key(self):
+ """A directory does not get scanned."""
+ return None
+
+ def get_text_contents(self):
+ """We already emit things in text, so just return the binary
+ version."""
+ return self.get_contents()
+
+ def get_contents(self):
+ """Return content signatures and names of all our children
+ separated by new-lines. Ensure that the nodes are sorted."""
+ contents = []
+ for node in sorted(self.children(), key=lambda t: t.name):
+ contents.append('%s %s\n' % (node.get_csig(), node.name))
+ return ''.join(contents)
+
+ def get_csig(self):
+ """Compute the content signature for Directory nodes. In
+ general, this is not needed and the content signature is not
+ stored in the DirNodeInfo. However, if get_contents on a Dir
+ node is called which has a child directory, the child
+ directory should return the hash of its contents."""
+ contents = self.get_contents()
+ return SCons.Util.MD5signature(contents)
+
+ def do_duplicate(self, src):
+ pass
+
+ changed_since_last_build = SCons.Node.Node.state_has_changed
+
+ def is_up_to_date(self):
+ """If any child is not up-to-date, then this directory isn't,
+ either."""
+ if self.builder is not MkdirBuilder and not self.exists():
+ return 0
+ up_to_date = SCons.Node.up_to_date
+ for kid in self.children():
+ if kid.get_state() > up_to_date:
+ return 0
+ return 1
+
+ def rdir(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.dir_on_disk(self.name)
+ if node and node.exists() and \
+ (isinstance(dir, Dir) or isinstance(dir, Entry)):
+ return node
+ return self
+
+ def sconsign(self):
+ """Return the .sconsign file info for this directory,
+ creating it first if necessary."""
+ if not self._sconsign:
+ import SCons.SConsign
+ self._sconsign = SCons.SConsign.ForDirectory(self)
+ return self._sconsign
+
+ def srcnode(self):
+ """Dir has a special need for srcnode()...if we
+ have a srcdir attribute set, then that *is* our srcnode."""
+ if self.srcdir:
+ return self.srcdir
+ return Base.srcnode(self)
+
+ def get_timestamp(self):
+ """Return the latest timestamp from among our children"""
+ stamp = 0
+ for kid in self.children():
+ if kid.get_timestamp() > stamp:
+ stamp = kid.get_timestamp()
+ return stamp
+
+ def entry_abspath(self, name):
+ return self.abspath + OS_SEP + name
+
+ def entry_labspath(self, name):
+ return self.labspath + '/' + name
+
+ def entry_path(self, name):
+ return self.path + OS_SEP + name
+
+ def entry_tpath(self, name):
+ return self.tpath + OS_SEP + name
+
+ def entry_exists_on_disk(self, name):
+ try:
+ d = self.on_disk_entries
+ except AttributeError:
+ d = {}
+ try:
+ entries = os.listdir(self.abspath)
+ except OSError:
+ pass
+ else:
+ for entry in map(_my_normcase, entries):
+ d[entry] = True
+ self.on_disk_entries = d
+ if sys.platform == 'win32' or sys.platform == 'cygwin':
+ name = _my_normcase(name)
+ result = d.get(name)
+ if result is None:
+ # Belt-and-suspenders for Windows: check directly for
+ # 8.3 file names that don't show up in os.listdir().
+ result = os.path.exists(self.abspath + OS_SEP + name)
+ d[name] = result
+ return result
+ else:
+ return name in d
+
+ memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
+
+ def srcdir_list(self):
+ try:
+ return self._memo['srcdir_list']
+ except KeyError:
+ pass
+
+ result = []
+
+ dirname = '.'
+ dir = self
+ while dir:
+ if dir.srcdir:
+ result.append(dir.srcdir.Dir(dirname))
+ dirname = dir.name + OS_SEP + dirname
+ dir = dir.up()
+
+ self._memo['srcdir_list'] = result
+
+ return result
+
+ def srcdir_duplicate(self, name):
+ for dir in self.srcdir_list():
+ if self.is_under(dir):
+ # We shouldn't source from something in the build path;
+ # variant_dir is probably under src_dir, in which case
+ # we are reflecting.
+ break
+ if dir.entry_exists_on_disk(name):
+ srcnode = dir.Entry(name).disambiguate()
+ if self.duplicate:
+ node = self.Entry(name).disambiguate()
+ node.do_duplicate(srcnode)
+ return node
+ else:
+ return srcnode
+ return None
+
+ def _srcdir_find_file_key(self, filename):
+ return filename
+
+ memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
+
+ def srcdir_find_file(self, filename):
+ try:
+ memo_dict = self._memo['srcdir_find_file']
+ except KeyError:
+ memo_dict = {}
+ self._memo['srcdir_find_file'] = memo_dict
+ else:
+ try:
+ return memo_dict[filename]
+ except KeyError:
+ pass
+
+ def func(node):
+ if (isinstance(node, File) or isinstance(node, Entry)) and \
+ (node.is_derived() or node.exists()):
+ return node
+ return None
+
+ norm_name = _my_normcase(filename)
+
+ for rdir in self.get_all_rdirs():
+ try: node = rdir.entries[norm_name]
+ except KeyError: node = rdir.file_on_disk(filename)
+ else: node = func(node)
+ if node:
+ result = (node, self)
+ memo_dict[filename] = result
+ return result
+
+ for srcdir in self.srcdir_list():
+ for rdir in srcdir.get_all_rdirs():
+ try: node = rdir.entries[norm_name]
+ except KeyError: node = rdir.file_on_disk(filename)
+ else: node = func(node)
+ if node:
+ result = (File(filename, self, self.fs), srcdir)
+ memo_dict[filename] = result
+ return result
+
+ result = (None, None)
+ memo_dict[filename] = result
+ return result
+
+ def dir_on_disk(self, name):
+ if self.entry_exists_on_disk(name):
+ try: return self.Dir(name)
+ except TypeError: pass
+ node = self.srcdir_duplicate(name)
+ if isinstance(node, File):
+ return None
+ return node
+
+ def file_on_disk(self, name):
+ if self.entry_exists_on_disk(name) or \
+ diskcheck_rcs(self, name) or \
+ diskcheck_sccs(self, name):
+ try: return self.File(name)
+ except TypeError: pass
+ node = self.srcdir_duplicate(name)
+ if isinstance(node, Dir):
+ return None
+ return node
+
+ def walk(self, func, arg):
+ """
+ Walk this directory tree by calling the specified function
+ for each directory in the tree.
+
+ This behaves like the os.path.walk() function, but for in-memory
+ Node.FS.Dir objects. The function takes the same arguments as
+ the functions passed to os.path.walk():
+
+ func(arg, dirname, fnames)
+
+ Except that "dirname" will actually be the directory *Node*,
+ not the string. The '.' and '..' entries are excluded from
+ fnames. The fnames list may be modified in-place to filter the
+ subdirectories visited or otherwise impose a specific order.
+ The "arg" argument is always passed to func() and may be used
+ in any way (or ignored, passing None is common).
+ """
+ entries = self.entries
+ names = list(entries.keys())
+ names.remove('.')
+ names.remove('..')
+ func(arg, self, names)
+ for dirname in [n for n in names if isinstance(entries[n], Dir)]:
+ entries[dirname].walk(func, arg)
+
+ def glob(self, pathname, ondisk=True, source=False, strings=False):
+ """
+ Returns a list of Nodes (or strings) matching a specified
+ pathname pattern.
+
+ Pathname patterns follow UNIX shell semantics: * matches
+ any-length strings of any characters, ? matches any character,
+ and [] can enclose lists or ranges of characters. Matches do
+ not span directory separators.
+
+ The matches take into account Repositories, returning local
+ Nodes if a corresponding entry exists in a Repository (either
+ an in-memory Node or something on disk).
+
+ By defafult, the glob() function matches entries that exist
+ on-disk, in addition to in-memory Nodes. Setting the "ondisk"
+ argument to False (or some other non-true value) causes the glob()
+ function to only match in-memory Nodes. The default behavior is
+ to return both the on-disk and in-memory Nodes.
+
+ The "source" argument, when true, specifies that corresponding
+ source Nodes must be returned if you're globbing in a build
+ directory (initialized with VariantDir()). The default behavior
+ is to return Nodes local to the VariantDir().
+
+ The "strings" argument, when true, returns the matches as strings,
+ not Nodes. The strings are path names relative to this directory.
+
+ The underlying algorithm is adapted from the glob.glob() function
+ in the Python library (but heavily modified), and uses fnmatch()
+ under the covers.
+ """
+ dirname, basename = os.path.split(pathname)
+ if not dirname:
+ return sorted(self._glob1(basename, ondisk, source, strings),
+ key=lambda t: str(t))
+ if has_glob_magic(dirname):
+ list = self.glob(dirname, ondisk, source, strings=False)
+ else:
+ list = [self.Dir(dirname, create=True)]
+ result = []
+ for dir in list:
+ r = dir._glob1(basename, ondisk, source, strings)
+ if strings:
+ r = [os.path.join(str(dir), x) for x in r]
+ result.extend(r)
+ return sorted(result, key=lambda a: str(a))
+
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ """
+ Globs for and returns a list of entry names matching a single
+ pattern in this directory.
+
+ This searches any repositories and source directories for
+ corresponding entries and returns a Node (or string) relative
+ to the current directory if an entry is found anywhere.
+
+ TODO: handle pattern with no wildcard
+ """
+ search_dir_list = self.get_all_rdirs()
+ for srcdir in self.srcdir_list():
+ search_dir_list.extend(srcdir.get_all_rdirs())
+
+ selfEntry = self.Entry
+ names = []
+ for dir in search_dir_list:
+ # We use the .name attribute from the Node because the keys of
+ # the dir.entries dictionary are normalized (that is, all upper
+ # case) on case-insensitive systems like Windows.
+ node_names = [ v.name for k, v in dir.entries.items()
+ if k not in ('.', '..') ]
+ names.extend(node_names)
+ if not strings:
+ # Make sure the working directory (self) actually has
+ # entries for all Nodes in repositories or variant dirs.
+ for name in node_names: selfEntry(name)
+ if ondisk:
+ try:
+ disk_names = os.listdir(dir.abspath)
+ except os.error:
+ continue
+ names.extend(disk_names)
+ if not strings:
+ # We're going to return corresponding Nodes in
+ # the local directory, so we need to make sure
+ # those Nodes exist. We only want to create
+ # Nodes for the entries that will match the
+ # specified pattern, though, which means we
+ # need to filter the list here, even though
+ # the overall list will also be filtered later,
+ # after we exit this loop.
+ if pattern[0] != '.':
+ #disk_names = [ d for d in disk_names if d[0] != '.' ]
+ disk_names = [x for x in disk_names if x[0] != '.']
+ disk_names = fnmatch.filter(disk_names, pattern)
+ dirEntry = dir.Entry
+ for name in disk_names:
+ # Add './' before disk filename so that '#' at
+ # beginning of filename isn't interpreted.
+ name = './' + name
+ node = dirEntry(name).disambiguate()
+ n = selfEntry(name)
+ if n.__class__ != node.__class__:
+ n.__class__ = node.__class__
+ n._morph()
+
+ names = set(names)
+ if pattern[0] != '.':
+ #names = [ n for n in names if n[0] != '.' ]
+ names = [x for x in names if x[0] != '.']
+ names = fnmatch.filter(names, pattern)
+
+ if strings:
+ return names
+
+ #return [ self.entries[_my_normcase(n)] for n in names ]
+ return [self.entries[_my_normcase(n)] for n in names]
+
+class RootDir(Dir):
+ """A class for the root directory of a file system.
+
+ This is the same as a Dir class, except that the path separator
+ ('/' or '\\') is actually part of the name, so we don't need to
+ add a separator when creating the path names of entries within
+ this directory.
+ """
+ def __init__(self, drive, fs):
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
+ # We're going to be our own parent directory (".." entry and .dir
+ # attribute) so we have to set up some values so Base.__init__()
+ # won't gag won't it calls some of our methods.
+ self.abspath = ''
+ self.labspath = ''
+ self.path = ''
+ self.tpath = ''
+ self.path_elements = []
+ self.duplicate = 0
+ self.root = self
+
+ # Handle all the types of drives:
+ if drive == '':
+ # No drive, regular UNIX root or Windows default drive.
+ name = OS_SEP
+ dirname = OS_SEP
+ elif drive == '//':
+ # UNC path
+ name = UNC_PREFIX
+ dirname = UNC_PREFIX
+ else:
+ # Windows drive letter
+ name = drive
+ dirname = drive + OS_SEP
+
+ Base.__init__(self, name, self, fs)
+
+ # Now set our paths to what we really want them to be. The
+ # name should already contain any necessary separators, such
+ # as the initial drive letter (the name) plus the directory
+ # separator, except for the "lookup abspath," which does not
+ # have the drive letter.
+ self.abspath = dirname
+ self.labspath = ''
+ self.path = dirname
+ self.tpath = dirname
+ self._morph()
+
+ # Must be reset after Dir._morph() is invoked...
+ self.dirname = dirname
+
+ self._lookupDict = {}
+
+ self._lookupDict[''] = self
+ self._lookupDict['/'] = self
+
+ # The // entry is necessary because os.path.normpath()
+ # preserves double slashes at the beginning of a path on Posix
+ # platforms.
+ if not has_unc:
+ self._lookupDict['//'] = self
+
+ def must_be_same(self, klass):
+ if klass is Dir:
+ return
+ Base.must_be_same(self, klass)
+
+ def _lookup_abs(self, p, klass, create=1):
+ """
+ Fast (?) lookup of a *normalized* absolute path.
+
+ This method is intended for use by internal lookups with
+ already-normalized path data. For general-purpose lookups,
+ use the FS.Entry(), FS.Dir() or FS.File() methods.
+
+ The caller is responsible for making sure we're passed a
+ normalized absolute path; we merely let Python's dictionary look
+ up and return the One True Node.FS object for the path.
+
+ If a Node for the specified "p" doesn't already exist, and
+ "create" is specified, the Node may be created after recursive
+ invocation to find or create the parent directory or directories.
+ """
+ k = _my_normcase(p)
+ try:
+ result = self._lookupDict[k]
+ except KeyError:
+ if not create:
+ msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
+ raise SCons.Errors.UserError(msg)
+ # There is no Node for this path name, and we're allowed
+ # to create it.
+ # (note: would like to use p.rsplit('/',1) here but
+ # that's not in python 2.3)
+ # e.g.: dir_name, file_name = p.rsplit('/',1)
+ last_slash = p.rindex('/')
+ if (last_slash >= 0):
+ dir_name = p[:last_slash]
+ file_name = p[last_slash+1:]
+ else:
+ dir_name = p # shouldn't happen, just in case
+ file_name = ''
+
+ dir_node = self._lookup_abs(dir_name, Dir)
+ result = klass(file_name, dir_node, self.fs)
+
+ # Double-check on disk (as configured) that the Node we
+ # created matches whatever is out there in the real world.
+ result.diskcheck_match()
+
+ self._lookupDict[k] = result
+ dir_node.entries[_my_normcase(file_name)] = result
+ dir_node.implicit = None
+ else:
+ # There is already a Node for this path name. Allow it to
+ # complain if we were looking for an inappropriate type.
+ result.must_be_same(klass)
+ return result
+
+ def __str__(self):
+ return self.abspath
+
+ def entry_abspath(self, name):
+ return self.abspath + name
+
+ def entry_labspath(self, name):
+ return '/' + name
+
+ def entry_path(self, name):
+ return self.path + name
+
+ def entry_tpath(self, name):
+ return self.tpath + name
+
+ def is_under(self, dir):
+ if self is dir:
+ return 1
+ else:
+ return 0
+
+ def up(self):
+ return None
+
+ def get_dir(self):
+ return None
+
+ def src_builder(self):
+ return _null
+
+class FileNodeInfo(SCons.Node.NodeInfoBase):
+ current_version_id = 1
+
+ field_list = ['csig', 'timestamp', 'size']
+
+ # This should get reset by the FS initialization.
+ fs = None
+
+ def str_to_node(self, s):
+ top = self.fs.Top
+ root = top.root
+ if do_splitdrive:
+ drive, s = _my_splitdrive(s)
+ if drive:
+ root = self.fs.get_root(drive)
+ if not os.path.isabs(s):
+ s = top.labspath + '/' + s
+ return root._lookup_abs(s, Entry)
+
+class FileBuildInfo(SCons.Node.BuildInfoBase):
+ current_version_id = 1
+
+ def convert_to_sconsign(self):
+ """
+ Converts this FileBuildInfo object for writing to a .sconsign file
+
+ This replaces each Node in our various dependency lists with its
+ usual string representation: relative to the top-level SConstruct
+ directory, or an absolute path if it's outside.
+ """
+ if os_sep_is_slash:
+ node_to_str = str
+ else:
+ def node_to_str(n):
+ try:
+ s = n.path
+ except AttributeError:
+ s = str(n)
+ else:
+ s = s.replace(OS_SEP, '/')
+ return s
+ for attr in ['bsources', 'bdepends', 'bimplicit']:
+ try:
+ val = getattr(self, attr)
+ except AttributeError:
+ pass
+ else:
+ setattr(self, attr, list(map(node_to_str, val)))
+ def convert_from_sconsign(self, dir, name):
+ """
+ Converts a newly-read FileBuildInfo object for in-SCons use
+
+ For normal up-to-date checking, we don't have any conversion to
+ perform--but we're leaving this method here to make that clear.
+ """
+ pass
+ def prepare_dependencies(self):
+ """
+ Prepares a FileBuildInfo object for explaining what changed
+
+ The bsources, bdepends and bimplicit lists have all been
+ stored on disk as paths relative to the top-level SConstruct
+ directory. Convert the strings to actual Nodes (for use by the
+ --debug=explain code and --implicit-cache).
+ """
+ attrs = [
+ ('bsources', 'bsourcesigs'),
+ ('bdepends', 'bdependsigs'),
+ ('bimplicit', 'bimplicitsigs'),
+ ]
+ for (nattr, sattr) in attrs:
+ try:
+ strings = getattr(self, nattr)
+ nodeinfos = getattr(self, sattr)
+ except AttributeError:
+ continue
+ nodes = []
+ for s, ni in zip(strings, nodeinfos):
+ if not isinstance(s, SCons.Node.Node):
+ s = ni.str_to_node(s)
+ nodes.append(s)
+ setattr(self, nattr, nodes)
+ def format(self, names=0):
+ result = []
+ bkids = self.bsources + self.bdepends + self.bimplicit
+ bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
+ for bkid, bkidsig in zip(bkids, bkidsigs):
+ result.append(str(bkid) + ': ' +
+ ' '.join(bkidsig.format(names=names)))
+ result.append('%s [%s]' % (self.bactsig, self.bact))
+ return '\n'.join(result)
+
+class File(Base):
+ """A class for files in a file system.
+ """
+
+ memoizer_counters = []
+
+ NodeInfo = FileNodeInfo
+ BuildInfo = FileBuildInfo
+
+ md5_chunksize = 64
+
+ def diskcheck_match(self):
+ diskcheck_match(self, self.isdir,
+ "Directory %s found where file expected.")
+
+ def __init__(self, name, directory, fs):
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File')
+ Base.__init__(self, name, directory, fs)
+ self._morph()
+
+ def Entry(self, name):
+ """Create an entry node named 'name' relative to
+ the directory of this file."""
+ return self.dir.Entry(name)
+
+ def Dir(self, name, create=True):
+ """Create a directory node named 'name' relative to
+ the directory of this file."""
+ return self.dir.Dir(name, create=create)
+
+ def Dirs(self, pathlist):
+ """Create a list of directories relative to the SConscript
+ directory of this file."""
+ return [self.Dir(p) for p in pathlist]
+
+ def File(self, name):
+ """Create a file node named 'name' relative to
+ the directory of this file."""
+ return self.dir.File(name)
+
+ #def generate_build_dict(self):
+ # """Return an appropriate dictionary of values for building
+ # this File."""
+ # return {'Dir' : self.Dir,
+ # 'File' : self.File,
+ # 'RDirs' : self.RDirs}
+
+ def _morph(self):
+ """Turn a file system node into a File object."""
+ self.scanner_paths = {}
+ if not hasattr(self, '_local'):
+ self._local = 0
+ if not hasattr(self, 'released_target_info'):
+ self.released_target_info = False
+
+ # If there was already a Builder set on this entry, then
+ # we need to make sure we call the target-decider function,
+ # not the source-decider. Reaching in and doing this by hand
+ # is a little bogus. We'd prefer to handle this by adding
+ # an Entry.builder_set() method that disambiguates like the
+ # other methods, but that starts running into problems with the
+ # fragile way we initialize Dir Nodes with their Mkdir builders,
+ # yet still allow them to be overridden by the user. Since it's
+ # not clear right now how to fix that, stick with what works
+ # until it becomes clear...
+ if self.has_builder():
+ self.changed_since_last_build = self.decide_target
+
+ def scanner_key(self):
+ return self.get_suffix()
+
+ def get_contents(self):
+ if not self.rexists():
+ return ''
+ fname = self.rfile().abspath
+ try:
+ contents = open(fname, "rb").read()
+ except EnvironmentError, e:
+ if not e.filename:
+ e.filename = fname
+ raise
+ return contents
+
+ # This attempts to figure out what the encoding of the text is
+ # based upon the BOM bytes, and then decodes the contents so that
+ # it's a valid python string.
+ def get_text_contents(self):
+ contents = self.get_contents()
+ # The behavior of various decode() methods and functions
+ # w.r.t. the initial BOM bytes is different for different
+ # encodings and/or Python versions. ('utf-8' does not strip
+ # them, but has a 'utf-8-sig' which does; 'utf-16' seems to
+ # strip them; etc.) Just sidestep all the complication by
+ # explicitly stripping the BOM before we decode().
+ if contents.startswith(codecs.BOM_UTF8):
+ return contents[len(codecs.BOM_UTF8):].decode('utf-8')
+ if contents.startswith(codecs.BOM_UTF16_LE):
+ return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
+ if contents.startswith(codecs.BOM_UTF16_BE):
+ return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
+ return contents
+
+ def get_content_hash(self):
+ """
+ Compute and return the MD5 hash for this file.
+ """
+ if not self.rexists():
+ return SCons.Util.MD5signature('')
+ fname = self.rfile().abspath
+ try:
+ cs = SCons.Util.MD5filesignature(fname,
+ chunksize=SCons.Node.FS.File.md5_chunksize*1024)
+ except EnvironmentError, e:
+ if not e.filename:
+ e.filename = fname
+ raise
+ return cs
+
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
+
+ def get_size(self):
+ try:
+ return self._memo['get_size']
+ except KeyError:
+ pass
+
+ if self.rexists():
+ size = self.rfile().getsize()
+ else:
+ size = 0
+
+ self._memo['get_size'] = size
+
+ return size
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
+
+ def get_timestamp(self):
+ try:
+ return self._memo['get_timestamp']
+ except KeyError:
+ pass
+
+ if self.rexists():
+ timestamp = self.rfile().getmtime()
+ else:
+ timestamp = 0
+
+ self._memo['get_timestamp'] = timestamp
+
+ return timestamp
+
+ def store_info(self):
+ # Merge our build information into the already-stored entry.
+ # This accomodates "chained builds" where a file that's a target
+ # in one build (SConstruct file) is a source in a different build.
+ # See test/chained-build.py for the use case.
+ if do_store_info:
+ self.dir.sconsign().store_info(self.name, self)
+
+ convert_copy_attrs = [
+ 'bsources',
+ 'bimplicit',
+ 'bdepends',
+ 'bact',
+ 'bactsig',
+ 'ninfo',
+ ]
+
+
+ convert_sig_attrs = [
+ 'bsourcesigs',
+ 'bimplicitsigs',
+ 'bdependsigs',
+ ]
+
+ def convert_old_entry(self, old_entry):
+ # Convert a .sconsign entry from before the Big Signature
+ # Refactoring, doing what we can to convert its information
+ # to the new .sconsign entry format.
+ #
+ # The old format looked essentially like this:
+ #
+ # BuildInfo
+ # .ninfo (NodeInfo)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bsources
+ # .bsourcesigs ("signature" list)
+ # .bdepends
+ # .bdependsigs ("signature" list)
+ # .bimplicit
+ # .bimplicitsigs ("signature" list)
+ # .bact
+ # .bactsig
+ #
+ # The new format looks like this:
+ #
+ # .ninfo (NodeInfo)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .binfo (BuildInfo)
+ # .bsources
+ # .bsourcesigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bdepends
+ # .bdependsigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bimplicit
+ # .bimplicitsigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bact
+ # .bactsig
+ #
+ # The basic idea of the new structure is that a NodeInfo always
+ # holds all available information about the state of a given Node
+ # at a certain point in time. The various .b*sigs lists can just
+ # be a list of pointers to the .ninfo attributes of the different
+ # dependent nodes, without any copying of information until it's
+ # time to pickle it for writing out to a .sconsign file.
+ #
+ # The complicating issue is that the *old* format only stored one
+ # "signature" per dependency, based on however the *last* build
+ # was configured. We don't know from just looking at it whether
+ # it was a build signature, a content signature, or a timestamp
+ # "signature". Since we no longer use build signatures, the
+ # best we can do is look at the length and if it's thirty two,
+ # assume that it was (or might have been) a content signature.
+ # If it was actually a build signature, then it will cause a
+ # rebuild anyway when it doesn't match the new content signature,
+ # but that's probably the best we can do.
+ import SCons.SConsign
+ new_entry = SCons.SConsign.SConsignEntry()
+ new_entry.binfo = self.new_binfo()
+ binfo = new_entry.binfo
+ for attr in self.convert_copy_attrs:
+ try:
+ value = getattr(old_entry, attr)
+ except AttributeError:
+ continue
+ setattr(binfo, attr, value)
+ delattr(old_entry, attr)
+ for attr in self.convert_sig_attrs:
+ try:
+ sig_list = getattr(old_entry, attr)
+ except AttributeError:
+ continue
+ value = []
+ for sig in sig_list:
+ ninfo = self.new_ninfo()
+ if len(sig) == 32:
+ ninfo.csig = sig
+ else:
+ ninfo.timestamp = sig
+ value.append(ninfo)
+ setattr(binfo, attr, value)
+ delattr(old_entry, attr)
+ return new_entry
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
+
+ def get_stored_info(self):
+ try:
+ return self._memo['get_stored_info']
+ except KeyError:
+ pass
+
+ try:
+ sconsign_entry = self.dir.sconsign().get_entry(self.name)
+ except (KeyError, EnvironmentError):
+ import SCons.SConsign
+ sconsign_entry = SCons.SConsign.SConsignEntry()
+ sconsign_entry.binfo = self.new_binfo()
+ sconsign_entry.ninfo = self.new_ninfo()
+ else:
+ if isinstance(sconsign_entry, FileBuildInfo):
+ # This is a .sconsign file from before the Big Signature
+ # Refactoring; convert it as best we can.
+ sconsign_entry = self.convert_old_entry(sconsign_entry)
+ try:
+ delattr(sconsign_entry.ninfo, 'bsig')
+ except AttributeError:
+ pass
+
+ self._memo['get_stored_info'] = sconsign_entry
+
+ return sconsign_entry
+
+ def get_stored_implicit(self):
+ binfo = self.get_stored_info().binfo
+ binfo.prepare_dependencies()
+ try: return binfo.bimplicit
+ except AttributeError: return None
+
+ def rel_path(self, other):
+ return self.dir.rel_path(other)
+
+ def _get_found_includes_key(self, env, scanner, path):
+ return (id(env), id(scanner), path)
+
+ memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
+
+ def get_found_includes(self, env, scanner, path):
+ """Return the included implicit dependencies in this file.
+ Cache results so we only scan the file once per path
+ regardless of how many times this information is requested.
+ """
+ memo_key = (id(env), id(scanner), path)
+ try:
+ memo_dict = self._memo['get_found_includes']
+ except KeyError:
+ memo_dict = {}
+ self._memo['get_found_includes'] = memo_dict
+ else:
+ try:
+ return memo_dict[memo_key]
+ except KeyError:
+ pass
+
+ if scanner:
+ # result = [n.disambiguate() for n in scanner(self, env, path)]
+ result = scanner(self, env, path)
+ result = [N.disambiguate() for N in result]
+ else:
+ result = []
+
+ memo_dict[memo_key] = result
+
+ return result
+
+ def _createDir(self):
+ # ensure that the directories for this node are
+ # created.
+ self.dir._create()
+
+ def push_to_cache(self):
+ """Try to push the node into a cache
+ """
+ # This should get called before the Nodes' .built() method is
+ # called, which would clear the build signature if the file has
+ # a source scanner.
+ #
+ # We have to clear the local memoized values *before* we push
+ # the node to cache so that the memoization of the self.exists()
+ # return value doesn't interfere.
+ if self.nocache:
+ return
+ self.clear_memoized_values()
+ if self.exists():
+ self.get_build_env().get_CacheDir().push(self)
+
+ def retrieve_from_cache(self):
+ """Try to retrieve the node's content from a cache
+
+ This method is called from multiple threads in a parallel build,
+ so only do thread safe stuff here. Do thread unsafe stuff in
+ built().
+
+ Returns true if the node was successfully retrieved.
+ """
+ if self.nocache:
+ return None
+ if not self.is_derived():
+ return None
+ return self.get_build_env().get_CacheDir().retrieve(self)
+
+ def visited(self):
+ if self.exists() and self.executor is not None:
+ self.get_build_env().get_CacheDir().push_if_forced(self)
+
+ ninfo = self.get_ninfo()
+
+ csig = self.get_max_drift_csig()
+ if csig:
+ ninfo.csig = csig
+
+ ninfo.timestamp = self.get_timestamp()
+ ninfo.size = self.get_size()
+
+ if not self.has_builder():
+ # This is a source file, but it might have been a target file
+ # in another build that included more of the DAG. Copy
+ # any build information that's stored in the .sconsign file
+ # into our binfo object so it doesn't get lost.
+ old = self.get_stored_info()
+ self.get_binfo().__dict__.update(old.binfo.__dict__)
+
+ self.store_info()
+
+ def release_target_info(self):
+ """Called just after this node has been marked
+ up-to-date or was built completely.
+
+ This is where we try to release as many target node infos
+ as possible for clean builds and update runs, in order
+ to minimize the overall memory consumption.
+
+ We'd like to remove a lot more attributes like self.sources
+ and self.sources_set, but they might get used
+ in a next build step. For example, during configuration
+ the source files for a built *.o file are used to figure out
+ which linker to use for the resulting Program (gcc vs. g++)!
+ That's why we check for the 'keep_targetinfo' attribute,
+ config Nodes and the Interactive mode just don't allow
+ an early release of most variables.
+
+ In the same manner, we can't simply remove the self.attributes
+ here. The smart linking relies on the shared flag, and some
+ parts of the java Tool use it to transport information
+ about nodes...
+
+ @see: built() and Node.release_target_info()
+ """
+ if (self.released_target_info or SCons.Node.interactive):
+ return
+
+ if not hasattr(self.attributes, 'keep_targetinfo'):
+ # Cache some required values, before releasing
+ # stuff like env, executor and builder...
+ self.changed(allowcache=True)
+ self.get_contents_sig()
+ self.get_build_env()
+ # Now purge unneeded stuff to free memory...
+ self.executor = None
+ self._memo.pop('rfile', None)
+ self.prerequisites = None
+ # Cleanup lists, but only if they're empty
+ if not len(self.ignore_set):
+ self.ignore_set = None
+ if not len(self.implicit_set):
+ self.implicit_set = None
+ if not len(self.depends_set):
+ self.depends_set = None
+ if not len(self.ignore):
+ self.ignore = None
+ if not len(self.depends):
+ self.depends = None
+ # Mark this node as done, we only have to release
+ # the memory once...
+ self.released_target_info = True
+
+ def find_src_builder(self):
+ if self.rexists():
+ return None
+ scb = self.dir.src_builder()
+ if scb is _null:
+ if diskcheck_sccs(self.dir, self.name):
+ scb = get_DefaultSCCSBuilder()
+ elif diskcheck_rcs(self.dir, self.name):
+ scb = get_DefaultRCSBuilder()
+ else:
+ scb = None
+ if scb is not None:
+ try:
+ b = self.builder
+ except AttributeError:
+ b = None
+ if b is None:
+ self.builder_set(scb)
+ return scb
+
+ def has_src_builder(self):
+ """Return whether this Node has a source builder or not.
+
+ If this Node doesn't have an explicit source code builder, this
+ is where we figure out, on the fly, if there's a transparent
+ source code builder for it.
+
+ Note that if we found a source builder, we also set the
+ self.builder attribute, so that all of the methods that actually
+ *build* this file don't have to do anything different.
+ """
+ try:
+ scb = self.sbuilder
+ except AttributeError:
+ scb = self.sbuilder = self.find_src_builder()
+ return scb is not None
+
+ def alter_targets(self):
+ """Return any corresponding targets in a variant directory.
+ """
+ if self.is_derived():
+ return [], None
+ return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
+
+ def _rmv_existing(self):
+ self.clear_memoized_values()
+ if print_duplicate:
+ print "dup: removing existing target %s"%self
+ e = Unlink(self, [], None)
+ if isinstance(e, SCons.Errors.BuildError):
+ raise e
+
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def make_ready(self):
+ self.has_src_builder()
+ self.get_binfo()
+
+ def prepare(self):
+ """Prepare for this file to be created."""
+ SCons.Node.Node.prepare(self)
+
+ if self.get_state() != SCons.Node.up_to_date:
+ if self.exists():
+ if self.is_derived() and not self.precious:
+ self._rmv_existing()
+ else:
+ try:
+ self._createDir()
+ except SCons.Errors.StopError, drive:
+ desc = "No drive `%s' for target `%s'." % (drive, self)
+ raise SCons.Errors.StopError(desc)
+
+ #
+ #
+ #
+
+ def remove(self):
+ """Remove this file."""
+ if self.exists() or self.islink():
+ self.fs.unlink(self.path)
+ return 1
+ return None
+
+ def do_duplicate(self, src):
+ self._createDir()
+ if print_duplicate:
+ print "dup: relinking variant '%s' from '%s'"%(self, src)
+ Unlink(self, None, None)
+ e = Link(self, src, None)
+ if isinstance(e, SCons.Errors.BuildError):
+ desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
+ raise SCons.Errors.StopError(desc)
+ self.linked = 1
+ # The Link() action may or may not have actually
+ # created the file, depending on whether the -n
+ # option was used or not. Delete the _exists and
+ # _rexists attributes so they can be reevaluated.
+ self.clear()
+
+ memoizer_counters.append(SCons.Memoize.CountValue('exists'))
+
+ def exists(self):
+ try:
+ return self._memo['exists']
+ except KeyError:
+ pass
+ # Duplicate from source path if we are set up to do this.
+ if self.duplicate and not self.is_derived() and not self.linked:
+ src = self.srcnode()
+ if src is not self:
+ # At this point, src is meant to be copied in a variant directory.
+ src = src.rfile()
+ if src.abspath != self.abspath:
+ if src.exists():
+ self.do_duplicate(src)
+ # Can't return 1 here because the duplication might
+ # not actually occur if the -n option is being used.
+ else:
+ # 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
+ if Base.exists(self) or self.islink():
+ self.fs.unlink(self.path)
+ # Return None explicitly because the Base.exists() call
+ # above will have cached its value if the file existed.
+ self._memo['exists'] = None
+ return None
+ result = Base.exists(self)
+ self._memo['exists'] = result
+ return result
+
+ #
+ # SIGNATURE SUBSYSTEM
+ #
+
+ def get_max_drift_csig(self):
+ """
+ Returns the content signature currently stored for this node
+ if it's been unmodified longer than the max_drift value, or the
+ max_drift value is 0. Returns None otherwise.
+ """
+ old = self.get_stored_info()
+ mtime = self.get_timestamp()
+
+ max_drift = self.fs.max_drift
+ if max_drift > 0:
+ if (time.time() - mtime) > max_drift:
+ try:
+ n = old.ninfo
+ if n.timestamp and n.csig and n.timestamp == mtime:
+ return n.csig
+ except AttributeError:
+ pass
+ elif max_drift == 0:
+ try:
+ return old.ninfo.csig
+ except AttributeError:
+ pass
+
+ return None
+
+ def get_csig(self):
+ """
+ Generate a node's content signature, the digested signature
+ of its content.
+
+ node - the node
+ cache - alternate node to use for the signature cache
+ returns - the content signature
+ """
+ ninfo = self.get_ninfo()
+ try:
+ return ninfo.csig
+ except AttributeError:
+ pass
+
+ csig = self.get_max_drift_csig()
+ if csig is None:
+
+ try:
+ if self.get_size() < SCons.Node.FS.File.md5_chunksize:
+ contents = self.get_contents()
+ else:
+ csig = self.get_content_hash()
+ except IOError:
+ # This can happen if there's actually a directory on-disk,
+ # which can be the case if they've disabled disk checks,
+ # or if an action with a File target actually happens to
+ # create a same-named directory by mistake.
+ csig = ''
+ else:
+ if not csig:
+ csig = SCons.Util.MD5signature(contents)
+
+ ninfo.csig = csig
+
+ return csig
+
+ #
+ # DECISION SUBSYSTEM
+ #
+
+ def builder_set(self, builder):
+ SCons.Node.Node.builder_set(self, builder)
+ self.changed_since_last_build = self.decide_target
+
+ def built(self):
+ """Called just after this File node is successfully built.
+
+ Just like for 'release_target_info' we try to release
+ some more target node attributes in order to minimize the
+ overall memory consumption.
+
+ @see: release_target_info
+ """
+
+ SCons.Node.Node.built(self)
+
+ if (not SCons.Node.interactive and
+ not hasattr(self.attributes, 'keep_targetinfo')):
+ # Ensure that the build infos get computed and cached...
+ self.store_info()
+ # ... then release some more variables.
+ self._specific_sources = False
+ self.labspath = None
+ self._save_str()
+ self.cwd = None
+
+ self.scanner_paths = None
+
+ def changed(self, node=None, allowcache=False):
+ """
+ Returns if the node is up-to-date with respect to the BuildInfo
+ stored last time it was built.
+
+ For File nodes this is basically a wrapper around Node.changed(),
+ but we allow the return value to get cached after the reference
+ to the Executor got released in release_target_info().
+
+ @see: Node.changed()
+ """
+ if node is None:
+ try:
+ return self._memo['changed']
+ except KeyError:
+ pass
+
+ has_changed = SCons.Node.Node.changed(self, node)
+ if allowcache:
+ self._memo['changed'] = has_changed
+ return has_changed
+
+ def changed_content(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+ def changed_state(self, target, prev_ni):
+ 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):
+ try:
+ self.get_ninfo().csig = prev_ni.csig
+ except AttributeError:
+ pass
+ return False
+ return self.changed_content(target, prev_ni)
+
+ def changed_timestamp_newer(self, target, prev_ni):
+ try:
+ return self.get_timestamp() > target.get_timestamp()
+ except AttributeError:
+ return 1
+
+ def changed_timestamp_match(self, target, prev_ni):
+ try:
+ return self.get_timestamp() != prev_ni.timestamp
+ except AttributeError:
+ return 1
+
+ def decide_source(self, target, prev_ni):
+ return target.get_build_env().decide_source(self, target, prev_ni)
+
+ def decide_target(self, target, prev_ni):
+ return target.get_build_env().decide_target(self, target, prev_ni)
+
+ # Initialize this Node's decider function to decide_source() because
+ # every file is a source file until it has a Builder attached...
+ changed_since_last_build = decide_source
+
+ def is_up_to_date(self):
+ 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...
+ r = self.rfile()
+ if r != self:
+ # ...but there is one in a Repository...
+ if not self.changed(r):
+ if T: Trace(' changed(%s):' % r)
+ # ...and it's even up-to-date...
+ if self._local:
+ # ...and they'd like a local copy.
+ e = LocalCopy(self, r, None)
+ if isinstance(e, SCons.Errors.BuildError):
+ raise
+ self.store_info()
+ if T: Trace(' 1\n')
+ return 1
+ self.changed()
+ if T: Trace(' None\n')
+ return None
+ else:
+ r = self.changed()
+ if T: Trace(' self.exists(): %s\n' % r)
+ return not r
+
+ memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
+
+ def rfile(self):
+ try:
+ return self._memo['rfile']
+ except KeyError:
+ pass
+ 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)
+ if node and node.exists() and \
+ (isinstance(node, File) or isinstance(node, Entry) \
+ or not node.is_derived()):
+ result = node
+ # Copy over our local attributes to the repository
+ # Node so we identify shared object files in the
+ # repository and don't assume they're static.
+ #
+ # This isn't perfect; the attribute would ideally
+ # be attached to the object in the repository in
+ # case it was built statically in the repository
+ # and we changed it to shared locally, but that's
+ # rarely the case and would only occur if you
+ # intentionally used the same suffix for both
+ # shared and static objects anyway. So this
+ # should work well in practice.
+ result.attributes = self.attributes
+ break
+ self._memo['rfile'] = result
+ return result
+
+ def rstr(self):
+ return str(self.rfile())
+
+ def get_cachedir_csig(self):
+ """
+ Fetch a Node's content signature for purposes of computing
+ another Node's cachesig.
+
+ This is a wrapper around the normal get_csig() method that handles
+ the somewhat obscure case of using CacheDir with the -n option.
+ Any files that don't exist would normally be "built" by fetching
+ them from the cache, but the normal get_csig() method will try
+ to open up the local file, which doesn't exist because the -n
+ option meant we didn't actually pull the file from cachedir.
+ But since the file *does* actually exist in the cachedir, we
+ can use its contents for the csig.
+ """
+ try:
+ return self.cachedir_csig
+ except AttributeError:
+ pass
+
+ cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
+ if not self.exists() and cachefile and os.path.exists(cachefile):
+ self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
+ SCons.Node.FS.File.md5_chunksize * 1024)
+ else:
+ self.cachedir_csig = self.get_csig()
+ return self.cachedir_csig
+
+ def get_contents_sig(self):
+ """
+ A helper method for get_cachedir_bsig.
+
+ It computes and returns the signature for this
+ node's contents.
+ """
+
+ try:
+ return self.contentsig
+ except AttributeError:
+ pass
+
+ executor = self.get_executor()
+
+ result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
+ return result
+
+ def get_cachedir_bsig(self):
+ """
+ Return the signature for a cached file, including
+ its children.
+
+ It adds the path of the cached file to the cache signature,
+ because multiple targets built by the same action will all
+ have the same build signature, and we have to differentiate
+ them somehow.
+ """
+ try:
+ return self.cachesig
+ except AttributeError:
+ pass
+
+ # 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.path)
+ # Merge this all into a single signature
+ result = self.cachesig = SCons.Util.MD5collect(sigs)
+ return result
+
+default_fs = None
+
+def get_default_fs():
+ global default_fs
+ if not default_fs:
+ default_fs = FS()
+ return default_fs
+
+class FileFinder(object):
+ """
+ """
+ if SCons.Memoize.use_memoizer:
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+ memoizer_counters = []
+
+ def __init__(self):
+ self._memo = {}
+
+ def filedir_lookup(self, p, fd=None):
+ """
+ A helper method for find_file() that looks up a directory for
+ a file we're trying to find. This only creates the Dir Node if
+ it exists on-disk, since if the directory doesn't exist we know
+ we won't find any files in it... :-)
+
+ It would be more compact to just use this as a nested function
+ with a default keyword argument (see the commented-out version
+ below), but that doesn't work unless you have nested scopes,
+ so we define it here just so this work under Python 1.5.2.
+ """
+ if fd is None:
+ fd = self.default_filedir
+ dir, name = os.path.split(fd)
+ drive, d = _my_splitdrive(dir)
+ if not name and d[:1] in ('/', OS_SEP):
+ #return p.fs.get_root(drive).dir_on_disk(name)
+ return p.fs.get_root(drive)
+ if dir:
+ p = self.filedir_lookup(p, dir)
+ if not p:
+ return None
+ norm_name = _my_normcase(name)
+ try:
+ node = p.entries[norm_name]
+ except KeyError:
+ return p.dir_on_disk(name)
+ if isinstance(node, Dir):
+ return node
+ if isinstance(node, Entry):
+ node.must_be_same(Dir)
+ return node
+ return None
+
+ def _find_file_key(self, filename, paths, verbose=None):
+ return (filename, paths)
+
+ memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
+
+ def find_file(self, filename, paths, verbose=None):
+ """
+ find_file(str, [Dir()]) -> [nodes]
+
+ filename - a filename to find
+ paths - a list of directory path *nodes* to search in. Can be
+ represented as a list, a tuple, or a callable that is
+ called with no arguments and returns the list or tuple.
+
+ returns - the node created from the found file.
+
+ Find a node corresponding to either a derived file or a file
+ that exists already.
+
+ Only the first file found is returned, and none is returned
+ if no file is found.
+ """
+ memo_key = self._find_file_key(filename, paths)
+ try:
+ memo_dict = self._memo['find_file']
+ except KeyError:
+ memo_dict = {}
+ self._memo['find_file'] = memo_dict
+ else:
+ try:
+ return memo_dict[memo_key]
+ except KeyError:
+ pass
+
+ if verbose and not callable(verbose):
+ if not SCons.Util.is_String(verbose):
+ verbose = "find_file"
+ _verbose = u' %s: ' % verbose
+ verbose = lambda s: sys.stdout.write(_verbose + s)
+
+ filedir, filename = os.path.split(filename)
+ if filedir:
+ # More compact code that we can't use until we drop
+ # support for Python 1.5.2:
+ #
+ #def filedir_lookup(p, fd=filedir):
+ # """
+ # A helper function that looks up a directory for a file
+ # we're trying to find. This only creates the Dir Node
+ # if it exists on-disk, since if the directory doesn't
+ # exist we know we won't find any files in it... :-)
+ # """
+ # dir, name = os.path.split(fd)
+ # if dir:
+ # p = filedir_lookup(p, dir)
+ # if not p:
+ # return None
+ # norm_name = _my_normcase(name)
+ # try:
+ # node = p.entries[norm_name]
+ # except KeyError:
+ # return p.dir_on_disk(name)
+ # if isinstance(node, Dir):
+ # return node
+ # if isinstance(node, Entry):
+ # node.must_be_same(Dir)
+ # return node
+ # if isinstance(node, Dir) or isinstance(node, Entry):
+ # return node
+ # return None
+ #paths = [_f for _f in map(filedir_lookup, paths) if _f]
+
+ self.default_filedir = filedir
+ paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
+
+ result = None
+ for dir in paths:
+ if verbose:
+ verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
+ node, d = dir.srcdir_find_file(filename)
+ if node:
+ if verbose:
+ verbose("... FOUND '%s' in '%s'\n" % (filename, d))
+ result = node
+ break
+
+ memo_dict[memo_key] = result
+
+ return result
+
+find_file = FileFinder().find_file
+
+
+def invalidate_node_memos(targets):
+ """
+ Invalidate the memoized values of all Nodes (files or directories)
+ that are associated with the given entries. Has been added to
+ clear the cache of nodes affected by a direct execution of an
+ action (e.g. Delete/Copy/Chmod). Existing Node caches become
+ inconsistent if the action is run through Execute(). The argument
+ `targets` can be a single Node object or filename, or a sequence
+ of Nodes/filenames.
+ """
+ from traceback import extract_stack
+
+ # First check if the cache really needs to be flushed. Only
+ # actions run in the SConscript with Execute() seem to be
+ # affected. XXX The way to check if Execute() is in the stacktrace
+ # is a very dirty hack and should be replaced by a more sensible
+ # solution.
+ for f in extract_stack():
+ if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
+ break
+ else:
+ # Dont have to invalidate, so return
+ return
+
+ if not SCons.Util.is_List(targets):
+ targets = [targets]
+
+ for entry in targets:
+ # If the target is a Node object, clear the cache. If it is a
+ # filename, look up potentially existing Node object first.
+ try:
+ entry.clear_memoized_values()
+ except AttributeError:
+ # Not a Node object, try to look up Node by filename. XXX
+ # This creates Node objects even for those filenames which
+ # do not correspond to an existing Node object.
+ node = get_default_fs().Entry(entry)
+ if node:
+ node.clear_memoized_values()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/engine/SCons/Node/Python.py b/engine/SCons/Node/Python.py
new file mode 100644
index 0000000..bbf994a
--- /dev/null
+++ b/engine/SCons/Node/Python.py
@@ -0,0 +1,128 @@
+"""scons.Node.Python
+
+Python nodes.
+
+"""
+
+#
+# Copyright (c) 2001 - 2014 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/Node/Python.py 2014/09/27 12:51:43 garyo"
+
+import SCons.Node
+
+class ValueNodeInfo(SCons.Node.NodeInfoBase):
+ current_version_id = 1
+
+ field_list = ['csig']
+
+ def str_to_node(self, s):
+ return Value(s)
+
+class ValueBuildInfo(SCons.Node.BuildInfoBase):
+ current_version_id = 1
+
+class Value(SCons.Node.Node):
+ """A class for Python variables, typically passed on the command line
+ or generated by a script, but not from a file or some other source.
+ """
+
+ NodeInfo = ValueNodeInfo
+ BuildInfo = ValueBuildInfo
+
+ def __init__(self, value, built_value=None):
+ SCons.Node.Node.__init__(self)
+ self.value = value
+ if built_value is not None:
+ self.built_value = built_value
+
+ def str_for_display(self):
+ return repr(self.value)
+
+ def __str__(self):
+ return str(self.value)
+
+ def make_ready(self):
+ self.get_csig()
+
+ def build(self, **kw):
+ if not hasattr(self, 'built_value'):
+ SCons.Node.Node.build(self, **kw)
+
+ is_up_to_date = SCons.Node.Node.children_are_up_to_date
+
+ def is_under(self, dir):
+ # Make Value nodes get built regardless of
+ # what directory scons was run from. Value nodes
+ # are outside the filesystem:
+ return 1
+
+ def write(self, built_value):
+ """Set the value of the node."""
+ self.built_value = built_value
+
+ def read(self):
+ """Return the value. If necessary, the value is built."""
+ self.build()
+ if not hasattr(self, 'built_value'):
+ self.built_value = self.value
+ return self.built_value
+
+ def get_text_contents(self):
+ """By the assumption that the node.built_value is a
+ deterministic product of the sources, the contents of a Value
+ are the concatenation of all the contents of its sources. As
+ the value need not be built when get_contents() is called, we
+ cannot use the actual node.built_value."""
+ ###TODO: something reasonable about universal newlines
+ contents = str(self.value)
+ for kid in self.children(None):
+ contents = contents + kid.get_contents()
+ return contents
+
+ get_contents = get_text_contents ###TODO should return 'bytes' value
+
+ def changed_since_last_build(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+ 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."""
+ try:
+ return self.ninfo.csig
+ except AttributeError:
+ pass
+ contents = self.get_contents()
+ self.get_ninfo().csig = contents
+ return contents
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/engine/SCons/Node/__init__.py b/engine/SCons/Node/__init__.py
new file mode 100644
index 0000000..01c06f6
--- /dev/null
+++ b/engine/SCons/Node/__init__.py
@@ -0,0 +1,1374 @@
+"""SCons.Node
+
+The Node package for the SCons software construction utility.
+
+This is, in many ways, the heart of SCons.
+
+A Node is where we encapsulate all of the dependency information about
+any thing that SCons can build, or about any thing which SCons can use
+to build some other thing. The canonical "thing," of course, is a file,
+but a Node can also represent something remote (like a web page) or
+something completely abstract (like an Alias).
+
+Each specific type of "thing" is specifically represented by a subclass
+of the Node base class: Node.FS.File for files, Node.Alias for aliases,
+etc. Dependency information is kept here in the base class, and
+information specific to files/aliases/etc. is in the subclass. The
+goal, if we've done this correctly, is that any type of "thing" should
+be able to depend on any other type of "thing."
+
+"""
+
+#
+# Copyright (c) 2001 - 2014 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "src/engine/SCons/Node/__init__.py 2014/09/27 12:51:43 garyo"
+
+import collections
+import copy
+from itertools import chain
+
+import SCons.Debug
+from SCons.Debug import logInstanceCreation
+import SCons.Executor
+import SCons.Memoize
+import SCons.Util
+
+from SCons.Debug import Trace
+
+def classname(obj):
+ return str(obj.__class__).split('.')[-1]
+
+# Set to false if we're doing a dry run. There's more than one of these
+# little treats
+do_store_info = True
+
+# Node states
+#
+# These are in "priority" order, so that the maximum value for any
+# child/dependency of a node represents the state of that node if
+# it has no builder of its own. The canonical example is a file
+# system directory, which is only up to date if all of its children
+# were up to date.
+no_state = 0
+pending = 1
+executing = 2
+up_to_date = 3
+executed = 4
+failed = 5
+
+StateString = {
+ 0 : "no_state",
+ 1 : "pending",
+ 2 : "executing",
+ 3 : "up_to_date",
+ 4 : "executed",
+ 5 : "failed",
+}
+
+# controls whether implicit dependencies are cached:
+implicit_cache = 0
+
+# controls whether implicit dep changes are ignored:
+implicit_deps_unchanged = 0
+
+# controls whether the cached implicit deps are ignored:
+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
+
+Annotate = do_nothing
+
+# Gets set to 'True' if we're running in interactive mode. Is
+# currently used to release parts of a target's info during
+# clean builds and update runs (see release_target_info).
+interactive = False
+
+# Classes for signature info for Nodes.
+
+class NodeInfoBase(object):
+ """
+ The generic base class for signature information for a Node.
+
+ Node subclasses should subclass NodeInfoBase to provide their own
+ logic for dealing with their own Node-specific signature information.
+ """
+ current_version_id = 1
+ def __init__(self, node=None):
+ # Create an object attribute from the class attribute so it ends up
+ # in the pickled data in the .sconsign file.
+ self._version_id = self.current_version_id
+ def update(self, node):
+ try:
+ field_list = self.field_list
+ except AttributeError:
+ return
+ for f in field_list:
+ try:
+ delattr(self, f)
+ except AttributeError:
+ pass
+ try:
+ func = getattr(node, 'get_' + f)
+ except AttributeError:
+ pass
+ else:
+ setattr(self, f, func())
+ def convert(self, node, val):
+ pass
+ def merge(self, other):
+ self.__dict__.update(other.__dict__)
+ def format(self, field_list=None, names=0):
+ if field_list is None:
+ try:
+ field_list = self.field_list
+ except AttributeError:
+ field_list = sorted(self.__dict__.keys())
+ fields = []
+ for field in field_list:
+ try:
+ f = getattr(self, field)
+ except AttributeError:
+ f = None
+ f = str(f)
+ if names:
+ f = field + ': ' + f
+ fields.append(f)
+ return fields
+
+class BuildInfoBase(object):
+ """
+ The generic base class for build information for a Node.
+
+ This is what gets stored in a .sconsign file for each target file.
+ It contains a NodeInfo instance for this node (signature information
+ that's specific to the type of Node) and direct attributes for the
+ generic build stuff we have to track: sources, explicit dependencies,
+ implicit dependencies, and action information.
+ """
+ current_version_id = 1
+ def __init__(self, node=None):
+ # Create an object attribute from the class attribute so it ends up
+ # in the pickled data in the .sconsign file.
+ self._version_id = self.current_version_id
+ self.bsourcesigs = []
+ self.bdependsigs = []
+ self.bimplicitsigs = []
+ self.bactsig = None
+ def merge(self, other):
+ self.__dict__.update(other.__dict__)
+
+class Node(object):
+ """The base Node class, for entities that we know how to
+ build, or use to build other Nodes.
+ """
+
+ if SCons.Memoize.use_memoizer:
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+ memoizer_counters = []
+
+ class Attrs(object):
+ pass
+
+ def __init__(self):
+ if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.Node')
+ # Note that we no longer explicitly initialize a self.builder
+ # attribute to None here. That's because the self.builder
+ # attribute may be created on-the-fly later by a subclass (the
+ # canonical example being a builder to fetch a file from a
+ # source code system like CVS or Subversion).
+
+ # Each list of children that we maintain is accompanied by a
+ # dictionary used to look up quickly whether a node is already
+ # present in the list. Empirical tests showed that it was
+ # fastest to maintain them as side-by-side Node attributes in
+ # this way, instead of wrapping up each list+dictionary pair in
+ # a class. (Of course, we could always still do that in the
+ # future if we had a good reason to...).
+ self.sources = [] # source files used to build node
+ self.sources_set = set()
+ self._specific_sources = False
+ self.depends = [] # explicit dependencies (from Depends)
+ self.depends_set = set()
+ self.ignore = [] # dependencies to ignore
+ self.ignore_set = set()
+ self.prerequisites = None
+ self.implicit = None # implicit (scanned) dependencies (None means not scanned yet)
+ self.waiting_parents = set()
+ self.waiting_s_e = set()
+ self.ref_count = 0
+ self.wkids = None # Kids yet to walk, when it's an array
+
+ self.env = None
+ self.state = no_state
+ self.precious = None
+ self.pseudo = False
+ self.noclean = 0
+ self.nocache = 0
+ self.cached = 0 # is this node pulled from cache?
+ self.always_build = None
+ self.includes = None
+ self.attributes = self.Attrs() # Generic place to stick information about the Node.
+ self.side_effect = 0 # true iff this node is a side effect
+ self.side_effects = [] # the side effects of building this target
+ self.linked = 0 # is this node linked to the variant directory?
+
+ self.clear_memoized_values()
+
+ # Let the interface in which the build engine is embedded
+ # annotate this Node with its own info (like a description of
+ # what line in what file created the node, for example).
+ Annotate(self)
+
+ def disambiguate(self, must_exist=None):
+ return self
+
+ def get_suffix(self):
+ return ''
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
+
+ def get_build_env(self):
+ """Fetch the appropriate Environment to build this node.
+ """
+ try:
+ return self._memo['get_build_env']
+ except KeyError:
+ pass
+ result = self.get_executor().get_build_env()
+ self._memo['get_build_env'] = result
+ return result
+
+ def get_build_scanner_path(self, scanner):
+ """Fetch the appropriate scanner path for this node."""
+ return self.get_executor().get_build_scanner_path(scanner)
+
+ def set_executor(self, executor):
+ """Set the action executor for this node."""
+ self.executor = executor
+
+ def get_executor(self, create=1):
+ """Fetch the action executor for this node. Create one if
+ there isn't already one, and requested to do so."""
+ try:
+ executor = self.executor
+ except AttributeError:
+ if not create:
+ raise
+ try:
+ act = self.builder.action
+ except AttributeError:
+ executor = SCons.Executor.Null(targets=[self])
+ else:
+ executor = SCons.Executor.Executor(act,
+ self.env or self.builder.env,
+ [self.builder.overrides],
+ [self],
+ self.sources)
+ self.executor = executor
+ return executor
+
+ def executor_cleanup(self):
+ """Let the executor clean up any cached information."""
+ try:
+ executor = self.get_executor(create=None)
+ except AttributeError:
+ pass
+ else:
+ if executor is not None:
+ executor.cleanup()
+
+ def reset_executor(self):
+ "Remove cached executor; forces recompute when needed."
+ try:
+ delattr(self, 'executor')
+ except AttributeError:
+ pass
+
+ def push_to_cache(self):
+ """Try to push a node into a cache
+ """
+ pass
+
+ def retrieve_from_cache(self):
+ """Try to retrieve the node's content from a cache
+
+ This method is called from multiple threads in a parallel build,
+ so only do thread safe stuff here. Do thread unsafe stuff in
+ built().
+
+ Returns true if the node was successfully retrieved.
+ """
+ return 0
+
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def make_ready(self):
+ """Get a Node ready for evaluation.
+
+ This is called before the Taskmaster decides if the Node is
+ up-to-date or not. Overriding this method allows for a Node
+ subclass to be disambiguated if necessary, or for an implicit
+ source builder to be attached.
+ """
+ pass
+
+ def prepare(self):
+ """Prepare for this Node to be built.
+
+ This is called after the Taskmaster has decided that the Node
+ is out-of-date and must be rebuilt, but before actually calling
+ the method to build the Node.
+
+ This default implementation checks that explicit or implicit
+ dependencies either exist or are derived, and initializes the
+ BuildInfo structure that will hold the information about how
+ this node is, uh, built.
+
+ (The existence of source files is checked separately by the
+ Executor, which aggregates checks for all of the targets built
+ by a specific action.)
+
+ Overriding this method allows for for a Node subclass to remove
+ the underlying file from the file system. Note that subclass
+ methods should call this base class method to get the child
+ check and the BuildInfo structure.
+ """
+ if self.depends is not None:
+ for d in self.depends:
+ if d.missing():
+ msg = "Explicit dependency `%s' not found, needed by target `%s'."
+ raise SCons.Errors.StopError(msg % (d, self))
+ if self.implicit is not None:
+ for i in self.implicit:
+ if i.missing():
+ msg = "Implicit dependency `%s' not found, needed by target `%s'."
+ raise SCons.Errors.StopError(msg % (i, self))
+ self.binfo = self.get_binfo()
+
+ def build(self, **kw):
+ """Actually build the node.
+
+ This is called by the Taskmaster after it's decided that the
+ Node is out-of-date and must be rebuilt, and after the prepare()
+ method has gotten everything, uh, prepared.
+
+ This method is called from multiple threads in a parallel build,
+ so only do thread safe stuff here. Do thread unsafe stuff
+ in built().
+
+ """
+ try:
+ self.get_executor()(self, **kw)
+ except SCons.Errors.BuildError, e:
+ e.node = self
+ raise
+
+ def built(self):
+ """Called just after this node is successfully built."""
+
+ # Clear the implicit dependency caches of any Nodes
+ # waiting for this Node to be built.
+ for parent in self.waiting_parents:
+ parent.implicit = None
+
+ self.clear()
+
+ if self.pseudo:
+ if self.exists():
+ raise SCons.Errors.UserError("Pseudo target " + str(self) + " must not exist")
+ else:
+ if not self.exists() and do_store_info:
+ SCons.Warnings.warn(SCons.Warnings.TargetNotBuiltWarning,
+ "Cannot find target " + str(self) + " after building")
+ self.ninfo.update(self)
+
+ def visited(self):
+ """Called just after this node has been visited (with or
+ without a build)."""
+ try:
+ binfo = self.binfo
+ except AttributeError:
+ # Apparently this node doesn't need build info, so
+ # don't bother calculating or storing it.
+ pass
+ else:
+ self.ninfo.update(self)
+ self.store_info()
+
+ def release_target_info(self):
+ """Called just after this node has been marked
+ up-to-date or was built completely.
+
+ This is where we try to release as many target node infos
+ as possible for clean builds and update runs, in order
+ to minimize the overall memory consumption.
+
+ By purging attributes that aren't needed any longer after
+ a Node (=File) got built, we don't have to care that much how
+ many KBytes a Node actually requires...as long as we free
+ the memory shortly afterwards.
+
+ @see: built() and File.release_target_info()
+ """
+ pass
+
+ #
+ #
+ #
+
+ def add_to_waiting_s_e(self, node):
+ self.waiting_s_e.add(node)
+
+ def add_to_waiting_parents(self, node):
+ """
+ Returns the number of nodes added to our waiting parents list:
+ 1 if we add a unique waiting parent, 0 if not. (Note that the
+ returned values are intended to be used to increment a reference
+ count, so don't think you can "clean up" this function by using
+ True and False instead...)
+ """
+ wp = self.waiting_parents
+ if node in wp:
+ return 0
+ wp.add(node)
+ return 1
+
+ def postprocess(self):
+ """Clean up anything we don't need to hang onto after we've
+ been built."""
+ self.executor_cleanup()
+ self.waiting_parents = set()
+
+ def clear(self):
+ """Completely clear a Node of all its cached state (so that it
+ can be re-evaluated by interfaces that do continuous integration
+ builds).
+ """
+ # The del_binfo() call here isn't necessary for normal execution,
+ # but is for interactive mode, where we might rebuild the same
+ # target and need to start from scratch.
+ self.del_binfo()
+ self.clear_memoized_values()
+ self.ninfo = self.new_ninfo()
+ self.executor_cleanup()
+ try:
+ delattr(self, '_calculated_sig')
+ except AttributeError:
+ pass
+ self.includes = None
+
+ def clear_memoized_values(self):
+ self._memo = {}
+
+ def builder_set(self, builder):
+ self.builder = builder
+ try:
+ del self.executor
+ except AttributeError:
+ pass
+
+ def has_builder(self):
+ """Return whether this Node has a builder or not.
+
+ In Boolean tests, this turns out to be a *lot* more efficient
+ than simply examining the builder attribute directly ("if
+ node.builder: ..."). When the builder attribute is examined
+ directly, it ends up calling __getattr__ for both the __len__
+ and __nonzero__ attributes on instances of our Builder Proxy
+ class(es), generating a bazillion extra calls and slowing
+ things down immensely.
+ """
+ try:
+ b = self.builder
+ except AttributeError:
+ # There was no explicit builder for this Node, so initialize
+ # the self.builder attribute to None now.
+ b = self.builder = None
+ return b is not None
+
+ def set_explicit(self, is_explicit):
+ self.is_explicit = is_explicit
+
+ def has_explicit_builder(self):
+ """Return whether this Node has an explicit builder
+
+ This allows an internal Builder created by SCons to be marked
+ non-explicit, so that it can be overridden by an explicit
+ builder that the user supplies (the canonical example being
+ directories)."""
+ try:
+ return self.is_explicit
+ except AttributeError:
+ self.is_explicit = None
+ return self.is_explicit
+
+ def get_builder(self, default_builder=None):
+ """Return the set builder, or a specified default value"""
+ try:
+ return self.builder
+ except AttributeError:
+ return default_builder
+
+ multiple_side_effect_has_builder = has_builder
+
+ def is_derived(self):
+ """
+ Returns true if this node is derived (i.e. built).
+
+ This should return true only for nodes whose path should be in
+ the variant directory when duplicate=0 and should contribute their build
+ signatures when they are used as source files to other derived files. For
+ example: source with source builders are not derived in this sense,
+ and hence should not return true.
+ """
+ return self.has_builder() or self.side_effect
+
+ def alter_targets(self):
+ """Return a list of alternate targets for this Node.
+ """
+ return [], None
+
+ def get_found_includes(self, env, scanner, path):
+ """Return the scanned include lines (implicit dependencies)
+ found in this node.
+
+ The default is no implicit dependencies. We expect this method
+ to be overridden by any subclass that can be scanned for
+ implicit dependencies.
+ """
+ return []
+
+ def get_implicit_deps(self, env, scanner, path):
+ """Return a list of implicit dependencies for this node.
+
+ This method exists to handle recursive invocation of the scanner
+ on the implicit dependencies returned by the scanner, if the
+ scanner's recursive flag says that we should.
+ """
+ if not scanner:
+ return []
+
+ # Give the scanner a chance to select a more specific scanner
+ # for this Node.
+ #scanner = scanner.select(self)
+
+ nodes = [self]
+ seen = {}
+ seen[self] = 1
+ deps = []
+ while nodes:
+ n = nodes.pop(0)
+ d = [x for x in n.get_found_includes(env, scanner, path) if x not in seen]
+ if d:
+ deps.extend(d)
+ for n in d:
+ seen[n] = 1
+ nodes.extend(scanner.recurse_nodes(d))
+
+ return deps
+
+ def get_env_scanner(self, env, kw={}):
+ return env.get_scanner(self.scanner_key())
+
+ def get_target_scanner(self):
+ return self.builder.target_scanner
+
+ def get_source_scanner(self, node):
+ """Fetch the source scanner for the specified node
+
+ NOTE: "self" is the target being built, "node" is
+ the source file for which we want to fetch the scanner.
+
+ Implies self.has_builder() is true; again, expect to only be
+ called from locations where this is already verified.
+
+ This function may be called very often; it attempts to cache
+ the scanner found to improve performance.
+ """
+ scanner = None
+ try:
+ scanner = self.builder.source_scanner
+ except AttributeError:
+ pass
+ if not scanner:
+ # The builder didn't have an explicit scanner, so go look up
+ # a scanner from env['SCANNERS'] based on the node's scanner
+ # key (usually the file extension).
+ scanner = self.get_env_scanner(self.get_build_env())
+ if scanner:
+ scanner = scanner.select(node)
+ return scanner
+
+ def add_to_implicit(self, deps):
+ if not hasattr(self, 'implicit') or self.implicit is None:
+ self.implicit = []
+ self.implicit_set = set()
+ self._children_reset()
+ self._add_child(self.implicit, self.implicit_set, deps)
+
+ def scan(self):
+ """Scan this node's dependents for implicit dependencies."""
+ # Don't bother scanning non-derived files, because we don't
+ # care what their dependencies are.
+ # Don't scan again, if we already have scanned.
+ if self.implicit is not None:
+ return
+ self.implicit = []
+ self.implicit_set = set()
+ self._children_reset()
+ if not self.has_builder():
+ return
+
+ build_env = self.get_build_env()
+ executor = self.get_executor()
+
+ # Here's where we implement --implicit-cache.
+ if implicit_cache and not implicit_deps_changed:
+ implicit = self.get_stored_implicit()
+ if implicit is not None:
+ # We now add the implicit dependencies returned from the
+ # stored .sconsign entry to have already been converted
+ # to Nodes for us. (We used to run them through a
+ # source_factory function here.)
+
+ # Update all of the targets with them. This
+ # essentially short-circuits an N*M scan of the
+ # sources for each individual target, which is a hell
+ # of a lot more efficient.
+ for tgt in executor.get_all_targets():
+ tgt.add_to_implicit(implicit)
+
+ if implicit_deps_unchanged or self.is_up_to_date():
+ return
+ # one of this node's sources has changed,
+ # so we must recalculate the implicit deps for all targets
+ for tgt in executor.get_all_targets():
+ tgt.implicit = []
+ tgt.implicit_set = set()
+
+ # Have the executor scan the sources.
+ executor.scan_sources(self.builder.source_scanner)
+
+ # If there's a target scanner, have the executor scan the target
+ # node itself and associated targets that might be built.
+ scanner = self.get_target_scanner()
+ if scanner:
+ executor.scan_targets(scanner)
+
+ def scanner_key(self):
+ return None
+
+ def select_scanner(self, scanner):
+ """Selects a scanner for this Node.
+
+ This is a separate method so it can be overridden by Node
+ subclasses (specifically, Node.FS.Dir) that *must* use their
+ own Scanner and don't select one the Scanner.Selector that's
+ configured for the target.
+ """
+ return scanner.select(self)
+
+ def env_set(self, env, safe=0):
+ if safe and self.env:
+ return
+ self.env = env
+
+ #
+ # SIGNATURE SUBSYSTEM
+ #
+
+ NodeInfo = NodeInfoBase
+ BuildInfo = BuildInfoBase
+
+ def new_ninfo(self):
+ ninfo = self.NodeInfo(self)
+ return ninfo
+
+ def get_ninfo(self):
+ try:
+ return self.ninfo
+ except AttributeError:
+ self.ninfo = self.new_ninfo()
+ return self.ninfo
+
+ def new_binfo(self):
+ binfo = self.BuildInfo(self)
+ return binfo
+
+ def get_binfo(self):
+ """
+ Fetch a node's build information.
+
+ node - the node whose sources will be collected
+ cache - alternate node to use for the signature cache
+ returns - the build signature
+
+ This no longer handles the recursive descent of the
+ node's children's signatures. We expect that they're
+ already built and updated by someone else, if that's
+ what's wanted.
+ """
+ try:
+ return self.binfo
+ except AttributeError:
+ pass
+
+ binfo = self.new_binfo()
+ self.binfo = binfo
+
+ executor = self.get_executor()
+ ignore_set = self.ignore_set
+
+ if self.has_builder():
+ binfo.bact = str(executor)
+ binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
+
+ if self._specific_sources:
+ sources = []
+ for s in self.sources:
+ if s not in ignore_set:
+ sources.append(s)
+ else:
+ sources = executor.get_unignored_sources(self, self.ignore)
+ seen = set()
+ bsources = []
+ bsourcesigs = []
+ for s in sources:
+ if not s in seen:
+ seen.add(s)
+ bsources.append(s)
+ bsourcesigs.append(s.get_ninfo())
+ binfo.bsources = bsources
+ binfo.bsourcesigs = bsourcesigs
+
+ depends = self.depends
+ dependsigs = []
+ for d in depends:
+ if d not in ignore_set:
+ dependsigs.append(d.get_ninfo())
+ binfo.bdepends = depends
+ binfo.bdependsigs = dependsigs
+
+ implicit = self.implicit or []
+ implicitsigs = []
+ for i in implicit:
+ if i not in ignore_set:
+ implicitsigs.append(i.get_ninfo())
+ binfo.bimplicit = implicit
+ binfo.bimplicitsigs = implicitsigs
+
+ return binfo
+
+ def del_binfo(self):
+ """Delete the build info from this node."""
+ try:
+ delattr(self, 'binfo')
+ except AttributeError:
+ pass
+
+ def get_csig(self):
+ try:
+ return self.ninfo.csig
+ except AttributeError:
+ ninfo = self.get_ninfo()
+ ninfo.csig = SCons.Util.MD5signature(self.get_contents())
+ return self.ninfo.csig
+
+ def get_cachedir_csig(self):
+ return self.get_csig()
+
+ def store_info(self):
+ """Make the build signature permanent (that is, store it in the
+ .sconsign file or equivalent)."""
+ pass
+
+ def do_not_store_info(self):
+ pass
+
+ def get_stored_info(self):
+ return None
+
+ def get_stored_implicit(self):
+ """Fetch the stored implicit dependencies"""
+ return None
+
+ #
+ #
+ #
+
+ def set_precious(self, precious = 1):
+ """Set the Node's precious value."""
+ self.precious = precious
+
+ def set_pseudo(self, pseudo = True):
+ """Set the Node's precious value."""
+ self.pseudo = pseudo
+
+ def set_noclean(self, noclean = 1):
+ """Set the Node's noclean value."""
+ # Make sure noclean is an integer so the --debug=stree
+ # output in Util.py can use it as an index.
+ self.noclean = noclean and 1 or 0
+
+ def set_nocache(self, nocache = 1):
+ """Set the Node's nocache value."""
+ # Make sure nocache is an integer so the --debug=stree
+ # output in Util.py can use it as an index.
+ self.nocache = nocache and 1 or 0
+
+ def set_always_build(self, always_build = 1):
+ """Set the Node's always_build value."""
+ self.always_build = always_build
+
+ def exists(self):
+ """Does this node exists?"""
+ # All node exist by default:
+ return 1
+
+ def rexists(self):
+ """Does this node exist locally or in a repositiory?"""
+ # There are no repositories by default:
+ return self.exists()
+
+ def missing(self):
+ return not self.is_derived() and \
+ not self.linked and \
+ not self.rexists()
+
+ def remove(self):
+ """Remove this Node: no-op by default."""
+ return None
+
+ def add_dependency(self, depend):
+ """Adds dependencies."""
+ try:
+ self._add_child(self.depends, self.depends_set, depend)
+ except TypeError, e:
+ e = e.args[0]
+ if SCons.Util.is_List(e):
+ s = list(map(str, e))
+ else:
+ s = str(e)
+ raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
+
+ def add_prerequisite(self, prerequisite):
+ """Adds prerequisites"""
+ if self.prerequisites is None:
+ self.prerequisites = SCons.Util.UniqueList()
+ self.prerequisites.extend(prerequisite)
+ self._children_reset()
+
+ def add_ignore(self, depend):
+ """Adds dependencies to ignore."""
+ try:
+ self._add_child(self.ignore, self.ignore_set, depend)
+ except TypeError, e:
+ e = e.args[0]
+ if SCons.Util.is_List(e):
+ s = list(map(str, e))
+ else:
+ s = str(e)
+ raise SCons.Errors.UserError("attempted to ignore a non-Node dependency of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
+
+ def add_source(self, source):
+ """Adds sources."""
+ if self._specific_sources:
+ return
+ try:
+ self._add_child(self.sources, self.sources_set, source)
+ except TypeError, e:
+ e = e.args[0]
+ if SCons.Util.is_List(e):
+ s = list(map(str, e))
+ else:
+ s = str(e)
+ raise SCons.Errors.UserError("attempted to add a non-Node as source of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
+
+ def _add_child(self, collection, set, child):
+ """Adds 'child' to 'collection', first checking 'set' to see if it's
+ already present."""
+ #if type(child) is not type([]):
+ # child = [child]
+ #for c in child:
+ # if not isinstance(c, Node):
+ # raise TypeError, c
+ added = None
+ for c in child:
+ if c not in set:
+ set.add(c)
+ collection.append(c)
+ added = 1
+ if added:
+ self._children_reset()
+
+ def set_specific_source(self, source):
+ self.add_source(source)
+ self._specific_sources = True
+
+ def add_wkid(self, wkid):
+ """Add a node to the list of kids waiting to be evaluated"""
+ if self.wkids is not None:
+ self.wkids.append(wkid)
+
+ def _children_reset(self):
+ self.clear_memoized_values()
+ # We need to let the Executor clear out any calculated
+ # build info that it's cached so we can re-calculate it.
+ self.executor_cleanup()
+
+ memoizer_counters.append(SCons.Memoize.CountValue('_children_get'))
+
+ def _children_get(self):
+ try:
+ return self._memo['children_get']
+ except KeyError:
+ pass
+
+ # The return list may contain duplicate Nodes, especially in
+ # source trees where there are a lot of repeated #includes
+ # of a tangle of .h files. Profiling shows, however, that
+ # eliminating the duplicates with a brute-force approach that
+ # preserves the order (that is, something like:
+ #
+ # u = []
+ # for n in list:
+ # if n not in u:
+ # u.append(n)"
+ #
+ # takes more cycles than just letting the underlying methods
+ # hand back cached values if a Node's information is requested
+ # multiple times. (Other methods of removing duplicates, like
+ # using dictionary keys, lose the order, and the only ordered
+ # dictionary patterns I found all ended up using "not in"
+ # internally anyway...)
+ if self.ignore_set:
+ iter = chain.from_iterable(filter(None, [self.sources, self.depends, self.implicit]))
+
+ children = []
+ for i in iter:
+ if i not in self.ignore_set:
+ children.append(i)
+ else:
+ children = self.all_children(scan=0)
+
+ self._memo['children_get'] = children
+ return children
+
+ def all_children(self, scan=1):
+ """Return a list of all the node's direct children."""
+ if scan:
+ self.scan()
+
+ # The return list may contain duplicate Nodes, especially in
+ # source trees where there are a lot of repeated #includes
+ # of a tangle of .h files. Profiling shows, however, that
+ # eliminating the duplicates with a brute-force approach that
+ # preserves the order (that is, something like:
+ #
+ # u = []
+ # for n in list:
+ # if n not in u:
+ # u.append(n)"
+ #
+ # takes more cycles than just letting the underlying methods
+ # hand back cached values if a Node's information is requested
+ # multiple times. (Other methods of removing duplicates, like
+ # using dictionary keys, lose the order, and the only ordered
+ # dictionary patterns I found all ended up using "not in"
+ # internally anyway...)
+ return list(chain.from_iterable(filter(None, [self.sources, self.depends, self.implicit])))
+
+ def children(self, scan=1):
+ """Return a list of the node's direct children, minus those
+ that are ignored by this node."""
+ if scan:
+ self.scan()
+ return self._children_get()
+
+ def set_state(self, state):
+ self.state = state
+
+ def get_state(self):
+ return self.state
+
+ def state_has_changed(self, target, prev_ni):
+ return (self.state != SCons.Node.up_to_date)
+
+ def get_env(self):
+ env = self.env
+ if not env:
+ import SCons.Defaults
+ env = SCons.Defaults.DefaultEnvironment()
+ return env
+
+ def changed_since_last_build(self, target, prev_ni):
+ """
+
+ Must be overridden in a specific subclass to return True if this
+ Node (a dependency) has changed since the last time it was used
+ to build the specified target. prev_ni is this Node's state (for
+ example, its file timestamp, length, maybe content signature)
+ as of the last time the target was built.
+
+ Note that this method is called through the dependency, not the
+ target, because a dependency Node must be able to use its own
+ logic to decide if it changed. For example, File Nodes need to
+ obey if we're configured to use timestamps, but Python Value Nodes
+ never use timestamps and always use the content. If this method
+ were called through the target, then each Node's implementation
+ of this method would have to have more complicated logic to
+ handle all the different Node types on which it might depend.
+ """
+ raise NotImplementedError
+
+ def Decider(self, function):
+ SCons.Util.AddMethod(self, function, 'changed_since_last_build')
+
+ def changed(self, node=None, allowcache=False):
+ """
+ Returns if the node is up-to-date with respect to the BuildInfo
+ stored last time it was built. The default behavior is to compare
+ it against our own previously stored BuildInfo, but the stored
+ BuildInfo from another Node (typically one in a Repository)
+ can be used instead.
+
+ Note that we now *always* check every dependency. We used to
+ short-circuit the check by returning as soon as we detected
+ any difference, but we now rely on checking every dependency
+ to make sure that any necessary Node information (for example,
+ the content signature of an #included .h file) is updated.
+
+ The allowcache option was added for supporting the early
+ release of the executor/builder structures, right after
+ a File target was built. When set to true, the return
+ value of this changed method gets cached for File nodes.
+ Like this, the executor isn't needed any longer for subsequent
+ calls to changed().
+
+ @see: FS.File.changed(), FS.File.release_target_info()
+ """
+ t = 0
+ if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
+ if node is None:
+ node = self
+
+ result = False
+
+ bi = node.get_stored_info().binfo
+ then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs
+ children = self.children()
+
+ diff = len(children) - len(then)
+ if diff:
+ # The old and new dependency lists are different lengths.
+ # This always indicates that the Node must be rebuilt.
+ # We also extend the old dependency list with enough None
+ # entries to equal the new dependency list, for the benefit
+ # of the loop below that updates node information.
+ then.extend([None] * diff)
+ if t: Trace(': old %s new %s' % (len(then), len(children)))
+ result = True
+
+ for child, prev_ni in zip(children, then):
+ if child.changed_since_last_build(self, prev_ni):
+ if t: Trace(': %s changed' % child)
+ result = True
+
+ contents = self.get_executor().get_contents()
+ if self.has_builder():
+ import SCons.Util
+ newsig = SCons.Util.MD5signature(contents)
+ if bi.bactsig != newsig:
+ if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
+ result = True
+
+ if not result:
+ if t: Trace(': up to date')
+
+ if t: Trace('\n')
+
+ return result
+
+ def is_up_to_date(self):
+ """Default check for whether the Node is current: unknown Node
+ subtypes are always out of date, so they will always get built."""
+ return None
+
+ def children_are_up_to_date(self):
+ """Alternate check for whether the Node is current: If all of
+ our children were up-to-date, then this Node was up-to-date, too.
+
+ The SCons.Node.Alias and SCons.Node.Python.Value subclasses
+ rebind their current() method to this method."""
+ # Allow the children to calculate their signatures.
+ self.binfo = self.get_binfo()
+ if self.always_build:
+ return None
+ state = 0
+ for kid in self.children(None):
+ s = kid.get_state()
+ if s and (not state or s > state):
+ state = s
+ return (state == 0 or state == SCons.Node.up_to_date)
+
+ def is_literal(self):
+ """Always pass the string representation of a Node to
+ the command interpreter literally."""
+ return 1
+
+ def render_include_tree(self):
+ """
+ Return a text representation, suitable for displaying to the
+ user, of the include tree for the sources of this node.
+ """
+ if self.is_derived():
+ env = self.get_build_env()
+ if env:
+ for s in self.sources:
+ scanner = self.get_source_scanner(s)
+ if scanner:
+ path = self.get_build_scanner_path(scanner)
+ else:
+ path = None
+ def f(node, env=env, scanner=scanner, path=path):
+ return node.get_found_includes(env, scanner, path)
+ return SCons.Util.render_tree(s, f, 1)
+ else:
+ return None
+
+ def get_abspath(self):
+ """
+ Return an absolute path to the Node. This will return simply
+ str(Node) by default, but for Node types that have a concept of
+ relative path, this might return something different.
+ """
+ return str(self)
+
+ def for_signature(self):
+ """
+ Return a string representation of the Node that will always
+ be the same for this particular Node, no matter what. This
+ is by contrast to the __str__() method, which might, for
+ instance, return a relative path for a file Node. The purpose
+ of this method is to generate a value to be used in signature
+ calculation for the command line used to build a target, and
+ we use this method instead of str() to avoid unnecessary
+ rebuilds. This method does not need to return something that
+ would actually work in a command line; it can return any kind of
+ nonsense, so long as it does not change.
+ """
+ return str(self)
+
+ def get_string(self, for_signature):
+ """This is a convenience function designed primarily to be
+ used in command generators (i.e., CommandGeneratorActions or
+ Environment variables that are callable), which are called
+ with a for_signature argument that is nonzero if the command
+ generator is being called to generate a signature for the
+ command line, which determines if we should rebuild or not.
+
+ Such command generators should use this method in preference
+ to str(Node) when converting a Node to a string, passing
+ in the for_signature parameter, such that we will call
+ Node.for_signature() or str(Node) properly, depending on whether
+ we are calculating a signature or actually constructing a
+ command line."""
+ if for_signature:
+ return self.for_signature()
+ return str(self)
+
+ def get_subst_proxy(self):
+ """
+ This method is expected to return an object that will function
+ exactly like this Node, except that it implements any additional
+ special features that we would like to be in effect for
+ Environment variable substitution. The principle use is that
+ some Nodes would like to implement a __getattr__() method,
+ but putting that in the Node type itself has a tendency to kill
+ performance. We instead put it in a proxy and return it from
+ this method. It is legal for this method to return self
+ if no new functionality is needed for Environment substitution.
+ """
+ return self
+
+ def explain(self):
+ if not self.exists():
+ return "building `%s' because it doesn't exist\n" % self
+
+ if self.always_build:
+ return "rebuilding `%s' because AlwaysBuild() is specified\n" % self
+
+ old = self.get_stored_info()
+ if old is None:
+ return None
+
+ old = old.binfo
+ old.prepare_dependencies()
+
+ try:
+ old_bkids = old.bsources + old.bdepends + old.bimplicit
+ old_bkidsigs = old.bsourcesigs + old.bdependsigs + old.bimplicitsigs
+ except AttributeError:
+ return "Cannot explain why `%s' is being rebuilt: No previous build information found\n" % self
+
+ new = self.get_binfo()
+
+ new_bkids = new.bsources + new.bdepends + new.bimplicit
+ new_bkidsigs = new.bsourcesigs + new.bdependsigs + new.bimplicitsigs
+
+ osig = dict(zip(old_bkids, old_bkidsigs))
+ nsig = dict(zip(new_bkids, new_bkidsigs))
+
+ # The sources and dependencies we'll want to report are all stored
+ # as relative paths to this target's directory, but we want to
+ # report them relative to the top-level SConstruct directory,
+ # 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 ) :
+ 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]
+ if removed:
+ removed = list(map(stringify, 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:
+ lines.append("`%s' is a new dependency\n" % stringify(k))
+ elif k.changed_since_last_build(self, osig[k]):
+ 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))))
+
+ if len(lines) == 0:
+ def fmt_with_title(title, strlines):
+ lines = strlines.split('\n')
+ sep = '\n' + ' '*(15 + len(title))
+ return ' '*15 + title + sep.join(lines) + '\n'
+ if old.bactsig != new.bactsig:
+ if old.bact == new.bact:
+ lines.append("the contents of the build action changed\n" +
+ fmt_with_title('action: ', new.bact))
+ else:
+ lines.append("the build action changed:\n" +
+ fmt_with_title('old: ', old.bact) +
+ fmt_with_title('new: ', new.bact))
+
+ if len(lines) == 0:
+ return "rebuilding `%s' for unknown reasons\n" % self
+
+ preamble = "rebuilding `%s' because" % self
+ if len(lines) == 1:
+ return "%s %s" % (preamble, lines[0])
+ else:
+ lines = ["%s:\n" % preamble] + lines
+ return ( ' '*11).join(lines)
+
+class NodeList(collections.UserList):
+ def __str__(self):
+ return str(list(map(str, self.data)))
+
+def get_children(node, parent): return node.children()
+def ignore_cycle(node, stack): pass
+def do_nothing(node, parent): pass
+
+class Walker(object):
+ """An iterator for walking a Node tree.
+
+ 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.
+
+ This class does not get caught in node cycles caused, for example,
+ by C header file include loops.
+ """
+ def __init__(self, node, kids_func=get_children,
+ cycle_func=ignore_cycle,
+ eval_func=do_nothing):
+ self.kids_func = kids_func
+ self.cycle_func = cycle_func
+ self.eval_func = eval_func
+ node.wkids = copy.copy(kids_func(node, None))
+ self.stack = [node]
+ self.history = {} # used to efficiently detect and avoid cycles
+ self.history[node] = None
+
+ def get_next(self):
+ """Return the next node for this walk of the tree.
+
+ This function is intentionally iterative, not recursive,
+ to sidestep any issues of stack size limitations.
+ """
+
+ while self.stack:
+ if self.stack[-1].wkids:
+ node = self.stack[-1].wkids.pop(0)
+ if not self.stack[-1].wkids:
+ self.stack[-1].wkids = None
+ if node in self.history:
+ self.cycle_func(node, self.stack)
+ else:
+ node.wkids = copy.copy(self.kids_func(node, self.stack[-1]))
+ self.stack.append(node)
+ self.history[node] = None
+ else:
+ node = self.stack.pop()
+ del self.history[node]
+ if node:
+ if self.stack:
+ parent = self.stack[-1]
+ else:
+ parent = None
+ self.eval_func(node, parent)
+ return node
+ return None
+
+ def is_done(self):
+ return not self.stack
+
+
+arg2nodes_lookups = []
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: