waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

Utils.py (25647B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2005-2018 (ita)
      4 
      5 """
      6 Utilities and platform-specific fixes
      7 
      8 The portability fixes try to provide a consistent behavior of the Waf API
      9 through Python versions 2.5 to 3.X and across different platforms (win32, linux, etc)
     10 """
     11 
     12 from __future__ import with_statement
     13 
     14 import atexit, os, sys, errno, inspect, re, datetime, platform, base64, signal, functools, time, shlex
     15 
     16 try:
     17 	import cPickle
     18 except ImportError:
     19 	import pickle as cPickle
     20 
     21 # leave this
     22 if os.name == 'posix' and sys.version_info[0] < 3:
     23 	try:
     24 		import subprocess32 as subprocess
     25 	except ImportError:
     26 		import subprocess
     27 else:
     28 	import subprocess
     29 
     30 try:
     31 	TimeoutExpired = subprocess.TimeoutExpired
     32 except AttributeError:
     33 	class TimeoutExpired(Exception):
     34 		pass
     35 
     36 from collections import deque, defaultdict
     37 
     38 try:
     39 	import _winreg as winreg
     40 except ImportError:
     41 	try:
     42 		import winreg
     43 	except ImportError:
     44 		winreg = None
     45 
     46 from waflib import Errors
     47 
     48 try:
     49 	from hashlib import sha1 as md5
     50 except ImportError:
     51 	# never fail to enable potential fixes from another module
     52 	pass
     53 
     54 try:
     55 	import threading
     56 except ImportError:
     57 	if not 'JOBS' in os.environ:
     58 		# no threading :-(
     59 		os.environ['JOBS'] = '1'
     60 
     61 	class threading(object):
     62 		"""
     63 		A fake threading class for platforms lacking the threading module.
     64 		Use ``waf -j1`` on those platforms
     65 		"""
     66 		pass
     67 	class Lock(object):
     68 		"""Fake Lock class"""
     69 		def acquire(self):
     70 			pass
     71 		def release(self):
     72 			pass
     73 	threading.Lock = threading.Thread = Lock
     74 
     75 SIG_NIL = 'SIG_NIL_SIG_NIL_'.encode()
     76 """Arbitrary null value for hashes. Modify this value according to the hash function in use"""
     77 
     78 O644 = 420
     79 """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
     80 
     81 O755 = 493
     82 """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
     83 
     84 rot_chr = ['\\', '|', '/', '-']
     85 "List of characters to use when displaying the throbber (progress bar)"
     86 
     87 rot_idx = 0
     88 "Index of the current throbber character (progress bar)"
     89 
     90 class ordered_iter_dict(dict):
     91 	"""Ordered dictionary that provides iteration from the most recently inserted keys first"""
     92 	def __init__(self, *k, **kw):
     93 		self.lst = deque()
     94 		dict.__init__(self, *k, **kw)
     95 	def clear(self):
     96 		dict.clear(self)
     97 		self.lst = deque()
     98 	def __setitem__(self, key, value):
     99 		if key in dict.keys(self):
    100 			self.lst.remove(key)
    101 		dict.__setitem__(self, key, value)
    102 		self.lst.append(key)
    103 	def __delitem__(self, key):
    104 		dict.__delitem__(self, key)
    105 		try:
    106 			self.lst.remove(key)
    107 		except ValueError:
    108 			pass
    109 	def __iter__(self):
    110 		return reversed(self.lst)
    111 	def keys(self):
    112 		return reversed(self.lst)
    113 
    114 class lru_node(object):
    115 	"""
    116 	Used by :py:class:`waflib.Utils.lru_cache`
    117 	"""
    118 	__slots__ = ('next', 'prev', 'key', 'val')
    119 	def __init__(self):
    120 		self.next = self
    121 		self.prev = self
    122 		self.key = None
    123 		self.val = None
    124 
    125 class lru_cache(object):
    126 	"""
    127 	A simple least-recently used cache with lazy allocation
    128 	"""
    129 	__slots__ = ('maxlen', 'table', 'head')
    130 	def __init__(self, maxlen=100):
    131 		self.maxlen = maxlen
    132 		"""
    133 		Maximum amount of elements in the cache
    134 		"""
    135 		self.table = {}
    136 		"""
    137 		Mapping key-value
    138 		"""
    139 		self.head = lru_node()
    140 		self.head.next = self.head
    141 		self.head.prev = self.head
    142 
    143 	def __getitem__(self, key):
    144 		node = self.table[key]
    145 		# assert(key==node.key)
    146 		if node is self.head:
    147 			return node.val
    148 
    149 		# detach the node found
    150 		node.prev.next = node.next
    151 		node.next.prev = node.prev
    152 
    153 		# replace the head
    154 		node.next = self.head.next
    155 		node.prev = self.head
    156 		self.head = node.next.prev = node.prev.next = node
    157 
    158 		return node.val
    159 
    160 	def __setitem__(self, key, val):
    161 		if key in self.table:
    162 			# update the value for an existing key
    163 			node = self.table[key]
    164 			node.val = val
    165 			self.__getitem__(key)
    166 		else:
    167 			if len(self.table) < self.maxlen:
    168 				# the very first item is unused until the maximum is reached
    169 				node = lru_node()
    170 				node.prev = self.head
    171 				node.next = self.head.next
    172 				node.prev.next = node.next.prev = node
    173 			else:
    174 				node = self.head = self.head.next
    175 				try:
    176 					# that's another key
    177 					del self.table[node.key]
    178 				except KeyError:
    179 					pass
    180 
    181 			node.key = key
    182 			node.val = val
    183 			self.table[key] = node
    184 
    185 class lazy_generator(object):
    186 	def __init__(self, fun, params):
    187 		self.fun = fun
    188 		self.params = params
    189 
    190 	def __iter__(self):
    191 		return self
    192 
    193 	def __next__(self):
    194 		try:
    195 			it = self.it
    196 		except AttributeError:
    197 			it = self.it = self.fun(*self.params)
    198 		return next(it)
    199 
    200 	next = __next__
    201 
    202 is_win32 = os.sep == '\\' or sys.platform == 'win32' or os.name == 'nt' # msys2
    203 """
    204 Whether this system is a Windows series
    205 """
    206 
    207 def readf(fname, m='r', encoding='latin-1'):
    208 	"""
    209 	Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
    210 
    211 		def build(ctx):
    212 			from waflib import Utils
    213 			txt = Utils.readf(self.path.find_node('wscript').abspath())
    214 			txt = ctx.path.find_node('wscript').read()
    215 
    216 	:type  fname: string
    217 	:param fname: Path to file
    218 	:type  m: string
    219 	:param m: Open mode
    220 	:type encoding: string
    221 	:param encoding: encoding value, only used for python 3
    222 	:rtype: string
    223 	:return: Content of the file
    224 	"""
    225 
    226 	if sys.hexversion > 0x3000000 and not 'b' in m:
    227 		m += 'b'
    228 		with open(fname, m) as f:
    229 			txt = f.read()
    230 		if encoding:
    231 			txt = txt.decode(encoding)
    232 		else:
    233 			txt = txt.decode()
    234 	else:
    235 		with open(fname, m) as f:
    236 			txt = f.read()
    237 	return txt
    238 
    239 def writef(fname, data, m='w', encoding='latin-1'):
    240 	"""
    241 	Writes an entire file from a string.
    242 	See also :py:meth:`waflib.Node.Node.writef`::
    243 
    244 		def build(ctx):
    245 			from waflib import Utils
    246 			txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
    247 			self.path.make_node('i_like_kittens').write('some data')
    248 
    249 	:type  fname: string
    250 	:param fname: Path to file
    251 	:type   data: string
    252 	:param  data: The contents to write to the file
    253 	:type  m: string
    254 	:param m: Open mode
    255 	:type encoding: string
    256 	:param encoding: encoding value, only used for python 3
    257 	"""
    258 	if sys.hexversion > 0x3000000 and not 'b' in m:
    259 		data = data.encode(encoding)
    260 		m += 'b'
    261 	with open(fname, m) as f:
    262 		f.write(data)
    263 
    264 def h_file(fname):
    265 	"""
    266 	Computes a hash value for a file by using md5. Use the md5_tstamp
    267 	extension to get faster build hashes if necessary.
    268 
    269 	:type fname: string
    270 	:param fname: path to the file to hash
    271 	:return: hash of the file contents
    272 	:rtype: string or bytes
    273 	"""
    274 	m = md5()
    275 	with open(fname, 'rb') as f:
    276 		while fname:
    277 			fname = f.read(200000)
    278 			m.update(fname)
    279 	return m.digest()
    280 
    281 def readf_win32(f, m='r', encoding='latin-1'):
    282 	flags = os.O_NOINHERIT | os.O_RDONLY
    283 	if 'b' in m:
    284 		flags |= os.O_BINARY
    285 	if '+' in m:
    286 		flags |= os.O_RDWR
    287 	try:
    288 		fd = os.open(f, flags)
    289 	except OSError:
    290 		raise IOError('Cannot read from %r' % f)
    291 
    292 	if sys.hexversion > 0x3000000 and not 'b' in m:
    293 		m += 'b'
    294 		with os.fdopen(fd, m) as f:
    295 			txt = f.read()
    296 		if encoding:
    297 			txt = txt.decode(encoding)
    298 		else:
    299 			txt = txt.decode()
    300 	else:
    301 		with os.fdopen(fd, m) as f:
    302 			txt = f.read()
    303 	return txt
    304 
    305 def writef_win32(f, data, m='w', encoding='latin-1'):
    306 	if sys.hexversion > 0x3000000 and not 'b' in m:
    307 		data = data.encode(encoding)
    308 		m += 'b'
    309 	flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | os.O_NOINHERIT
    310 	if 'b' in m:
    311 		flags |= os.O_BINARY
    312 	if '+' in m:
    313 		flags |= os.O_RDWR
    314 	try:
    315 		fd = os.open(f, flags)
    316 	except OSError:
    317 		raise OSError('Cannot write to %r' % f)
    318 	with os.fdopen(fd, m) as f:
    319 		f.write(data)
    320 
    321 def h_file_win32(fname):
    322 	try:
    323 		fd = os.open(fname, os.O_BINARY | os.O_RDONLY | os.O_NOINHERIT)
    324 	except OSError:
    325 		raise OSError('Cannot read from %r' % fname)
    326 	m = md5()
    327 	with os.fdopen(fd, 'rb') as f:
    328 		while fname:
    329 			fname = f.read(200000)
    330 			m.update(fname)
    331 	return m.digest()
    332 
    333 # always save these
    334 readf_unix = readf
    335 writef_unix = writef
    336 h_file_unix = h_file
    337 if hasattr(os, 'O_NOINHERIT') and sys.hexversion < 0x3040000:
    338 	# replace the default functions
    339 	readf = readf_win32
    340 	writef = writef_win32
    341 	h_file = h_file_win32
    342 
    343 try:
    344 	x = ''.encode('hex')
    345 except LookupError:
    346 	import binascii
    347 	def to_hex(s):
    348 		ret = binascii.hexlify(s)
    349 		if not isinstance(ret, str):
    350 			ret = ret.decode('utf-8')
    351 		return ret
    352 else:
    353 	def to_hex(s):
    354 		return s.encode('hex')
    355 
    356 to_hex.__doc__ = """
    357 Return the hexadecimal representation of a string
    358 
    359 :param s: string to convert
    360 :type s: string
    361 """
    362 
    363 def listdir_win32(s):
    364 	"""
    365 	Lists the contents of a folder in a portable manner.
    366 	On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
    367 
    368 	:type s: string
    369 	:param s: a string, which can be empty on Windows
    370 	"""
    371 	if not s:
    372 		try:
    373 			import ctypes
    374 		except ImportError:
    375 			# there is nothing much we can do
    376 			return [x + ':\\' for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
    377 		else:
    378 			dlen = 4 # length of "?:\\x00"
    379 			maxdrives = 26
    380 			buf = ctypes.create_string_buffer(maxdrives * dlen)
    381 			ndrives = ctypes.windll.kernel32.GetLogicalDriveStringsA(maxdrives*dlen, ctypes.byref(buf))
    382 			return [ str(buf.raw[4*i:4*i+2].decode('ascii')) for i in range(int(ndrives/dlen)) ]
    383 
    384 	if len(s) == 2 and s[1] == ":":
    385 		s += os.sep
    386 
    387 	if not os.path.isdir(s):
    388 		e = OSError('%s is not a directory' % s)
    389 		e.errno = errno.ENOENT
    390 		raise e
    391 	return os.listdir(s)
    392 
    393 listdir = os.listdir
    394 if is_win32:
    395 	listdir = listdir_win32
    396 
    397 def num2ver(ver):
    398 	"""
    399 	Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
    400 
    401 		from waflib.Utils import num2ver
    402 		num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
    403 
    404 	:type ver: string or tuple of numbers
    405 	:param ver: a version number
    406 	"""
    407 	if isinstance(ver, str):
    408 		ver = tuple(ver.split('.'))
    409 	if isinstance(ver, tuple):
    410 		ret = 0
    411 		for i in range(4):
    412 			if i < len(ver):
    413 				ret += 256**(3 - i) * int(ver[i])
    414 		return ret
    415 	return ver
    416 
    417 def to_list(val):
    418 	"""
    419 	Converts a string argument to a list by splitting it by spaces.
    420 	Returns the object if not a string::
    421 
    422 		from waflib.Utils import to_list
    423 		lst = to_list('a b c d')
    424 
    425 	:param val: list of string or space-separated string
    426 	:rtype: list
    427 	:return: Argument converted to list
    428 	"""
    429 	if isinstance(val, str):
    430 		return val.split()
    431 	else:
    432 		return val
    433 
    434 def console_encoding():
    435 	try:
    436 		import ctypes
    437 	except ImportError:
    438 		pass
    439 	else:
    440 		try:
    441 			codepage = ctypes.windll.kernel32.GetConsoleCP()
    442 		except AttributeError:
    443 			pass
    444 		else:
    445 			if codepage:
    446 				if 65001 == codepage and sys.version_info < (3, 3):
    447 					return 'utf-8'
    448 				return 'cp%d' % codepage
    449 	return sys.stdout.encoding or ('cp1252' if is_win32 else 'latin-1')
    450 
    451 def split_path_unix(path):
    452 	return path.split('/')
    453 
    454 def split_path_cygwin(path):
    455 	if path.startswith('//'):
    456 		ret = path.split('/')[2:]
    457 		ret[0] = '/' + ret[0]
    458 		return ret
    459 	return path.split('/')
    460 
    461 re_sp = re.compile('[/\\\\]+')
    462 def split_path_win32(path):
    463 	if path.startswith('\\\\'):
    464 		ret = re_sp.split(path)[1:]
    465 		ret[0] = '\\\\' + ret[0]
    466 		if ret[0] == '\\\\?':
    467 			return ret[1:]
    468 		return ret
    469 	return re_sp.split(path)
    470 
    471 msysroot = None
    472 def split_path_msys(path):
    473 	if path.startswith(('/', '\\')) and not path.startswith(('//', '\\\\')):
    474 		# msys paths can be in the form /usr/bin
    475 		global msysroot
    476 		if not msysroot:
    477 			# msys has python 2.7 or 3, so we can use this
    478 			msysroot = subprocess.check_output(['cygpath', '-w', '/']).decode(sys.stdout.encoding or 'latin-1')
    479 			msysroot = msysroot.strip()
    480 		path = os.path.normpath(msysroot + os.sep + path)
    481 	return split_path_win32(path)
    482 
    483 if sys.platform == 'cygwin':
    484 	split_path = split_path_cygwin
    485 elif is_win32:
    486 	# Consider this an MSYSTEM environment if $MSYSTEM is set and python
    487 	# reports is executable from a unix like path on a windows host.
    488 	if os.environ.get('MSYSTEM') and sys.executable.startswith('/'):
    489 		split_path = split_path_msys
    490 	else:
    491 		split_path = split_path_win32
    492 else:
    493 	split_path = split_path_unix
    494 
    495 split_path.__doc__ = """
    496 Splits a path by / or \\; do not confuse this function with with ``os.path.split``
    497 
    498 :type  path: string
    499 :param path: path to split
    500 :return:     list of string
    501 """
    502 
    503 def check_dir(path):
    504 	"""
    505 	Ensures that a directory exists (similar to ``mkdir -p``).
    506 
    507 	:type  path: string
    508 	:param path: Path to directory
    509 	:raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
    510 	"""
    511 	if not os.path.isdir(path):
    512 		try:
    513 			os.makedirs(path)
    514 		except OSError as e:
    515 			if not os.path.isdir(path):
    516 				raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
    517 
    518 def check_exe(name, env=None):
    519 	"""
    520 	Ensures that a program exists
    521 
    522 	:type name: string
    523 	:param name: path to the program
    524 	:param env: configuration object
    525 	:type env: :py:class:`waflib.ConfigSet.ConfigSet`
    526 	:return: path of the program or None
    527 	:raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
    528 	"""
    529 	if not name:
    530 		raise ValueError('Cannot execute an empty string!')
    531 	def is_exe(fpath):
    532 		return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
    533 
    534 	fpath, fname = os.path.split(name)
    535 	if fpath and is_exe(name):
    536 		return os.path.abspath(name)
    537 	else:
    538 		env = env or os.environ
    539 		for path in env['PATH'].split(os.pathsep):
    540 			path = path.strip('"')
    541 			exe_file = os.path.join(path, name)
    542 			if is_exe(exe_file):
    543 				return os.path.abspath(exe_file)
    544 	return None
    545 
    546 def def_attrs(cls, **kw):
    547 	"""
    548 	Sets default attributes on a class instance
    549 
    550 	:type cls: class
    551 	:param cls: the class to update the given attributes in.
    552 	:type kw: dict
    553 	:param kw: dictionary of attributes names and values.
    554 	"""
    555 	for k, v in kw.items():
    556 		if not hasattr(cls, k):
    557 			setattr(cls, k, v)
    558 
    559 def quote_define_name(s):
    560 	"""
    561 	Converts a string into an identifier suitable for C defines.
    562 
    563 	:type  s: string
    564 	:param s: String to convert
    565 	:rtype: string
    566 	:return: Identifier suitable for C defines
    567 	"""
    568 	fu = re.sub('[^a-zA-Z0-9]', '_', s)
    569 	fu = re.sub('_+', '_', fu)
    570 	fu = fu.upper()
    571 	return fu
    572 
    573 # shlex.quote didn't exist until python 3.3. Prior to that it was a non-documented
    574 # function in pipes.
    575 try:
    576 	shell_quote = shlex.quote
    577 except AttributeError:
    578 	import pipes
    579 	shell_quote = pipes.quote
    580 
    581 def shell_escape(cmd):
    582 	"""
    583 	Escapes a command:
    584 	['ls', '-l', 'arg space'] -> ls -l 'arg space'
    585 	"""
    586 	if isinstance(cmd, str):
    587 		return cmd
    588 	return ' '.join(shell_quote(x) for x in cmd)
    589 
    590 def h_list(lst):
    591 	"""
    592 	Hashes lists of ordered data.
    593 
    594 	Using hash(tup) for tuples would be much more efficient,
    595 	but Python now enforces hash randomization
    596 
    597 	:param lst: list to hash
    598 	:type lst: list of strings
    599 	:return: hash of the list
    600 	"""
    601 	return md5(repr(lst).encode()).digest()
    602 
    603 if sys.hexversion < 0x3000000:
    604 	def h_list_python2(lst):
    605 		return md5(repr(lst)).digest()
    606 	h_list_python2.__doc__ = h_list.__doc__
    607 	h_list = h_list_python2
    608 
    609 def h_fun(fun):
    610 	"""
    611 	Hash functions
    612 
    613 	:param fun: function to hash
    614 	:type  fun: function
    615 	:return: hash of the function
    616 	:rtype: string or bytes
    617 	"""
    618 	try:
    619 		return fun.code
    620 	except AttributeError:
    621 		if isinstance(fun, functools.partial):
    622 			code = list(fun.args)
    623 			# The method items() provides a sequence of tuples where the first element
    624 			# represents an optional argument of the partial function application
    625 			#
    626 			# The sorting result outcome will be consistent because:
    627 			# 1. tuples are compared in order of their elements
    628 			# 2. optional argument namess are unique
    629 			code.extend(sorted(fun.keywords.items()))
    630 			code.append(h_fun(fun.func))
    631 			fun.code = h_list(code)
    632 			return fun.code
    633 		try:
    634 			h = inspect.getsource(fun)
    635 		except EnvironmentError:
    636 			h = 'nocode'
    637 		try:
    638 			fun.code = h
    639 		except AttributeError:
    640 			pass
    641 		return h
    642 
    643 def h_cmd(ins):
    644 	"""
    645 	Hashes objects recursively
    646 
    647 	:param ins: input object
    648 	:type ins: string or list or tuple or function
    649 	:rtype: string or bytes
    650 	"""
    651 	# this function is not meant to be particularly fast
    652 	if isinstance(ins, str):
    653 		# a command is either a string
    654 		ret = ins
    655 	elif isinstance(ins, list) or isinstance(ins, tuple):
    656 		# or a list of functions/strings
    657 		ret = str([h_cmd(x) for x in ins])
    658 	else:
    659 		# or just a python function
    660 		ret = str(h_fun(ins))
    661 	if sys.hexversion > 0x3000000:
    662 		ret = ret.encode('latin-1', 'xmlcharrefreplace')
    663 	return ret
    664 
    665 reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
    666 def subst_vars(expr, params):
    667 	"""
    668 	Replaces ${VAR} with the value of VAR taken from a dict or a config set::
    669 
    670 		from waflib import Utils
    671 		s = Utils.subst_vars('${PREFIX}/bin', env)
    672 
    673 	:type  expr: string
    674 	:param expr: String to perform substitution on
    675 	:param params: Dictionary or config set to look up variable values.
    676 	"""
    677 	def repl_var(m):
    678 		if m.group(1):
    679 			return '\\'
    680 		if m.group(2):
    681 			return '$'
    682 		try:
    683 			# ConfigSet instances may contain lists
    684 			return params.get_flat(m.group(3))
    685 		except AttributeError:
    686 			return params[m.group(3)]
    687 		# if you get a TypeError, it means that 'expr' is not a string...
    688 		# Utils.subst_vars(None, env)  will not work
    689 	return reg_subst.sub(repl_var, expr)
    690 
    691 def destos_to_binfmt(key):
    692 	"""
    693 	Returns the binary format based on the unversioned platform name,
    694 	and defaults to ``elf`` if nothing is found.
    695 
    696 	:param key: platform name
    697 	:type  key: string
    698 	:return: string representing the binary format
    699 	"""
    700 	if key == 'darwin':
    701 		return 'mac-o'
    702 	elif key in ('win32', 'cygwin', 'uwin', 'msys'):
    703 		return 'pe'
    704 	return 'elf'
    705 
    706 def unversioned_sys_platform():
    707 	"""
    708 	Returns the unversioned platform name.
    709 	Some Python platform names contain versions, that depend on
    710 	the build environment, e.g. linux2, freebsd6, etc.
    711 	This returns the name without the version number. Exceptions are
    712 	os2 and win32, which are returned verbatim.
    713 
    714 	:rtype: string
    715 	:return: Unversioned platform name
    716 	"""
    717 	s = sys.platform
    718 	if s.startswith('java'):
    719 		# The real OS is hidden under the JVM.
    720 		from java.lang import System
    721 		s = System.getProperty('os.name')
    722 		# see http://lopica.sourceforge.net/os.html for a list of possible values
    723 		if s == 'Mac OS X':
    724 			return 'darwin'
    725 		elif s.startswith('Windows '):
    726 			return 'win32'
    727 		elif s == 'OS/2':
    728 			return 'os2'
    729 		elif s == 'HP-UX':
    730 			return 'hp-ux'
    731 		elif s in ('SunOS', 'Solaris'):
    732 			return 'sunos'
    733 		else: s = s.lower()
    734 
    735 	# powerpc == darwin for our purposes
    736 	if s == 'powerpc':
    737 		return 'darwin'
    738 	if s == 'win32' or s == 'os2':
    739 		return s
    740 	if s == 'cli' and os.name == 'nt':
    741 		# ironpython is only on windows as far as we know
    742 		return 'win32'
    743 	return re.split(r'\d+$', s)[0]
    744 
    745 def nada(*k, **kw):
    746 	"""
    747 	Does nothing
    748 
    749 	:return: None
    750 	"""
    751 	pass
    752 
    753 class Timer(object):
    754 	"""
    755 	Simple object for timing the execution of commands.
    756 	Its string representation is the duration::
    757 
    758 		from waflib.Utils import Timer
    759 		timer = Timer()
    760 		a_few_operations()
    761 		s = str(timer)
    762 	"""
    763 	def __init__(self):
    764 		self.start_time = self.now()
    765 
    766 	def __str__(self):
    767 		delta = self.now() - self.start_time
    768 		if not isinstance(delta, datetime.timedelta):
    769 			delta = datetime.timedelta(seconds=delta)
    770 		days = delta.days
    771 		hours, rem = divmod(delta.seconds, 3600)
    772 		minutes, seconds = divmod(rem, 60)
    773 		seconds += delta.microseconds * 1e-6
    774 		result = ''
    775 		if days:
    776 			result += '%dd' % days
    777 		if days or hours:
    778 			result += '%dh' % hours
    779 		if days or hours or minutes:
    780 			result += '%dm' % minutes
    781 		return '%s%.3fs' % (result, seconds)
    782 
    783 	def now(self):
    784 		return datetime.datetime.utcnow()
    785 
    786 	if hasattr(time, 'perf_counter'):
    787 		def now(self):
    788 			return time.perf_counter()
    789 
    790 def read_la_file(path):
    791 	"""
    792 	Reads property files, used by msvc.py
    793 
    794 	:param path: file to read
    795 	:type path: string
    796 	"""
    797 	sp = re.compile(r'^([^=]+)=\'(.*)\'$')
    798 	dc = {}
    799 	for line in readf(path).splitlines():
    800 		try:
    801 			_, left, right, _ = sp.split(line.strip())
    802 			dc[left] = right
    803 		except ValueError:
    804 			pass
    805 	return dc
    806 
    807 def run_once(fun):
    808 	"""
    809 	Decorator: let a function cache its results, use like this::
    810 
    811 		@run_once
    812 		def foo(k):
    813 			return 345*2343
    814 
    815 	.. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
    816 
    817 	:param fun: function to execute
    818 	:type fun: function
    819 	:return: the return value of the function executed
    820 	"""
    821 	cache = {}
    822 	def wrap(*k):
    823 		try:
    824 			return cache[k]
    825 		except KeyError:
    826 			ret = fun(*k)
    827 			cache[k] = ret
    828 			return ret
    829 	wrap.__cache__ = cache
    830 	wrap.__name__ = fun.__name__
    831 	return wrap
    832 
    833 def get_registry_app_path(key, filename):
    834 	"""
    835 	Returns the value of a registry key for an executable
    836 
    837 	:type key: string
    838 	:type filename: list of string
    839 	"""
    840 	if not winreg:
    841 		return None
    842 	try:
    843 		result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
    844 	except OSError:
    845 		pass
    846 	else:
    847 		if os.path.isfile(result):
    848 			return result
    849 
    850 def lib64():
    851 	"""
    852 	Guess the default ``/usr/lib`` extension for 64-bit applications
    853 
    854 	:return: '64' or ''
    855 	:rtype: string
    856 	"""
    857 	# default settings for /usr/lib
    858 	if os.sep == '/':
    859 		if platform.architecture()[0] == '64bit':
    860 			if os.path.exists('/usr/lib64') and not os.path.exists('/usr/lib32'):
    861 				return '64'
    862 	return ''
    863 
    864 def loose_version(ver_str):
    865 	# private for the time being!
    866 	# see #2402
    867 	lst = re.split(r'([.]|\\d+|[a-zA-Z])', ver_str)
    868 	ver = []
    869 	for i, val in enumerate(lst):
    870 		try:
    871 			ver.append(int(val))
    872 		except ValueError:
    873 			if val != '.':
    874 				ver.append(val)
    875 	return ver
    876 
    877 def sane_path(p):
    878 	# private function for the time being!
    879 	return os.path.abspath(os.path.expanduser(p))
    880 
    881 process_pool = []
    882 """
    883 List of processes started to execute sub-process commands
    884 """
    885 
    886 def get_process():
    887 	"""
    888 	Returns a process object that can execute commands as sub-processes
    889 
    890 	:rtype: subprocess.Popen
    891 	"""
    892 	try:
    893 		return process_pool.pop()
    894 	except IndexError:
    895 		filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor.py'
    896 		cmd = [sys.executable, '-c', readf(filepath)]
    897 		return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0, close_fds=not is_win32)
    898 
    899 def run_prefork_process(cmd, kwargs, cargs):
    900 	"""
    901 	Delegates process execution to a pre-forked process instance.
    902 	"""
    903 	if not kwargs.get('env'):
    904 		kwargs['env'] = dict(os.environ)
    905 	try:
    906 		obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
    907 	except (TypeError, AttributeError):
    908 		return run_regular_process(cmd, kwargs, cargs)
    909 
    910 	proc = get_process()
    911 	if not proc:
    912 		return run_regular_process(cmd, kwargs, cargs)
    913 
    914 	proc.stdin.write(obj)
    915 	proc.stdin.write('\n'.encode())
    916 	proc.stdin.flush()
    917 	obj = proc.stdout.readline()
    918 	if not obj:
    919 		raise OSError('Preforked sub-process %r died' % proc.pid)
    920 
    921 	process_pool.append(proc)
    922 	lst = cPickle.loads(base64.b64decode(obj))
    923 	# Jython wrapper failures (bash/execvp)
    924 	assert len(lst) == 5
    925 	ret, out, err, ex, trace = lst
    926 	if ex:
    927 		if ex == 'OSError':
    928 			raise OSError(trace)
    929 		elif ex == 'ValueError':
    930 			raise ValueError(trace)
    931 		elif ex == 'TimeoutExpired':
    932 			exc = TimeoutExpired(cmd, timeout=cargs['timeout'], output=out)
    933 			exc.stderr = err
    934 			raise exc
    935 		else:
    936 			raise Exception(trace)
    937 	return ret, out, err
    938 
    939 def lchown(path, user=-1, group=-1):
    940 	"""
    941 	Change the owner/group of a path, raises an OSError if the
    942 	ownership change fails.
    943 
    944 	:param user: user to change
    945 	:type user: int or str
    946 	:param group: group to change
    947 	:type group: int or str
    948 	"""
    949 	if isinstance(user, str):
    950 		import pwd
    951 		entry = pwd.getpwnam(user)
    952 		if not entry:
    953 			raise OSError('Unknown user %r' % user)
    954 		user = entry[2]
    955 	if isinstance(group, str):
    956 		import grp
    957 		entry = grp.getgrnam(group)
    958 		if not entry:
    959 			raise OSError('Unknown group %r' % group)
    960 		group = entry[2]
    961 	return os.lchown(path, user, group)
    962 
    963 def run_regular_process(cmd, kwargs, cargs={}):
    964 	"""
    965 	Executes a subprocess command by using subprocess.Popen
    966 	"""
    967 	proc = subprocess.Popen(cmd, **kwargs)
    968 	if kwargs.get('stdout') or kwargs.get('stderr'):
    969 		try:
    970 			out, err = proc.communicate(**cargs)
    971 		except TimeoutExpired:
    972 			if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
    973 				os.killpg(proc.pid, signal.SIGKILL)
    974 			else:
    975 				proc.kill()
    976 			out, err = proc.communicate()
    977 			exc = TimeoutExpired(proc.args, timeout=cargs['timeout'], output=out)
    978 			exc.stderr = err
    979 			raise exc
    980 		status = proc.returncode
    981 	else:
    982 		out, err = (None, None)
    983 		try:
    984 			status = proc.wait(**cargs)
    985 		except TimeoutExpired as e:
    986 			if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
    987 				os.killpg(proc.pid, signal.SIGKILL)
    988 			else:
    989 				proc.kill()
    990 			proc.wait()
    991 			raise e
    992 	return status, out, err
    993 
    994 def run_process(cmd, kwargs, cargs={}):
    995 	"""
    996 	Executes a subprocess by using a pre-forked process when possible
    997 	or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
    998 	and :py:func:`waflib.Utils.run_regular_process`
    999 	"""
   1000 	if kwargs.get('stdout') and kwargs.get('stderr'):
   1001 		return run_prefork_process(cmd, kwargs, cargs)
   1002 	else:
   1003 		return run_regular_process(cmd, kwargs, cargs)
   1004 
   1005 def alloc_process_pool(n, force=False):
   1006 	"""
   1007 	Allocates an amount of processes to the default pool so its size is at least *n*.
   1008 	It is useful to call this function early so that the pre-forked
   1009 	processes use as little memory as possible.
   1010 
   1011 	:param n: pool size
   1012 	:type n: integer
   1013 	:param force: if True then *n* more processes are added to the existing pool
   1014 	:type force: bool
   1015 	"""
   1016 	# mandatory on python2, unnecessary on python >= 3.2
   1017 	global run_process, get_process, alloc_process_pool
   1018 	if not force:
   1019 		n = max(n - len(process_pool), 0)
   1020 	try:
   1021 		lst = [get_process() for x in range(n)]
   1022 	except OSError:
   1023 		run_process = run_regular_process
   1024 		get_process = alloc_process_pool = nada
   1025 	else:
   1026 		for x in lst:
   1027 			process_pool.append(x)
   1028 
   1029 def atexit_pool():
   1030 	for k in process_pool:
   1031 		try:
   1032 			os.kill(k.pid, 9)
   1033 		except OSError:
   1034 			pass
   1035 		else:
   1036 			k.wait()
   1037 # see #1889
   1038 if (sys.hexversion<0x207000f and not is_win32) or sys.hexversion>=0x306000f:
   1039 	atexit.register(atexit_pool)
   1040 
   1041 if os.environ.get('WAF_NO_PREFORK') or sys.platform == 'cli' or not sys.executable:
   1042 	run_process = run_regular_process
   1043 	get_process = alloc_process_pool = nada