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