diff options
Diffstat (limited to 'engine/SCons/Util.py')
-rw-r--r-- | engine/SCons/Util.py | 256 |
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 |