# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
+from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+from itertools import izip
import fnmatch
import os
import os.path
import re
import shutil
import stat
-import string
import sys
import time
-import cStringIO
+
+try:
+ import codecs
+except ImportError:
+ pass
+else:
+ # TODO(2.2): Remove when 2.3 becomes the minimal supported version.
+ try:
+ codecs.BOM_UTF8
+ except AttributeError:
+ codecs.BOM_UTF8 = '\xef\xbb\xbf'
+ try:
+ codecs.BOM_UTF16_LE
+ codecs.BOM_UTF16_BE
+ except AttributeError:
+ codecs.BOM_UTF16_LE = '\xff\xfe'
+ codecs.BOM_UTF16_BE = '\xfe\xff'
+
+ # Provide a wrapper function to handle decoding differences in
+ # different versions of Python. Normally, we'd try to do this in the
+ # compat layer (and maybe it still makes sense to move there?) but
+ # that doesn't provide a way to supply the string class used in
+ # pre-2.3 Python versions with a .decode() method that all strings
+ # naturally have. Plus, the 2.[01] encodings behave differently
+ # enough that we have to settle for a lowest-common-denominator
+ # wrapper approach.
+ #
+ # Note that the 2.[012] implementations below may be inefficient
+ # because they perform an explicit look up of the encoding for every
+ # decode, but they're old enough (and we want to stop supporting
+ # them soon enough) that it's not worth complicating the interface.
+ # Think of it as additional incentive for people to upgrade...
+ try:
+ ''.decode
+ except AttributeError:
+ # 2.0 through 2.2: strings have no .decode() method
+ try:
+ codecs.lookup('ascii').decode
+ except AttributeError:
+ # 2.0 and 2.1: encodings are a tuple of functions, and the
+ # decode() function returns a (result, length) tuple.
+ def my_decode(contents, encoding):
+ return codecs.lookup(encoding)[1](contents)[0]
+ else:
+ # 2.2: encodings are an object with methods, and the
+ # .decode() method returns just the decoded bytes.
+ def my_decode(contents, encoding):
+ return codecs.lookup(encoding).decode(contents)
+ else:
+ # 2.3 or later: use the .decode() string method
+ def my_decode(contents, encoding):
+ return contents.decode(encoding)
import SCons.Action
from SCons.Debug import logInstanceCreation
from SCons.Debug import Trace
+do_store_info = True
+
+
+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
}
if not duplicate in Valid_Duplicates:
- raise SCons.Errors.InternalError, ("The argument of set_duplicate "
+ raise SCons.Errors.InternalError("The argument of set_duplicate "
"should be in Valid_Duplicates")
global Link_Funcs
Link_Funcs = []
- for func in string.split(duplicate,'-'):
+ for func in duplicate.split('-'):
if link_dict[func]:
Link_Funcs.append(link_dict[func])
if func == Link_Funcs[-1]:
# exception of the last link method (copy) are fatal
raise
- else:
- pass
return 0
Link = SCons.Action.Action(LinkFunc, None)
return x
else:
def _my_normcase(x):
- return string.upper(x)
+ return x.upper()
except (AttributeError, KeyError):
pass
if result:
- raise TypeError, errorfmt % node.abspath
+ raise TypeError(errorfmt % node.abspath)
def ignore_diskcheck_match(node, predicate, errorfmt):
pass
dc.set(list)
def diskcheck_types():
- return map(lambda dc: dc.type, diskcheckers)
+ return [dc.type for dc in diskcheckers]
return self
else:
entry = self.get()
- r = string.replace(entry.get_path(), os.sep, '/')
+ r = entry.get_path().replace(os.sep, '/')
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_windows_path(self):
return self
else:
entry = self.get()
- r = string.replace(entry.get_path(), os.sep, '\\')
+ r = entry.get_path().replace(os.sep, '\\')
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
def __get_srcnode(self):
except KeyError:
try:
attr = SCons.Util.Proxy.__getattr__(self, name)
- except AttributeError:
- entry = self.get()
- classname = string.split(str(entry.__class__), '.')[-1]
- if classname[-2:] == "'>":
- # new-style classes report their name as:
- # "<class 'something'>"
- # instead of the classic classes:
- # "something"
- classname = classname[:-2]
- raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, 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)
if __debug__: logInstanceCreation(self, 'Node.FS.Base')
SCons.Node.Node.__init__(self)
- self.name = name
- self.suffix = SCons.Util.splitext(name)[1]
+ # Filenames and paths are probably reused and are intern'ed to
+ # save some memory.
+ self.name = SCons.Util.silent_intern(name)
+ self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
self.fs = fs
assert directory, "A directory must be provided"
- self.abspath = directory.entry_abspath(name)
- self.labspath = directory.entry_labspath(name)
+ 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 = name
+ self.path = SCons.Util.silent_intern(name)
else:
- self.path = directory.entry_path(name)
+ self.path = SCons.Util.silent_intern(directory.entry_path(name))
if directory.tpath == '.':
- self.tpath = name
+ self.tpath = SCons.Util.silent_intern(name)
else:
- self.tpath = directory.entry_tpath(name)
+ 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 self.__class__ is klass or klass is Entry:
+ 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__)
+ raise TypeError("Tried to lookup %s '%s' as a %s." %\
+ (self.__class__.__name__, self.path, klass.__name__))
def get_dir(self):
return self.dir
return self._memo['_save_str']
except KeyError:
pass
- result = self._get_str()
+ result = sys.intern(self._get_str())
self._memo['_save_str'] = result
return result
if self.duplicate or self.is_derived():
return self.get_path()
srcnode = self.srcnode()
- if srcnode.stat() is None and not self.stat() is None:
+ if srcnode.stat() is None and self.stat() is not None:
result = self.get_path()
else:
result = srcnode.get_path()
# values that the underlying stat() method saved.
try: del self._memo['stat']
except KeyError: pass
- if not self is srcnode:
+ if self is not srcnode:
try: del srcnode._memo['stat']
except KeyError: pass
return result
return result
def exists(self):
- return not self.stat() is None
+ return self.stat() is not None
def rexists(self):
return self.rfile().exists()
def isdir(self):
st = self.stat()
- return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
+ return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
def isfile(self):
st = self.stat()
- return not st is None and stat.S_ISREG(st[stat.ST_MODE])
+ return st is not None and stat.S_ISREG(st[stat.ST_MODE])
if hasattr(os, 'symlink'):
def islink(self):
try: i = path_elems.index(dir)
except ValueError: pass
else: path_elems = path_elems[i+1:]
- path_elems = map(lambda n: n.name, path_elems)
- return string.join(path_elems, os.sep)
+ path_elems = [n.name for n in path_elems]
+ return os.sep.join(path_elems)
def set_src_builder(self, builder):
"""Set the source code builder for this node."""
self._morph()
elif must_exist:
msg = "No such file or directory: '%s'" % self.abspath
- raise SCons.Errors.UserError, msg
+ raise SCons.Errors.UserError(msg)
else:
self.__class__ = File
self._morph()
return self.get_suffix()
def get_contents(self):
- """Fetch the contents of the entry.
-
- Since this should return the real contents from the file
- system, we check to see into what sort of subclass we should
- morph this Entry."""
+ """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:
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 not self.__class__ is klass:
+ if self.__class__ is not klass:
self.__class__ = klass
self._morph()
- self.clear
+ self.clear()
# The following methods can get called before the Taskmaster has
# had a chance to call disambiguate() directly to see if this Entry
def rel_path(self, other):
d = self.disambiguate()
- if d.__class__ == Entry:
- raise "rel_path() could not disambiguate File/Dir"
+ if d.__class__ is Entry:
+ raise Exception("rel_path() could not disambiguate File/Dir")
return d.rel_path(other)
def new_ninfo(self):
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
"""
curr=self._cwd
try:
- if not dir is None:
+ if dir is not None:
self._cwd = dir
if change_os_dir:
os.chdir(dir.abspath)
root = directory.root
if os.sep != '/':
- p = string.replace(p, os.sep, '/')
+ p = p.replace(os.sep, '/')
return root._lookup_abs(p, fsclass, create)
def Entry(self, name, directory = None, create = 1):
- """Lookup or create a generic Entry node with the specified name.
+ """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
return self._lookup(name, directory, Entry, create)
def File(self, name, directory = None, create = 1):
- """Lookup or create a File node with the specified name. If
+ """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
return self._lookup(name, directory, File, create)
def Dir(self, name, directory = None, create = True):
- """Lookup or create a Dir node with the specified name. If
+ """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
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."
+ 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)
+ 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):
if start_dir.is_under(bd):
# If already in the build-dir location, don't reflect
return [orig], fmt % str(orig)
- p = apply(os.path.join, [bd.path] + tail)
+ p = os.path.join(bd.path, *tail)
targets.append(self.Entry(p))
tail = [dir.name] + tail
dir = dir.up()
if targets:
- message = fmt % string.join(map(str, targets))
+ message = fmt % ' '.join(map(str, targets))
return targets, message
def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
del node._srcreps
except AttributeError:
pass
- if duplicate != None:
+ if duplicate is not None:
node.duplicate=duplicate
def __resetDuplicate(self, node):
Looks up or creates a directory node named 'name' relative to
this directory.
"""
- dir = self.fs.Dir(name, self, create)
- return dir
+ return self.fs.Dir(name, self, create)
def File(self, name):
"""
def get_all_rdirs(self):
try:
- return self._memo['get_all_rdirs']
+ return list(self._memo['get_all_rdirs'])
except KeyError:
pass
fname = dir.name + os.sep + fname
dir = dir.up()
- self._memo['get_all_rdirs'] = result
+ self._memo['get_all_rdirs'] = list(result)
return result
"""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.
+ # 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..
+ # 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']
pass
if self is other:
-
result = '.'
elif not other in self.path_elements:
-
try:
other_dir = other.get_dir()
except AttributeError:
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) \
- + map(lambda n: n.name, other.path_elements[i:])
+ + [n.name for n in other.path_elements[i:]]
- result = string.join(path_elems, os.sep)
+ result = os.sep.join(path_elems)
memo_dict[other] = result
def build(self, **kw):
"""A null "builder" for directories."""
global MkdirBuilder
- if not self.builder is MkdirBuilder:
- apply(SCons.Node.Node.build, [self,], kw)
+ if self.builder is not MkdirBuilder:
+ SCons.Node.Node.build(self, **kw)
#
#
listDirs.append(parent)
p = parent.up()
if p is None:
- raise SCons.Errors.StopError, parent.path
+ # 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:
def multiple_side_effect_has_builder(self):
global MkdirBuilder
- return not self.builder is MkdirBuilder and self.has_builder()
+ return self.builder is not MkdirBuilder and self.has_builder()
def alter_targets(self):
"""Return any corresponding targets in a variant directory.
"""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 aggregate contents of all our children."""
- contents = map(lambda n: n.get_contents(), self.children())
- return string.join(contents, '')
+ """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
def is_up_to_date(self):
"""If any child is not up-to-date, then this directory isn't,
either."""
- if not self.builder is MkdirBuilder and not self.exists():
+ 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():
pass
else:
for entry in map(_my_normcase, entries):
- d[entry] = 1
+ d[entry] = True
self.on_disk_entries = d
- return d.has_key(_my_normcase(name))
+ if sys.platform == 'win32':
+ 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'))
if self.entry_exists_on_disk(name):
try: return self.Dir(name)
except TypeError: pass
- return None
+ 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 \
except TypeError: pass
node = self.srcdir_duplicate(name)
if isinstance(node, Dir):
- node = None
+ return None
return node
def walk(self, func, arg):
names.remove('.')
names.remove('..')
func(arg, self, names)
- select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
- for dirname in filter(select_dirs, 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):
"""
dirname, basename = os.path.split(pathname)
if not dirname:
- return self._glob1(basename, ondisk, source, strings)
+ 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:
for dir in list:
r = dir._glob1(basename, ondisk, source, strings)
if strings:
- r = map(lambda x, d=str(dir): os.path.join(d, x), r)
+ r = [os.path.join(str(dir), x) for x in r]
result.extend(r)
+ result.sort(lambda a, b: cmp(str(a), str(b)))
return result
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
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 ('.', '..') ]
- entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
- node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
+ 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:
- pass
- else:
- 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 = filter(lambda x: x[0] != '.', disk_names)
- disk_names = fnmatch.filter(disk_names, pattern)
- rep_nodes = map(dir.Entry, disk_names)
- #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
- rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
- for node, name in zip(rep_nodes, disk_names):
- n = self.Entry(name)
- if n.__class__ != node.__class__:
- n.__class__ = node.__class__
- n._morph()
+ 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 = filter(lambda x: x[0] != '.', names)
+ 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 map(lambda n, e=self.entries: e[_my_normcase(n)], names)
+ return [self.entries[_my_normcase(n)] for n in names]
class RootDir(Dir):
"""A class for the root directory of a file system.
result = self._lookupDict[k]
except KeyError:
if not create:
- raise SCons.Errors.UserError
+ 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.
dir_name, file_name = os.path.split(p)
dir_node = self._lookup_abs(dir_name, Dir)
result = klass(file_name, dir_node, self.fs)
- self._lookupDict[k] = result
- dir_node.entries[_my_normcase(file_name)] = result
- dir_node.implicit = None
# 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.
except AttributeError:
s = str(n)
else:
- s = string.replace(s, os.sep, '/')
+ s = s.replace(os.sep, '/')
return s
for attr in ['bsources', 'bdepends', 'bimplicit']:
try:
except AttributeError:
pass
else:
- setattr(self, attr, map(node_to_str, val))
+ 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
strings = getattr(self, nattr)
nodeinfos = getattr(self, sattr)
except AttributeError:
- pass
- else:
- 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)
+ continue
+ nodes = []
+ for s, ni in izip(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):
+ for bkid, bkidsig in izip(bkids, bkidsigs):
result.append(str(bkid) + ': ' +
- string.join(bkidsig.format(names=names), ' '))
+ ' '.join(bkidsig.format(names=names)))
result.append('%s [%s]' % (self.bactsig, self.bact))
- return string.join(result, '\n')
+ return '\n'.join(result)
class File(Base):
"""A class for files in a file system.
NodeInfo = FileNodeInfo
BuildInfo = FileBuildInfo
+ md5_chunksize = 64
+
def diskcheck_match(self):
diskcheck_match(self, self.isdir,
"Directory %s found where file expected.")
def Entry(self, name):
"""Create an entry node named 'name' relative to
- the SConscript directory of this file."""
- return self.cwd.Entry(name)
+ 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 SConscript directory of this file."""
- return self.cwd.Dir(name, create)
+ 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 map(lambda p, s=self: s.Dir(p), pathlist)
+ # TODO(1.5)
+ # return [self.Dir(p) for p in pathlist]
+ return [self.Dir(p) for p in pathlist]
def File(self, name):
"""Create a file node named 'name' relative to
- the SConscript directory of this file."""
- return self.cwd.File(name)
+ the directory of this file."""
+ return self.dir.File(name)
#def generate_build_dict(self):
# """Return an appropriate dictionary of values for building
return ''
fname = self.rfile().abspath
try:
- r = open(fname, "rb").read()
+ contents = open(fname, "rb").read()
+ except EnvironmentError, e:
+ if not e.filename:
+ e.filename = fname
+ raise
+ return contents
+
+ try:
+ import codecs
+ except ImportError:
+ get_text_contents = get_contents
+ else:
+ # 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 side step all the complication by
+ # explicitly stripping the BOM before we decode().
+ if contents.startswith(codecs.BOM_UTF8):
+ contents = contents[len(codecs.BOM_UTF8):]
+ # TODO(2.2): Remove when 2.3 becomes floor.
+ #contents = contents.decode('utf-8')
+ contents = my_decode(contents, 'utf-8')
+ elif contents.startswith(codecs.BOM_UTF16_LE):
+ contents = contents[len(codecs.BOM_UTF16_LE):]
+ # TODO(2.2): Remove when 2.3 becomes floor.
+ #contents = contents.decode('utf-16-le')
+ contents = my_decode(contents, 'utf-16-le')
+ elif contents.startswith(codecs.BOM_UTF16_BE):
+ contents = contents[len(codecs.BOM_UTF16_BE):]
+ # TODO(2.2): Remove when 2.3 becomes floor.
+ #contents = contents.decode('utf-16-be')
+ contents = my_decode(contents, '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 r
+ return cs
+
memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
# 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.
- self.dir.sconsign().store_info(self.name, self)
+ if do_store_info:
+ self.dir.sconsign().store_info(self.name, self)
convert_copy_attrs = [
'bsources',
try:
value = getattr(old_entry, attr)
except AttributeError:
- pass
- else:
- setattr(binfo, attr, value)
- delattr(old_entry, attr)
+ 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:
- pass
- else:
- 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)
+ 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'))
try:
sconsign_entry = self.dir.sconsign().get_entry(self.name)
- except (KeyError, OSError):
+ except (KeyError, EnvironmentError):
import SCons.SConsign
sconsign_entry = SCons.SConsign.SConsignEntry()
sconsign_entry.binfo = self.new_binfo()
pass
if scanner:
+ # result = [n.disambiguate() for n in scanner(self, env, path)]
result = scanner(self, env, path)
- result = map(lambda N: N.disambiguate(), result)
+ result = [N.disambiguate() for N in result]
else:
result = []
# 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
return None
return self.get_build_env().get_CacheDir().retrieve(self)
- def built(self):
- """
- Called just after this node is successfully built.
- """
- # Push this file out to cache before the superclass Node.built()
- # method has a chance to clear the build signature, which it
- # will do if this file has a source scanner.
- #
- # We have to clear the memoized values *before* we push it to
- # cache so that the memoization of the self.exists() return
- # value doesn't interfere.
- self.clear_memoized_values()
- if self.exists():
- self.get_build_env().get_CacheDir().push(self)
- SCons.Node.Node.built(self)
-
def visited(self):
if self.exists():
self.get_build_env().get_CacheDir().push_if_forced(self)
scb = self.sbuilder
except AttributeError:
scb = self.sbuilder = self.find_src_builder()
- return not scb is None
+ return scb is not None
def alter_targets(self):
"""Return any corresponding targets in a variant directory.
self._createDir()
except SCons.Errors.StopError, drive:
desc = "No drive `%s' for target `%s'." % (drive, self)
- raise SCons.Errors.StopError, desc
+ raise SCons.Errors.StopError(desc)
#
#
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
+ 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
# 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 not src is self:
+ 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:
old = self.get_stored_info()
mtime = self.get_timestamp()
- csig = None
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:
- csig = n.csig
+ return n.csig
except AttributeError:
pass
elif max_drift == 0:
try:
- csig = old.ninfo.csig
+ return old.ninfo.csig
except AttributeError:
pass
- return csig
+ return None
def get_csig(self):
"""
if csig is None:
try:
- contents = self.get_contents()
+ 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,
# create a same-named directory by mistake.
csig = ''
else:
- csig = SCons.Util.MD5signature(contents)
+ if not csig:
+ csig = SCons.Util.MD5signature(contents)
ninfo.csig = csig
return 1
def changed_state(self, target, prev_ni):
- return (self.state != SCons.Node.up_to_date)
+ 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):
(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
cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
if not self.exists() and cachefile and os.path.exists(cachefile):
- contents = open(cachefile, 'rb').read()
- self.cachedir_csig = SCons.Util.MD5signature(contents)
+ 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
# Add the path 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.
- children = self.children()
- sigs = map(lambda n: n.get_cachedir_csig(), children)
+ children = self.children()
executor = self.get_executor()
+ # sigs = [n.get_cachedir_csig() for n in children]
+ sigs = [n.get_cachedir_csig() for n in children]
sigs.append(SCons.Util.MD5signature(executor.get_contents()))
sigs.append(self.path)
- self.cachesig = SCons.Util.MD5collect(sigs)
- return self.cachesig
+ result = self.cachesig = SCons.Util.MD5collect(sigs)
+ return result
+
default_fs = None
fd = self.default_filedir
dir, name = os.path.split(fd)
drive, d = os.path.splitdrive(dir)
- if d in ('/', os.sep):
- return p.fs.get_root(drive).dir_on_disk(name)
+ 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:
node = p.entries[norm_name]
except KeyError:
return p.dir_on_disk(name)
- # Once we move to Python 2.2 we can do:
- #if isinstance(node, (Dir, Entry)):
- if isinstance(node, Dir) or isinstance(node, Entry):
+ if isinstance(node, Dir):
+ return node
+ if isinstance(node, Entry):
+ node.must_be_same(Dir)
return node
return None
except KeyError:
pass
- if verbose:
+ if verbose and not callable(verbose):
if not SCons.Util.is_String(verbose):
verbose = "find_file"
- if not callable(verbose):
- verbose = ' %s: ' % verbose
- verbose = lambda s, v=verbose: sys.stdout.write(v + s)
- else:
- verbose = lambda x: x
+ _verbose = u' %s: ' % verbose
+ verbose = lambda s: sys.stdout.write(_verbose + s)
filedir, filename = os.path.split(filename)
if filedir:
# node = p.entries[norm_name]
# except KeyError:
# return p.dir_on_disk(name)
- # # Once we move to Python 2.2 we can do:
- # #if isinstance(node, (Dir, Entry)):
+ # 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 = filter(None, map(filedir_lookup, paths))
self.default_filedir = filedir
- paths = filter(None, map(self.filedir_lookup, paths))
+ paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
result = None
for dir in paths:
- verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
+ if verbose:
+ verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
node, d = dir.srcdir_find_file(filename)
if node:
- verbose("... FOUND '%s' in '%s'\n" % (filename, d))
+ if verbose:
+ verbose("... FOUND '%s' in '%s'\n" % (filename, d))
result = node
break
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: