3 # Thomas Nagy, 2005-2018 (ita)
6 Utilities and platform-specific fixes
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)
12 from __future__
import with_statement
14 import atexit
, os
, sys
, errno
, inspect
, re
, datetime
, platform
, base64
, signal
, functools
, time
, shlex
19 import pickle
as cPickle
22 if os
.name
== 'posix' and sys
.version_info
[0] < 3:
24 import subprocess32
as subprocess
31 TimeoutExpired
= subprocess
.TimeoutExpired
32 except AttributeError:
33 class TimeoutExpired(Exception):
36 from collections
import deque
, defaultdict
39 import _winreg
as winreg
46 from waflib
import Errors
49 from hashlib
import md5
52 from hashlib
import sha1
as md5
54 # never fail to enable potential fixes from another module
61 from hashlib
import sha1
as md5
66 if not 'JOBS' in os
.environ
:
68 os
.environ
['JOBS'] = '1'
70 class threading(object):
72 A fake threading class for platforms lacking the threading module.
73 Use ``waf -j1`` on those platforms
82 threading
.Lock
= threading
.Thread
= Lock
84 SIG_NIL
= 'SIG_NIL_SIG_NIL_'.encode()
85 """Arbitrary null value for hashes. Modify this value according to the hash function in use"""
88 """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
91 """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
93 rot_chr
= ['\\', '|', '/', '-']
94 "List of characters to use when displaying the throbber (progress bar)"
97 "Index of the current throbber character (progress bar)"
99 class ordered_iter_dict(dict):
100 """Ordered dictionary that provides iteration from the most recently inserted keys first"""
101 def __init__(self
, *k
, **kw
):
103 dict.__init
__(self
, *k
, **kw
)
107 def __setitem__(self
, key
, value
):
108 if key
in dict.keys(self
):
110 dict.__setitem
__(self
, key
, value
)
112 def __delitem__(self
, key
):
113 dict.__delitem
__(self
, key
)
119 return reversed(self
.lst
)
121 return reversed(self
.lst
)
123 class lru_node(object):
125 Used by :py:class:`waflib.Utils.lru_cache`
127 __slots__
= ('next', 'prev', 'key', 'val')
134 class lru_cache(object):
136 A simple least-recently used cache with lazy allocation
138 __slots__
= ('maxlen', 'table', 'head')
139 def __init__(self
, maxlen
=100):
142 Maximum amount of elements in the cache
148 self
.head
= lru_node()
149 self
.head
.next
= self
.head
150 self
.head
.prev
= self
.head
152 def __getitem__(self
, key
):
153 node
= self
.table
[key
]
154 # assert(key==node.key)
155 if node
is self
.head
:
158 # detach the node found
159 node
.prev
.next
= node
.next
160 node
.next
.prev
= node
.prev
163 node
.next
= self
.head
.next
164 node
.prev
= self
.head
165 self
.head
= node
.next
.prev
= node
.prev
.next
= node
169 def __setitem__(self
, key
, val
):
170 if key
in self
.table
:
171 # update the value for an existing key
172 node
= self
.table
[key
]
174 self
.__getitem
__(key
)
176 if len(self
.table
) < self
.maxlen
:
177 # the very first item is unused until the maximum is reached
179 node
.prev
= self
.head
180 node
.next
= self
.head
.next
181 node
.prev
.next
= node
.next
.prev
= node
183 node
= self
.head
= self
.head
.next
186 del self
.table
[node
.key
]
192 self
.table
[key
] = node
194 class lazy_generator(object):
195 def __init__(self
, fun
, params
):
205 except AttributeError:
206 it
= self
.it
= self
.fun(*self
.params
)
211 is_win32
= os
.sep
== '\\' or sys
.platform
== 'win32' or os
.name
== 'nt' # msys2
213 Whether this system is a Windows series
216 def readf(fname
, m
='r', encoding
='latin-1'):
218 Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
221 from waflib import Utils
222 txt = Utils.readf(self.path.find_node('wscript').abspath())
223 txt = ctx.path.find_node('wscript').read()
226 :param fname: Path to file
229 :type encoding: string
230 :param encoding: encoding value, only used for python 3
232 :return: Content of the file
235 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
237 with
open(fname
, m
) as f
:
240 txt
= txt
.decode(encoding
)
244 with
open(fname
, m
) as f
:
248 def writef(fname
, data
, m
='w', encoding
='latin-1'):
250 Writes an entire file from a string.
251 See also :py:meth:`waflib.Node.Node.writef`::
254 from waflib import Utils
255 txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
256 self.path.make_node('i_like_kittens').write('some data')
259 :param fname: Path to file
261 :param data: The contents to write to the file
264 :type encoding: string
265 :param encoding: encoding value, only used for python 3
267 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
268 data
= data
.encode(encoding
)
270 with
open(fname
, m
) as f
:
275 Computes a hash value for a file by using md5. Use the md5_tstamp
276 extension to get faster build hashes if necessary.
279 :param fname: path to the file to hash
280 :return: hash of the file contents
281 :rtype: string or bytes
284 with
open(fname
, 'rb') as f
:
286 fname
= f
.read(200000)
290 def readf_win32(f
, m
='r', encoding
='latin-1'):
291 flags
= os
.O_NOINHERIT | os
.O_RDONLY
297 fd
= os
.open(f
, flags
)
299 raise IOError('Cannot read from %r' % f
)
301 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
303 with os
.fdopen(fd
, m
) as f
:
306 txt
= txt
.decode(encoding
)
310 with os
.fdopen(fd
, m
) as f
:
314 def writef_win32(f
, data
, m
='w', encoding
='latin-1'):
315 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
316 data
= data
.encode(encoding
)
318 flags
= os
.O_CREAT | os
.O_TRUNC | os
.O_WRONLY | os
.O_NOINHERIT
324 fd
= os
.open(f
, flags
)
326 raise OSError('Cannot write to %r' % f
)
327 with os
.fdopen(fd
, m
) as f
:
330 def h_file_win32(fname
):
332 fd
= os
.open(fname
, os
.O_BINARY | os
.O_RDONLY | os
.O_NOINHERIT
)
334 raise OSError('Cannot read from %r' % fname
)
336 with os
.fdopen(fd
, 'rb') as f
:
338 fname
= f
.read(200000)
346 if hasattr(os
, 'O_NOINHERIT') and sys
.hexversion
< 0x3040000:
347 # replace the default functions
349 writef
= writef_win32
350 h_file
= h_file_win32
357 ret
= binascii
.hexlify(s
)
358 if not isinstance(ret
, str):
359 ret
= ret
.decode('utf-8')
363 return s
.encode('hex')
366 Return the hexadecimal representation of a string
368 :param s: string to convert
372 def listdir_win32(s
):
374 Lists the contents of a folder in a portable manner.
375 On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
378 :param s: a string, which can be empty on Windows
384 # there is nothing much we can do
385 return [x
+ ':\\' for x
in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
387 dlen
= 4 # length of "?:\\x00"
389 buf
= ctypes
.create_string_buffer(maxdrives
* dlen
)
390 ndrives
= ctypes
.windll
.kernel32
.GetLogicalDriveStringsA(maxdrives
*dlen
, ctypes
.byref(buf
))
391 return [ str(buf
.raw
[4*i
:4*i
+2].decode('ascii')) for i
in range(int(ndrives
/dlen
)) ]
393 if len(s
) == 2 and s
[1] == ":":
396 if not os
.path
.isdir(s
):
397 e
= OSError('%s is not a directory' % s
)
398 e
.errno
= errno
.ENOENT
404 listdir
= listdir_win32
408 Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
410 from waflib.Utils import num2ver
411 num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
413 :type ver: string or tuple of numbers
414 :param ver: a version number
416 if isinstance(ver
, str):
417 ver
= tuple(ver
.split('.'))
418 if isinstance(ver
, tuple):
422 ret
+= 256**(3 - i
) * int(ver
[i
])
428 Converts a string argument to a list by splitting it by spaces.
429 Returns the object if not a string::
431 from waflib.Utils import to_list
432 lst = to_list('a b c d')
434 :param val: list of string or space-separated string
436 :return: Argument converted to list
438 if isinstance(val
, str):
443 def console_encoding():
450 codepage
= ctypes
.windll
.kernel32
.GetConsoleCP()
451 except AttributeError:
455 if 65001 == codepage
and sys
.version_info
< (3, 3):
457 return 'cp%d' % codepage
458 return sys
.stdout
.encoding
or ('cp1252' if is_win32
else 'latin-1')
460 def split_path_unix(path
):
461 return path
.split('/')
463 def split_path_cygwin(path
):
464 if path
.startswith('//'):
465 ret
= path
.split('/')[2:]
466 ret
[0] = '/' + ret
[0]
468 return path
.split('/')
470 re_sp
= re
.compile('[/\\\\]+')
471 def split_path_win32(path
):
472 if path
.startswith('\\\\'):
473 ret
= re_sp
.split(path
)[1:]
474 ret
[0] = '\\\\' + ret
[0]
475 if ret
[0] == '\\\\?':
478 return re_sp
.split(path
)
481 def split_path_msys(path
):
482 if path
.startswith(('/', '\\')) and not path
.startswith(('//', '\\\\')):
483 # msys paths can be in the form /usr/bin
486 # msys has python 2.7 or 3, so we can use this
487 msysroot
= subprocess
.check_output(['cygpath', '-w', '/']).decode(sys
.stdout
.encoding
or 'latin-1')
488 msysroot
= msysroot
.strip()
489 path
= os
.path
.normpath(msysroot
+ os
.sep
+ path
)
490 return split_path_win32(path
)
492 if sys
.platform
== 'cygwin':
493 split_path
= split_path_cygwin
495 # Consider this an MSYSTEM environment if $MSYSTEM is set and python
496 # reports is executable from a unix like path on a windows host.
497 if os
.environ
.get('MSYSTEM') and sys
.executable
.startswith('/'):
498 split_path
= split_path_msys
500 split_path
= split_path_win32
502 split_path
= split_path_unix
504 split_path
.__doc
__ = """
505 Splits a path by / or \\; do not confuse this function with with ``os.path.split``
508 :param path: path to split
509 :return: list of string
514 Ensures that a directory exists (similar to ``mkdir -p``).
517 :param path: Path to directory
518 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
520 if not os
.path
.isdir(path
):
524 if not os
.path
.isdir(path
):
525 raise Errors
.WafError('Cannot create the folder %r' % path
, ex
=e
)
527 def check_exe(name
, env
=None):
529 Ensures that a program exists
532 :param name: path to the program
533 :param env: configuration object
534 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
535 :return: path of the program or None
536 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
539 raise ValueError('Cannot execute an empty string!')
541 return os
.path
.isfile(fpath
) and os
.access(fpath
, os
.X_OK
)
543 fpath
, fname
= os
.path
.split(name
)
544 if fpath
and is_exe(name
):
545 return os
.path
.abspath(name
)
547 env
= env
or os
.environ
548 for path
in env
['PATH'].split(os
.pathsep
):
549 path
= path
.strip('"')
550 exe_file
= os
.path
.join(path
, name
)
552 return os
.path
.abspath(exe_file
)
555 def def_attrs(cls
, **kw
):
557 Sets default attributes on a class instance
560 :param cls: the class to update the given attributes in.
562 :param kw: dictionary of attributes names and values.
564 for k
, v
in kw
.items():
565 if not hasattr(cls
, k
):
568 def quote_define_name(s
):
570 Converts a string into an identifier suitable for C defines.
573 :param s: String to convert
575 :return: Identifier suitable for C defines
577 fu
= re
.sub('[^a-zA-Z0-9]', '_', s
)
578 fu
= re
.sub('_+', '_', fu
)
582 # shlex.quote didn't exist until python 3.3. Prior to that it was a non-documented
585 shell_quote
= shlex
.quote
586 except AttributeError:
588 shell_quote
= pipes
.quote
590 def shell_escape(cmd
):
593 ['ls', '-l', 'arg space'] -> ls -l 'arg space'
595 if isinstance(cmd
, str):
597 return ' '.join(shell_quote(x
) for x
in cmd
)
601 Hashes lists of ordered data.
603 Using hash(tup) for tuples would be much more efficient,
604 but Python now enforces hash randomization
606 :param lst: list to hash
607 :type lst: list of strings
608 :return: hash of the list
610 return md5(repr(lst
).encode()).digest()
612 if sys
.hexversion
< 0x3000000:
613 def h_list_python2(lst
):
614 return md5(repr(lst
)).digest()
615 h_list_python2
.__doc__
= h_list
.__doc
__
616 h_list
= h_list_python2
622 :param fun: function to hash
624 :return: hash of the function
625 :rtype: string or bytes
629 except AttributeError:
630 if isinstance(fun
, functools
.partial
):
631 code
= list(fun
.args
)
632 # The method items() provides a sequence of tuples where the first element
633 # represents an optional argument of the partial function application
635 # The sorting result outcome will be consistent because:
636 # 1. tuples are compared in order of their elements
637 # 2. optional argument namess are unique
638 code
.extend(sorted(fun
.keywords
.items()))
639 code
.append(h_fun(fun
.func
))
640 fun
.code
= h_list(code
)
643 h
= inspect
.getsource(fun
)
644 except EnvironmentError:
648 except AttributeError:
654 Hashes objects recursively
656 :param ins: input object
657 :type ins: string or list or tuple or function
658 :rtype: string or bytes
660 # this function is not meant to be particularly fast
661 if isinstance(ins
, str):
662 # a command is either a string
664 elif isinstance(ins
, list) or isinstance(ins
, tuple):
665 # or a list of functions/strings
666 ret
= str([h_cmd(x
) for x
in ins
])
668 # or just a python function
669 ret
= str(h_fun(ins
))
670 if sys
.hexversion
> 0x3000000:
671 ret
= ret
.encode('latin-1', 'xmlcharrefreplace')
674 reg_subst
= re
.compile(r
"(\\\\)|(\$\$)|\$\{([^}]+)\}")
675 def subst_vars(expr
, params
):
677 Replaces ${VAR} with the value of VAR taken from a dict or a config set::
679 from waflib import Utils
680 s = Utils.subst_vars('${PREFIX}/bin', env)
683 :param expr: String to perform substitution on
684 :param params: Dictionary or config set to look up variable values.
692 # ConfigSet instances may contain lists
693 return params
.get_flat(m
.group(3))
694 except AttributeError:
695 return params
[m
.group(3)]
696 # if you get a TypeError, it means that 'expr' is not a string...
697 # Utils.subst_vars(None, env) will not work
698 return reg_subst
.sub(repl_var
, expr
)
700 def destos_to_binfmt(key
):
702 Returns the binary format based on the unversioned platform name,
703 and defaults to ``elf`` if nothing is found.
705 :param key: platform name
707 :return: string representing the binary format
711 elif key
in ('win32', 'cygwin', 'uwin', 'msys'):
715 def unversioned_sys_platform():
717 Returns the unversioned platform name.
718 Some Python platform names contain versions, that depend on
719 the build environment, e.g. linux2, freebsd6, etc.
720 This returns the name without the version number. Exceptions are
721 os2 and win32, which are returned verbatim.
724 :return: Unversioned platform name
727 if s
.startswith('java'):
728 # The real OS is hidden under the JVM.
729 from java
.lang
import System
730 s
= System
.getProperty('os.name')
731 # see http://lopica.sourceforge.net/os.html for a list of possible values
734 elif s
.startswith('Windows '):
740 elif s
in ('SunOS', 'Solaris'):
744 # powerpc == darwin for our purposes
747 if s
== 'win32' or s
== 'os2':
749 if s
== 'cli' and os
.name
== 'nt':
750 # ironpython is only on windows as far as we know
752 return re
.split(r
'\d+$', s
)[0]
764 Simple object for timing the execution of commands.
765 Its string representation is the duration::
767 from waflib.Utils import Timer
773 self
.start_time
= self
.now()
776 delta
= self
.now() - self
.start_time
777 if not isinstance(delta
, datetime
.timedelta
):
778 delta
= datetime
.timedelta(seconds
=delta
)
780 hours
, rem
= divmod(delta
.seconds
, 3600)
781 minutes
, seconds
= divmod(rem
, 60)
782 seconds
+= delta
.microseconds
* 1e-6
785 result
+= '%dd' % days
787 result
+= '%dh' % hours
788 if days
or hours
or minutes
:
789 result
+= '%dm' % minutes
790 return '%s%.3fs' % (result
, seconds
)
793 return datetime
.datetime
.utcnow()
795 if hasattr(time
, 'perf_counter'):
797 return time
.perf_counter()
799 def read_la_file(path
):
801 Reads property files, used by msvc.py
803 :param path: file to read
806 sp
= re
.compile(r
'^([^=]+)=\'(.*)\'$
')
808 for line in readf(path).splitlines():
810 _, left, right, _ = sp.split(line.strip())
818 Decorator: let a function cache its results, use like this::
824 .. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
826 :param fun: function to execute
828 :return: the return value of the function executed
838 wrap.__cache__ = cache
839 wrap.__name__ = fun.__name__
842 def get_registry_app_path(key, filename):
844 Returns the value of a registry key for an executable
847 :type filename: list of string
852 result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
856 if os.path.isfile(result):
861 Guess the default ``/usr/lib`` extension for 64-bit applications
866 # default settings for /usr/lib
868 if platform.architecture()[0] == '64bit
':
869 if os.path.exists('/usr
/lib64
') and not os.path.exists('/usr
/lib32
'):
873 def loose_version(ver_str):
874 # private for the time being!
876 lst = re.split(r'([.]|
\\d
+|
[a
-zA
-Z
])', ver_str)
878 for i, val in enumerate(lst):
887 # private function for the time being!
888 return os.path.abspath(os.path.expanduser(p))
892 List of processes started to execute sub-process commands
897 Returns a process object that can execute commands as sub-processes
899 :rtype: subprocess.Popen
902 return process_pool.pop()
904 filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor
.py
'
905 cmd = [sys.executable, '-c
', readf(filepath)]
906 return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0, close_fds=not is_win32)
908 def run_prefork_process(cmd, kwargs, cargs):
910 Delegates process execution to a pre-forked process instance.
912 if not kwargs.get('env
'):
913 kwargs['env
'] = dict(os.environ)
915 obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
916 except (TypeError, AttributeError):
917 return run_regular_process(cmd, kwargs, cargs)
921 return run_regular_process(cmd, kwargs, cargs)
923 proc.stdin.write(obj)
924 proc.stdin.write('\n'.encode())
926 obj = proc.stdout.readline()
928 raise OSError('Preforked sub
-process
%r died
' % proc.pid)
930 process_pool.append(proc)
931 lst = cPickle.loads(base64.b64decode(obj))
932 # Jython wrapper failures (bash/execvp)
934 ret, out, err, ex, trace = lst
938 elif ex == 'ValueError':
939 raise ValueError(trace)
940 elif ex == 'TimeoutExpired
':
941 exc = TimeoutExpired(cmd, timeout=cargs['timeout
'], output=out)
945 raise Exception(trace)
948 def lchown(path, user=-1, group=-1):
950 Change the owner/group of a path, raises an OSError if the
951 ownership change fails.
953 :param user: user to change
954 :type user: int or str
955 :param group: group to change
956 :type group: int or str
958 if isinstance(user, str):
960 entry = pwd.getpwnam(user)
962 raise OSError('Unknown user
%r' % user)
964 if isinstance(group, str):
966 entry = grp.getgrnam(group)
968 raise OSError('Unknown group
%r' % group)
970 return os.lchown(path, user, group)
972 def run_regular_process(cmd, kwargs, cargs={}):
974 Executes a subprocess command by using subprocess.Popen
976 proc = subprocess.Popen(cmd, **kwargs)
977 if kwargs.get('stdout
') or kwargs.get('stderr
'):
979 out, err = proc.communicate(**cargs)
980 except TimeoutExpired:
981 if kwargs.get('start_new_session
') and hasattr(os, 'killpg
'):
982 os.killpg(proc.pid, signal.SIGKILL)
985 out, err = proc.communicate()
986 exc = TimeoutExpired(proc.args, timeout=cargs['timeout
'], output=out)
989 status = proc.returncode
991 out, err = (None, None)
993 status = proc.wait(**cargs)
994 except TimeoutExpired as e:
995 if kwargs.get('start_new_session
') and hasattr(os, 'killpg
'):
996 os.killpg(proc.pid, signal.SIGKILL)
1001 return status, out, err
1003 def run_process(cmd, kwargs, cargs={}):
1005 Executes a subprocess by using a pre-forked process when possible
1006 or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
1007 and :py:func:`waflib.Utils.run_regular_process`
1009 if kwargs.get('stdout
') and kwargs.get('stderr
'):
1010 return run_prefork_process(cmd, kwargs, cargs)
1012 return run_regular_process(cmd, kwargs, cargs)
1014 def alloc_process_pool(n, force=False):
1016 Allocates an amount of processes to the default pool so its size is at least *n*.
1017 It is useful to call this function early so that the pre-forked
1018 processes use as little memory as possible.
1022 :param force: if True then *n* more processes are added to the existing pool
1025 # mandatory on python2, unnecessary on python >= 3.2
1026 global run_process, get_process, alloc_process_pool
1028 n = max(n - len(process_pool), 0)
1030 lst = [get_process() for x in range(n)]
1032 run_process = run_regular_process
1033 get_process = alloc_process_pool = nada
1036 process_pool.append(x)
1039 for k in process_pool:
1047 if (sys.hexversion<0x207000f and not is_win32) or sys.hexversion>=0x306000f:
1048 atexit.register(atexit_pool)
1050 if os.environ.get('WAF_NO_PREFORK
') or sys.platform == 'cli
' or not sys.executable:
1051 run_process = run_regular_process
1052 get_process = alloc_process_pool = nada