Utils.py 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2018 (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.5 to 3.X and across different platforms (win32, linux, etc)
  8. """
  9. from __future__ import with_statement
  10. import atexit, os, sys, errno, inspect, re, datetime, platform, base64, signal, functools, time
  11. try:
  12. import cPickle
  13. except ImportError:
  14. import pickle as cPickle
  15. # leave this
  16. if os.name == 'posix' and sys.version_info[0] < 3:
  17. try:
  18. import subprocess32 as subprocess
  19. except ImportError:
  20. import subprocess
  21. else:
  22. import subprocess
  23. try:
  24. TimeoutExpired = subprocess.TimeoutExpired
  25. except AttributeError:
  26. class TimeoutExpired(Exception):
  27. pass
  28. from collections import deque, defaultdict
  29. try:
  30. import _winreg as winreg
  31. except ImportError:
  32. try:
  33. import winreg
  34. except ImportError:
  35. winreg = None
  36. from waflib import Errors
  37. try:
  38. from hashlib import md5
  39. except ImportError:
  40. try:
  41. from md5 import md5
  42. except ImportError:
  43. # never fail to enable fixes from another module
  44. pass
  45. try:
  46. import threading
  47. except ImportError:
  48. if not 'JOBS' in os.environ:
  49. # no threading :-(
  50. os.environ['JOBS'] = '1'
  51. class threading(object):
  52. """
  53. A fake threading class for platforms lacking the threading module.
  54. Use ``waf -j1`` on those platforms
  55. """
  56. pass
  57. class Lock(object):
  58. """Fake Lock class"""
  59. def acquire(self):
  60. pass
  61. def release(self):
  62. pass
  63. threading.Lock = threading.Thread = Lock
  64. SIG_NIL = 'SIG_NIL_SIG_NIL_'.encode()
  65. """Arbitrary null value for hashes. Modify this value according to the hash function in use"""
  66. O644 = 420
  67. """Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
  68. O755 = 493
  69. """Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
  70. rot_chr = ['\\', '|', '/', '-']
  71. "List of characters to use when displaying the throbber (progress bar)"
  72. rot_idx = 0
  73. "Index of the current throbber character (progress bar)"
  74. class ordered_iter_dict(dict):
  75. """Ordered dictionary that provides iteration from the most recently inserted keys first"""
  76. def __init__(self, *k, **kw):
  77. self.lst = deque()
  78. dict.__init__(self, *k, **kw)
  79. def clear(self):
  80. dict.clear(self)
  81. self.lst = deque()
  82. def __setitem__(self, key, value):
  83. if key in dict.keys(self):
  84. self.lst.remove(key)
  85. dict.__setitem__(self, key, value)
  86. self.lst.append(key)
  87. def __delitem__(self, key):
  88. dict.__delitem__(self, key)
  89. try:
  90. self.lst.remove(key)
  91. except ValueError:
  92. pass
  93. def __iter__(self):
  94. return reversed(self.lst)
  95. def keys(self):
  96. return reversed(self.lst)
  97. class lru_node(object):
  98. """
  99. Used by :py:class:`waflib.Utils.lru_cache`
  100. """
  101. __slots__ = ('next', 'prev', 'key', 'val')
  102. def __init__(self):
  103. self.next = self
  104. self.prev = self
  105. self.key = None
  106. self.val = None
  107. class lru_cache(object):
  108. """
  109. A simple least-recently used cache with lazy allocation
  110. """
  111. __slots__ = ('maxlen', 'table', 'head')
  112. def __init__(self, maxlen=100):
  113. self.maxlen = maxlen
  114. """
  115. Maximum amount of elements in the cache
  116. """
  117. self.table = {}
  118. """
  119. Mapping key-value
  120. """
  121. self.head = lru_node()
  122. self.head.next = self.head
  123. self.head.prev = self.head
  124. def __getitem__(self, key):
  125. node = self.table[key]
  126. # assert(key==node.key)
  127. if node is self.head:
  128. return node.val
  129. # detach the node found
  130. node.prev.next = node.next
  131. node.next.prev = node.prev
  132. # replace the head
  133. node.next = self.head.next
  134. node.prev = self.head
  135. self.head = node.next.prev = node.prev.next = node
  136. return node.val
  137. def __setitem__(self, key, val):
  138. if key in self.table:
  139. # update the value for an existing key
  140. node = self.table[key]
  141. node.val = val
  142. self.__getitem__(key)
  143. else:
  144. if len(self.table) < self.maxlen:
  145. # the very first item is unused until the maximum is reached
  146. node = lru_node()
  147. node.prev = self.head
  148. node.next = self.head.next
  149. node.prev.next = node.next.prev = node
  150. else:
  151. node = self.head = self.head.next
  152. try:
  153. # that's another key
  154. del self.table[node.key]
  155. except KeyError:
  156. pass
  157. node.key = key
  158. node.val = val
  159. self.table[key] = node
  160. class lazy_generator(object):
  161. def __init__(self, fun, params):
  162. self.fun = fun
  163. self.params = params
  164. def __iter__(self):
  165. return self
  166. def __next__(self):
  167. try:
  168. it = self.it
  169. except AttributeError:
  170. it = self.it = self.fun(*self.params)
  171. return next(it)
  172. next = __next__
  173. is_win32 = os.sep == '\\' or sys.platform == 'win32' # msys2
  174. """
  175. Whether this system is a Windows series
  176. """
  177. def readf(fname, m='r', encoding='latin-1'):
  178. """
  179. Reads an entire file into a string. See also :py:meth:`waflib.Node.Node.readf`::
  180. def build(ctx):
  181. from waflib import Utils
  182. txt = Utils.readf(self.path.find_node('wscript').abspath())
  183. txt = ctx.path.find_node('wscript').read()
  184. :type fname: string
  185. :param fname: Path to file
  186. :type m: string
  187. :param m: Open mode
  188. :type encoding: string
  189. :param encoding: encoding value, only used for python 3
  190. :rtype: string
  191. :return: Content of the file
  192. """
  193. if sys.hexversion > 0x3000000 and not 'b' in m:
  194. m += 'b'
  195. with open(fname, m) as f:
  196. txt = f.read()
  197. if encoding:
  198. txt = txt.decode(encoding)
  199. else:
  200. txt = txt.decode()
  201. else:
  202. with open(fname, m) as f:
  203. txt = f.read()
  204. return txt
  205. def writef(fname, data, m='w', encoding='latin-1'):
  206. """
  207. Writes an entire file from a string.
  208. See also :py:meth:`waflib.Node.Node.writef`::
  209. def build(ctx):
  210. from waflib import Utils
  211. txt = Utils.writef(self.path.make_node('i_like_kittens').abspath(), 'some data')
  212. self.path.make_node('i_like_kittens').write('some data')
  213. :type fname: string
  214. :param fname: Path to file
  215. :type data: string
  216. :param data: The contents to write to the file
  217. :type m: string
  218. :param m: Open mode
  219. :type encoding: string
  220. :param encoding: encoding value, only used for python 3
  221. """
  222. if sys.hexversion > 0x3000000 and not 'b' in m:
  223. data = data.encode(encoding)
  224. m += 'b'
  225. with open(fname, m) as f:
  226. f.write(data)
  227. def h_file(fname):
  228. """
  229. Computes a hash value for a file by using md5. Use the md5_tstamp
  230. extension to get faster build hashes if necessary.
  231. :type fname: string
  232. :param fname: path to the file to hash
  233. :return: hash of the file contents
  234. :rtype: string or bytes
  235. """
  236. m = md5()
  237. with open(fname, 'rb') as f:
  238. while fname:
  239. fname = f.read(200000)
  240. m.update(fname)
  241. return m.digest()
  242. def readf_win32(f, m='r', encoding='latin-1'):
  243. flags = os.O_NOINHERIT | os.O_RDONLY
  244. if 'b' in m:
  245. flags |= os.O_BINARY
  246. if '+' in m:
  247. flags |= os.O_RDWR
  248. try:
  249. fd = os.open(f, flags)
  250. except OSError:
  251. raise IOError('Cannot read from %r' % f)
  252. if sys.hexversion > 0x3000000 and not 'b' in m:
  253. m += 'b'
  254. with os.fdopen(fd, m) as f:
  255. txt = f.read()
  256. if encoding:
  257. txt = txt.decode(encoding)
  258. else:
  259. txt = txt.decode()
  260. else:
  261. with os.fdopen(fd, m) as f:
  262. txt = f.read()
  263. return txt
  264. def writef_win32(f, data, m='w', encoding='latin-1'):
  265. if sys.hexversion > 0x3000000 and not 'b' in m:
  266. data = data.encode(encoding)
  267. m += 'b'
  268. flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | os.O_NOINHERIT
  269. if 'b' in m:
  270. flags |= os.O_BINARY
  271. if '+' in m:
  272. flags |= os.O_RDWR
  273. try:
  274. fd = os.open(f, flags)
  275. except OSError:
  276. raise OSError('Cannot write to %r' % f)
  277. with os.fdopen(fd, m) as f:
  278. f.write(data)
  279. def h_file_win32(fname):
  280. try:
  281. fd = os.open(fname, os.O_BINARY | os.O_RDONLY | os.O_NOINHERIT)
  282. except OSError:
  283. raise OSError('Cannot read from %r' % fname)
  284. m = md5()
  285. with os.fdopen(fd, 'rb') as f:
  286. while fname:
  287. fname = f.read(200000)
  288. m.update(fname)
  289. return m.digest()
  290. # always save these
  291. readf_unix = readf
  292. writef_unix = writef
  293. h_file_unix = h_file
  294. if hasattr(os, 'O_NOINHERIT') and sys.hexversion < 0x3040000:
  295. # replace the default functions
  296. readf = readf_win32
  297. writef = writef_win32
  298. h_file = h_file_win32
  299. try:
  300. x = ''.encode('hex')
  301. except LookupError:
  302. import binascii
  303. def to_hex(s):
  304. ret = binascii.hexlify(s)
  305. if not isinstance(ret, str):
  306. ret = ret.decode('utf-8')
  307. return ret
  308. else:
  309. def to_hex(s):
  310. return s.encode('hex')
  311. to_hex.__doc__ = """
  312. Return the hexadecimal representation of a string
  313. :param s: string to convert
  314. :type s: string
  315. """
  316. def listdir_win32(s):
  317. """
  318. Lists the contents of a folder in a portable manner.
  319. On Win32, returns the list of drive letters: ['C:', 'X:', 'Z:'] when an empty string is given.
  320. :type s: string
  321. :param s: a string, which can be empty on Windows
  322. """
  323. if not s:
  324. try:
  325. import ctypes
  326. except ImportError:
  327. # there is nothing much we can do
  328. return [x + ':\\' for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
  329. else:
  330. dlen = 4 # length of "?:\\x00"
  331. maxdrives = 26
  332. buf = ctypes.create_string_buffer(maxdrives * dlen)
  333. ndrives = ctypes.windll.kernel32.GetLogicalDriveStringsA(maxdrives*dlen, ctypes.byref(buf))
  334. return [ str(buf.raw[4*i:4*i+2].decode('ascii')) for i in range(int(ndrives/dlen)) ]
  335. if len(s) == 2 and s[1] == ":":
  336. s += os.sep
  337. if not os.path.isdir(s):
  338. e = OSError('%s is not a directory' % s)
  339. e.errno = errno.ENOENT
  340. raise e
  341. return os.listdir(s)
  342. listdir = os.listdir
  343. if is_win32:
  344. listdir = listdir_win32
  345. def num2ver(ver):
  346. """
  347. Converts a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
  348. from waflib.Utils import num2ver
  349. num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
  350. :type ver: string or tuple of numbers
  351. :param ver: a version number
  352. """
  353. if isinstance(ver, str):
  354. ver = tuple(ver.split('.'))
  355. if isinstance(ver, tuple):
  356. ret = 0
  357. for i in range(4):
  358. if i < len(ver):
  359. ret += 256**(3 - i) * int(ver[i])
  360. return ret
  361. return ver
  362. def to_list(val):
  363. """
  364. Converts a string argument to a list by splitting it by spaces.
  365. Returns the object if not a string::
  366. from waflib.Utils import to_list
  367. lst = to_list('a b c d')
  368. :param val: list of string or space-separated string
  369. :rtype: list
  370. :return: Argument converted to list
  371. """
  372. if isinstance(val, str):
  373. return val.split()
  374. else:
  375. return val
  376. def console_encoding():
  377. try:
  378. import ctypes
  379. except ImportError:
  380. pass
  381. else:
  382. try:
  383. codepage = ctypes.windll.kernel32.GetConsoleCP()
  384. except AttributeError:
  385. pass
  386. else:
  387. if codepage:
  388. return 'cp%d' % codepage
  389. return sys.stdout.encoding or ('cp1252' if is_win32 else 'latin-1')
  390. def split_path_unix(path):
  391. return path.split('/')
  392. def split_path_cygwin(path):
  393. if path.startswith('//'):
  394. ret = path.split('/')[2:]
  395. ret[0] = '/' + ret[0]
  396. return ret
  397. return path.split('/')
  398. re_sp = re.compile('[/\\\\]+')
  399. def split_path_win32(path):
  400. if path.startswith('\\\\'):
  401. ret = re_sp.split(path)[1:]
  402. ret[0] = '\\\\' + ret[0]
  403. if ret[0] == '\\\\?':
  404. return ret[1:]
  405. return ret
  406. return re_sp.split(path)
  407. msysroot = None
  408. def split_path_msys(path):
  409. if path.startswith(('/', '\\')) and not path.startswith(('//', '\\\\')):
  410. # msys paths can be in the form /usr/bin
  411. global msysroot
  412. if not msysroot:
  413. # msys has python 2.7 or 3, so we can use this
  414. msysroot = subprocess.check_output(['cygpath', '-w', '/']).decode(sys.stdout.encoding or 'latin-1')
  415. msysroot = msysroot.strip()
  416. path = os.path.normpath(msysroot + os.sep + path)
  417. return split_path_win32(path)
  418. if sys.platform == 'cygwin':
  419. split_path = split_path_cygwin
  420. elif is_win32:
  421. if os.environ.get('MSYSTEM'):
  422. split_path = split_path_msys
  423. else:
  424. split_path = split_path_win32
  425. else:
  426. split_path = split_path_unix
  427. split_path.__doc__ = """
  428. Splits a path by / or \\; do not confuse this function with with ``os.path.split``
  429. :type path: string
  430. :param path: path to split
  431. :return: list of string
  432. """
  433. def check_dir(path):
  434. """
  435. Ensures that a directory exists (similar to ``mkdir -p``).
  436. :type path: string
  437. :param path: Path to directory
  438. :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
  439. """
  440. if not os.path.isdir(path):
  441. try:
  442. os.makedirs(path)
  443. except OSError as e:
  444. if not os.path.isdir(path):
  445. raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
  446. def check_exe(name, env=None):
  447. """
  448. Ensures that a program exists
  449. :type name: string
  450. :param name: path to the program
  451. :param env: configuration object
  452. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  453. :return: path of the program or None
  454. :raises: :py:class:`waflib.Errors.WafError` if the folder cannot be added.
  455. """
  456. if not name:
  457. raise ValueError('Cannot execute an empty string!')
  458. def is_exe(fpath):
  459. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  460. fpath, fname = os.path.split(name)
  461. if fpath and is_exe(name):
  462. return os.path.abspath(name)
  463. else:
  464. env = env or os.environ
  465. for path in env['PATH'].split(os.pathsep):
  466. path = path.strip('"')
  467. exe_file = os.path.join(path, name)
  468. if is_exe(exe_file):
  469. return os.path.abspath(exe_file)
  470. return None
  471. def def_attrs(cls, **kw):
  472. """
  473. Sets default attributes on a class instance
  474. :type cls: class
  475. :param cls: the class to update the given attributes in.
  476. :type kw: dict
  477. :param kw: dictionary of attributes names and values.
  478. """
  479. for k, v in kw.items():
  480. if not hasattr(cls, k):
  481. setattr(cls, k, v)
  482. def quote_define_name(s):
  483. """
  484. Converts a string into an identifier suitable for C defines.
  485. :type s: string
  486. :param s: String to convert
  487. :rtype: string
  488. :return: Identifier suitable for C defines
  489. """
  490. fu = re.sub('[^a-zA-Z0-9]', '_', s)
  491. fu = re.sub('_+', '_', fu)
  492. fu = fu.upper()
  493. return fu
  494. re_sh = re.compile('\\s|\'|"')
  495. """
  496. Regexp used for shell_escape below
  497. """
  498. def shell_escape(cmd):
  499. """
  500. Escapes a command:
  501. ['ls', '-l', 'arg space'] -> ls -l 'arg space'
  502. """
  503. if isinstance(cmd, str):
  504. return cmd
  505. return ' '.join(repr(x) if re_sh.search(x) else x for x in cmd)
  506. def h_list(lst):
  507. """
  508. Hashes lists of ordered data.
  509. Using hash(tup) for tuples would be much more efficient,
  510. but Python now enforces hash randomization
  511. :param lst: list to hash
  512. :type lst: list of strings
  513. :return: hash of the list
  514. """
  515. return md5(repr(lst).encode()).digest()
  516. def h_fun(fun):
  517. """
  518. Hash functions
  519. :param fun: function to hash
  520. :type fun: function
  521. :return: hash of the function
  522. :rtype: string or bytes
  523. """
  524. try:
  525. return fun.code
  526. except AttributeError:
  527. if isinstance(fun, functools.partial):
  528. code = list(fun.args)
  529. # The method items() provides a sequence of tuples where the first element
  530. # represents an optional argument of the partial function application
  531. #
  532. # The sorting result outcome will be consistent because:
  533. # 1. tuples are compared in order of their elements
  534. # 2. optional argument namess are unique
  535. code.extend(sorted(fun.keywords.items()))
  536. code.append(h_fun(fun.func))
  537. fun.code = h_list(code)
  538. return fun.code
  539. try:
  540. h = inspect.getsource(fun)
  541. except EnvironmentError:
  542. h = 'nocode'
  543. try:
  544. fun.code = h
  545. except AttributeError:
  546. pass
  547. return h
  548. def h_cmd(ins):
  549. """
  550. Hashes objects recursively
  551. :param ins: input object
  552. :type ins: string or list or tuple or function
  553. :rtype: string or bytes
  554. """
  555. # this function is not meant to be particularly fast
  556. if isinstance(ins, str):
  557. # a command is either a string
  558. ret = ins
  559. elif isinstance(ins, list) or isinstance(ins, tuple):
  560. # or a list of functions/strings
  561. ret = str([h_cmd(x) for x in ins])
  562. else:
  563. # or just a python function
  564. ret = str(h_fun(ins))
  565. if sys.hexversion > 0x3000000:
  566. ret = ret.encode('latin-1', 'xmlcharrefreplace')
  567. return ret
  568. reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
  569. def subst_vars(expr, params):
  570. """
  571. Replaces ${VAR} with the value of VAR taken from a dict or a config set::
  572. from waflib import Utils
  573. s = Utils.subst_vars('${PREFIX}/bin', env)
  574. :type expr: string
  575. :param expr: String to perform substitution on
  576. :param params: Dictionary or config set to look up variable values.
  577. """
  578. def repl_var(m):
  579. if m.group(1):
  580. return '\\'
  581. if m.group(2):
  582. return '$'
  583. try:
  584. # ConfigSet instances may contain lists
  585. return params.get_flat(m.group(3))
  586. except AttributeError:
  587. return params[m.group(3)]
  588. # if you get a TypeError, it means that 'expr' is not a string...
  589. # Utils.subst_vars(None, env) will not work
  590. return reg_subst.sub(repl_var, expr)
  591. def destos_to_binfmt(key):
  592. """
  593. Returns the binary format based on the unversioned platform name,
  594. and defaults to ``elf`` if nothing is found.
  595. :param key: platform name
  596. :type key: string
  597. :return: string representing the binary format
  598. """
  599. if key == 'darwin':
  600. return 'mac-o'
  601. elif key in ('win32', 'cygwin', 'uwin', 'msys'):
  602. return 'pe'
  603. return 'elf'
  604. def unversioned_sys_platform():
  605. """
  606. Returns the unversioned platform name.
  607. Some Python platform names contain versions, that depend on
  608. the build environment, e.g. linux2, freebsd6, etc.
  609. This returns the name without the version number. Exceptions are
  610. os2 and win32, which are returned verbatim.
  611. :rtype: string
  612. :return: Unversioned platform name
  613. """
  614. s = sys.platform
  615. if s.startswith('java'):
  616. # The real OS is hidden under the JVM.
  617. from java.lang import System
  618. s = System.getProperty('os.name')
  619. # see http://lopica.sourceforge.net/os.html for a list of possible values
  620. if s == 'Mac OS X':
  621. return 'darwin'
  622. elif s.startswith('Windows '):
  623. return 'win32'
  624. elif s == 'OS/2':
  625. return 'os2'
  626. elif s == 'HP-UX':
  627. return 'hp-ux'
  628. elif s in ('SunOS', 'Solaris'):
  629. return 'sunos'
  630. else: s = s.lower()
  631. # powerpc == darwin for our purposes
  632. if s == 'powerpc':
  633. return 'darwin'
  634. if s == 'win32' or s == 'os2':
  635. return s
  636. if s == 'cli' and os.name == 'nt':
  637. # ironpython is only on windows as far as we know
  638. return 'win32'
  639. return re.split('\d+$', s)[0]
  640. def nada(*k, **kw):
  641. """
  642. Does nothing
  643. :return: None
  644. """
  645. pass
  646. class Timer(object):
  647. """
  648. Simple object for timing the execution of commands.
  649. Its string representation is the duration::
  650. from waflib.Utils import Timer
  651. timer = Timer()
  652. a_few_operations()
  653. s = str(timer)
  654. """
  655. def __init__(self):
  656. self.start_time = self.now()
  657. def __str__(self):
  658. delta = self.now() - self.start_time
  659. if not isinstance(delta, datetime.timedelta):
  660. delta = datetime.timedelta(seconds=delta)
  661. days = delta.days
  662. hours, rem = divmod(delta.seconds, 3600)
  663. minutes, seconds = divmod(rem, 60)
  664. seconds += delta.microseconds * 1e-6
  665. result = ''
  666. if days:
  667. result += '%dd' % days
  668. if days or hours:
  669. result += '%dh' % hours
  670. if days or hours or minutes:
  671. result += '%dm' % minutes
  672. return '%s%.3fs' % (result, seconds)
  673. def now(self):
  674. return datetime.datetime.utcnow()
  675. if hasattr(time, 'perf_counter'):
  676. def now(self):
  677. return time.perf_counter()
  678. def read_la_file(path):
  679. """
  680. Reads property files, used by msvc.py
  681. :param path: file to read
  682. :type path: string
  683. """
  684. sp = re.compile(r'^([^=]+)=\'(.*)\'$')
  685. dc = {}
  686. for line in readf(path).splitlines():
  687. try:
  688. _, left, right, _ = sp.split(line.strip())
  689. dc[left] = right
  690. except ValueError:
  691. pass
  692. return dc
  693. def run_once(fun):
  694. """
  695. Decorator: let a function cache its results, use like this::
  696. @run_once
  697. def foo(k):
  698. return 345*2343
  699. .. note:: in practice this can cause memory leaks, prefer a :py:class:`waflib.Utils.lru_cache`
  700. :param fun: function to execute
  701. :type fun: function
  702. :return: the return value of the function executed
  703. """
  704. cache = {}
  705. def wrap(*k):
  706. try:
  707. return cache[k]
  708. except KeyError:
  709. ret = fun(*k)
  710. cache[k] = ret
  711. return ret
  712. wrap.__cache__ = cache
  713. wrap.__name__ = fun.__name__
  714. return wrap
  715. def get_registry_app_path(key, filename):
  716. """
  717. Returns the value of a registry key for an executable
  718. :type key: string
  719. :type filename: list of string
  720. """
  721. if not winreg:
  722. return None
  723. try:
  724. result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
  725. except OSError:
  726. pass
  727. else:
  728. if os.path.isfile(result):
  729. return result
  730. def lib64():
  731. """
  732. Guess the default ``/usr/lib`` extension for 64-bit applications
  733. :return: '64' or ''
  734. :rtype: string
  735. """
  736. # default settings for /usr/lib
  737. if os.sep == '/':
  738. if platform.architecture()[0] == '64bit':
  739. if os.path.exists('/usr/lib64') and not os.path.exists('/usr/lib32'):
  740. return '64'
  741. return ''
  742. def sane_path(p):
  743. # private function for the time being!
  744. return os.path.abspath(os.path.expanduser(p))
  745. process_pool = []
  746. """
  747. List of processes started to execute sub-process commands
  748. """
  749. def get_process():
  750. """
  751. Returns a process object that can execute commands as sub-processes
  752. :rtype: subprocess.Popen
  753. """
  754. try:
  755. return process_pool.pop()
  756. except IndexError:
  757. filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor.py'
  758. cmd = [sys.executable, '-c', readf(filepath)]
  759. return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
  760. def run_prefork_process(cmd, kwargs, cargs):
  761. """
  762. Delegates process execution to a pre-forked process instance.
  763. """
  764. if not 'env' in kwargs:
  765. kwargs['env'] = dict(os.environ)
  766. try:
  767. obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
  768. except (TypeError, AttributeError):
  769. return run_regular_process(cmd, kwargs, cargs)
  770. proc = get_process()
  771. if not proc:
  772. return run_regular_process(cmd, kwargs, cargs)
  773. proc.stdin.write(obj)
  774. proc.stdin.write('\n'.encode())
  775. proc.stdin.flush()
  776. obj = proc.stdout.readline()
  777. if not obj:
  778. raise OSError('Preforked sub-process %r died' % proc.pid)
  779. process_pool.append(proc)
  780. lst = cPickle.loads(base64.b64decode(obj))
  781. # Jython wrapper failures (bash/execvp)
  782. assert len(lst) == 5
  783. ret, out, err, ex, trace = lst
  784. if ex:
  785. if ex == 'OSError':
  786. raise OSError(trace)
  787. elif ex == 'ValueError':
  788. raise ValueError(trace)
  789. elif ex == 'TimeoutExpired':
  790. exc = TimeoutExpired(cmd, timeout=cargs['timeout'], output=out)
  791. exc.stderr = err
  792. raise exc
  793. else:
  794. raise Exception(trace)
  795. return ret, out, err
  796. def lchown(path, user=-1, group=-1):
  797. """
  798. Change the owner/group of a path, raises an OSError if the
  799. ownership change fails.
  800. :param user: user to change
  801. :type user: int or str
  802. :param group: group to change
  803. :type group: int or str
  804. """
  805. if isinstance(user, str):
  806. import pwd
  807. entry = pwd.getpwnam(user)
  808. if not entry:
  809. raise OSError('Unknown user %r' % user)
  810. user = entry[2]
  811. if isinstance(group, str):
  812. import grp
  813. entry = grp.getgrnam(group)
  814. if not entry:
  815. raise OSError('Unknown group %r' % group)
  816. group = entry[2]
  817. return os.lchown(path, user, group)
  818. def run_regular_process(cmd, kwargs, cargs={}):
  819. """
  820. Executes a subprocess command by using subprocess.Popen
  821. """
  822. proc = subprocess.Popen(cmd, **kwargs)
  823. if kwargs.get('stdout') or kwargs.get('stderr'):
  824. try:
  825. out, err = proc.communicate(**cargs)
  826. except TimeoutExpired:
  827. if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
  828. os.killpg(proc.pid, signal.SIGKILL)
  829. else:
  830. proc.kill()
  831. out, err = proc.communicate()
  832. exc = TimeoutExpired(proc.args, timeout=cargs['timeout'], output=out)
  833. exc.stderr = err
  834. raise exc
  835. status = proc.returncode
  836. else:
  837. out, err = (None, None)
  838. try:
  839. status = proc.wait(**cargs)
  840. except TimeoutExpired as e:
  841. if kwargs.get('start_new_session') and hasattr(os, 'killpg'):
  842. os.killpg(proc.pid, signal.SIGKILL)
  843. else:
  844. proc.kill()
  845. proc.wait()
  846. raise e
  847. return status, out, err
  848. def run_process(cmd, kwargs, cargs={}):
  849. """
  850. Executes a subprocess by using a pre-forked process when possible
  851. or falling back to subprocess.Popen. See :py:func:`waflib.Utils.run_prefork_process`
  852. and :py:func:`waflib.Utils.run_regular_process`
  853. """
  854. if kwargs.get('stdout') and kwargs.get('stderr'):
  855. return run_prefork_process(cmd, kwargs, cargs)
  856. else:
  857. return run_regular_process(cmd, kwargs, cargs)
  858. def alloc_process_pool(n, force=False):
  859. """
  860. Allocates an amount of processes to the default pool so its size is at least *n*.
  861. It is useful to call this function early so that the pre-forked
  862. processes use as little memory as possible.
  863. :param n: pool size
  864. :type n: integer
  865. :param force: if True then *n* more processes are added to the existing pool
  866. :type force: bool
  867. """
  868. # mandatory on python2, unnecessary on python >= 3.2
  869. global run_process, get_process, alloc_process_pool
  870. if not force:
  871. n = max(n - len(process_pool), 0)
  872. try:
  873. lst = [get_process() for x in range(n)]
  874. except OSError:
  875. run_process = run_regular_process
  876. get_process = alloc_process_pool = nada
  877. else:
  878. for x in lst:
  879. process_pool.append(x)
  880. def atexit_pool():
  881. for k in process_pool:
  882. try:
  883. os.kill(k.pid, 9)
  884. except OSError:
  885. pass
  886. else:
  887. k.wait()
  888. # see #1889
  889. if (sys.hexversion<0x207000f and not is_win32) or sys.hexversion>=0x306000f:
  890. atexit.register(atexit_pool)
  891. if os.environ.get('WAF_NO_PREFORK') or sys.platform == 'cli' or not sys.executable:
  892. run_process = run_regular_process
  893. get_process = alloc_process_pool = nada