jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

778 lines
18KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Utilities and platform-specific fixes
  6. The portability fixes try to provide a consistent behavior of the Waf API
  7. through Python versions 2.3 to 3.X and across different platforms (win32, linux, etc)
  8. """
  9. import os, sys, errno, traceback, inspect, re, shutil, datetime, gc, platform
  10. import subprocess # <- leave this!
  11. from collections import deque, defaultdict
  12. try:
  13. import _winreg as winreg
  14. except ImportError:
  15. try:
  16. import winreg
  17. except ImportError:
  18. winreg = None
  19. from waflib import Errors
  20. try:
  21. from collections import UserDict
  22. except ImportError:
  23. from UserDict import UserDict
  24. try:
  25. from hashlib import md5
  26. except ImportError:
  27. try:
  28. from md5 import md5
  29. except ImportError:
  30. # never fail to enable fixes from another module
  31. pass
  32. try:
  33. import threading
  34. except ImportError:
  35. if not 'JOBS' in os.environ:
  36. # no threading :-(
  37. os.environ['JOBS'] = '1'
  38. class threading(object):
  39. """
  40. A fake threading class for platforms lacking the threading module.
  41. Use ``waf -j1`` on those platforms
  42. """
  43. pass
  44. class Lock(object):
  45. """Fake Lock class"""
  46. def acquire(self):
  47. pass
  48. def release(self):
  49. pass
  50. threading.Lock = threading.Thread = Lock
  51. else:
  52. run_old = threading.Thread.run
  53. def run(*args, **kwargs):
  54. try:
  55. run_old(*args, **kwargs)
  56. except (KeyboardInterrupt, SystemExit):
  57. raise
  58. except Exception:
  59. sys.excepthook(*sys.exc_info())
  60. threading.Thread.run = run
  61. SIG_NIL = 'iluvcuteoverload'.encode()
  62. """Arbitrary null value for a md5 hash. This value must be changed when the hash value is replaced (size)"""
  63. O644 = 420
  64. """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
  65. O755 = 493
  66. """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
  67. rot_chr = ['\\', '|', '/', '-']
  68. "List of characters to use when displaying the throbber (progress bar)"
  69. rot_idx = 0
  70. "Index of the current throbber character (progress bar)"
  71. try:
  72. from collections import OrderedDict as ordered_iter_dict
  73. except ImportError:
  74. class ordered_iter_dict(dict):
  75. def __init__(self, *k, **kw):
  76. self.lst = []
  77. dict.__init__(self, *k, **kw)
  78. def clear(self):
  79. dict.clear(self)
  80. self.lst = []
  81. def __setitem__(self, key, value):
  82. dict.__setitem__(self, key, value)
  83. try:
  84. self.lst.remove(key)
  85. except ValueError:
  86. pass
  87. self.lst.append(key)
  88. def __delitem__(self, key):
  89. dict.__delitem__(self, key)
  90. try:
  91. self.lst.remove(key)
  92. except ValueError:
  93. pass
  94. def __iter__(self):
  95. for x in self.lst:
  96. yield x
  97. def keys(self):
  98. return self.lst
  99. is_win32 = os.sep == '\\' or sys.platform == 'win32' # msys2
  100. def readf(fname, m='r', encoding='ISO8859-1'):
  101. """
  102. Read an entire file into a string, use this function instead of os.open() whenever possible.
  103. In practice the wrapper node.read(..) should be preferred to this function::
  104. def build(ctx):
  105. from waflib import Utils
  106. txt = Utils.readf(self.path.find_node('wscript').abspath())
  107. txt = ctx.path.find_node('wscript').read()
  108. :type fname: string
  109. :param fname: Path to file
  110. :type m: string
  111. :param m: Open mode
  112. :type encoding: string
  113. :param encoding: encoding value, only used for python 3
  114. :rtype: string
  115. :return: Content of the file
  116. """
  117. if sys.hexversion > 0x3000000 and not 'b' in m:
  118. m += 'b'
  119. f = open(fname, m)
  120. try:
  121. txt = f.read()
  122. finally:
  123. f.close()
  124. if encoding:
  125. txt = txt.decode(encoding)
  126. else:
  127. txt = txt.decode()
  128. else:
  129. f = open(fname, m)
  130. try:
  131. txt = f.read()
  132. finally:
  133. f.close()
  134. return txt
  135. def writef(fname, data, m='w', encoding='ISO8859-1'):
  136. """
  137. Write an entire file from a string, use this function instead of os.open() whenever possible.
  138. In practice the wrapper node.write(..) should be preferred to this function::
  139. def build(ctx):
  140. from waflib import Utils
  141. txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
  142. self.path.make_node('i_like_kittens').write('some data')
  143. :type fname: string
  144. :param fname: Path to file
  145. :type data: string
  146. :param data: The contents to write to the file
  147. :type m: string
  148. :param m: Open mode
  149. :type encoding: string
  150. :param encoding: encoding value, only used for python 3
  151. """
  152. if sys.hexversion > 0x3000000 and not 'b' in m:
  153. data = data.encode(encoding)
  154. m += 'b'
  155. f = open(fname, m)
  156. try:
  157. f.write(data)
  158. finally:
  159. f.close()
  160. def h_file(fname):
  161. """
  162. Compute a hash value for a file by using md5. This method may be replaced by
  163. a faster version if necessary. The following uses the file size and the timestamp value::
  164. import stat
  165. from waflib import Utils
  166. def h_file(fname):
  167. st = os.stat(fname)
  168. if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file')
  169. m = Utils.md5()
  170. m.update(str(st.st_mtime))
  171. m.update(str(st.st_size))
  172. m.update(fname)
  173. return m.digest()
  174. Utils.h_file = h_file
  175. :type fname: string
  176. :param fname: path to the file to hash
  177. :return: hash of the file contents
  178. """
  179. f = open(fname, 'rb')
  180. m = md5()
  181. try:
  182. while fname:
  183. fname = f.read(200000)
  184. m.update(fname)
  185. finally:
  186. f.close()
  187. return m.digest()
  188. def readf_win32(f, m='r', encoding='ISO8859-1'):
  189. flags = os.O_NOINHERIT | os.O_RDONLY
  190. if 'b' in m:
  191. flags |= os.O_BINARY
  192. if '+' in m:
  193. flags |= os.O_RDWR
  194. try:
  195. fd = os.open(f, flags)
  196. except OSError:
  197. raise IOError('Cannot read from %r' % f)
  198. if sys.hexversion > 0x3000000 and not 'b' in m:
  199. m += 'b'
  200. f = os.fdopen(fd, m)
  201. try:
  202. txt = f.read()
  203. finally:
  204. f.close()
  205. if encoding:
  206. txt = txt.decode(encoding)
  207. else:
  208. txt = txt.decode()
  209. else:
  210. f = os.fdopen(fd, m)
  211. try:
  212. txt = f.read()
  213. finally:
  214. f.close()
  215. return txt
  216. def writef_win32(f, data, m='w', encoding='ISO8859-1'):
  217. if sys.hexversion > 0x3000000 and not 'b' in m:
  218. data = data.encode(encoding)
  219. m += 'b'
  220. flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | os.O_NOINHERIT
  221. if 'b' in m:
  222. flags |= os.O_BINARY
  223. if '+' in m:
  224. flags |= os.O_RDWR
  225. try:
  226. fd = os.open(f, flags)
  227. except OSError:
  228. raise IOError('Cannot write to %r' % f)
  229. f = os.fdopen(fd, m)
  230. try:
  231. f.write(data)
  232. finally:
  233. f.close()
  234. def h_file_win32(fname):
  235. try:
  236. fd = os.open(fname, os.O_BINARY | os.O_RDONLY | os.O_NOINHERIT)
  237. except OSError:
  238. raise IOError('Cannot read from %r' % fname)
  239. f = os.fdopen(fd, 'rb')
  240. m = md5()
  241. try:
  242. while fname:
  243. fname = f.read(200000)
  244. m.update(fname)
  245. finally:
  246. f.close()
  247. return m.digest()
  248. # always save these
  249. readf_unix = readf
  250. writef_unix = writef
  251. h_file_unix = h_file
  252. if hasattr(os, 'O_NOINHERIT') and sys.hexversion < 0x3040000:
  253. # replace the default functions
  254. readf = readf_win32
  255. writef = writef_win32
  256. h_file = h_file_win32
  257. try:
  258. x = ''.encode('hex')
  259. except LookupError:
  260. import binascii
  261. def to_hex(s):
  262. ret = binascii.hexlify(s)
  263. if not isinstance(ret, str):
  264. ret = ret.decode('utf-8')
  265. return ret
  266. else:
  267. def to_hex(s):
  268. return s.encode('hex')
  269. to_hex.__doc__ = """
  270. Return the hexadecimal representation of a string
  271. :param s: string to convert
  272. :type s: string
  273. """
  274. def listdir_win32(s):
  275. """
  276. List the contents of a folder in a portable manner.
  277. On Win32, return the list of drive letters: ['C:', 'X:', 'Z:']
  278. :type s: string
  279. :param s: a string, which can be empty on Windows
  280. """
  281. if not s:
  282. try:
  283. import ctypes
  284. except ImportError:
  285. # there is nothing much we can do
  286. return [x + ':\\' for x in list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')]
  287. else:
  288. dlen = 4 # length of "?:\\x00"
  289. maxdrives = 26
  290. buf = ctypes.create_string_buffer(maxdrives * dlen)
  291. ndrives = ctypes.windll.kernel32.GetLogicalDriveStringsA(maxdrives*dlen, ctypes.byref(buf))
  292. return [ str(buf.raw[4*i:4*i+2].decode('ascii')) for i in range(int(ndrives/dlen)) ]
  293. if len(s) == 2 and s[1] == ":":
  294. s += os.sep
  295. if not os.path.isdir(s):
  296. e = OSError('%s is not a directory' % s)
  297. e.errno = errno.ENOENT
  298. raise e
  299. return os.listdir(s)
  300. listdir = os.listdir
  301. if is_win32:
  302. listdir = listdir_win32
  303. def num2ver(ver):
  304. """
  305. Convert a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
  306. from waflib.Utils import num2ver
  307. num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
  308. :type ver: string or tuple of numbers
  309. :param ver: a version number
  310. """
  311. if isinstance(ver, str):
  312. ver = tuple(ver.split('.'))
  313. if isinstance(ver, tuple):
  314. ret = 0
  315. for i in range(4):
  316. if i < len(ver):
  317. ret += 256**(3 - i) * int(ver[i])
  318. return ret
  319. return ver
  320. def ex_stack():
  321. """
  322. Extract the stack to display exceptions
  323. :return: a string represening the last exception
  324. """
  325. exc_type, exc_value, tb = sys.exc_info()
  326. exc_lines = traceback.format_exception(exc_type, exc_value, tb)
  327. return ''.join(exc_lines)
  328. def to_list(sth):
  329. """
  330. Convert a string argument to a list by splitting on spaces, and pass
  331. through a list argument unchanged::
  332. from waflib.Utils import to_list
  333. lst = to_list("a b c d")
  334. :param sth: List or a string of items separated by spaces
  335. :rtype: list
  336. :return: Argument converted to list
  337. """
  338. if isinstance(sth, str):
  339. return sth.split()
  340. else:
  341. return sth
  342. def split_path_unix(path):
  343. return path.split('/')
  344. def split_path_cygwin(path):
  345. if path.startswith('//'):
  346. ret = path.split('/')[2:]
  347. ret[0] = '/' + ret[0]
  348. return ret
  349. return path.split('/')
  350. re_sp = re.compile('[/\\\\]')
  351. def split_path_win32(path):
  352. if path.startswith('\\\\'):
  353. ret = re.split(re_sp, path)[2:]
  354. ret[0] = '\\' + ret[0]
  355. return ret
  356. return re.split(re_sp, path)
  357. msysroot = None
  358. def split_path_msys(path):
  359. if (path.startswith('/') or path.startswith('\\')) and not path.startswith('//') and not path.startswith('\\\\'):
  360. # msys paths can be in the form /usr/bin
  361. global msysroot
  362. if not msysroot:
  363. # msys has python 2.7 or 3, so we can use this
  364. msysroot = subprocess.check_output(['cygpath', '-w', '/']).decode(sys.stdout.encoding or 'iso8859-1')
  365. msysroot = msysroot.strip()
  366. path = os.path.normpath(msysroot + os.sep + path)
  367. return split_path_win32(path)
  368. if sys.platform == 'cygwin':
  369. split_path = split_path_cygwin
  370. elif is_win32:
  371. if os.environ.get('MSYSTEM', None):
  372. split_path = split_path_msys
  373. else:
  374. split_path = split_path_win32
  375. else:
  376. split_path = split_path_unix
  377. split_path.__doc__ = """
  378. Split a path by / or \\. This function is not like os.path.split
  379. :type path: string
  380. :param path: path to split
  381. :return: list of strings
  382. """
  383. def check_dir(path):
  384. """
  385. Ensure that a directory exists (similar to ``mkdir -p``).
  386. :type path: string
  387. :param path: Path to directory
  388. """
  389. if not os.path.isdir(path):
  390. try:
  391. os.makedirs(path)
  392. except OSError as e:
  393. if not os.path.isdir(path):
  394. raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
  395. def check_exe(name, env=None):
  396. """
  397. Ensure that a program exists
  398. :type name: string
  399. :param name: name or path to program
  400. :return: path of the program or None
  401. """
  402. if not name:
  403. raise ValueError('Cannot execute an empty string!')
  404. def is_exe(fpath):
  405. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  406. fpath, fname = os.path.split(name)
  407. if fpath and is_exe(name):
  408. return os.path.abspath(name)
  409. else:
  410. env = env or os.environ
  411. for path in env["PATH"].split(os.pathsep):
  412. path = path.strip('"')
  413. exe_file = os.path.join(path, name)
  414. if is_exe(exe_file):
  415. return os.path.abspath(exe_file)
  416. return None
  417. def def_attrs(cls, **kw):
  418. """
  419. Set default attributes on a class instance
  420. :type cls: class
  421. :param cls: the class to update the given attributes in.
  422. :type kw: dict
  423. :param kw: dictionary of attributes names and values.
  424. """
  425. for k, v in kw.items():
  426. if not hasattr(cls, k):
  427. setattr(cls, k, v)
  428. def quote_define_name(s):
  429. """
  430. Convert a string to an identifier suitable for C defines.
  431. :type s: string
  432. :param s: String to convert
  433. :rtype: string
  434. :return: Identifier suitable for C defines
  435. """
  436. fu = re.sub('[^a-zA-Z0-9]', '_', s)
  437. fu = re.sub('_+', '_', fu)
  438. fu = fu.upper()
  439. return fu
  440. def h_list(lst):
  441. """
  442. Hash lists. For tuples, using hash(tup) is much more efficient,
  443. except on python >= 3.3 where hash randomization assumes everybody is running a web application.
  444. :param lst: list to hash
  445. :type lst: list of strings
  446. :return: hash of the list
  447. """
  448. m = md5()
  449. m.update(str(lst).encode())
  450. return m.digest()
  451. def h_fun(fun):
  452. """
  453. Hash functions
  454. :param fun: function to hash
  455. :type fun: function
  456. :return: hash of the function
  457. """
  458. try:
  459. return fun.code
  460. except AttributeError:
  461. try:
  462. h = inspect.getsource(fun)
  463. except IOError:
  464. h = "nocode"
  465. try:
  466. fun.code = h
  467. except AttributeError:
  468. pass
  469. return h
  470. def h_cmd(ins):
  471. """
  472. Task command hashes are calculated by calling this function. The inputs can be
  473. strings, functions, tuples/lists containing strings/functions
  474. """
  475. # this function is not meant to be particularly fast
  476. if isinstance(ins, str):
  477. # a command is either a string
  478. ret = ins
  479. elif isinstance(ins, list) or isinstance(ins, tuple):
  480. # or a list of functions/strings
  481. ret = str([h_cmd(x) for x in ins])
  482. else:
  483. # or just a python function
  484. ret = str(h_fun(ins))
  485. if sys.hexversion > 0x3000000:
  486. ret = ret.encode('iso8859-1', 'xmlcharrefreplace')
  487. return ret
  488. reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
  489. def subst_vars(expr, params):
  490. """
  491. Replace ${VAR} with the value of VAR taken from a dict or a config set::
  492. from waflib import Utils
  493. s = Utils.subst_vars('${PREFIX}/bin', env)
  494. :type expr: string
  495. :param expr: String to perform substitution on
  496. :param params: Dictionary or config set to look up variable values.
  497. """
  498. def repl_var(m):
  499. if m.group(1):
  500. return '\\'
  501. if m.group(2):
  502. return '$'
  503. try:
  504. # ConfigSet instances may contain lists
  505. return params.get_flat(m.group(3))
  506. except AttributeError:
  507. return params[m.group(3)]
  508. # if you get a TypeError, it means that 'expr' is not a string...
  509. # Utils.subst_vars(None, env) will not work
  510. return reg_subst.sub(repl_var, expr)
  511. def destos_to_binfmt(key):
  512. """
  513. Return the binary format based on the unversioned platform name.
  514. :param key: platform name
  515. :type key: string
  516. :return: string representing the binary format
  517. """
  518. if key == 'darwin':
  519. return 'mac-o'
  520. elif key in ('win32', 'cygwin', 'uwin', 'msys'):
  521. return 'pe'
  522. return 'elf'
  523. def unversioned_sys_platform():
  524. """
  525. Return the unversioned platform name.
  526. Some Python platform names contain versions, that depend on
  527. the build environment, e.g. linux2, freebsd6, etc.
  528. This returns the name without the version number. Exceptions are
  529. os2 and win32, which are returned verbatim.
  530. :rtype: string
  531. :return: Unversioned platform name
  532. """
  533. s = sys.platform
  534. if s.startswith('java'):
  535. # The real OS is hidden under the JVM.
  536. from java.lang import System
  537. s = System.getProperty('os.name')
  538. # see http://lopica.sourceforge.net/os.html for a list of possible values
  539. if s == 'Mac OS X':
  540. return 'darwin'
  541. elif s.startswith('Windows '):
  542. return 'win32'
  543. elif s == 'OS/2':
  544. return 'os2'
  545. elif s == 'HP-UX':
  546. return 'hp-ux'
  547. elif s in ('SunOS', 'Solaris'):
  548. return 'sunos'
  549. else: s = s.lower()
  550. # powerpc == darwin for our purposes
  551. if s == 'powerpc':
  552. return 'darwin'
  553. if s == 'win32' or s == 'os2':
  554. return s
  555. if s == 'cli' and os.name == 'nt':
  556. # ironpython is only on windows as far as we know
  557. return 'win32'
  558. return re.split('\d+$', s)[0]
  559. def nada(*k, **kw):
  560. """
  561. A function that does nothing
  562. :return: None
  563. """
  564. pass
  565. class Timer(object):
  566. """
  567. Simple object for timing the execution of commands.
  568. Its string representation is the current time::
  569. from waflib.Utils import Timer
  570. timer = Timer()
  571. a_few_operations()
  572. s = str(timer)
  573. """
  574. def __init__(self):
  575. self.start_time = datetime.datetime.utcnow()
  576. def __str__(self):
  577. delta = datetime.datetime.utcnow() - self.start_time
  578. days = delta.days
  579. hours, rem = divmod(delta.seconds, 3600)
  580. minutes, seconds = divmod(rem, 60)
  581. seconds += delta.microseconds * 1e-6
  582. result = ''
  583. if days:
  584. result += '%dd' % days
  585. if days or hours:
  586. result += '%dh' % hours
  587. if days or hours or minutes:
  588. result += '%dm' % minutes
  589. return '%s%.3fs' % (result, seconds)
  590. if is_win32:
  591. old = shutil.copy2
  592. def copy2(src, dst):
  593. """
  594. shutil.copy2 does not copy the file attributes on windows, so we
  595. hack into the shutil module to fix the problem
  596. """
  597. old(src, dst)
  598. shutil.copystat(src, dst)
  599. setattr(shutil, 'copy2', copy2)
  600. if os.name == 'java':
  601. # Jython cannot disable the gc but they can enable it ... wtf?
  602. try:
  603. gc.disable()
  604. gc.enable()
  605. except NotImplementedError:
  606. gc.disable = gc.enable
  607. def read_la_file(path):
  608. """
  609. Read property files, used by msvc.py
  610. :param path: file to read
  611. :type path: string
  612. """
  613. sp = re.compile(r'^([^=]+)=\'(.*)\'$')
  614. dc = {}
  615. for line in readf(path).splitlines():
  616. try:
  617. _, left, right, _ = sp.split(line.strip())
  618. dc[left] = right
  619. except ValueError:
  620. pass
  621. return dc
  622. def nogc(fun):
  623. """
  624. Decorator: let a function disable the garbage collector during its execution.
  625. It is used in the build context when storing/loading the build cache file (pickle)
  626. :param fun: function to execute
  627. :type fun: function
  628. :return: the return value of the function executed
  629. """
  630. def f(*k, **kw):
  631. try:
  632. gc.disable()
  633. ret = fun(*k, **kw)
  634. finally:
  635. gc.enable()
  636. return ret
  637. f.__doc__ = fun.__doc__
  638. return f
  639. def run_once(fun):
  640. """
  641. Decorator: let a function cache its results, use like this::
  642. @run_once
  643. def foo(k):
  644. return 345*2343
  645. :param fun: function to execute
  646. :type fun: function
  647. :return: the return value of the function executed
  648. """
  649. cache = {}
  650. def wrap(k):
  651. try:
  652. return cache[k]
  653. except KeyError:
  654. ret = fun(k)
  655. cache[k] = ret
  656. return ret
  657. wrap.__cache__ = cache
  658. wrap.__name__ = fun.__name__
  659. return wrap
  660. def get_registry_app_path(key, filename):
  661. if not winreg:
  662. return None
  663. try:
  664. result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
  665. except WindowsError:
  666. pass
  667. else:
  668. if os.path.isfile(result):
  669. return result
  670. def lib64():
  671. # default settings for /usr/lib
  672. if os.sep == '/':
  673. if platform.architecture()[0] == '64bit':
  674. if os.path.exists('/usr/lib64') and not os.path.exists('/usr/lib32'):
  675. return '64'
  676. return ''
  677. def sane_path(p):
  678. # private function for the time being!
  679. return os.path.abspath(os.path.expanduser(p))