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
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
54 # never fail to enable fixes from another module
60 if not 'JOBS' in os
.environ
:
62 os
.environ
['JOBS'] = '1'
64 class threading(object):
66 A fake threading class for platforms lacking the threading module.
67 Use ``waf -j1`` on those platforms
76 threading
.Lock
= threading
.Thread
= Lock
78 SIG_NIL
= 'SIG_NIL_SIG_NIL_'.encode()
79 """Arbitrary null value for hashes. Modify this value according to the hash function in use"""
82 """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
85 """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
87 rot_chr
= ['\\', '|', '/', '-']
88 "List of characters to use when displaying the throbber (progress bar)"
91 "Index of the current throbber character (progress bar)"
93 class ordered_iter_dict(dict):
94 """Ordered dictionary that provides iteration from the most recently inserted keys first"""
95 def __init__(self
, *k
, **kw
):
97 dict.__init
__(self
, *k
, **kw
)
101 def __setitem__(self
, key
, value
):
102 if key
in dict.keys(self
):
104 dict.__setitem
__(self
, key
, value
)
106 def __delitem__(self
, key
):
107 dict.__delitem
__(self
, key
)
113 return reversed(self
.lst
)
115 return reversed(self
.lst
)
117 class lru_node(object):
119 Used by :py:class:`waflib.Utils.lru_cache`
121 __slots__
= ('next', 'prev', 'key', 'val')
128 class lru_cache(object):
130 A simple least-recently used cache with lazy allocation
132 __slots__
= ('maxlen', 'table', 'head')
133 def __init__(self
, maxlen
=100):
136 Maximum amount of elements in the cache
142 self
.head
= lru_node()
143 self
.head
.next
= self
.head
144 self
.head
.prev
= self
.head
146 def __getitem__(self
, key
):
147 node
= self
.table
[key
]
148 # assert(key==node.key)
149 if node
is self
.head
:
152 # detach the node found
153 node
.prev
.next
= node
.next
154 node
.next
.prev
= node
.prev
157 node
.next
= self
.head
.next
158 node
.prev
= self
.head
159 self
.head
= node
.next
.prev
= node
.prev
.next
= node
163 def __setitem__(self
, key
, val
):
164 if key
in self
.table
:
165 # update the value for an existing key
166 node
= self
.table
[key
]
168 self
.__getitem
__(key
)
170 if len(self
.table
) < self
.maxlen
:
171 # the very first item is unused until the maximum is reached
173 node
.prev
= self
.head
174 node
.next
= self
.head
.next
175 node
.prev
.next
= node
.next
.prev
= node
177 node
= self
.head
= self
.head
.next
180 del self
.table
[node
.key
]
186 self
.table
[key
] = node
188 class lazy_generator(object):
189 def __init__(self
, fun
, params
):
199 except AttributeError:
200 it
= self
.it
= self
.fun(*self
.params
)
205 is_win32
= os
.sep
== '\\' or sys
.platform
== 'win32' # msys2
207 Whether this system is a Windows series
210 def readf(fname
, m
='r', encoding
='latin-1'):
212 Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
215 from waflib import Utils
216 txt = Utils.readf(self.path.find_node('wscript').abspath())
217 txt = ctx.path.find_node('wscript').read()
220 :param fname: Path to file
223 :type encoding: string
224 :param encoding: encoding value, only used for python 3
226 :return: Content of the file
229 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
231 with
open(fname
, m
) as f
:
234 txt
= txt
.decode(encoding
)
238 with
open(fname
, m
) as f
:
242 def writef(fname
, data
, m
='w', encoding
='latin-1'):
244 Writes an entire file from a string.
245 See also :py:meth:`waflib.Node.Node.writef`::
248 from waflib import Utils
249 txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
250 self.path.make_node('i_like_kittens').write('some data')
253 :param fname: Path to file
255 :param data: The contents to write to the file
258 :type encoding: string
259 :param encoding: encoding value, only used for python 3
261 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
262 data
= data
.encode(encoding
)
264 with
open(fname
, m
) as f
:
269 Computes a hash value for a file by using md5. Use the md5_tstamp
270 extension to get faster build hashes if necessary.
273 :param fname: path to the file to hash
274 :return: hash of the file contents
275 :rtype: string or bytes
278 with
open(fname
, 'rb') as f
:
280 fname
= f
.read(200000)
284 def readf_win32(f
, m
='r', encoding
='latin-1'):
285 flags
= os
.O_NOINHERIT | os
.O_RDONLY
291 fd
= os
.open(f
, flags
)
293 raise IOError('Cannot read from %r' % f
)
295 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
297 with os
.fdopen(fd
, m
) as f
:
300 txt
= txt
.decode(encoding
)
304 with os
.fdopen(fd
, m
) as f
:
308 def writef_win32(f
, data
, m
='w', encoding
='latin-1'):
309 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
310 data
= data
.encode(encoding
)
312 flags
= os
.O_CREAT | os
.O_TRUNC | os
.O_WRONLY | os
.O_NOINHERIT
318 fd
= os
.open(f
, flags
)
320 raise OSError('Cannot write to %r' % f
)
321 with os
.fdopen(fd
, m
) as f
:
324 def h_file_win32(fname
):
326 fd
= os
.open(fname
, os
.O_BINARY | os
.O_RDONLY | os
.O_NOINHERIT
)
328 raise OSError('Cannot read from %r' % fname
)
330 with os
.fdopen(fd
, 'rb') as f
:
332 fname
= f
.read(200000)
340 if hasattr(os
, 'O_NOINHERIT') and sys
.hexversion
< 0x3040000:
341 # replace the default functions
343 writef
= writef_win32
344 h_file
= h_file_win32
351 ret
= binascii
.hexlify(s
)
352 if not isinstance(ret
, str):
353 ret
= ret
.decode('utf-8')
357 return s
.encode('hex')
360 Return the hexadecimal representation of a string
362 :param s: string to convert
366 def listdir_win32(s
):
368 Lists the contents of a folder in a portable manner.
369 On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
372 :param s: a string, which can be empty on Windows
378 # there is nothing much we can do
379 return [x
+ ':\\' for x
in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
381 dlen
= 4 # length of "?:\\x00"
383 buf
= ctypes
.create_string_buffer(maxdrives
* dlen
)
384 ndrives
= ctypes
.windll
.kernel32
.GetLogicalDriveStringsA(maxdrives
*dlen
, ctypes
.byref(buf
))
385 return [ str(buf
.raw
[4*i
:4*i
+2].decode('ascii')) for i
in range(int(ndrives
/dlen
)) ]
387 if len(s
) == 2 and s
[1] == ":":
390 if not os
.path
.isdir(s
):
391 e
= OSError('%s is not a directory' % s
)
392 e
.errno
= errno
.ENOENT
398 listdir
= listdir_win32
402 Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
404 from waflib.Utils import num2ver
405 num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
407 :type ver: string or tuple of numbers
408 :param ver: a version number
410 if isinstance(ver
, str):
411 ver
= tuple(ver
.split('.'))
412 if isinstance(ver
, tuple):
416 ret
+= 256**(3 - i
) * int(ver
[i
])
422 Converts a string argument to a list by splitting it by spaces.
423 Returns the object if not a string::
425 from waflib.Utils import to_list
426 lst = to_list('a b c d')
428 :param val: list of string or space-separated string
430 :return: Argument converted to list
432 if isinstance(val
, str):
437 def console_encoding():
444 codepage
= ctypes
.windll
.kernel32
.GetConsoleCP()
445 except AttributeError:
449 return 'cp%d' % codepage
450 return sys
.stdout
.encoding
or ('cp1252' if is_win32
else 'latin-1')
452 def split_path_unix(path
):
453 return path
.split('/')
455 def split_path_cygwin(path
):
456 if path
.startswith('//'):
457 ret
= path
.split('/')[2:]
458 ret
[0] = '/' + ret
[0]
460 return path
.split('/')
462 re_sp
= re
.compile('[/\\\\]+')
463 def split_path_win32(path
):
464 if path
.startswith('\\\\'):
465 ret
= re_sp
.split(path
)[1:]
466 ret
[0] = '\\\\' + ret
[0]
467 if ret
[0] == '\\\\?':
470 return re_sp
.split(path
)
473 def split_path_msys(path
):
474 if path
.startswith(('/', '\\')) and not path
.startswith(('//', '\\\\')):
475 # msys paths can be in the form /usr/bin
478 # msys has python 2.7 or 3, so we can use this
479 msysroot
= subprocess
.check_output(['cygpath', '-w', '/']).decode(sys
.stdout
.encoding
or 'latin-1')
480 msysroot
= msysroot
.strip()
481 path
= os
.path
.normpath(msysroot
+ os
.sep
+ path
)
482 return split_path_win32(path
)
484 if sys
.platform
== 'cygwin':
485 split_path
= split_path_cygwin
487 if os
.environ
.get('MSYSTEM'):
488 split_path
= split_path_msys
490 split_path
= split_path_win32
492 split_path
= split_path_unix
494 split_path
.__doc
__ = """
495 Splits a path by / or \\; do not confuse this function with with ``os.path.split``
498 :param path: path to split
499 :return: list of string
504 Ensures that a directory exists (similar to ``mkdir -p``).
507 :param path: Path to directory
508 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
510 if not os
.path
.isdir(path
):
514 if not os
.path
.isdir(path
):
515 raise Errors
.WafError('Cannot create the folder %r' % path
, ex
=e
)
517 def check_exe(name
, env
=None):
519 Ensures that a program exists
522 :param name: path to the program
523 :param env: configuration object
524 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
525 :return: path of the program or None
526 :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
529 raise ValueError('Cannot execute an empty string!')
531 return os
.path
.isfile(fpath
) and os
.access(fpath
, os
.X_OK
)
533 fpath
, fname
= os
.path
.split(name
)
534 if fpath
and is_exe(name
):
535 return os
.path
.abspath(name
)
537 env
= env
or os
.environ
538 for path
in env
['PATH'].split(os
.pathsep
):
539 path
= path
.strip('"')
540 exe_file
= os
.path
.join(path
, name
)
542 return os
.path
.abspath(exe_file
)
545 def def_attrs(cls
, **kw
):
547 Sets default attributes on a class instance
550 :param cls: the class to update the given attributes in.
552 :param kw: dictionary of attributes names and values.
554 for k
, v
in kw
.items():
555 if not hasattr(cls
, k
):
558 def quote_define_name(s
):
560 Converts a string into an identifier suitable for C defines.
563 :param s: String to convert
565 :return: Identifier suitable for C defines
567 fu
= re
.sub('[^a-zA-Z0-9]', '_', s
)
568 fu
= re
.sub('_+', '_', fu
)
572 re_sh
= re
.compile('\\s|\'|"')
574 Regexp used for shell_escape below
577 def shell_escape(cmd
):
580 ['ls', '-l', 'arg space'] -> ls -l 'arg space'
582 if isinstance(cmd
, str):
584 return ' '.join(repr(x
) if re_sh
.search(x
) else x
for x
in cmd
)
588 Hashes lists of ordered data.
590 Using hash(tup) for tuples would be much more efficient,
591 but Python now enforces hash randomization
593 :param lst: list to hash
594 :type lst: list of strings
595 :return: hash of the list
597 return md5(repr(lst
).encode()).digest()
603 :param fun: function to hash
605 :return: hash of the function
606 :rtype: string or bytes
610 except AttributeError:
611 if isinstance(fun
, functools
.partial
):
612 code
= list(fun
.args
)
613 # The method items() provides a sequence of tuples where the first element
614 # represents an optional argument of the partial function application
616 # The sorting result outcome will be consistent because:
617 # 1. tuples are compared in order of their elements
618 # 2. optional argument namess are unique
619 code
.extend(sorted(fun
.keywords
.items()))
620 code
.append(h_fun(fun
.func
))
621 fun
.code
= h_list(code
)
624 h
= inspect
.getsource(fun
)
625 except EnvironmentError:
629 except AttributeError:
635 Hashes objects recursively
637 :param ins: input object
638 :type ins: string or list or tuple or function
639 :rtype: string or bytes
641 # this function is not meant to be particularly fast
642 if isinstance(ins
, str):
643 # a command is either a string
645 elif isinstance(ins
, list) or isinstance(ins
, tuple):
646 # or a list of functions/strings
647 ret
= str([h_cmd(x
) for x
in ins
])
649 # or just a python function
650 ret
= str(h_fun(ins
))
651 if sys
.hexversion
> 0x3000000:
652 ret
= ret
.encode('latin-1', 'xmlcharrefreplace')
655 reg_subst
= re
.compile(r
"(\\\\)|(\$\$)|\$\{([^}]+)\}")
656 def subst_vars(expr
, params
):
658 Replaces ${VAR} with the value of VAR taken from a dict or a config set::
660 from waflib import Utils
661 s = Utils.subst_vars('${PREFIX}/bin', env)
664 :param expr: String to perform substitution on
665 :param params: Dictionary or config set to look up variable values.
673 # ConfigSet instances may contain lists
674 return params
.get_flat(m
.group(3))
675 except AttributeError:
676 return params
[m
.group(3)]
677 # if you get a TypeError, it means that 'expr' is not a string...
678 # Utils.subst_vars(None, env) will not work
679 return reg_subst
.sub(repl_var
, expr
)
681 def destos_to_binfmt(key
):
683 Returns the binary format based on the unversioned platform name,
684 and defaults to ``elf`` if nothing is found.
686 :param key: platform name
688 :return: string representing the binary format
692 elif key
in ('win32', 'cygwin', 'uwin', 'msys'):
696 def unversioned_sys_platform():
698 Returns the unversioned platform name.
699 Some Python platform names contain versions, that depend on
700 the build environment, e.g. linux2, freebsd6, etc.
701 This returns the name without the version number. Exceptions are
702 os2 and win32, which are returned verbatim.
705 :return: Unversioned platform name
708 if s
.startswith('java'):
709 # The real OS is hidden under the JVM.
710 from java
.lang
import System
711 s
= System
.getProperty('os.name')
712 # see http://lopica.sourceforge.net/os.html for a list of possible values
715 elif s
.startswith('Windows '):
721 elif s
in ('SunOS', 'Solaris'):
725 # powerpc == darwin for our purposes
728 if s
== 'win32' or s
== 'os2':
730 if s
== 'cli' and os
.name
== 'nt':
731 # ironpython is only on windows as far as we know
733 return re
.split('\d+$', s
)[0]
745 Simple object for timing the execution of commands.
746 Its string representation is the duration::
748 from waflib.Utils import Timer
754 self
.start_time
= self
.now()
757 delta
= self
.now() - self
.start_time
758 if not isinstance(delta
, datetime
.timedelta
):
759 delta
= datetime
.timedelta(seconds
=delta
)
761 hours
, rem
= divmod(delta
.seconds
, 3600)
762 minutes
, seconds
= divmod(rem
, 60)
763 seconds
+= delta
.microseconds
* 1e-6
766 result
+= '%dd' % days
768 result
+= '%dh' % hours
769 if days
or hours
or minutes
:
770 result
+= '%dm' % minutes
771 return '%s%.3fs' % (result
, seconds
)
774 return datetime
.datetime
.utcnow()
776 if hasattr(time
, 'perf_counter'):
778 return time
.perf_counter()
780 def read_la_file(path
):
782 Reads property files, used by msvc.py
784 :param path: file to read
787 sp
= re
.compile(r
'^([^=]+)=\'(.*)\'$
')
789 for line in readf(path).splitlines():
791 _, left, right, _ = sp.split(line.strip())
799 Decorator: let a function cache its results, use like this::
805 .. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
807 :param fun: function to execute
809 :return: the return value of the function executed
819 wrap.__cache__ = cache
820 wrap.__name__ = fun.__name__
823 def get_registry_app_path(key, filename):
825 Returns the value of a registry key for an executable
828 :type filename: list of string
833 result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
837 if os.path.isfile(result):
842 Guess the default ``/usr/lib`` extension for 64-bit applications
847 # default settings for /usr/lib
849 if platform.architecture()[0] == '64bit
':
850 if os.path.exists('/usr
/lib64
') and not os.path.exists('/usr
/lib32
'):
855 # private function for the time being!
856 return os.path.abspath(os.path.expanduser(p))
860 List of processes started to execute sub-process commands
865 Returns a process object that can execute commands as sub-processes
867 :rtype: subprocess.Popen
870 return process_pool.pop()
872 filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor
.py
'
873 cmd = [sys.executable, '-c
', readf(filepath)]
874 return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
876 def run_prefork_process(cmd, kwargs, cargs):
878 Delegates process execution to a pre-forked process instance.
880 if not 'env
' in kwargs:
881 kwargs['env
'] = dict(os.environ)
883 obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
884 except (TypeError, AttributeError):
885 return run_regular_process(cmd, kwargs, cargs)
889 return run_regular_process(cmd, kwargs, cargs)
891 proc.stdin.write(obj)
892 proc.stdin.write('\n'.encode())
894 obj = proc.stdout.readline()
896 raise OSError('Preforked sub
-process
%r died
' % proc.pid)
898 process_pool.append(proc)
899 lst = cPickle.loads(base64.b64decode(obj))
900 # Jython wrapper failures (bash/execvp)
902 ret, out, err, ex, trace = lst
906 elif ex == 'ValueError':
907 raise ValueError(trace)
908 elif ex == 'TimeoutExpired
':
909 exc = TimeoutExpired(cmd, timeout=cargs['timeout
'], output=out)
913 raise Exception(trace)
916 def lchown(path, user=-1, group=-1):
918 Change the owner/group of a path, raises an OSError if the
919 ownership change fails.
921 :param user: user to change
922 :type user: int or str
923 :param group: group to change
924 :type group: int or str
926 if isinstance(user, str):
928 entry = pwd.getpwnam(user)
930 raise OSError('Unknown user
%r' % user)
932 if isinstance(group, str):
934 entry = grp.getgrnam(group)
936 raise OSError('Unknown group
%r' % group)
938 return os.lchown(path, user, group)
940 def run_regular_process(cmd, kwargs, cargs={}):
942 Executes a subprocess command by using subprocess.Popen
944 proc = subprocess.Popen(cmd, **kwargs)
945 if kwargs.get('stdout
') or kwargs.get('stderr
'):
947 out, err = proc.communicate(**cargs)
948 except TimeoutExpired:
949 if kwargs.get('start_new_session
') and hasattr(os, 'killpg
'):
950 os.killpg(proc.pid, signal.SIGKILL)
953 out, err = proc.communicate()
954 exc = TimeoutExpired(proc.args, timeout=cargs['timeout
'], output=out)
957 status = proc.returncode
959 out, err = (None, None)
961 status = proc.wait(**cargs)
962 except TimeoutExpired as e:
963 if kwargs.get('start_new_session
') and hasattr(os, 'killpg
'):
964 os.killpg(proc.pid, signal.SIGKILL)
969 return status, out, err
971 def run_process(cmd, kwargs, cargs={}):
973 Executes a subprocess by using a pre-forked process when possible
974 or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
975 and :py:func:`waflib.Utils.run_regular_process`
977 if kwargs.get('stdout
') and kwargs.get('stderr
'):
978 return run_prefork_process(cmd, kwargs, cargs)
980 return run_regular_process(cmd, kwargs, cargs)
982 def alloc_process_pool(n, force=False):
984 Allocates an amount of processes to the default pool so its size is at least *n*.
985 It is useful to call this function early so that the pre-forked
986 processes use as little memory as possible.
990 :param force: if True then *n* more processes are added to the existing pool
993 # mandatory on python2, unnecessary on python >= 3.2
994 global run_process, get_process, alloc_process_pool
996 n = max(n - len(process_pool), 0)
998 lst = [get_process() for x in range(n)]
1000 run_process = run_regular_process
1001 get_process = alloc_process_pool = nada
1004 process_pool.append(x)
1007 for k in process_pool:
1015 if (sys.hexversion<0x207000f and not is_win32) or sys.hexversion>=0x306000f:
1016 atexit.register(atexit_pool)
1018 if os.environ.get('WAF_NO_PREFORK
') or sys.platform == 'cli
' or not sys.executable:
1019 run_process = run_regular_process
1020 get_process = alloc_process_pool = nada