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.

733 lines
16KB

  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. if sys.platform == 'cygwin':
  358. split_path = split_path_cygwin
  359. elif is_win32:
  360. split_path = split_path_win32
  361. else:
  362. split_path = split_path_unix
  363. split_path.__doc__ = """
  364. Split a path by / or \\. This function is not like os.path.split
  365. :type path: string
  366. :param path: path to split
  367. :return: list of strings
  368. """
  369. def check_dir(path):
  370. """
  371. Ensure that a directory exists (similar to ``mkdir -p``).
  372. :type path: string
  373. :param path: Path to directory
  374. """
  375. if not os.path.isdir(path):
  376. try:
  377. os.makedirs(path)
  378. except OSError as e:
  379. if not os.path.isdir(path):
  380. raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
  381. def check_exe(name, env=None):
  382. """
  383. Ensure that a program exists
  384. :type name: string
  385. :param name: name or path to program
  386. :return: path of the program or None
  387. """
  388. if not name:
  389. raise ValueError('Cannot execute an empty string!')
  390. def is_exe(fpath):
  391. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  392. fpath, fname = os.path.split(name)
  393. if fpath and is_exe(name):
  394. return os.path.abspath(name)
  395. else:
  396. env = env or os.environ
  397. for path in env["PATH"].split(os.pathsep):
  398. path = path.strip('"')
  399. exe_file = os.path.join(path, name)
  400. if is_exe(exe_file):
  401. return os.path.abspath(exe_file)
  402. return None
  403. def def_attrs(cls, **kw):
  404. """
  405. Set default attributes on a class instance
  406. :type cls: class
  407. :param cls: the class to update the given attributes in.
  408. :type kw: dict
  409. :param kw: dictionary of attributes names and values.
  410. """
  411. for k, v in kw.items():
  412. if not hasattr(cls, k):
  413. setattr(cls, k, v)
  414. def quote_define_name(s):
  415. """
  416. Convert a string to an identifier suitable for C defines.
  417. :type s: string
  418. :param s: String to convert
  419. :rtype: string
  420. :return: Identifier suitable for C defines
  421. """
  422. fu = re.sub('[^a-zA-Z0-9]', '_', s)
  423. fu = re.sub('_+', '_', fu)
  424. fu = fu.upper()
  425. return fu
  426. def h_list(lst):
  427. """
  428. Hash lists. For tuples, using hash(tup) is much more efficient,
  429. except on python >= 3.3 where hash randomization assumes everybody is running a web application.
  430. :param lst: list to hash
  431. :type lst: list of strings
  432. :return: hash of the list
  433. """
  434. m = md5()
  435. m.update(str(lst).encode())
  436. return m.digest()
  437. def h_fun(fun):
  438. """
  439. Hash functions
  440. :param fun: function to hash
  441. :type fun: function
  442. :return: hash of the function
  443. """
  444. try:
  445. return fun.code
  446. except AttributeError:
  447. try:
  448. h = inspect.getsource(fun)
  449. except IOError:
  450. h = "nocode"
  451. try:
  452. fun.code = h
  453. except AttributeError:
  454. pass
  455. return h
  456. reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
  457. def subst_vars(expr, params):
  458. """
  459. Replace ${VAR} with the value of VAR taken from a dict or a config set::
  460. from waflib import Utils
  461. s = Utils.subst_vars('${PREFIX}/bin', env)
  462. :type expr: string
  463. :param expr: String to perform substitution on
  464. :param params: Dictionary or config set to look up variable values.
  465. """
  466. def repl_var(m):
  467. if m.group(1):
  468. return '\\'
  469. if m.group(2):
  470. return '$'
  471. try:
  472. # ConfigSet instances may contain lists
  473. return params.get_flat(m.group(3))
  474. except AttributeError:
  475. return params[m.group(3)]
  476. return reg_subst.sub(repl_var, expr)
  477. def destos_to_binfmt(key):
  478. """
  479. Return the binary format based on the unversioned platform name.
  480. :param key: platform name
  481. :type key: string
  482. :return: string representing the binary format
  483. """
  484. if key == 'darwin':
  485. return 'mac-o'
  486. elif key in ('win32', 'cygwin', 'uwin', 'msys'):
  487. return 'pe'
  488. return 'elf'
  489. def unversioned_sys_platform():
  490. """
  491. Return the unversioned platform name.
  492. Some Python platform names contain versions, that depend on
  493. the build environment, e.g. linux2, freebsd6, etc.
  494. This returns the name without the version number. Exceptions are
  495. os2 and win32, which are returned verbatim.
  496. :rtype: string
  497. :return: Unversioned platform name
  498. """
  499. s = sys.platform
  500. if s.startswith('java'):
  501. # The real OS is hidden under the JVM.
  502. from java.lang import System
  503. s = System.getProperty('os.name')
  504. # see http://lopica.sourceforge.net/os.html for a list of possible values
  505. if s == 'Mac OS X':
  506. return 'darwin'
  507. elif s.startswith('Windows '):
  508. return 'win32'
  509. elif s == 'OS/2':
  510. return 'os2'
  511. elif s == 'HP-UX':
  512. return 'hp-ux'
  513. elif s in ('SunOS', 'Solaris'):
  514. return 'sunos'
  515. else: s = s.lower()
  516. # powerpc == darwin for our purposes
  517. if s == 'powerpc':
  518. return 'darwin'
  519. if s == 'win32' or s == 'os2':
  520. return s
  521. return re.split('\d+$', s)[0]
  522. def nada(*k, **kw):
  523. """
  524. A function that does nothing
  525. :return: None
  526. """
  527. pass
  528. class Timer(object):
  529. """
  530. Simple object for timing the execution of commands.
  531. Its string representation is the current time::
  532. from waflib.Utils import Timer
  533. timer = Timer()
  534. a_few_operations()
  535. s = str(timer)
  536. """
  537. def __init__(self):
  538. self.start_time = datetime.datetime.utcnow()
  539. def __str__(self):
  540. delta = datetime.datetime.utcnow() - self.start_time
  541. days = delta.days
  542. hours, rem = divmod(delta.seconds, 3600)
  543. minutes, seconds = divmod(rem, 60)
  544. seconds += delta.microseconds * 1e-6
  545. result = ''
  546. if days:
  547. result += '%dd' % days
  548. if days or hours:
  549. result += '%dh' % hours
  550. if days or hours or minutes:
  551. result += '%dm' % minutes
  552. return '%s%.3fs' % (result, seconds)
  553. if is_win32:
  554. old = shutil.copy2
  555. def copy2(src, dst):
  556. """
  557. shutil.copy2 does not copy the file attributes on windows, so we
  558. hack into the shutil module to fix the problem
  559. """
  560. old(src, dst)
  561. shutil.copystat(src, dst)
  562. setattr(shutil, 'copy2', copy2)
  563. if os.name == 'java':
  564. # Jython cannot disable the gc but they can enable it ... wtf?
  565. try:
  566. gc.disable()
  567. gc.enable()
  568. except NotImplementedError:
  569. gc.disable = gc.enable
  570. def read_la_file(path):
  571. """
  572. Read property files, used by msvc.py
  573. :param path: file to read
  574. :type path: string
  575. """
  576. sp = re.compile(r'^([^=]+)=\'(.*)\'$')
  577. dc = {}
  578. for line in readf(path).splitlines():
  579. try:
  580. _, left, right, _ = sp.split(line.strip())
  581. dc[left] = right
  582. except ValueError:
  583. pass
  584. return dc
  585. def nogc(fun):
  586. """
  587. Decorator: let a function disable the garbage collector during its execution.
  588. It is used in the build context when storing/loading the build cache file (pickle)
  589. :param fun: function to execute
  590. :type fun: function
  591. :return: the return value of the function executed
  592. """
  593. def f(*k, **kw):
  594. try:
  595. gc.disable()
  596. ret = fun(*k, **kw)
  597. finally:
  598. gc.enable()
  599. return ret
  600. f.__doc__ = fun.__doc__
  601. return f
  602. def run_once(fun):
  603. """
  604. Decorator: let a function cache its results, use like this::
  605. @run_once
  606. def foo(k):
  607. return 345*2343
  608. :param fun: function to execute
  609. :type fun: function
  610. :return: the return value of the function executed
  611. """
  612. cache = {}
  613. def wrap(k):
  614. try:
  615. return cache[k]
  616. except KeyError:
  617. ret = fun(k)
  618. cache[k] = ret
  619. return ret
  620. wrap.__cache__ = cache
  621. return wrap
  622. def get_registry_app_path(key, filename):
  623. if not winreg:
  624. return None
  625. try:
  626. result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
  627. except WindowsError:
  628. pass
  629. else:
  630. if os.path.isfile(result):
  631. return result
  632. def lib64():
  633. # default settings for /usr/lib
  634. if os.sep == '/':
  635. if platform.architecture()[0] == '64bit':
  636. if os.path.exists('/usr/lib64') and not os.path.exists('/usr/lib32'):
  637. return '64'
  638. return ''