| """SCons.Util |
| |
| Various utility functions go here. |
| """ |
| # |
| # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 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/Util.py 5134 2010/08/16 23:02:40 bdeegan" |
| |
| import os |
| import sys |
| import copy |
| import re |
| import types |
| |
| from collections import UserDict, UserList, UserString |
| |
| # Don't "from types import ..." these because we need to get at the |
| # types module later to look for UnicodeType. |
| InstanceType = types.InstanceType |
| MethodType = types.MethodType |
| FunctionType = types.FunctionType |
| try: unicode |
| except NameError: UnicodeType = None |
| else: UnicodeType = unicode |
| |
| def dictify(keys, values, result={}): |
| for k, v in zip(keys, values): |
| result[k] = v |
| return result |
| |
| _altsep = os.altsep |
| if _altsep is None and sys.platform == 'win32': |
| # My ActivePython 2.0.1 doesn't set os.altsep! What gives? |
| _altsep = '/' |
| if _altsep: |
| def rightmost_separator(path, sep): |
| return max(path.rfind(sep), path.rfind(_altsep)) |
| else: |
| def rightmost_separator(path, sep): |
| return path.rfind(sep) |
| |
| # First two from the Python Cookbook, just for completeness. |
| # (Yeah, yeah, YAGNI...) |
| def containsAny(str, set): |
| """Check whether sequence str contains ANY of the items in set.""" |
| for c in set: |
| if c in str: return 1 |
| return 0 |
| |
| def containsAll(str, set): |
| """Check whether sequence str contains ALL of the items in set.""" |
| for c in set: |
| if c not in str: return 0 |
| return 1 |
| |
| def containsOnly(str, set): |
| """Check whether sequence str contains ONLY items in set.""" |
| for c in str: |
| if c not in set: return 0 |
| return 1 |
| |
| def splitext(path): |
| "Same as os.path.splitext() but faster." |
| sep = rightmost_separator(path, os.sep) |
| dot = path.rfind('.') |
| # An ext is only real if it has at least one non-digit char |
| if dot > sep and not containsOnly(path[dot:], "0123456789."): |
| return path[:dot],path[dot:] |
| else: |
| return path,"" |
| |
| def updrive(path): |
| """ |
| Make the drive letter (if any) upper case. |
| This is useful because Windows is inconsitent on the case |
| of the drive letter, which can cause inconsistencies when |
| calculating command signatures. |
| """ |
| drive, rest = os.path.splitdrive(path) |
| if drive: |
| path = drive.upper() + rest |
| return path |
| |
| class NodeList(UserList): |
| """This class is almost exactly like a regular list of Nodes |
| (actually it can hold any object), with one important difference. |
| If you try to get an attribute from this list, it will return that |
| attribute from every item in the list. For example: |
| |
| >>> someList = NodeList([ ' foo ', ' bar ' ]) |
| >>> someList.strip() |
| [ 'foo', 'bar' ] |
| """ |
| def __nonzero__(self): |
| return len(self.data) != 0 |
| |
| def __str__(self): |
| return ' '.join(map(str, self.data)) |
| |
| def __iter__(self): |
| return iter(self.data) |
| |
| def __call__(self, *args, **kwargs): |
| result = [x(*args, **kwargs) for x in self.data] |
| return self.__class__(result) |
| |
| def __getattr__(self, name): |
| result = [getattr(x, name) for x in self.data] |
| return self.__class__(result) |
| |
| |
| _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$') |
| |
| def get_environment_var(varstr): |
| """Given a string, first determine if it looks like a reference |
| to a single environment variable, like "$FOO" or "${FOO}". |
| If so, return that variable with no decorations ("FOO"). |
| If not, return None.""" |
| mo=_get_env_var.match(to_String(varstr)) |
| if mo: |
| var = mo.group(1) |
| if var[0] == '{': |
| return var[1:-1] |
| else: |
| return var |
| else: |
| return None |
| |
| class DisplayEngine(object): |
| print_it = True |
| def __call__(self, text, append_newline=1): |
| if not self.print_it: |
| return |
| if append_newline: text = text + '\n' |
| try: |
| sys.stdout.write(unicode(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 |
| # is that the user has press ctrl-c. It this is the case, |
| # then SCons is currently shutdown. We therefore ignore |
| # IOError's here so that SCons can continue and shutdown |
| # properly so that the .sconsign is correctly written |
| # before SCons exits. |
| pass |
| |
| def set_mode(self, mode): |
| self.print_it = mode |
| |
| def render_tree(root, child_func, prune=0, margin=[0], visited={}): |
| """ |
| 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. |
| """ |
| |
| rname = str(root) |
| |
| children = child_func(root) |
| retval = "" |
| for pipe in margin[:-1]: |
| if pipe: |
| retval = retval + "| " |
| else: |
| retval = retval + " " |
| |
| if rname in visited: |
| return retval + "+-[" + rname + "]\n" |
| |
| retval = retval + "+-" + rname + "\n" |
| if not prune: |
| visited = copy.copy(visited) |
| 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.pop() |
| |
| return retval |
| |
| IDX = lambda N: N and 1 or 0 |
| |
| def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}): |
| """ |
| 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. |
| """ |
| |
| rname = str(root) |
| |
| if showtags: |
| |
| if showtags == 2: |
| legend = (' E = exists\n' + |
| ' R = exists in repository only\n' + |
| ' b = implicit builder\n' + |
| ' B = explicit builder\n' + |
| ' S = side effect\n' + |
| ' P = precious\n' + |
| ' A = always build\n' + |
| ' C = current\n' + |
| ' N = no clean\n' + |
| ' H = no cache\n' + |
| '\n') |
| sys.stdout.write(unicode(legend)) |
| |
| tags = ['['] |
| tags.append(' E'[IDX(root.exists())]) |
| tags.append(' R'[IDX(root.rexists() and not root.exists())]) |
| tags.append(' BbB'[[0,1][IDX(root.has_explicit_builder())] + |
| [0,2][IDX(root.has_builder())]]) |
| tags.append(' S'[IDX(root.side_effect)]) |
| tags.append(' P'[IDX(root.precious)]) |
| tags.append(' A'[IDX(root.always_build)]) |
| tags.append(' C'[IDX(root.is_up_to_date())]) |
| tags.append(' N'[IDX(root.noclean)]) |
| tags.append(' H'[IDX(root.nocache)]) |
| tags.append(']') |
| |
| else: |
| tags = [] |
| |
| def MMM(m): |
| return [" ","| "][m] |
| margins = list(map(MMM, margin[:-1])) |
| |
| children = child_func(root) |
| |
| if prune and rname in visited and children: |
| sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + u'\n') |
| return |
| |
| sys.stdout.write(''.join(tags + margins + ['+-', rname]) + u'\n') |
| |
| visited[rname] = 1 |
| |
| if children: |
| margin.append(1) |
| idx = IDX(showtags) |
| for C in children[:-1]: |
| print_tree(C, child_func, prune, idx, margin, visited) |
| margin[-1] = 0 |
| print_tree(children[-1], child_func, prune, idx, margin, visited) |
| margin.pop() |
| |
| |
| |
| # Functions for deciding if things are like various types, mainly to |
| # handle UserDict, UserList and UserString like their underlying types. |
| # |
| # Yes, all of this manual testing breaks polymorphism, and the real |
| # Pythonic way to do all of this would be to just try it and handle the |
| # exception, but handling the exception when it's not the right type is |
| # often too slow. |
| |
| # We are using the following trick to speed up these |
| # functions. Default arguments are used to take a snapshot of the |
| # the global functions and constants used by these functions. This |
| # transforms accesses to global variable into local variables |
| # accesses (i.e. LOAD_FAST instead of LOAD_GLOBAL). |
| |
| DictTypes = (dict, UserDict) |
| ListTypes = (list, UserList) |
| SequenceTypes = (list, tuple, UserList) |
| |
| # Note that profiling data shows a speed-up when comparing |
| # explicitely with str and unicode instead of simply comparing |
| # with basestring. (at least on Python 2.5.1) |
| StringTypes = (str, unicode, UserString) |
| |
| # Empirically, it is faster to check explicitely for str and |
| # unicode than for basestring. |
| BaseStringTypes = (str, unicode) |
| |
| def is_Dict(obj, isinstance=isinstance, DictTypes=DictTypes): |
| return isinstance(obj, DictTypes) |
| |
| def is_List(obj, isinstance=isinstance, ListTypes=ListTypes): |
| return isinstance(obj, ListTypes) |
| |
| def is_Sequence(obj, isinstance=isinstance, SequenceTypes=SequenceTypes): |
| return isinstance(obj, SequenceTypes) |
| |
| def is_Tuple(obj, isinstance=isinstance, tuple=tuple): |
| return isinstance(obj, tuple) |
| |
| def is_String(obj, isinstance=isinstance, StringTypes=StringTypes): |
| return isinstance(obj, StringTypes) |
| |
| def is_Scalar(obj, isinstance=isinstance, StringTypes=StringTypes, SequenceTypes=SequenceTypes): |
| # Profiling shows that there is an impressive speed-up of 2x |
| # when explicitely checking for strings instead of just not |
| # sequence when the argument (i.e. obj) is already a string. |
| # But, if obj is a not string then it is twice as fast to |
| # check only for 'not sequence'. The following code therefore |
| # assumes that the obj argument is a string must of the time. |
| return isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes) |
| |
| def do_flatten(sequence, result, isinstance=isinstance, |
| StringTypes=StringTypes, SequenceTypes=SequenceTypes): |
| for item in sequence: |
| if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes): |
| result.append(item) |
| else: |
| do_flatten(item, result) |
| |
| def flatten(obj, isinstance=isinstance, StringTypes=StringTypes, |
| SequenceTypes=SequenceTypes, do_flatten=do_flatten): |
| """Flatten a sequence to a non-nested list. |
| |
| Flatten() converts either a single scalar or a nested sequence |
| to a non-nested list. Note that flatten() considers strings |
| to be scalars instead of sequences like Python would. |
| """ |
| if isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes): |
| return [obj] |
| result = [] |
| for item in obj: |
| if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes): |
| result.append(item) |
| else: |
| do_flatten(item, result) |
| return result |
| |
| def flatten_sequence(sequence, isinstance=isinstance, StringTypes=StringTypes, |
| SequenceTypes=SequenceTypes, do_flatten=do_flatten): |
| """Flatten a sequence to a non-nested list. |
| |
| Same as flatten(), but it does not handle the single scalar |
| case. This is slightly more efficient when one knows that |
| the sequence to flatten can not be a scalar. |
| """ |
| result = [] |
| for item in sequence: |
| if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes): |
| result.append(item) |
| else: |
| do_flatten(item, result) |
| return result |
| |
| # Generic convert-to-string functions that abstract away whether or |
| # not the Python we're executing has Unicode support. The wrapper |
| # to_String_for_signature() will use a for_signature() method if the |
| # specified object has one. |
| # |
| def to_String(s, |
| isinstance=isinstance, str=str, |
| UserString=UserString, BaseStringTypes=BaseStringTypes): |
| if isinstance(s,BaseStringTypes): |
| # Early out when already a string! |
| return s |
| elif isinstance(s, UserString): |
| # s.data can only be either a unicode or a regular |
| # string. Please see the UserString initializer. |
| return s.data |
| else: |
| return str(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 |
| elif isinstance(s, SequenceTypes): |
| l = [] |
| for e in s: |
| l.append(to_String_for_subst(e)) |
| return ' '.join( s ) |
| elif isinstance(s, UserString): |
| # s.data can only be either a unicode or a regular |
| # string. Please see the UserString initializer. |
| return s.data |
| else: |
| return str(s) |
| |
| 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) |
| else: |
| return f() |
| |
| |
| # The SCons "semi-deep" copy. |
| # |
| # This makes separate copies of lists (including UserList objects) |
| # dictionaries (including UserDict objects) and tuples, but just copies |
| # references to anything else it finds. |
| # |
| # A special case is any object that has a __semi_deepcopy__() method, |
| # which we invoke to create the copy, which is used by the BuilderDict |
| # class because of its extra initialization argument. |
| # |
| # The dispatch table approach used here is a direct rip-off from the |
| # normal Python copy module. |
| |
| _semi_deepcopy_dispatch = d = {} |
| |
| def _semi_deepcopy_dict(x): |
| copy = {} |
| for key, val in x.items(): |
| # The regular Python copy.deepcopy() also deepcopies the key, |
| # as follows: |
| # |
| # copy[semi_deepcopy(key)] = semi_deepcopy(val) |
| # |
| # Doesn't seem like we need to, but we'll comment it just in case. |
| copy[key] = semi_deepcopy(val) |
| return copy |
| d[dict] = _semi_deepcopy_dict |
| |
| def _semi_deepcopy_list(x): |
| return list(map(semi_deepcopy, x)) |
| d[list] = _semi_deepcopy_list |
| |
| def _semi_deepcopy_tuple(x): |
| return tuple(map(semi_deepcopy, x)) |
| d[tuple] = _semi_deepcopy_tuple |
| |
| def semi_deepcopy(x): |
| copier = _semi_deepcopy_dispatch.get(type(x)) |
| if copier: |
| return copier(x) |
| else: |
| if hasattr(x, '__semi_deepcopy__'): |
| return x.__semi_deepcopy__() |
| elif isinstance(x, UserDict): |
| return x.__class__(_semi_deepcopy_dict(x)) |
| elif isinstance(x, UserList): |
| return x.__class__(_semi_deepcopy_list(x)) |
| |
| return x |
| |
| |
| |
| class Proxy(object): |
| """A simple generic Proxy class, forwarding all calls to |
| subject. So, for the benefit of the python newbie, what does |
| this really mean? Well, it means that you can take an object, let's |
| call it 'objA', and wrap it in this Proxy class, with a statement |
| like this |
| |
| proxyObj = Proxy(objA), |
| |
| Then, if in the future, you do something like this |
| |
| x = proxyObj.var1, |
| |
| since Proxy does not have a 'var1' attribute (but presumably objA does), |
| the request actually is equivalent to saying |
| |
| x = objA.var1 |
| |
| Inherit from this class to create a Proxy. |
| |
| Note that, with new-style classes, this does *not* work transparently |
| for Proxy subclasses that use special .__*__() method names, because |
| those names are now bound to the class, not the individual instances. |
| You now need to know in advance which .__*__() method names you want |
| to pass on to the underlying Proxy object, and specifically delegate |
| their calls like this: |
| |
| class Foo(Proxy): |
| __str__ = Delegate('__str__') |
| """ |
| |
| def __init__(self, subject): |
| """Wrap an object as a Proxy object""" |
| self._subject = subject |
| |
| def __getattr__(self, name): |
| """Retrieve an attribute from the wrapped object. If the named |
| attribute doesn't exist, AttributeError is raised""" |
| return getattr(self._subject, name) |
| |
| def get(self): |
| """Retrieve the entire wrapped object""" |
| return self._subject |
| |
| def __cmp__(self, other): |
| if issubclass(other.__class__, self._subject.__class__): |
| return cmp(self._subject, other) |
| return cmp(self.__dict__, other.__dict__) |
| |
| class Delegate(object): |
| """A Python Descriptor class that delegates attribute fetches |
| to an underlying wrapped subject of a Proxy. Typical use: |
| |
| class Foo(Proxy): |
| __str__ = Delegate('__str__') |
| """ |
| def __init__(self, attribute): |
| self.attribute = attribute |
| def __get__(self, obj, cls): |
| if isinstance(obj, cls): |
| return getattr(obj._subject, self.attribute) |
| else: |
| return self |
| |
| # attempt to load the windows registry module: |
| can_read_reg = 0 |
| try: |
| import winreg |
| |
| can_read_reg = 1 |
| hkey_mod = winreg |
| |
| RegOpenKeyEx = winreg.OpenKeyEx |
| RegEnumKey = winreg.EnumKey |
| RegEnumValue = winreg.EnumValue |
| RegQueryValueEx = winreg.QueryValueEx |
| RegError = winreg.error |
| |
| except ImportError: |
| try: |
| import win32api |
| import win32con |
| can_read_reg = 1 |
| hkey_mod = win32con |
| |
| RegOpenKeyEx = win32api.RegOpenKeyEx |
| RegEnumKey = win32api.RegEnumKey |
| RegEnumValue = win32api.RegEnumValue |
| RegQueryValueEx = win32api.RegQueryValueEx |
| RegError = win32api.error |
| |
| except ImportError: |
| class _NoError(Exception): |
| pass |
| RegError = _NoError |
| |
| if can_read_reg: |
| HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT |
| HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE |
| HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER |
| HKEY_USERS = hkey_mod.HKEY_USERS |
| |
| def RegGetValue(root, key): |
| """This utility function returns a value in the registry |
| without having to open the key first. Only available on |
| Windows platforms with a version of Python that can read the |
| registry. Returns the same thing as |
| SCons.Util.RegQueryValueEx, except you just specify the entire |
| path to the value, and don't have to bother opening the key |
| first. So: |
| |
| Instead of: |
| k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, |
| r'SOFTWARE\Microsoft\Windows\CurrentVersion') |
| out = SCons.Util.RegQueryValueEx(k, |
| 'ProgramFilesDir') |
| |
| You can write: |
| out = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, |
| r'SOFTWARE\Microsoft\Windows\CurrentVersion\ProgramFilesDir') |
| """ |
| # I would use os.path.split here, but it's not a filesystem |
| # path... |
| p = key.rfind('\\') + 1 |
| keyp = key[:p-1] # -1 to omit trailing slash |
| val = key[p:] |
| k = RegOpenKeyEx(root, keyp) |
| return RegQueryValueEx(k,val) |
| else: |
| try: |
| e = WindowsError |
| except NameError: |
| # Make sure we have a definition of WindowsError so we can |
| # run platform-independent tests of Windows functionality on |
| # platforms other than Windows. (WindowsError is, in fact, an |
| # OSError subclass on Windows.) |
| class WindowsError(OSError): |
| pass |
| import builtins |
| builtins.WindowsError = WindowsError |
| else: |
| del e |
| |
| HKEY_CLASSES_ROOT = None |
| HKEY_LOCAL_MACHINE = None |
| HKEY_CURRENT_USER = None |
| HKEY_USERS = None |
| |
| def RegGetValue(root, key): |
| raise WindowsError |
| |
| def RegOpenKeyEx(root, key): |
| raise WindowsError |
| |
| if sys.platform == 'win32': |
| |
| def WhereIs(file, path=None, pathext=None, reject=[]): |
| if path is None: |
| try: |
| path = os.environ['PATH'] |
| except KeyError: |
| return None |
| if is_String(path): |
| path = path.split(os.pathsep) |
| if pathext is None: |
| try: |
| pathext = os.environ['PATHEXT'] |
| except KeyError: |
| pathext = '.COM;.EXE;.BAT;.CMD' |
| if is_String(pathext): |
| pathext = pathext.split(os.pathsep) |
| for ext in pathext: |
| if ext.lower() == file[-len(ext):].lower(): |
| pathext = [''] |
| break |
| if not is_List(reject) and not is_Tuple(reject): |
| reject = [reject] |
| for dir in path: |
| f = os.path.join(dir, file) |
| for ext in pathext: |
| fext = f + ext |
| if os.path.isfile(fext): |
| try: |
| reject.index(fext) |
| except ValueError: |
| return os.path.normpath(fext) |
| continue |
| return None |
| |
| elif os.name == 'os2': |
| |
| def WhereIs(file, path=None, pathext=None, reject=[]): |
| if path is None: |
| try: |
| path = os.environ['PATH'] |
| except KeyError: |
| return None |
| if is_String(path): |
| path = path.split(os.pathsep) |
| if pathext is None: |
| pathext = ['.exe', '.cmd'] |
| for ext in pathext: |
| if ext.lower() == file[-len(ext):].lower(): |
| pathext = [''] |
| break |
| if not is_List(reject) and not is_Tuple(reject): |
| reject = [reject] |
| for dir in path: |
| f = os.path.join(dir, file) |
| for ext in pathext: |
| fext = f + ext |
| if os.path.isfile(fext): |
| try: |
| reject.index(fext) |
| except ValueError: |
| return os.path.normpath(fext) |
| continue |
| return None |
| |
| else: |
| |
| def WhereIs(file, path=None, pathext=None, reject=[]): |
| import stat |
| if path is None: |
| try: |
| path = os.environ['PATH'] |
| except KeyError: |
| return None |
| if is_String(path): |
| path = path.split(os.pathsep) |
| if not is_List(reject) and not is_Tuple(reject): |
| reject = [reject] |
| for d in path: |
| f = os.path.join(d, file) |
| if os.path.isfile(f): |
| try: |
| st = os.stat(f) |
| except OSError: |
| # os.stat() raises OSError, not IOError if the file |
| # doesn't exist, so in this case we let IOError get |
| # raised so as to not mask possibly serious disk or |
| # network issues. |
| continue |
| if stat.S_IMODE(st[stat.ST_MODE]) & 0111: |
| try: |
| reject.index(f) |
| except ValueError: |
| return os.path.normpath(f) |
| continue |
| return None |
| |
| 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 |
| and ignoring the rest, to preserve path order), and will |
| os.path.normpath and os.path.normcase all paths to help assure |
| this. This can also handle the case where the given old path |
| variable is a list instead of a string, in which case a list will |
| be returned instead of a string. |
| |
| Example: |
| Old Path: "/foo/bar:/foo" |
| New Path: "/biz/boom:/foo" |
| Result: "/biz/boom:/foo:/foo/bar" |
| |
| If delete_existing is 0, then adding a path that exists will |
| 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 |
| newpath before use. |
| """ |
| |
| orig = oldpath |
| is_list = 1 |
| paths = orig |
| if not is_List(orig) and not is_Tuple(orig): |
| paths = paths.split(sep) |
| is_list = 0 |
| |
| if is_String(newpath): |
| newpaths = newpath.split(sep) |
| elif not is_List(newpath) and not is_Tuple(newpath): |
| newpaths = [ newpath ] # might be a Dir |
| else: |
| newpaths = newpath |
| |
| if canonicalize: |
| newpaths=list(map(canonicalize, newpaths)) |
| |
| if not delete_existing: |
| # 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 |
| # if they're not already in the normpaths list. |
| result = [] |
| normpaths = [] |
| for path in paths: |
| if not path: |
| continue |
| normpath = os.path.normpath(os.path.normcase(path)) |
| if normpath not in normpaths: |
| result.append(path) |
| normpaths.append(normpath) |
| newpaths.reverse() # since we're inserting at the head |
| for path in newpaths: |
| if not path: |
| continue |
| normpath = os.path.normpath(os.path.normcase(path)) |
| if normpath not in normpaths: |
| result.insert(0, path) |
| normpaths.append(normpath) |
| paths = result |
| |
| else: |
| newpaths = newpaths + paths # prepend new paths |
| |
| normpaths = [] |
| paths = [] |
| # now we add them only if they are unique |
| for path in newpaths: |
| normpath = os.path.normpath(os.path.normcase(path)) |
| if path and not normpath in normpaths: |
| paths.append(path) |
| normpaths.append(normpath) |
| |
| if is_list: |
| return paths |
| else: |
| return sep.join(paths) |
| |
| 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 |
| encounters and ignoring the rest, to preserve path order), and |
| will os.path.normpath and os.path.normcase all paths to help |
| assure this. This can also handle the case where the given old |
| path variable is a list instead of a string, in which case a list |
| will be returned instead of a string. |
| |
| Example: |
| Old Path: "/foo/bar:/foo" |
| New Path: "/biz/boom:/foo" |
| Result: "/foo/bar:/biz/boom:/foo" |
| |
| 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 |
| newpath before use. |
| """ |
| |
| orig = oldpath |
| is_list = 1 |
| paths = orig |
| if not is_List(orig) and not is_Tuple(orig): |
| paths = paths.split(sep) |
| is_list = 0 |
| |
| if is_String(newpath): |
| newpaths = newpath.split(sep) |
| elif not is_List(newpath) and not is_Tuple(newpath): |
| newpaths = [ newpath ] # might be a Dir |
| else: |
| newpaths = newpath |
| |
| if canonicalize: |
| newpaths=list(map(canonicalize, newpaths)) |
| |
| if not delete_existing: |
| # add old paths to result, then |
| # add new paths if not already present |
| # (I thought about using a dict for normpaths for speed, |
| # but it's not clear hashing the strings would be faster |
| # than linear searching these typically short lists.) |
| result = [] |
| normpaths = [] |
| for path in paths: |
| if not path: |
| continue |
| result.append(path) |
| normpaths.append(os.path.normpath(os.path.normcase(path))) |
| for path in newpaths: |
| if not path: |
| continue |
| normpath = os.path.normpath(os.path.normcase(path)) |
| if normpath not in normpaths: |
| result.append(path) |
| normpaths.append(normpath) |
| paths = result |
| else: |
| # start w/ new paths, add old ones if not present, |
| # then reverse. |
| newpaths = paths + newpaths # append new paths |
| newpaths.reverse() |
| |
| normpaths = [] |
| paths = [] |
| # now we add them only if they are unique |
| for path in newpaths: |
| normpath = os.path.normpath(os.path.normcase(path)) |
| if path and not normpath in normpaths: |
| paths.append(path) |
| normpaths.append(normpath) |
| paths.reverse() |
| |
| if is_list: |
| return paths |
| else: |
| return sep.join(paths) |
| |
| if sys.platform == 'cygwin': |
| def get_native_path(path): |
| """Transforms an absolute path into a native path for the system. In |
| Cygwin, this converts from a Cygwin path to a Windows one.""" |
| return os.popen('cygpath -w ' + path).read().replace('\n', '') |
| else: |
| def get_native_path(path): |
| """Transforms an absolute path into a native path for the system. |
| Non-Cygwin version, just leave the path alone.""" |
| return path |
| |
| display = DisplayEngine() |
| |
| def Split(arg): |
| if is_List(arg) or is_Tuple(arg): |
| return arg |
| elif is_String(arg): |
| return arg.split() |
| else: |
| return [arg] |
| |
| class CLVar(UserList): |
| """A class for command-line construction variables. |
| |
| This is a list that uses Split() to split an initial string along |
| white-space arguments, and similarly to split any strings that get |
| added. This allows us to Do the Right Thing with Append() and |
| Prepend() (as well as straight Python foo = env['VAR'] + 'arg1 |
| arg2') regardless of whether a user adds a list or a string to a |
| command-line construction variable. |
| """ |
| def __init__(self, seq = []): |
| UserList.__init__(self, Split(seq)) |
| def __add__(self, other): |
| return UserList.__add__(self, CLVar(other)) |
| def __radd__(self, other): |
| return UserList.__radd__(self, CLVar(other)) |
| def __coerce__(self, other): |
| return (self, CLVar(other)) |
| def __str__(self): |
| return ' '.join(self.data) |
| |
| # A dictionary that preserves the order in which items are added. |
| # Submitted by David Benjamin to ActiveState's Python Cookbook web site: |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 |
| # Including fixes/enhancements from the follow-on discussions. |
| class OrderedDict(UserDict): |
| def __init__(self, dict = None): |
| self._keys = [] |
| UserDict.__init__(self, dict) |
| |
| def __delitem__(self, key): |
| UserDict.__delitem__(self, key) |
| self._keys.remove(key) |
| |
| def __setitem__(self, key, item): |
| UserDict.__setitem__(self, key, item) |
| if key not in self._keys: self._keys.append(key) |
| |
| def clear(self): |
| UserDict.clear(self) |
| self._keys = [] |
| |
| def copy(self): |
| dict = OrderedDict() |
| dict.update(self) |
| return dict |
| |
| def items(self): |
| return list(zip(self._keys, list(self.values()))) |
| |
| def keys(self): |
| return self._keys[:] |
| |
| def popitem(self): |
| try: |
| key = self._keys[-1] |
| except IndexError: |
| raise KeyError('dictionary is empty') |
| |
| val = self[key] |
| del self[key] |
| |
| return (key, val) |
| |
| def setdefault(self, key, failobj = None): |
| UserDict.setdefault(self, key, failobj) |
| if key not in self._keys: self._keys.append(key) |
| |
| def update(self, dict): |
| for (key, val) in dict.items(): |
| self.__setitem__(key, val) |
| |
| def values(self): |
| return list(map(self.get, self._keys)) |
| |
| class Selector(OrderedDict): |
| """A callable ordered dictionary that maps file suffixes to |
| dictionary values. We preserve the order in which items are added |
| so that get_suffix() calls always return the first suffix added.""" |
| def __call__(self, env, source, ext=None): |
| if ext is None: |
| try: |
| ext = source[0].suffix |
| except IndexError: |
| ext = "" |
| try: |
| return self[ext] |
| except KeyError: |
| # Try to perform Environment substitution on the keys of |
| # the dictionary before giving up. |
| s_dict = {} |
| for (k,v) in self.items(): |
| if k is not None: |
| s_k = env.subst(k) |
| if s_k in s_dict: |
| # We only raise an error when variables point |
| # to the same suffix. If one suffix is literal |
| # and a variable suffix contains this literal, |
| # the literal wins and we don't raise an error. |
| raise KeyError(s_dict[s_k][0], k, s_k) |
| s_dict[s_k] = (k,v) |
| try: |
| return s_dict[ext][1] |
| except KeyError: |
| try: |
| return self[None] |
| except KeyError: |
| return None |
| |
| |
| if sys.platform == 'cygwin': |
| # On Cygwin, os.path.normcase() lies, so just report back the |
| # fact that the underlying Windows OS is case-insensitive. |
| def case_sensitive_suffixes(s1, s2): |
| return 0 |
| else: |
| def case_sensitive_suffixes(s1, s2): |
| return (os.path.normcase(s1) != os.path.normcase(s2)) |
| |
| def adjustixes(fname, pre, suf, ensure_suffix=False): |
| if pre: |
| path, fn = os.path.split(os.path.normpath(fname)) |
| if fn[:len(pre)] != pre: |
| fname = os.path.join(path, pre + fn) |
| # Only append a suffix if the suffix we're going to add isn't already |
| # there, and if either we've been asked to ensure the specific suffix |
| # is present or there's no suffix on it at all. |
| if suf and fname[-len(suf):] != suf and \ |
| (ensure_suffix or not splitext(fname)[1]): |
| fname = fname + suf |
| return fname |
| |
| |
| |
| # From Tim Peters, |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 |
| # ASPN: Python Cookbook: Remove duplicates from a sequence |
| # (Also in the printed Python Cookbook.) |
| |
| def unique(s): |
| """Return a list of the elements in s, but without duplicates. |
| |
| For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3], |
| unique("abcabc") some permutation of ["a", "b", "c"], and |
| unique(([1, 2], [2, 3], [1, 2])) some permutation of |
| [[2, 3], [1, 2]]. |
| |
| For best speed, all sequence elements should be hashable. Then |
| unique() will usually work in linear time. |
| |
| If not possible, the sequence elements should enjoy a total |
| ordering, and if list(s).sort() doesn't raise TypeError it's |
| assumed that they do enjoy a total ordering. Then unique() will |
| usually work in O(N*log2(N)) time. |
| |
| If that's not possible either, the sequence elements must support |
| equality-testing. Then unique() will usually work in quadratic |
| time. |
| """ |
| |
| n = len(s) |
| if n == 0: |
| return [] |
| |
| # Try using a dict first, as that's the fastest and will usually |
| # work. If it doesn't work, it will usually fail quickly, so it |
| # usually doesn't cost much to *try* it. It requires that all the |
| # sequence elements be hashable, and support equality comparison. |
| u = {} |
| try: |
| for x in s: |
| u[x] = 1 |
| except TypeError: |
| pass # move on to the next method |
| else: |
| return list(u.keys()) |
| del u |
| |
| # We can't hash all the elements. Second fastest is to sort, |
| # which brings the equal elements together; then duplicates are |
| # easy to weed out in a single pass. |
| # NOTE: Python's list.sort() was designed to be efficient in the |
| # presence of many duplicate elements. This isn't true of all |
| # sort functions in all languages or libraries, so this approach |
| # is more effective in Python than it may be elsewhere. |
| try: |
| t = sorted(s) |
| except TypeError: |
| pass # move on to the next method |
| else: |
| assert n > 0 |
| last = t[0] |
| lasti = i = 1 |
| while i < n: |
| if t[i] != last: |
| t[lasti] = last = t[i] |
| lasti = lasti + 1 |
| i = i + 1 |
| return t[:lasti] |
| del t |
| |
| # Brute force is all that's left. |
| u = [] |
| for x in s: |
| if x not in u: |
| u.append(x) |
| return u |
| |
| |
| |
| # From Alex Martelli, |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 |
| # ASPN: Python Cookbook: Remove duplicates from a sequence |
| # First comment, dated 2001/10/13. |
| # (Also in the printed Python Cookbook.) |
| |
| def uniquer(seq, idfun=None): |
| if idfun is None: |
| def idfun(x): return x |
| seen = {} |
| result = [] |
| for item in seq: |
| marker = idfun(item) |
| # in old Python versions: |
| # if seen.has_key(marker) |
| # but in new ones: |
| if marker in seen: continue |
| seen[marker] = 1 |
| result.append(item) |
| return result |
| |
| # A more efficient implementation of Alex's uniquer(), this avoids the |
| # idfun() argument and function-call overhead by assuming that all |
| # items in the sequence are hashable. |
| |
| def uniquer_hashables(seq): |
| seen = {} |
| result = [] |
| for item in seq: |
| #if not item in seen: |
| if item not in seen: |
| seen[item] = 1 |
| result.append(item) |
| return result |
| |
| |
| |
| # Much of the logic here was originally based on recipe 4.9 from the |
| # Python CookBook, but we had to dumb it way down for Python 1.5.2. |
| class LogicalLines(object): |
| |
| def __init__(self, fileobj): |
| self.fileobj = fileobj |
| |
| def readline(self): |
| result = [] |
| while True: |
| line = self.fileobj.readline() |
| if not line: |
| break |
| if line[-2:] == '\\\n': |
| result.append(line[:-2]) |
| else: |
| result.append(line) |
| break |
| return ''.join(result) |
| |
| def readlines(self): |
| result = [] |
| while True: |
| line = self.readline() |
| if not line: |
| break |
| result.append(line) |
| return result |
| |
| |
| |
| class UniqueList(UserList): |
| def __init__(self, seq = []): |
| UserList.__init__(self, seq) |
| self.unique = True |
| def __make_unique(self): |
| if not self.unique: |
| self.data = uniquer_hashables(self.data) |
| self.unique = True |
| def __lt__(self, other): |
| self.__make_unique() |
| return UserList.__lt__(self, other) |
| def __le__(self, other): |
| self.__make_unique() |
| return UserList.__le__(self, other) |
| def __eq__(self, other): |
| self.__make_unique() |
| return UserList.__eq__(self, other) |
| def __ne__(self, other): |
| self.__make_unique() |
| return UserList.__ne__(self, other) |
| def __gt__(self, other): |
| self.__make_unique() |
| return UserList.__gt__(self, other) |
| def __ge__(self, other): |
| self.__make_unique() |
| return UserList.__ge__(self, other) |
| def __cmp__(self, other): |
| self.__make_unique() |
| return UserList.__cmp__(self, other) |
| def __len__(self): |
| self.__make_unique() |
| return UserList.__len__(self) |
| def __getitem__(self, i): |
| self.__make_unique() |
| return UserList.__getitem__(self, i) |
| def __setitem__(self, i, item): |
| UserList.__setitem__(self, i, item) |
| self.unique = False |
| def __getslice__(self, i, j): |
| self.__make_unique() |
| return UserList.__getslice__(self, i, j) |
| def __setslice__(self, i, j, other): |
| UserList.__setslice__(self, i, j, other) |
| self.unique = False |
| def __add__(self, other): |
| result = UserList.__add__(self, other) |
| result.unique = False |
| return result |
| def __radd__(self, other): |
| result = UserList.__radd__(self, other) |
| result.unique = False |
| return result |
| def __iadd__(self, other): |
| result = UserList.__iadd__(self, other) |
| result.unique = False |
| return result |
| def __mul__(self, other): |
| result = UserList.__mul__(self, other) |
| result.unique = False |
| return result |
| def __rmul__(self, other): |
| result = UserList.__rmul__(self, other) |
| result.unique = False |
| return result |
| def __imul__(self, other): |
| result = UserList.__imul__(self, other) |
| result.unique = False |
| return result |
| def append(self, item): |
| UserList.append(self, item) |
| self.unique = False |
| def insert(self, i): |
| UserList.insert(self, i) |
| self.unique = False |
| def count(self, item): |
| self.__make_unique() |
| return UserList.count(self, item) |
| def index(self, item): |
| self.__make_unique() |
| return UserList.index(self, item) |
| def reverse(self): |
| self.__make_unique() |
| UserList.reverse(self) |
| def sort(self, *args, **kwds): |
| self.__make_unique() |
| return UserList.sort(self, *args, **kwds) |
| def extend(self, other): |
| UserList.extend(self, other) |
| self.unique = False |
| |
| |
| class Unbuffered(object): |
| """ |
| A proxy class that wraps a file object, flushing after every write, |
| and delegating everything else to the wrapped object. |
| """ |
| def __init__(self, file): |
| self.file = file |
| self.softspace = 0 ## backward compatibility; not supported in Py3k |
| def write(self, arg): |
| try: |
| self.file.write(arg) |
| self.file.flush() |
| except IOError: |
| # Stdout might be connected to a pipe that has been closed |
| # by now. The most likely reason for the pipe being closed |
| # is that the user has press ctrl-c. It this is the case, |
| # then SCons is currently shutdown. We therefore ignore |
| # IOError's here so that SCons can continue and shutdown |
| # properly so that the .sconsign is correctly written |
| # before SCons exits. |
| pass |
| def __getattr__(self, attr): |
| return getattr(self.file, attr) |
| |
| def make_path_relative(path): |
| """ makes an absolute path name to a relative pathname. |
| """ |
| if os.path.isabs(path): |
| drive_s,path = os.path.splitdrive(path) |
| |
| import re |
| if not drive_s: |
| path=re.compile("/*(.*)").findall(path)[0] |
| else: |
| path=path[1:] |
| |
| assert( not os.path.isabs( path ) ), path |
| return path |
| |
| |
| |
| # The original idea for AddMethod() and RenameFunction() come from the |
| # following post to the ActiveState Python Cookbook: |
| # |
| # ASPN: Python Cookbook : Install bound methods in an instance |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/223613 |
| # |
| # That code was a little fragile, though, so the following changes |
| # have been wrung on it: |
| # |
| # * Switched the installmethod() "object" and "function" arguments, |
| # so the order reflects that the left-hand side is the thing being |
| # "assigned to" and the right-hand side is the value being assigned. |
| # |
| # * Changed explicit type-checking to the "try: klass = object.__class__" |
| # block in installmethod() below so that it still works with the |
| # old-style classes that SCons uses. |
| # |
| # * Replaced the by-hand creation of methods and functions with use of |
| # the "new" module, as alluded to in Alex Martelli's response to the |
| # following Cookbook post: |
| # |
| # ASPN: Python Cookbook : Dynamically added methods to a class |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732 |
| |
| 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 |
| is used by default. |
| 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) |
| """ |
| if name is None: |
| name = function.func_name |
| else: |
| function = RenameFunction(function, name) |
| |
| 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__)) |
| else: |
| # "obj" is a class, so it gets an unbound method. |
| setattr(obj, name, MethodType(function, None, obj)) |
| |
| 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, |
| name, |
| function.func_defaults) |
| |
| |
| md5 = False |
| def MD5signature(s): |
| return str(s) |
| |
| def MD5filesignature(fname, chunksize=65536): |
| f = open(fname, "rb") |
| result = f.read() |
| f.close() |
| return result |
| |
| try: |
| import hashlib |
| except ImportError: |
| pass |
| else: |
| if hasattr(hashlib, 'md5'): |
| md5 = True |
| def MD5signature(s): |
| m = hashlib.md5() |
| m.update(str(s)) |
| return m.hexdigest() |
| |
| def MD5filesignature(fname, chunksize=65536): |
| m = hashlib.md5() |
| f = open(fname, "rb") |
| while True: |
| blck = f.read(chunksize) |
| if not blck: |
| break |
| m.update(str(blck)) |
| f.close() |
| return m.hexdigest() |
| |
| def MD5collect(signatures): |
| """ |
| Collects a list of signatures into an aggregate signature. |
| |
| signatures - a list of signatures |
| returns - the aggregate signature |
| """ |
| if len(signatures) == 1: |
| return signatures[0] |
| else: |
| return MD5signature(', '.join(signatures)) |
| |
| |
| |
| def silent_intern(x): |
| """ |
| Perform sys.intern() on the passed argument and return the result. |
| If the input is ineligible (e.g. a unicode string) the original argument is |
| returned and no exception is thrown. |
| """ |
| try: |
| return sys.intern(x) |
| except TypeError: |
| return x |
| |
| |
| |
| # From Dinu C. Gherman, |
| # Python Cookbook, second edition, recipe 6.17, p. 277. |
| # Also: |
| # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205 |
| # ASPN: Python Cookbook: Null Object Design Pattern |
| |
| #TODO??? class Null(object): |
| class Null(object): |
| """ Null objects always and reliably "do nothing." """ |
| def __new__(cls, *args, **kwargs): |
| if not '_instance' in vars(cls): |
| cls._instance = super(Null, cls).__new__(cls, *args, **kwargs) |
| return cls._instance |
| def __init__(self, *args, **kwargs): |
| pass |
| def __call__(self, *args, **kwargs): |
| return self |
| def __repr__(self): |
| return "Null(0x%08X)" % id(self) |
| def __nonzero__(self): |
| return False |
| def __getattr__(self, name): |
| return self |
| def __setattr__(self, name, value): |
| return self |
| def __delattr__(self, name): |
| return self |
| |
| class NullSeq(Null): |
| def __len__(self): |
| return 0 |
| def __iter__(self): |
| return iter(()) |
| def __getitem__(self, i): |
| return self |
| def __delitem__(self, i): |
| return self |
| def __setitem__(self, i, v): |
| return self |
| |
| |
| del __revision__ |
| |
| # Local Variables: |
| # tab-width:4 |
| # indent-tabs-mode:nil |
| # End: |
| # vim: set expandtab tabstop=4 shiftwidth=4: |