summaryrefslogtreecommitdiff
path: root/engine/SCons/Util.py
diff options
context:
space:
mode:
Diffstat (limited to 'engine/SCons/Util.py')
-rw-r--r--engine/SCons/Util.py256
1 files changed, 181 insertions, 75 deletions
diff --git a/engine/SCons/Util.py b/engine/SCons/Util.py
index 2370a9c..2a1604b 100644
--- a/engine/SCons/Util.py
+++ b/engine/SCons/Util.py
@@ -3,7 +3,7 @@
Various utility functions go here.
"""
#
-# Copyright (c) 2001 - 2016 The SCons Foundation
+# Copyright (c) 2001 - 2017 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -24,24 +24,50 @@ Various utility functions go here.
# 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/Util.py rel_2.5.1:3735:9dc6cee5c168 2016/11/03 14:02:02 bdbaddog"
+__revision__ = "src/engine/SCons/Util.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
import os
import sys
import copy
import re
import types
+import codecs
+import pprint
-from collections import UserDict, UserList, UserString
+PY3 = sys.version_info[0] == 3
+
+try:
+ from UserDict import UserDict
+except ImportError as e:
+ from collections import UserDict
+
+try:
+ from UserList import UserList
+except ImportError as e:
+ from collections import UserList
+
+from collections import Iterable
+
+try:
+ from UserString import UserString
+except ImportError as e:
+ from collections import UserString
# Don't "from types import ..." these because we need to get at the
# types module later to look for UnicodeType.
-InstanceType = types.InstanceType
+
+# Below not used?
+# InstanceType = types.InstanceType
+
MethodType = types.MethodType
FunctionType = types.FunctionType
-try: unicode
-except NameError: UnicodeType = None
-else: UnicodeType = unicode
+
+try:
+ unicode
+except NameError:
+ UnicodeType = str
+else:
+ UnicodeType = unicode
def dictify(keys, values, result={}):
for k, v in zip(keys, values):
@@ -111,9 +137,28 @@ class NodeList(UserList):
>>> someList.strip()
[ 'foo', 'bar' ]
"""
+
+# def __init__(self, initlist=None):
+# self.data = []
+# # print("TYPE:%s"%type(initlist))
+# if initlist is not None:
+# # XXX should this accept an arbitrary sequence?
+# if type(initlist) == type(self.data):
+# self.data[:] = initlist
+# elif isinstance(initlist, (UserList, NodeList)):
+# self.data[:] = initlist.data[:]
+# elif isinstance(initlist, Iterable):
+# self.data = list(initlist)
+# else:
+# self.data = [ initlist,]
+
+
def __nonzero__(self):
return len(self.data) != 0
+ def __bool__(self):
+ return self.__nonzero__()
+
def __str__(self):
return ' '.join(map(str, self.data))
@@ -128,6 +173,25 @@ class NodeList(UserList):
result = [getattr(x, name) for x in self.data]
return self.__class__(result)
+ def __getitem__(self, index):
+ """
+ This comes for free on py2,
+ but py3 slices of NodeList are returning a list
+ breaking slicing nodelist and refering to
+ properties and methods on contained object
+ """
+# return self.__class__(self.data[index])
+
+ if isinstance(index, slice):
+ # Expand the slice object using range()
+ # limited by number of items in self.data
+ indices = index.indices(len(self.data))
+ return self.__class__([self[x] for x in
+ range(*indices)])
+ else:
+ # Return one item of the tart
+ return self.data[index]
+
_get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
@@ -153,7 +217,7 @@ class DisplayEngine(object):
return
if append_newline: text = text + '\n'
try:
- sys.stdout.write(unicode(text))
+ sys.stdout.write(UnicodeType(text))
except IOError:
# Stdout might be connected to a pipe that has been closed
# by now. The most likely reason for the pipe being closed
@@ -167,16 +231,17 @@ class DisplayEngine(object):
def set_mode(self, mode):
self.print_it = mode
+
def render_tree(root, child_func, prune=0, margin=[0], visited=None):
"""
Render a tree of nodes into an ASCII tree view.
- root - the root node of the tree
- child_func - the function called to get the children of a node
- prune - don't visit the same node twice
- margin - the format of the left margin to use for children of root.
- 1 results in a pipe, and 0 results in no pipe.
- visited - a dictionary of visited nodes in the current branch if not prune,
- or in the whole tree if prune.
+
+ :Parameters:
+ - `root`: the root node of the tree
+ - `child_func`: the function called to get the children of a node
+ - `prune`: don't visit the same node twice
+ - `margin`: the format of the left margin to use for children of root. 1 results in a pipe, and 0 results in no pipe.
+ - `visited`: a dictionary of visited nodes in the current branch if not prune, or in the whole tree if prune.
"""
rname = str(root)
@@ -202,33 +267,33 @@ def render_tree(root, child_func, prune=0, margin=[0], visited=None):
visited[rname] = 1
for i in range(len(children)):
- margin.append(i<len(children)-1)
- retval = retval + render_tree(children[i], child_func, prune, margin, visited
-)
+ margin.append(i < len(children)-1)
+ retval = retval + render_tree(children[i], child_func, prune, margin, visited)
margin.pop()
return retval
IDX = lambda N: N and 1 or 0
+
def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
"""
Print a tree of nodes. This is like render_tree, except it prints
lines directly instead of creating a string representation in memory,
so that huge trees can be printed.
- root - the root node of the tree
- child_func - the function called to get the children of a node
- prune - don't visit the same node twice
- showtags - print status information to the left of each node line
- margin - the format of the left margin to use for children of root.
- 1 results in a pipe, and 0 results in no pipe.
- visited - a dictionary of visited nodes in the current branch if not prune,
- or in the whole tree if prune.
+ :Parameters:
+ - `root` - the root node of the tree
+ - `child_func` - the function called to get the children of a node
+ - `prune` - don't visit the same node twice
+ - `showtags` - print status information to the left of each node line
+ - `margin` - the format of the left margin to use for children of root. 1 results in a pipe, and 0 results in no pipe.
+ - `visited` - a dictionary of visited nodes in the current branch if not prune, or in the whole tree if prune.
"""
rname = str(root)
-
+
+
# Initialize 'visited' dict, if required
if visited is None:
visited = {}
@@ -247,7 +312,7 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
' N = no clean\n' +
' H = no cache\n' +
'\n')
- sys.stdout.write(unicode(legend))
+ sys.stdout.write(legend)
tags = ['[']
tags.append(' E'[IDX(root.exists())])
@@ -272,10 +337,10 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
children = child_func(root)
if prune and rname in visited and children:
- sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + u'\n')
+ sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + '\n')
return
- sys.stdout.write(''.join(tags + margins + ['+-', rname]) + u'\n')
+ sys.stdout.write(''.join(tags + margins + ['+-', rname]) + '\n')
visited[rname] = 1
@@ -311,11 +376,17 @@ SequenceTypes = (list, tuple, UserList)
# Note that profiling data shows a speed-up when comparing
# explicitly with str and unicode instead of simply comparing
# with basestring. (at least on Python 2.5.1)
-StringTypes = (str, unicode, UserString)
+try:
+ StringTypes = (str, unicode, UserString)
+except NameError:
+ StringTypes = (str, UserString)
# Empirically, it is faster to check explicitly for str and
# unicode than for basestring.
-BaseStringTypes = (str, unicode)
+try:
+ BaseStringTypes = (str, unicode)
+except NameError:
+ BaseStringTypes = (str)
def is_Dict(obj, isinstance=isinstance, DictTypes=DictTypes):
return isinstance(obj, DictTypes)
@@ -341,7 +412,7 @@ def is_Scalar(obj, isinstance=isinstance, StringTypes=StringTypes, SequenceTypes
# assumes that the obj argument is a string most of the time.
return isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes)
-def do_flatten(sequence, result, isinstance=isinstance,
+def do_flatten(sequence, result, isinstance=isinstance,
StringTypes=StringTypes, SequenceTypes=SequenceTypes):
for item in sequence:
if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes):
@@ -349,7 +420,7 @@ def do_flatten(sequence, result, isinstance=isinstance,
else:
do_flatten(item, result)
-def flatten(obj, isinstance=isinstance, StringTypes=StringTypes,
+def flatten(obj, isinstance=isinstance, StringTypes=StringTypes,
SequenceTypes=SequenceTypes, do_flatten=do_flatten):
"""Flatten a sequence to a non-nested list.
@@ -367,7 +438,7 @@ def flatten(obj, isinstance=isinstance, StringTypes=StringTypes,
do_flatten(item, result)
return result
-def flatten_sequence(sequence, isinstance=isinstance, StringTypes=StringTypes,
+def flatten_sequence(sequence, isinstance=isinstance, StringTypes=StringTypes,
SequenceTypes=SequenceTypes, do_flatten=do_flatten):
"""Flatten a sequence to a non-nested list.
@@ -388,7 +459,7 @@ def flatten_sequence(sequence, isinstance=isinstance, StringTypes=StringTypes,
# to_String_for_signature() will use a for_signature() method if the
# specified object has one.
#
-def to_String(s,
+def to_String(s,
isinstance=isinstance, str=str,
UserString=UserString, BaseStringTypes=BaseStringTypes):
if isinstance(s,BaseStringTypes):
@@ -401,11 +472,11 @@ def to_String(s,
else:
return str(s)
-def to_String_for_subst(s,
+def to_String_for_subst(s,
isinstance=isinstance, str=str, to_String=to_String,
BaseStringTypes=BaseStringTypes, SequenceTypes=SequenceTypes,
UserString=UserString):
-
+
# Note that the test cases are sorted by order of probability.
if isinstance(s, BaseStringTypes):
return s
@@ -421,12 +492,18 @@ def to_String_for_subst(s,
else:
return str(s)
-def to_String_for_signature(obj, to_String_for_subst=to_String_for_subst,
+def to_String_for_signature(obj, to_String_for_subst=to_String_for_subst,
AttributeError=AttributeError):
try:
f = obj.for_signature
except AttributeError:
- return to_String_for_subst(obj)
+ if isinstance(obj, dict):
+ # pprint will output dictionary in key sorted order
+ # with py3.5 the order was randomized. In general depending on dictionary order
+ # which was undefined until py3.6 (where it's by insertion order) was not wise.
+ return pprint.pformat(obj, width=1000000)
+ else:
+ return to_String_for_subst(obj)
else:
return f()
@@ -479,7 +556,7 @@ def semi_deepcopy(x):
return x.__class__(semi_deepcopy_dict(x))
elif isinstance(x, UserList):
return x.__class__(_semi_deepcopy_list(x))
-
+
return x
@@ -527,10 +604,10 @@ class Proxy(object):
"""Retrieve the entire wrapped object"""
return self._subject
- def __cmp__(self, other):
+ def __eq__(self, other):
if issubclass(other.__class__, self._subject.__class__):
- return cmp(self._subject, other)
- return cmp(self.__dict__, other.__dict__)
+ return self._subject == other
+ return self.__dict__ == other.__dict__
class Delegate(object):
"""A Python Descriptor class that delegates attribute fetches
@@ -725,7 +802,7 @@ else:
# raised so as to not mask possibly serious disk or
# network issues.
continue
- if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+ if stat.S_IMODE(st[stat.ST_MODE]) & 0o111:
try:
reject.index(f)
except ValueError:
@@ -733,7 +810,7 @@ else:
continue
return None
-def PrependPath(oldpath, newpath, sep = os.pathsep,
+def PrependPath(oldpath, newpath, sep = os.pathsep,
delete_existing=1, canonicalize=None):
"""This prepends newpath elements to the given oldpath. Will only
add any particular path once (leaving the first one it encounters
@@ -752,7 +829,7 @@ def PrependPath(oldpath, newpath, sep = os.pathsep,
not move it to the beginning; it will stay where it is in the
list.
- If canonicalize is not None, it is applied to each element of
+ If canonicalize is not None, it is applied to each element of
newpath before use.
"""
@@ -774,7 +851,7 @@ def PrependPath(oldpath, newpath, sep = os.pathsep,
newpaths=list(map(canonicalize, newpaths))
if not delete_existing:
- # First uniquify the old paths, making sure to
+ # First uniquify the old paths, making sure to
# preserve the first instance (in Unix/Linux,
# the first one wins), and remembering them in normpaths.
# Then insert the new paths at the head of the list
@@ -815,7 +892,7 @@ def PrependPath(oldpath, newpath, sep = os.pathsep,
else:
return sep.join(paths)
-def AppendPath(oldpath, newpath, sep = os.pathsep,
+def AppendPath(oldpath, newpath, sep = os.pathsep,
delete_existing=1, canonicalize=None):
"""This appends new path elements to the given old path. Will
only add any particular path once (leaving the last one it
@@ -833,7 +910,7 @@ def AppendPath(oldpath, newpath, sep = os.pathsep,
If delete_existing is 0, then adding a path that exists
will not move it to the end; it will stay where it is in the list.
- If canonicalize is not None, it is applied to each element of
+ If canonicalize is not None, it is applied to each element of
newpath before use.
"""
@@ -1206,11 +1283,11 @@ def logical_lines(physical_lines, joiner=''.join):
class LogicalLines(object):
""" Wrapper class for the logical_lines method.
-
+
Allows us to read all "logical" lines at once from a
given file object.
"""
-
+
def __init__(self, fileobj):
self.fileobj = fileobj
@@ -1377,50 +1454,61 @@ def make_path_relative(path):
def AddMethod(obj, function, name=None):
"""
- Adds either a bound method to an instance or an unbound method to
- a class. If name is ommited the name of the specified function
+ Adds either a bound method to an instance or the function itself (or an unbound method in Python 2) to a class.
+ If name is ommited the name of the specified function
is used by default.
- Example:
- a = A()
- def f(self, x, y):
+
+ Example::
+
+ a = A()
+ def f(self, x, y):
self.z = x + y
- AddMethod(f, A, "add")
- a.add(2, 4)
- print a.z
- AddMethod(lambda self, i: self.l[i], a, "listIndex")
- print a.listIndex(5)
+ AddMethod(f, A, "add")
+ a.add(2, 4)
+ print(a.z)
+ AddMethod(lambda self, i: self.l[i], a, "listIndex")
+ print(a.listIndex(5))
"""
if name is None:
- name = function.func_name
+ name = function.__name__
else:
function = RenameFunction(function, name)
+ # Note the Python version checks - WLB
+ # Python 3.3 dropped the 3rd parameter from types.MethodType
if hasattr(obj, '__class__') and obj.__class__ is not type:
# "obj" is an instance, so it gets a bound method.
- setattr(obj, name, MethodType(function, obj, obj.__class__))
+ if sys.version_info[:2] > (3, 2):
+ method = MethodType(function, obj)
+ else:
+ method = MethodType(function, obj, obj.__class__)
else:
- # "obj" is a class, so it gets an unbound method.
- setattr(obj, name, MethodType(function, None, obj))
+ # Handle classes
+ method = function
+
+ setattr(obj, name, method)
def RenameFunction(function, name):
"""
Returns a function identical to the specified function, but with
the specified name.
"""
- return FunctionType(function.func_code,
- function.func_globals,
+ return FunctionType(function.__code__,
+ function.__globals__,
name,
- function.func_defaults)
+ function.__defaults__)
md5 = False
+
+
def MD5signature(s):
return str(s)
+
def MD5filesignature(fname, chunksize=65536):
- f = open(fname, "rb")
- result = f.read()
- f.close()
+ with open(fname, "rb") as f:
+ result = f.read()
return result
try:
@@ -1430,9 +1518,15 @@ except ImportError:
else:
if hasattr(hashlib, 'md5'):
md5 = True
+
def MD5signature(s):
m = hashlib.md5()
- m.update(str(s))
+
+ try:
+ m.update(to_bytes(s))
+ except TypeError as e:
+ m.update(to_bytes(str(s)))
+
return m.hexdigest()
def MD5filesignature(fname, chunksize=65536):
@@ -1442,10 +1536,10 @@ else:
blck = f.read(chunksize)
if not blck:
break
- m.update(str(blck))
+ m.update(to_bytes(blck))
f.close()
return m.hexdigest()
-
+
def MD5collect(signatures):
"""
Collects a list of signatures into an aggregate signature.
@@ -1494,6 +1588,8 @@ class Null(object):
return "Null(0x%08X)" % id(self)
def __nonzero__(self):
return False
+ def __bool__(self):
+ return False
def __getattr__(self, name):
return self
def __setattr__(self, name, value):
@@ -1516,6 +1612,16 @@ class NullSeq(Null):
del __revision__
+def to_bytes (s):
+ if isinstance (s, (bytes, bytearray)) or bytes is str:
+ return s
+ return bytes (s, 'utf-8')
+
+def to_str (s):
+ if bytes is str or is_String(s):
+ return s
+ return str (s, 'utf-8')
+
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil