3 # Thomas Nagy, 2005-2010 (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.3 to 3.X and across different platforms (win32, linux, etc)
12 import os
, sys
, errno
, traceback
, inspect
, re
, shutil
, datetime
, gc
, platform
13 import subprocess
# <- leave this!
15 from collections
import deque
, defaultdict
18 import _winreg
as winreg
25 from waflib
import Errors
28 from collections
import UserDict
30 from UserDict
import UserDict
33 from hashlib
import md5
38 # never fail to enable fixes from another module
44 if not 'JOBS' in os
.environ
:
46 os
.environ
['JOBS'] = '1'
48 class threading(object):
50 A fake threading class for platforms lacking the threading module.
51 Use ``waf -j1`` on those platforms
60 threading
.Lock
= threading
.Thread
= Lock
62 run_old
= threading
.Thread
.run
63 def run(*args
, **kwargs
):
65 run_old(*args
, **kwargs
)
66 except (KeyboardInterrupt, SystemExit):
69 sys
.excepthook(*sys
.exc_info())
70 threading
.Thread
.run
= run
72 SIG_NIL
= 'iluvcuteoverload'.encode()
73 """Arbitrary null value for a md5 hash. This value must be changed when the hash value is replaced (size)"""
76 """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
79 """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
81 rot_chr
= ['\\', '|', '/', '-']
82 "List of characters to use when displaying the throbber (progress bar)"
85 "Index of the current throbber character (progress bar)"
88 from collections
import OrderedDict
as ordered_iter_dict
90 class ordered_iter_dict(dict):
91 def __init__(self
, *k
, **kw
):
93 dict.__init
__(self
, *k
, **kw
)
97 def __setitem__(self
, key
, value
):
98 dict.__setitem
__(self
, key
, value
)
104 def __delitem__(self
, key
):
105 dict.__delitem
__(self
, key
)
116 is_win32
= os
.sep
== '\\' or sys
.platform
== 'win32' # msys2
118 def readf(fname
, m
='r', encoding
='ISO8859-1'):
120 Read an entire file into a string, use this function instead of os.open() whenever possible.
122 In practice the wrapper node.read(..) should be preferred to this function::
125 from waflib import Utils
126 txt = Utils.readf(self.path.find_node('wscript').abspath())
127 txt = ctx.path.find_node('wscript').read()
130 :param fname: Path to file
133 :type encoding: string
134 :param encoding: encoding value, only used for python 3
136 :return: Content of the file
139 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
147 txt
= txt
.decode(encoding
)
158 def writef(fname
, data
, m
='w', encoding
='ISO8859-1'):
160 Write an entire file from a string, use this function instead of os.open() whenever possible.
162 In practice the wrapper node.write(..) should be preferred to this function::
165 from waflib import Utils
166 txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
167 self.path.make_node('i_like_kittens').write('some data')
170 :param fname: Path to file
172 :param data: The contents to write to the file
175 :type encoding: string
176 :param encoding: encoding value, only used for python 3
178 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
179 data
= data
.encode(encoding
)
189 Compute a hash value for a file by using md5. This method may be replaced by
190 a faster version if necessary. The following uses the file size and the timestamp value::
193 from waflib import Utils
196 if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file')
198 m.update(str(st.st_mtime))
199 m.update(str(st.st_size))
202 Utils.h_file = h_file
205 :param fname: path to the file to hash
206 :return: hash of the file contents
208 f
= open(fname
, 'rb')
212 fname
= f
.read(200000)
218 def readf_win32(f
, m
='r', encoding
='ISO8859-1'):
219 flags
= os
.O_NOINHERIT | os
.O_RDONLY
225 fd
= os
.open(f
, flags
)
227 raise IOError('Cannot read from %r' % f
)
229 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
237 txt
= txt
.decode(encoding
)
248 def writef_win32(f
, data
, m
='w', encoding
='ISO8859-1'):
249 if sys
.hexversion
> 0x3000000 and not 'b' in m
:
250 data
= data
.encode(encoding
)
252 flags
= os
.O_CREAT | os
.O_TRUNC | os
.O_WRONLY | os
.O_NOINHERIT
258 fd
= os
.open(f
, flags
)
260 raise IOError('Cannot write to %r' % f
)
267 def h_file_win32(fname
):
269 fd
= os
.open(fname
, os
.O_BINARY | os
.O_RDONLY | os
.O_NOINHERIT
)
271 raise IOError('Cannot read from %r' % fname
)
272 f
= os
.fdopen(fd
, 'rb')
276 fname
= f
.read(200000)
286 if hasattr(os
, 'O_NOINHERIT') and sys
.hexversion
< 0x3040000:
287 # replace the default functions
289 writef
= writef_win32
290 h_file
= h_file_win32
297 ret
= binascii
.hexlify(s
)
298 if not isinstance(ret
, str):
299 ret
= ret
.decode('utf-8')
303 return s
.encode('hex')
306 Return the hexadecimal representation of a string
308 :param s: string to convert
312 def listdir_win32(s
):
314 List the contents of a folder in a portable manner.
315 On Win32, return the list of drive letters: ['C:', 'X:', 'Z:']
318 :param s: a string, which can be empty on Windows
324 # there is nothing much we can do
325 return [x
+ ':\\' for x
in list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')]
327 dlen
= 4 # length of "?:\\x00"
329 buf
= ctypes
.create_string_buffer(maxdrives
* dlen
)
330 ndrives
= ctypes
.windll
.kernel32
.GetLogicalDriveStringsA(maxdrives
*dlen
, ctypes
.byref(buf
))
331 return [ str(buf
.raw
[4*i
:4*i
+2].decode('ascii')) for i
in range(int(ndrives
/dlen
)) ]
333 if len(s
) == 2 and s
[1] == ":":
336 if not os
.path
.isdir(s
):
337 e
= OSError('%s is not a directory' % s
)
338 e
.errno
= errno
.ENOENT
344 listdir
= listdir_win32
348 Convert a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
350 from waflib.Utils import num2ver
351 num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
353 :type ver: string or tuple of numbers
354 :param ver: a version number
356 if isinstance(ver
, str):
357 ver
= tuple(ver
.split('.'))
358 if isinstance(ver
, tuple):
362 ret
+= 256**(3 - i
) * int(ver
[i
])
368 Extract the stack to display exceptions
370 :return: a string represening the last exception
372 exc_type
, exc_value
, tb
= sys
.exc_info()
373 exc_lines
= traceback
.format_exception(exc_type
, exc_value
, tb
)
374 return ''.join(exc_lines
)
378 Convert a string argument to a list by splitting on spaces, and pass
379 through a list argument unchanged::
381 from waflib.Utils import to_list
382 lst = to_list("a b c d")
384 :param sth: List or a string of items separated by spaces
386 :return: Argument converted to list
389 if isinstance(sth
, str):
394 def split_path_unix(path
):
395 return path
.split('/')
397 def split_path_cygwin(path
):
398 if path
.startswith('//'):
399 ret
= path
.split('/')[2:]
400 ret
[0] = '/' + ret
[0]
402 return path
.split('/')
404 re_sp
= re
.compile('[/\\\\]')
405 def split_path_win32(path
):
406 if path
.startswith('\\\\'):
407 ret
= re
.split(re_sp
, path
)[2:]
408 ret
[0] = '\\' + ret
[0]
410 return re
.split(re_sp
, path
)
413 def split_path_msys(path
):
414 if (path
.startswith('/') or path
.startswith('\\')) and not path
.startswith('//') and not path
.startswith('\\\\'):
415 # msys paths can be in the form /usr/bin
418 # msys has python 2.7 or 3, so we can use this
419 msysroot
= subprocess
.check_output(['cygpath', '-w', '/']).decode(sys
.stdout
.encoding
or 'iso8859-1')
420 msysroot
= msysroot
.strip()
421 path
= os
.path
.normpath(msysroot
+ os
.sep
+ path
)
422 return split_path_win32(path
)
424 if sys
.platform
== 'cygwin':
425 split_path
= split_path_cygwin
427 if os
.environ
.get('MSYSTEM', None):
428 split_path
= split_path_msys
430 split_path
= split_path_win32
432 split_path
= split_path_unix
434 split_path
.__doc
__ = """
435 Split a path by / or \\. This function is not like os.path.split
438 :param path: path to split
439 :return: list of strings
444 Ensure that a directory exists (similar to ``mkdir -p``).
447 :param path: Path to directory
449 if not os
.path
.isdir(path
):
453 if not os
.path
.isdir(path
):
454 raise Errors
.WafError('Cannot create the folder %r' % path
, ex
=e
)
456 def check_exe(name
, env
=None):
458 Ensure that a program exists
461 :param name: name or path to program
462 :return: path of the program or None
465 raise ValueError('Cannot execute an empty string!')
467 return os
.path
.isfile(fpath
) and os
.access(fpath
, os
.X_OK
)
469 fpath
, fname
= os
.path
.split(name
)
470 if fpath
and is_exe(name
):
471 return os
.path
.abspath(name
)
473 env
= env
or os
.environ
474 for path
in env
["PATH"].split(os
.pathsep
):
475 path
= path
.strip('"')
476 exe_file
= os
.path
.join(path
, name
)
478 return os
.path
.abspath(exe_file
)
481 def def_attrs(cls
, **kw
):
483 Set default attributes on a class instance
486 :param cls: the class to update the given attributes in.
488 :param kw: dictionary of attributes names and values.
490 for k
, v
in kw
.items():
491 if not hasattr(cls
, k
):
494 def quote_define_name(s
):
496 Convert a string to an identifier suitable for C defines.
499 :param s: String to convert
501 :return: Identifier suitable for C defines
503 fu
= re
.sub('[^a-zA-Z0-9]', '_', s
)
504 fu
= re
.sub('_+', '_', fu
)
510 Hash lists. For tuples, using hash(tup) is much more efficient,
511 except on python >= 3.3 where hash randomization assumes everybody is running a web application.
513 :param lst: list to hash
514 :type lst: list of strings
515 :return: hash of the list
518 m
.update(str(lst
).encode())
525 :param fun: function to hash
527 :return: hash of the function
531 except AttributeError:
533 h
= inspect
.getsource(fun
)
538 except AttributeError:
544 Task command hashes are calculated by calling this function. The inputs can be
545 strings, functions, tuples/lists containing strings/functions
547 # this function is not meant to be particularly fast
548 if isinstance(ins
, str):
549 # a command is either a string
551 elif isinstance(ins
, list) or isinstance(ins
, tuple):
552 # or a list of functions/strings
553 ret
= str([h_cmd(x
) for x
in ins
])
555 # or just a python function
556 ret
= str(h_fun(ins
))
557 if sys
.hexversion
> 0x3000000:
558 ret
= ret
.encode('iso8859-1', 'xmlcharrefreplace')
561 reg_subst
= re
.compile(r
"(\\\\)|(\$\$)|\$\{([^}]+)\}")
562 def subst_vars(expr
, params
):
564 Replace ${VAR} with the value of VAR taken from a dict or a config set::
566 from waflib import Utils
567 s = Utils.subst_vars('${PREFIX}/bin', env)
570 :param expr: String to perform substitution on
571 :param params: Dictionary or config set to look up variable values.
579 # ConfigSet instances may contain lists
580 return params
.get_flat(m
.group(3))
581 except AttributeError:
582 return params
[m
.group(3)]
583 # if you get a TypeError, it means that 'expr' is not a string...
584 # Utils.subst_vars(None, env) will not work
585 return reg_subst
.sub(repl_var
, expr
)
587 def destos_to_binfmt(key
):
589 Return the binary format based on the unversioned platform name.
591 :param key: platform name
593 :return: string representing the binary format
597 elif key
in ('win32', 'cygwin', 'uwin', 'msys'):
601 def unversioned_sys_platform():
603 Return the unversioned platform name.
604 Some Python platform names contain versions, that depend on
605 the build environment, e.g. linux2, freebsd6, etc.
606 This returns the name without the version number. Exceptions are
607 os2 and win32, which are returned verbatim.
610 :return: Unversioned platform name
613 if s
.startswith('java'):
614 # The real OS is hidden under the JVM.
615 from java
.lang
import System
616 s
= System
.getProperty('os.name')
617 # see http://lopica.sourceforge.net/os.html for a list of possible values
620 elif s
.startswith('Windows '):
626 elif s
in ('SunOS', 'Solaris'):
630 # powerpc == darwin for our purposes
633 if s
== 'win32' or s
== 'os2':
635 if s
== 'cli' and os
.name
== 'nt':
636 # ironpython is only on windows as far as we know
638 return re
.split('\d+$', s
)[0]
642 A function that does nothing
650 Simple object for timing the execution of commands.
651 Its string representation is the current time::
653 from waflib.Utils import Timer
659 self
.start_time
= datetime
.datetime
.utcnow()
662 delta
= datetime
.datetime
.utcnow() - self
.start_time
664 hours
, rem
= divmod(delta
.seconds
, 3600)
665 minutes
, seconds
= divmod(rem
, 60)
666 seconds
+= delta
.microseconds
* 1e-6
669 result
+= '%dd' % days
671 result
+= '%dh' % hours
672 if days
or hours
or minutes
:
673 result
+= '%dm' % minutes
674 return '%s%.3fs' % (result
, seconds
)
680 shutil.copy2 does not copy the file attributes on windows, so we
681 hack into the shutil module to fix the problem
684 shutil
.copystat(src
, dst
)
685 setattr(shutil
, 'copy2', copy2
)
687 if os
.name
== 'java':
688 # Jython cannot disable the gc but they can enable it ... wtf?
692 except NotImplementedError:
693 gc
.disable
= gc
.enable
695 def read_la_file(path
):
697 Read property files, used by msvc.py
699 :param path: file to read
702 sp
= re
.compile(r
'^([^=]+)=\'(.*)\'$
')
704 for line in readf(path).splitlines():
706 _, left, right, _ = sp.split(line.strip())
714 Decorator: let a function disable the garbage collector during its execution.
715 It is used in the build context when storing/loading the build cache file (pickle)
717 :param fun: function to execute
719 :return: the return value of the function executed
728 f.__doc__ = fun.__doc__
733 Decorator: let a function cache its results, use like this::
739 :param fun: function to execute
741 :return: the return value of the function executed
751 wrap.__cache__ = cache
752 wrap.__name__ = fun.__name__
755 def get_registry_app_path(key, filename):
759 result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
763 if os.path.isfile(result):
767 # default settings for /usr/lib
769 if platform.architecture()[0] == '64bit
':
770 if os.path.exists('/usr
/lib64
') and not os.path.exists('/usr
/lib32
'):
775 # private function for the time being!
776 return os.path.abspath(os.path.expanduser(p))