3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
7 # Thomas Nagy, 2005-2018 (ita)
10 Utilities and platform-specific fixes
12 The portability fixes try to provide a consistent behavior of the Waf API
13 through Python versions 2.5 to 3.X and across different platforms (win32, linux, etc)
16 from __future__ import with_statement
18 import atexit, os, sys, errno, inspect, re, datetime, platform, base64, signal, functools, time
23 import pickle as cPickle
26 if os.name == 'posix' and sys.version_info[0] < 3:
28 import subprocess32 as subprocess
35 TimeoutExpired = subprocess.TimeoutExpired
36 except AttributeError:
37 class TimeoutExpired(Exception):
40 from collections import deque, defaultdict
43 import _winreg as winreg
50 from waflib import Errors
53 from hashlib import md5
58 # never fail to enable fixes from another module
64 if not 'JOBS' in os.environ:
66 os.environ['JOBS'] = '1'
68 class threading(object):
70 A fake threading class for platforms lacking the threading module.
71 Use ``waf -j1`` on those platforms
80 threading.Lock = threading.Thread = Lock
82 SIG_NIL = 'SIG_NIL_SIG_NIL_'.encode()
83 """Arbitrary null value for hashes. Modify this value according to the hash function in use"""
86 """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
89 """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
91 rot_chr = ['\\', '|', '/', '-']
92 "List of characters to use when displaying the throbber (progress bar)"
95 "Index of the current throbber character (progress bar)"
97 class ordered_iter_dict(dict):
98 """Ordered dictionary that provides iteration from the most recently inserted keys first"""
99 def __init__(self, *k, **kw):
101 dict.__init__(self, *k, **kw)
105 def __setitem__(self, key, value):
106 if key in dict.keys(self):
108 dict.__setitem__(self, key, value)
110 def __delitem__(self, key):
111 dict.__delitem__(self, key)
117 return reversed(self.lst)
119 return reversed(self.lst)
121 class lru_node(object):
123 Used by :py:class:`waflib.Utils.lru_cache`
125 __slots__ = ('next', 'prev', 'key', 'val')
132 class lru_cache(object):
134 A simple least-recently used cache with lazy allocation
136 __slots__ = ('maxlen', 'table', 'head')
137 def __init__(self, maxlen=100):
140 Maximum amount of elements in the cache
146 self.head = lru_node()
147 self.head.next = self.head
148 self.head.prev = self.head
150 def __getitem__(self, key):
151 node = self.table[key]
152 # assert(key==node.key)
153 if node is self.head:
156 # detach the node found
157 node.prev.next = node.next
158 node.next.prev = node.prev
161 node.next = self.head.next
162 node.prev = self.head
163 self.head = node.next.prev = node.prev.next = node
167 def __setitem__(self, key, val):
168 if key in self.table:
169 # update the value for an existing key
170 node = self.table[key]
172 self.__getitem__(key)
174 if len(self.table) < self.maxlen:
175 # the very first item is unused until the maximum is reached
177 node.prev = self.head
178 node.next = self.head.next
179 node.prev.next = node.next.prev = node
181 node = self.head = self.head.next
184 del self.table[node.key]
190 self.table[key] = node
192 class lazy_generator(object):
193 def __init__(self, fun, params):
203 except AttributeError:
204 it = self.it = self.fun(*self.params)
207 is_win32 = os.sep == '\\' or sys.platform == 'win32' # msys2
209 Whether this system is a Windows series
212 def readf(fname, m='r', encoding='latin-1'):
214 Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
217 from waflib import Utils
218 txt = Utils.readf(self.path.find_node('wscript').abspath())
219 txt = ctx.path.find_node('wscript').read()
222 :param fname: Path to file
225 :type encoding: string
226 :param encoding: encoding value, only used for python 3
228 :return: Content of the file
231 if sys.hexversion > 0x3000000 and not 'b' in m:
233 with open(fname, m) as f:
236 txt = txt.decode(encoding)
240 with open(fname, m) as f:
244 def writef(fname, data, m='w', encoding='latin-1'):
246 Writes an entire file from a string.
247 See also :py:meth:`waflib.Node.Node.writef`::
250 from waflib import Utils
251 txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
252 self.path.make_node('i_like_kittens').write('some data')
255 :param fname: Path to file
257 :param data: The contents to write to the file
260 :type encoding: string
261 :param encoding: encoding value, only used for python 3
263 if sys.hexversion > 0x3000000 and not 'b' in m:
264 data = data.encode(encoding)
266 with open(fname, m) as f:
271 Computes a hash value for a file by using md5. Use the md5_tstamp
272 extension to get faster build hashes if necessary.
275 :param fname: path to the file to hash
276 :return: hash of the file contents
277 :rtype: string or bytes
280 with open(fname, 'rb') as f:
282 fname = f.read(200000)
286 def readf_win32(f, m='r', encoding='latin-1'):
287 flags = os.O_NOINHERIT | os.O_RDONLY
293 fd = os.open(f, flags)
295 raise IOError('Cannot read from %r' % f)
297 if sys.hexversion > 0x3000000 and not 'b' in m:
299 with os.fdopen(fd, m) as f:
302 txt = txt.decode(encoding)
306 with os.fdopen(fd, m) as f:
310 def writef_win32(f, data, m='w', encoding='latin-1'):
311 if sys.hexversion > 0x3000000 and not 'b' in m:
312 data = data.encode(encoding)
314 flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | os.O_NOINHERIT
320 fd = os.open(f, flags)
322 raise OSError('Cannot write to %r' % f)
323 with os.fdopen(fd, m) as f:
326 def h_file_win32(fname):
328 fd = os.open(fname, os.O_BINARY | os.O_RDONLY | os.O_NOINHERIT)
330 raise OSError('Cannot read from %r' % fname)
332 with os.fdopen(fd, 'rb') as f:
334 fname = f.read(200000)
342 if hasattr(os, 'O_NOINHERIT') and sys.hexversion < 0x3040000:
343 # replace the default functions
345 writef = writef_win32
346 h_file = h_file_win32
353 ret = binascii.hexlify(s)
354 if not isinstance(ret, str):
355 ret = ret.decode('utf-8')
359 return s.encode('hex')
362 Return the hexadecimal representation of a string
364 :param s: string to convert
368 def listdir_win32(s):
370 Lists the contents of a folder in a portable manner.
371 On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
374 :param s: a string, which can be empty on Windows
380 # there is nothing much we can do
381 return [x + ':\\' for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
383 dlen = 4 # length of "?:\\x00"
385 buf = ctypes.create_string_buffer(maxdrives * dlen)
386 ndrives = ctypes.windll.kernel32.GetLogicalDriveStringsA(maxdrives*dlen, ctypes.byref(buf))
387 return [ str(buf.raw[4*i:4*i+2].decode('ascii')) for i in range(int(ndrives/dlen)) ]
389 if len(s) == 2 and s[1] == ":":
392 if not os.path.isdir(s):
393 e = OSError('%s is not a directory' % s)
394 e.errno = errno.ENOENT
400 listdir = listdir_win32
404 Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
406 from waflib.Utils import num2ver
407 num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
409 :type ver: string or tuple of numbers
410 :param ver: a version number
412 if isinstance(ver, str):
413 ver = tuple(ver.split('.'))
414 if isinstance(ver, tuple):
418 ret += 256**(3 - i) * int(ver[i])
424 Converts a string argument to a list by splitting it by spaces.
425 Returns the object if not a string::
427 from waflib.Utils import to_list
428 lst = to_list('a b c d')
430 :param val: list of string or space-separated string
432 :return: Argument converted to list
434 if isinstance(val, str):
439 def split_path_unix(path):
440 return path.split('/')
442 def split_path_cygwin(path):
443 if path.startswith('//'):
444 ret = path.split('/')[2:]
445 ret[0] = '/' + ret[0]
447 return path.split('/')
449 re_sp = re.compile('[/\\\\]+')
450 def split_path_win32(path):
451 if path.startswith('\\\\'):
452 ret = re_sp.split(path)[1:]
453 ret[0] = '\\\\' + ret[0]
454 if ret[0] == '\\\\?':
457 return re_sp.split(path)
460 def split_path_msys(path):
461 if path.startswith(('/', '\\')) and not path.startswith(('//', '\\\\')):
462 # msys paths can be in the form /usr/bin
465 # msys has python 2.7 or 3, so we can use this
466 msysroot = subprocess.check_output(['cygpath', '-w', '/']).decode(sys.stdout.encoding or 'latin-1')
467 msysroot = msysroot.strip()
468 path = os.path.normpath(msysroot + os.sep + path)
469 return split_path_win32(path)
471 if sys.platform == 'cygwin':
472 split_path = split_path_cygwin
474 if os.environ.get('MSYSTEM'):
475 split_path = split_path_msys
477 split_path = split_path_win32
479 split_path = split_path_unix
481 split_path.__doc__ = """
482 Splits a path by / or \\; do not confuse this function with with ``os.path.split``
485 :param path: path to split
486 :return: list of string
491 Ensures that a directory exists (similar to ``mkdir -p``).
494 :param path: Path to directory
495 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
497 if not os.path.isdir(path):
501 if not os.path.isdir(path):
502 raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
504 def check_exe(name, env=None):
506 Ensures that a program exists
509 :param name: path to the program
510 :param env: configuration object
511 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
512 :return: path of the program or None
513 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
516 raise ValueError('Cannot execute an empty string!')
518 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
520 fpath, fname = os.path.split(name)
521 if fpath and is_exe(name):
522 return os.path.abspath(name)
524 env = env or os.environ
525 for path in env['PATH'].split(os.pathsep):
526 path = path.strip('"')
527 exe_file = os.path.join(path, name)
529 return os.path.abspath(exe_file)
532 def def_attrs(cls, **kw):
534 Sets default attributes on a class instance
537 :param cls: the class to update the given attributes in.
539 :param kw: dictionary of attributes names and values.
541 for k, v in kw.items():
542 if not hasattr(cls, k):
545 def quote_define_name(s):
547 Converts a string into an identifier suitable for C defines.
550 :param s: String to convert
552 :return: Identifier suitable for C defines
554 fu = re.sub('[^a-zA-Z0-9]', '_', s)
555 fu = re.sub('_+', '_', fu)
559 re_sh = re.compile('\\s|\'|"')
561 Regexp used for shell_escape below
564 def shell_escape(cmd):
567 ['ls', '-l', 'arg space'] -> ls -l 'arg space'
569 if isinstance(cmd, str):
571 return ' '.join(repr(x) if re_sh.search(x) else x for x in cmd)
575 Hashes lists of ordered data.
577 Using hash(tup) for tuples would be much more efficient,
578 but Python now enforces hash randomization
580 :param lst: list to hash
581 :type lst: list of strings
582 :return: hash of the list
584 return md5(repr(lst).encode()).digest()
590 :param fun: function to hash
592 :return: hash of the function
593 :rtype: string or bytes
597 except AttributeError:
598 if isinstance(fun, functools.partial):
599 code = list(fun.args)
600 # The method items() provides a sequence of tuples where the first element
601 # represents an optional argument of the partial function application
603 # The sorting result outcome will be consistent because:
604 # 1. tuples are compared in order of their elements
605 # 2. optional argument namess are unique
606 code.extend(sorted(fun.keywords.items()))
607 code.append(h_fun(fun.func))
608 fun.code = h_list(code)
611 h = inspect.getsource(fun)
612 except EnvironmentError:
616 except AttributeError:
622 Hashes objects recursively
624 :param ins: input object
625 :type ins: string or list or tuple or function
626 :rtype: string or bytes
628 # this function is not meant to be particularly fast
629 if isinstance(ins, str):
630 # a command is either a string
632 elif isinstance(ins, list) or isinstance(ins, tuple):
633 # or a list of functions/strings
634 ret = str([h_cmd(x) for x in ins])
636 # or just a python function
637 ret = str(h_fun(ins))
638 if sys.hexversion > 0x3000000:
639 ret = ret.encode('latin-1', 'xmlcharrefreplace')
642 reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
643 def subst_vars(expr, params):
645 Replaces ${VAR} with the value of VAR taken from a dict or a config set::
647 from waflib import Utils
648 s = Utils.subst_vars('${PREFIX}/bin', env)
651 :param expr: String to perform substitution on
652 :param params: Dictionary or config set to look up variable values.
660 # ConfigSet instances may contain lists
661 return params.get_flat(m.group(3))
662 except AttributeError:
663 return params[m.group(3)]
664 # if you get a TypeError, it means that 'expr' is not a string...
665 # Utils.subst_vars(None, env) will not work
666 return reg_subst.sub(repl_var, expr)
668 def destos_to_binfmt(key):
670 Returns the binary format based on the unversioned platform name,
671 and defaults to ``elf`` if nothing is found.
673 :param key: platform name
675 :return: string representing the binary format
679 elif key in ('win32', 'cygwin', 'uwin', 'msys'):
683 def unversioned_sys_platform():
685 Returns the unversioned platform name.
686 Some Python platform names contain versions, that depend on
687 the build environment, e.g. linux2, freebsd6, etc.
688 This returns the name without the version number. Exceptions are
689 os2 and win32, which are returned verbatim.
692 :return: Unversioned platform name
695 if s.startswith('java'):
696 # The real OS is hidden under the JVM.
697 from java.lang import System
698 s = System.getProperty('os.name')
699 # see http://lopica.sourceforge.net/os.html for a list of possible values
702 elif s.startswith('Windows '):
708 elif s in ('SunOS', 'Solaris'):
712 # powerpc == darwin for our purposes
715 if s == 'win32' or s == 'os2':
717 if s == 'cli' and os.name == 'nt':
718 # ironpython is only on windows as far as we know
720 return re.split('\d+$', s)[0]
732 Simple object for timing the execution of commands.
733 Its string representation is the duration::
735 from waflib.Utils import Timer
741 self.start_time = self.now()
744 delta = self.now() - self.start_time
745 if not isinstance(delta, datetime.timedelta):
746 delta = datetime.timedelta(seconds=delta)
748 hours, rem = divmod(delta.seconds, 3600)
749 minutes, seconds = divmod(rem, 60)
750 seconds += delta.microseconds * 1e-6
753 result += '%dd' % days
755 result += '%dh' % hours
756 if days or hours or minutes:
757 result += '%dm' % minutes
758 return '%s%.3fs' % (result, seconds)
761 return datetime.datetime.utcnow()
763 if hasattr(time, 'perf_counter'):
765 return time.perf_counter()
767 def read_la_file(path):
769 Reads property files, used by msvc.py
771 :param path: file to read
774 sp = re.compile(r'^([^=]+)=\'(.*)\'$')
776 for line in readf(path).splitlines():
778 _, left, right, _ = sp.split(line.strip())
786 Decorator: let a function cache its results, use like this::
792 .. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
794 :param fun: function to execute
796 :return: the return value of the function executed
806 wrap.__cache__ = cache
807 wrap.__name__ = fun.__name__
810 def get_registry_app_path(key, filename):
812 Returns the value of a registry key for an executable
815 :type filename: list of string
820 result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
824 if os.path.isfile(result):
829 Guess the default ``/usr/lib`` extension for 64-bit applications
834 # default settings for /usr/lib
836 if platform.architecture()[0] == '64bit':
837 if os.path.exists('/usr/lib64') and not os.path.exists('/usr/lib32'):
842 # private function for the time being!
843 return os.path.abspath(os.path.expanduser(p))
847 List of processes started to execute sub-process commands
852 Returns a process object that can execute commands as sub-processes
854 :rtype: subprocess.Popen
857 return process_pool.pop()
859 filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor.py'
860 cmd = [sys.executable, '-c', readf(filepath)]
861 return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
863 def run_prefork_process(cmd, kwargs, cargs):
865 Delegates process execution to a pre-forked process instance.
867 if not 'env' in kwargs:
868 kwargs['env'] = dict(os.environ)
870 obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
871 except (TypeError, AttributeError):
872 return run_regular_process(cmd, kwargs, cargs)
876 return run_regular_process(cmd, kwargs, cargs)
878 proc.stdin.write(obj)
879 proc.stdin.write('\n'.encode())
881 obj = proc.stdout.readline()
883 raise OSError('Preforked sub-process %r died' % proc.pid)
885 process_pool.append(proc)
886 lst = cPickle.loads(base64.b64decode(obj))
887 # Jython wrapper failures (bash/execvp)
889 ret, out, err, ex, trace = lst
893 elif ex == 'ValueError':
894 raise ValueError(trace)
895 elif ex == 'TimeoutExpired':
896 exc = TimeoutExpired(cmd, timeout=cargs['timeout'], output=out)
900 raise Exception(trace)
903 def lchown(path, user=-1, group=-1):
905 Change the owner/group of a path, raises an OSError if the
906 ownership change fails.
908 :param user: user to change
909 :type user: int or str
910 :param group: group to change
911 :type group: int or str
913 if isinstance(user, str):
915 entry = pwd.getpwnam(user)
917 raise OSError('Unknown user %r' % user)
919 if isinstance(group, str):
921 entry = grp.getgrnam(group)
923 raise OSError('Unknown group %r' % group)
925 return os.lchown(path, user, group)
927 def run_regular_process(cmd, kwargs, cargs={}):
929 Executes a subprocess command by using subprocess.Popen
931 proc = subprocess.Popen(cmd, **kwargs)
932 if kwargs.get('stdout') or kwargs.get('stderr'):
934 out, err = proc.communicate(**cargs)
935 except TimeoutExpired:
936 if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
937 os.killpg(proc.pid, signal.SIGKILL)
940 out, err = proc.communicate()
941 exc = TimeoutExpired(proc.args, timeout=cargs['timeout'], output=out)
944 status = proc.returncode
946 out, err = (None, None)
948 status = proc.wait(**cargs)
949 except TimeoutExpired as e:
950 if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
951 os.killpg(proc.pid, signal.SIGKILL)
956 return status, out, err
958 def run_process(cmd, kwargs, cargs={}):
960 Executes a subprocess by using a pre-forked process when possible
961 or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
962 and :py:func:`waflib.Utils.run_regular_process`
964 if kwargs.get('stdout') and kwargs.get('stderr'):
965 return run_prefork_process(cmd, kwargs, cargs)
967 return run_regular_process(cmd, kwargs, cargs)
969 def alloc_process_pool(n, force=False):
971 Allocates an amount of processes to the default pool so its size is at least *n*.
972 It is useful to call this function early so that the pre-forked
973 processes use as little memory as possible.
977 :param force: if True then *n* more processes are added to the existing pool
980 # mandatory on python2, unnecessary on python >= 3.2
981 global run_process, get_process, alloc_process_pool
983 n = max(n - len(process_pool), 0)
985 lst = [get_process() for x in range(n)]
987 run_process = run_regular_process
988 get_process = alloc_process_pool = nada
991 process_pool.append(x)
994 for k in process_pool:
1002 if (sys.hexversion<0x207000f and not is_win32) or sys.hexversion>=0x306000f:
1003 atexit.register(atexit_pool)
1005 if os.environ.get('WAF_NO_PREFORK') or sys.platform == 'cli' or not sys.executable:
1006 run_process = run_regular_process
1007 get_process = alloc_process_pool = nada