Include waf as an extracted source directory, rather than as a one-in-a-file script.
[ira/wip.git] / buildtools / wafadmin / Utils.py
diff --git a/buildtools/wafadmin/Utils.py b/buildtools/wafadmin/Utils.py
new file mode 100644 (file)
index 0000000..41dad57
--- /dev/null
@@ -0,0 +1,726 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005 (ita)
+
+"""
+Utilities, the stable ones are the following:
+
+* h_file: compute a unique value for a file (hash), it uses
+  the module fnv if it is installed (see waf/utils/fnv & http://code.google.com/p/waf/wiki/FAQ)
+  else, md5 (see the python docs)
+
+  For large projects (projects with more than 15000 files) or slow hard disks and filesystems (HFS)
+  it is possible to use a hashing based on the path and the size (may give broken cache results)
+  The method h_file MUST raise an OSError if the file is a folder
+
+       import stat
+       def h_file(filename):
+               st = os.stat(filename)
+               if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file')
+               m = Utils.md5()
+               m.update(str(st.st_mtime))
+               m.update(str(st.st_size))
+               m.update(filename)
+               return m.digest()
+
+       To replace the function in your project, use something like this:
+       import Utils
+       Utils.h_file = h_file
+
+* h_list
+* h_fun
+* get_term_cols
+* ordered_dict
+
+"""
+
+import os, sys, imp, string, errno, traceback, inspect, re, shutil, datetime, gc
+
+# In python 3.0 we can get rid of all this
+try: from UserDict import UserDict
+except ImportError: from collections import UserDict
+if sys.hexversion >= 0x2060000 or os.name == 'java':
+       import subprocess as pproc
+else:
+       import pproc
+import Logs
+from Constants import *
+
+try:
+       from collections import deque
+except ImportError:
+       class deque(list):
+               def popleft(self):
+                       return self.pop(0)
+
+is_win32 = sys.platform == 'win32'
+
+try:
+       # defaultdict in python 2.5
+       from collections import defaultdict as DefaultDict
+except ImportError:
+       class DefaultDict(dict):
+               def __init__(self, default_factory):
+                       super(DefaultDict, self).__init__()
+                       self.default_factory = default_factory
+               def __getitem__(self, key):
+                       try:
+                               return super(DefaultDict, self).__getitem__(key)
+                       except KeyError:
+                               value = self.default_factory()
+                               self[key] = value
+                               return value
+
+class WafError(Exception):
+       def __init__(self, *args):
+               self.args = args
+               try:
+                       self.stack = traceback.extract_stack()
+               except:
+                       pass
+               Exception.__init__(self, *args)
+       def __str__(self):
+               return str(len(self.args) == 1 and self.args[0] or self.args)
+
+class WscriptError(WafError):
+       def __init__(self, message, wscript_file=None):
+               if wscript_file:
+                       self.wscript_file = wscript_file
+                       self.wscript_line = None
+               else:
+                       try:
+                               (self.wscript_file, self.wscript_line) = self.locate_error()
+                       except:
+                               (self.wscript_file, self.wscript_line) = (None, None)
+
+               msg_file_line = ''
+               if self.wscript_file:
+                       msg_file_line = "%s:" % self.wscript_file
+                       if self.wscript_line:
+                               msg_file_line += "%s:" % self.wscript_line
+               err_message = "%s error: %s" % (msg_file_line, message)
+               WafError.__init__(self, err_message)
+
+       def locate_error(self):
+               stack = traceback.extract_stack()
+               stack.reverse()
+               for frame in stack:
+                       file_name = os.path.basename(frame[0])
+                       is_wscript = (file_name == WSCRIPT_FILE or file_name == WSCRIPT_BUILD_FILE)
+                       if is_wscript:
+                               return (frame[0], frame[1])
+               return (None, None)
+
+indicator = is_win32 and '\x1b[A\x1b[K%s%s%s\r' or '\x1b[K%s%s%s\r'
+
+try:
+       from fnv import new as md5
+       import Constants
+       Constants.SIG_NIL = 'signofnv'
+
+       def h_file(filename):
+               m = md5()
+               try:
+                       m.hfile(filename)
+                       x = m.digest()
+                       if x is None: raise OSError("not a file")
+                       return x
+               except SystemError:
+                       raise OSError("not a file" + filename)
+
+except ImportError:
+       try:
+               try:
+                       from hashlib import md5
+               except ImportError:
+                       from md5 import md5
+
+               def h_file(filename):
+                       f = open(filename, 'rb')
+                       m = md5()
+                       while (filename):
+                               filename = f.read(100000)
+                               m.update(filename)
+                       f.close()
+                       return m.digest()
+       except ImportError:
+               # portability fixes may be added elsewhere (although, md5 should be everywhere by now)
+               md5 = None
+
+class ordered_dict(UserDict):
+       def __init__(self, dict = None):
+               self.allkeys = []
+               UserDict.__init__(self, dict)
+
+       def __delitem__(self, key):
+               self.allkeys.remove(key)
+               UserDict.__delitem__(self, key)
+
+       def __setitem__(self, key, item):
+               if key not in self.allkeys: self.allkeys.append(key)
+               UserDict.__setitem__(self, key, item)
+
+def exec_command(s, **kw):
+       if 'log' in kw:
+               kw['stdout'] = kw['stderr'] = kw['log']
+               del(kw['log'])
+       kw['shell'] = isinstance(s, str)
+
+       try:
+               proc = pproc.Popen(s, **kw)
+               return proc.wait()
+       except OSError:
+               return -1
+
+if is_win32:
+       def exec_command(s, **kw):
+               if 'log' in kw:
+                       kw['stdout'] = kw['stderr'] = kw['log']
+                       del(kw['log'])
+               kw['shell'] = isinstance(s, str)
+
+               if len(s) > 2000:
+                       startupinfo = pproc.STARTUPINFO()
+                       startupinfo.dwFlags |= pproc.STARTF_USESHOWWINDOW
+                       kw['startupinfo'] = startupinfo
+
+               try:
+                       if 'stdout' not in kw:
+                               kw['stdout'] = pproc.PIPE
+                               kw['stderr'] = pproc.PIPE
+                               kw['universal_newlines'] = True
+                               proc = pproc.Popen(s,**kw)
+                               (stdout, stderr) = proc.communicate()
+                               Logs.info(stdout)
+                               if stderr:
+                                       Logs.error(stderr)
+                               return proc.returncode
+                       else:
+                               proc = pproc.Popen(s,**kw)
+                               return proc.wait()
+               except OSError:
+                       return -1
+
+listdir = os.listdir
+if is_win32:
+       def listdir_win32(s):
+               if re.match('^[A-Za-z]:$', s):
+                       # os.path.isdir fails if s contains only the drive name... (x:)
+                       s += os.sep
+               if not os.path.isdir(s):
+                       e = OSError()
+                       e.errno = errno.ENOENT
+                       raise e
+               return os.listdir(s)
+       listdir = listdir_win32
+
+def waf_version(mini = 0x010000, maxi = 0x100000):
+       "Halts if the waf version is wrong"
+       ver = HEXVERSION
+       try: min_val = mini + 0
+       except TypeError: min_val = int(mini.replace('.', '0'), 16)
+
+       if min_val > ver:
+               Logs.error("waf version should be at least %s (%s found)" % (mini, ver))
+               sys.exit(1)
+
+       try: max_val = maxi + 0
+       except TypeError: max_val = int(maxi.replace('.', '0'), 16)
+
+       if max_val < ver:
+               Logs.error("waf version should be at most %s (%s found)" % (maxi, ver))
+               sys.exit(1)
+
+def python_24_guard():
+       if sys.hexversion < 0x20400f0 or sys.hexversion >= 0x3000000:
+               raise ImportError("Waf requires Python >= 2.3 but the raw source requires Python 2.4, 2.5 or 2.6")
+
+def ex_stack():
+       exc_type, exc_value, tb = sys.exc_info()
+       if Logs.verbose > 1:
+               exc_lines = traceback.format_exception(exc_type, exc_value, tb)
+               return ''.join(exc_lines)
+       return str(exc_value)
+
+def to_list(sth):
+       if isinstance(sth, str):
+               return sth.split()
+       else:
+               return sth
+
+g_loaded_modules = {}
+"index modules by absolute path"
+
+g_module=None
+"the main module is special"
+
+def load_module(file_path, name=WSCRIPT_FILE):
+       "this function requires an absolute path"
+       try:
+               return g_loaded_modules[file_path]
+       except KeyError:
+               pass
+
+       module = imp.new_module(name)
+
+       try:
+               code = readf(file_path, m='rU')
+       except (IOError, OSError):
+               raise WscriptError('Could not read the file %r' % file_path)
+
+       module.waf_hash_val = code
+
+       dt = os.path.dirname(file_path)
+       sys.path.insert(0, dt)
+       try:
+               exec(compile(code, file_path, 'exec'), module.__dict__)
+       except Exception:
+               exc_type, exc_value, tb = sys.exc_info()
+               raise WscriptError("".join(traceback.format_exception(exc_type, exc_value, tb)), file_path)
+       sys.path.remove(dt)
+
+       g_loaded_modules[file_path] = module
+
+       return module
+
+def set_main_module(file_path):
+       "Load custom options, if defined"
+       global g_module
+       g_module = load_module(file_path, 'wscript_main')
+       g_module.root_path = file_path
+
+       try:
+               g_module.APPNAME
+       except:
+               g_module.APPNAME = 'noname'
+       try:
+               g_module.VERSION
+       except:
+               g_module.VERSION = '1.0'
+
+       # note: to register the module globally, use the following:
+       # sys.modules['wscript_main'] = g_module
+
+def to_hashtable(s):
+       "used for importing env files"
+       tbl = {}
+       lst = s.split('\n')
+       for line in lst:
+               if not line: continue
+               mems = line.split('=')
+               tbl[mems[0]] = mems[1]
+       return tbl
+
+def get_term_cols():
+       "console width"
+       return 80
+try:
+       import struct, fcntl, termios
+except ImportError:
+       pass
+else:
+       if Logs.got_tty:
+               def myfun():
+                       dummy_lines, cols = struct.unpack("HHHH", \
+                       fcntl.ioctl(sys.stderr.fileno(),termios.TIOCGWINSZ , \
+                       struct.pack("HHHH", 0, 0, 0, 0)))[:2]
+                       return cols
+               # we actually try the function once to see if it is suitable
+               try:
+                       myfun()
+               except:
+                       pass
+               else:
+                       get_term_cols = myfun
+
+rot_idx = 0
+rot_chr = ['\\', '|', '/', '-']
+"the rotation character in the progress bar"
+
+
+def split_path(path):
+       return path.split('/')
+
+def split_path_cygwin(path):
+       if path.startswith('//'):
+               ret = path.split('/')[2:]
+               ret[0] = '/' + ret[0]
+               return ret
+       return path.split('/')
+
+re_sp = re.compile('[/\\\\]')
+def split_path_win32(path):
+       if path.startswith('\\\\'):
+               ret = re.split(re_sp, path)[2:]
+               ret[0] = '\\' + ret[0]
+               return ret
+       return re.split(re_sp, path)
+
+if sys.platform == 'cygwin':
+       split_path = split_path_cygwin
+elif is_win32:
+       split_path = split_path_win32
+
+def copy_attrs(orig, dest, names, only_if_set=False):
+       for a in to_list(names):
+               u = getattr(orig, a, ())
+               if u or not only_if_set:
+                       setattr(dest, a, u)
+
+def def_attrs(cls, **kw):
+       '''
+       set attributes for class.
+       @param cls [any class]: the class to update the given attributes in.
+       @param kw [dictionary]: dictionary of attributes names and values.
+
+       if the given class hasn't one (or more) of these attributes, add the attribute with its value to the class.
+       '''
+       for k, v in kw.iteritems():
+               if not hasattr(cls, k):
+                       setattr(cls, k, v)
+
+def quote_define_name(path):
+       fu = re.compile("[^a-zA-Z0-9]").sub("_", path)
+       fu = fu.upper()
+       return fu
+
+def quote_whitespace(path):
+       return (path.strip().find(' ') > 0 and '"%s"' % path or path).replace('""', '"')
+
+def trimquotes(s):
+       if not s: return ''
+       s = s.rstrip()
+       if s[0] == "'" and s[-1] == "'": return s[1:-1]
+       return s
+
+def h_list(lst):
+       m = md5()
+       m.update(str(lst))
+       return m.digest()
+
+def h_fun(fun):
+       try:
+               return fun.code
+       except AttributeError:
+               try:
+                       h = inspect.getsource(fun)
+               except IOError:
+                       h = "nocode"
+               try:
+                       fun.code = h
+               except AttributeError:
+                       pass
+               return h
+
+def pprint(col, str, label='', sep='\n'):
+       "print messages in color"
+       sys.stderr.write("%s%s%s %s%s" % (Logs.colors(col), str, Logs.colors.NORMAL, label, sep))
+
+def check_dir(dir):
+       """If a folder doesn't exists, create it."""
+       try:
+               os.stat(dir)
+       except OSError:
+               try:
+                       os.makedirs(dir)
+               except OSError, e:
+                       raise WafError("Cannot create folder '%s' (original error: %s)" % (dir, e))
+
+def cmd_output(cmd, **kw):
+
+       silent = False
+       if 'silent' in kw:
+               silent = kw['silent']
+               del(kw['silent'])
+
+       if 'e' in kw:
+               tmp = kw['e']
+               del(kw['e'])
+               kw['env'] = tmp
+
+       kw['shell'] = isinstance(cmd, str)
+       kw['stdout'] = pproc.PIPE
+       if silent:
+               kw['stderr'] = pproc.PIPE
+
+       try:
+               p = pproc.Popen(cmd, **kw)
+               output = p.communicate()[0]
+       except OSError, e:
+               raise ValueError(str(e))
+
+       if p.returncode:
+               if not silent:
+                       msg = "command execution failed: %s -> %r" % (cmd, str(output))
+                       raise ValueError(msg)
+               output = ''
+       return output
+
+reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
+def subst_vars(expr, params):
+       "substitute ${PREFIX}/bin in /usr/local/bin"
+       def repl_var(m):
+               if m.group(1):
+                       return '\\'
+               if m.group(2):
+                       return '$'
+               try:
+                       # environments may contain lists
+                       return params.get_flat(m.group(3))
+               except AttributeError:
+                       return params[m.group(3)]
+       return reg_subst.sub(repl_var, expr)
+
+def unversioned_sys_platform_to_binary_format(unversioned_sys_platform):
+       "infers the binary format from the unversioned_sys_platform name."
+
+       if unversioned_sys_platform in ('linux', 'freebsd', 'netbsd', 'openbsd', 'sunos', 'gnu'):
+               return 'elf'
+       elif unversioned_sys_platform == 'darwin':
+               return 'mac-o'
+       elif unversioned_sys_platform in ('win32', 'cygwin', 'uwin', 'msys'):
+               return 'pe'
+       # TODO we assume all other operating systems are elf, which is not true.
+       # we may set this to 'unknown' and have ccroot and other tools handle the case "gracefully" (whatever that means).
+       return 'elf'
+
+def unversioned_sys_platform():
+       """returns an unversioned name from sys.platform.
+       sys.plaform is not very well defined and depends directly on the python source tree.
+       The version appended to the names is unreliable as it's taken from the build environment at the time python was built,
+       i.e., it's possible to get freebsd7 on a freebsd8 system.
+       So we remove the version from the name, except for special cases where the os has a stupid name like os2 or win32.
+       Some possible values of sys.platform are, amongst others:
+               aix3 aix4 atheos beos5 darwin freebsd2 freebsd3 freebsd4 freebsd5 freebsd6 freebsd7
+               generic gnu0 irix5 irix6 linux2 mac netbsd1 next3 os2emx riscos sunos5 unixware7
+       Investigating the python source tree may reveal more values.
+       """
+       s = sys.platform
+       if s == 'java':
+               # The real OS is hidden under the JVM.
+               from java.lang import System
+               s = System.getProperty('os.name')
+               # see http://lopica.sourceforge.net/os.html for a list of possible values
+               if s == 'Mac OS X':
+                       return 'darwin'
+               elif s.startswith('Windows '):
+                       return 'win32'
+               elif s == 'OS/2':
+                       return 'os2'
+               elif s == 'HP-UX':
+                       return 'hpux'
+               elif s in ('SunOS', 'Solaris'):
+                       return 'sunos'
+               else: s = s.lower()
+       if s == 'win32' or s.endswith('os2') and s != 'sunos2': return s
+       return re.split('\d+$', s)[0]
+
+#@deprecated('use unversioned_sys_platform instead')
+def detect_platform():
+       """this function has been in the Utils module for some time.
+       It's hard to guess what people have used it for.
+       It seems its goal is to return an unversionned sys.platform, but it's not handling all platforms.
+       For example, the version is not removed on freebsd and netbsd, amongst others.
+       """
+       s = sys.platform
+
+       # known POSIX
+       for x in 'cygwin linux irix sunos hpux aix darwin gnu'.split():
+               # sys.platform may be linux2
+               if s.find(x) >= 0:
+                       return x
+
+       # unknown POSIX
+       if os.name in 'posix java os2'.split():
+               return os.name
+
+       return s
+
+def load_tool(tool, tooldir=None):
+       '''
+       load_tool: import a Python module, optionally using several directories.
+       @param tool [string]: name of tool to import.
+       @param tooldir [list]: directories to look for the tool.
+       @return: the loaded module.
+
+       Warning: this function is not thread-safe: plays with sys.path,
+                                        so must run in sequence.
+       '''
+       if tooldir:
+               assert isinstance(tooldir, list)
+               sys.path = tooldir + sys.path
+       else:
+               tooldir = []
+       try:
+               return __import__(tool)
+       finally:
+               for dt in tooldir:
+                       sys.path.remove(dt)
+
+def readf(fname, m='r'):
+       "get the contents of a file, it is not used anywhere for the moment"
+       f = open(fname, m)
+       try:
+               txt = f.read()
+       finally:
+               f.close()
+       return txt
+
+def nada(*k, **kw):
+       """A function that does nothing"""
+       pass
+
+def diff_path(top, subdir):
+       """difference between two absolute paths"""
+       top = os.path.normpath(top).replace('\\', '/').split('/')
+       subdir = os.path.normpath(subdir).replace('\\', '/').split('/')
+       if len(top) == len(subdir): return ''
+       diff = subdir[len(top) - len(subdir):]
+       return os.path.join(*diff)
+
+class Context(object):
+       """A base class for commands to be executed from Waf scripts"""
+
+       def set_curdir(self, dir):
+               self.curdir_ = dir
+
+       def get_curdir(self):
+               try:
+                       return self.curdir_
+               except AttributeError:
+                       self.curdir_ = os.getcwd()
+                       return self.get_curdir()
+
+       curdir = property(get_curdir, set_curdir)
+
+       def recurse(self, dirs, name=''):
+               """The function for calling scripts from folders, it tries to call wscript + function_name
+               and if that file does not exist, it will call the method 'function_name' from a file named wscript
+               the dirs can be a list of folders or a string containing space-separated folder paths
+               """
+               if not name:
+                       name = inspect.stack()[1][3]
+
+               if isinstance(dirs, str):
+                       dirs = to_list(dirs)
+
+               for x in dirs:
+                       if os.path.isabs(x):
+                               nexdir = x
+                       else:
+                               nexdir = os.path.join(self.curdir, x)
+
+                       base = os.path.join(nexdir, WSCRIPT_FILE)
+                       file_path = base + '_' + name
+
+                       try:
+                               txt = readf(file_path, m='rU')
+                       except (OSError, IOError):
+                               try:
+                                       module = load_module(base)
+                               except OSError:
+                                       raise WscriptError('No such script %s' % base)
+
+                               try:
+                                       f = module.__dict__[name]
+                               except KeyError:
+                                       raise WscriptError('No function %s defined in %s' % (name, base))
+
+                               if getattr(self.__class__, 'pre_recurse', None):
+                                       self.pre_recurse(f, base, nexdir)
+                               old = self.curdir
+                               self.curdir = nexdir
+                               try:
+                                       f(self)
+                               finally:
+                                       self.curdir = old
+                               if getattr(self.__class__, 'post_recurse', None):
+                                       self.post_recurse(module, base, nexdir)
+                       else:
+                               dc = {'ctx': self}
+                               if getattr(self.__class__, 'pre_recurse', None):
+                                       dc = self.pre_recurse(txt, file_path, nexdir)
+                               old = self.curdir
+                               self.curdir = nexdir
+                               try:
+                                       try:
+                                               exec(compile(txt, file_path, 'exec'), dc)
+                                       except Exception:
+                                               exc_type, exc_value, tb = sys.exc_info()
+                                               raise WscriptError("".join(traceback.format_exception(exc_type, exc_value, tb)), base)
+                               finally:
+                                       self.curdir = old
+                               if getattr(self.__class__, 'post_recurse', None):
+                                       self.post_recurse(txt, file_path, nexdir)
+
+if is_win32:
+       old = shutil.copy2
+       def copy2(src, dst):
+               old(src, dst)
+               shutil.copystat(src, src)
+       setattr(shutil, 'copy2', copy2)
+
+def zip_folder(dir, zip_file_name, prefix):
+       """
+       prefix represents the app to add in the archive
+       """
+       import zipfile
+       zip = zipfile.ZipFile(zip_file_name, 'w', compression=zipfile.ZIP_DEFLATED)
+       base = os.path.abspath(dir)
+
+       if prefix:
+               if prefix[-1] != os.sep:
+                       prefix += os.sep
+
+       n = len(base)
+       for root, dirs, files in os.walk(base):
+               for f in files:
+                       archive_name = prefix + root[n:] + os.sep + f
+                       zip.write(root + os.sep + f, archive_name, zipfile.ZIP_DEFLATED)
+       zip.close()
+
+def get_elapsed_time(start):
+       "Format a time delta (datetime.timedelta) using the format DdHhMmS.MSs"
+       delta = datetime.datetime.now() - start
+       # cast to int necessary for python 3.0
+       days = int(delta.days)
+       hours = int(delta.seconds / 3600)
+       minutes = int((delta.seconds - hours * 3600) / 60)
+       seconds = delta.seconds - hours * 3600 - minutes * 60 \
+               + float(delta.microseconds) / 1000 / 1000
+       result = ''
+       if days:
+               result += '%dd' % days
+       if days or hours:
+               result += '%dh' % hours
+       if days or hours or minutes:
+               result += '%dm' % minutes
+       return '%s%.3fs' % (result, seconds)
+
+if os.name == 'java':
+       # For Jython (they should really fix the inconsistency)
+       try:
+               gc.disable()
+               gc.enable()
+       except NotImplementedError:
+               gc.disable = gc.enable
+
+def run_once(fun):
+       """
+       decorator, make a function cache its results, use like this:
+
+       @run_once
+       def foo(k):
+               return 345*2343
+       """
+       cache = {}
+       def wrap(k):
+               try:
+                       return cache[k]
+               except KeyError:
+                       ret = fun(k)
+                       cache[k] = ret
+                       return ret
+       wrap.__cache__ = cache
+       return wrap
+