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.

1395 lines
38KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2018 (ita)
  4. """
  5. Tasks represent atomic operations such as processes.
  6. """
  7. import os, re, sys, tempfile, traceback
  8. from waflib import Utils, Logs, Errors
  9. # task states
  10. NOT_RUN = 0
  11. """The task was not executed yet"""
  12. MISSING = 1
  13. """The task has been executed but the files have not been created"""
  14. CRASHED = 2
  15. """The task execution returned a non-zero exit status"""
  16. EXCEPTION = 3
  17. """An exception occurred in the task execution"""
  18. CANCELED = 4
  19. """A dependency for the task is missing so it was cancelled"""
  20. SKIPPED = 8
  21. """The task did not have to be executed"""
  22. SUCCESS = 9
  23. """The task was successfully executed"""
  24. ASK_LATER = -1
  25. """The task is not ready to be executed"""
  26. SKIP_ME = -2
  27. """The task does not need to be executed"""
  28. RUN_ME = -3
  29. """The task must be executed"""
  30. CANCEL_ME = -4
  31. """The task cannot be executed because of a dependency problem"""
  32. COMPILE_TEMPLATE_SHELL = '''
  33. def f(tsk):
  34. env = tsk.env
  35. gen = tsk.generator
  36. bld = gen.bld
  37. cwdx = tsk.get_cwd()
  38. p = env.get_flat
  39. tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
  40. return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
  41. '''
  42. COMPILE_TEMPLATE_NOSHELL = '''
  43. def f(tsk):
  44. env = tsk.env
  45. gen = tsk.generator
  46. bld = gen.bld
  47. cwdx = tsk.get_cwd()
  48. def to_list(xx):
  49. if isinstance(xx, str): return [xx]
  50. return xx
  51. def merge(lst1, lst2):
  52. if lst1 and lst2:
  53. return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
  54. return lst1 + lst2
  55. lst = []
  56. %s
  57. if '' in lst:
  58. lst = [x for x in lst if x]
  59. tsk.last_cmd = lst
  60. return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
  61. '''
  62. COMPILE_TEMPLATE_SIG_VARS = '''
  63. def f(tsk):
  64. super(tsk.__class__, tsk).sig_vars()
  65. env = tsk.env
  66. gen = tsk.generator
  67. bld = gen.bld
  68. cwdx = tsk.get_cwd()
  69. p = env.get_flat
  70. buf = []
  71. %s
  72. tsk.m.update(repr(buf).encode())
  73. '''
  74. classes = {}
  75. """
  76. The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
  77. created by user scripts or Waf tools to this dict. It maps class names to class objects.
  78. """
  79. class store_task_type(type):
  80. """
  81. Metaclass: store the task classes into the dict pointed by the
  82. class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
  83. The attribute 'run_str' is compiled into a method 'run' bound to the task class.
  84. """
  85. def __init__(cls, name, bases, dict):
  86. super(store_task_type, cls).__init__(name, bases, dict)
  87. name = cls.__name__
  88. if name != 'evil' and name != 'Task':
  89. if getattr(cls, 'run_str', None):
  90. # if a string is provided, convert it to a method
  91. (f, dvars) = compile_fun(cls.run_str, cls.shell)
  92. cls.hcode = Utils.h_cmd(cls.run_str)
  93. cls.orig_run_str = cls.run_str
  94. # change the name of run_str or it is impossible to subclass with a function
  95. cls.run_str = None
  96. cls.run = f
  97. # process variables
  98. cls.vars = list(set(cls.vars + dvars))
  99. cls.vars.sort()
  100. if cls.vars:
  101. fun = compile_sig_vars(cls.vars)
  102. if fun:
  103. cls.sig_vars = fun
  104. elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
  105. # getattr(cls, 'hcode') would look in the upper classes
  106. cls.hcode = Utils.h_cmd(cls.run)
  107. # be creative
  108. getattr(cls, 'register', classes)[name] = cls
  109. evil = store_task_type('evil', (object,), {})
  110. "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
  111. class Task(evil):
  112. """
  113. Task objects represents actions to perform such as commands to execute by calling the `run` method.
  114. Detecting when to execute a task occurs in the method :py:meth:`waflib.Task.Task.runnable_status`.
  115. Detecting which tasks to execute is performed through a hash value returned by
  116. :py:meth:`waflib.Task.Task.signature`. The task signature is persistent from build to build.
  117. """
  118. vars = []
  119. """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
  120. always_run = False
  121. """Specify whether task instances must always be executed or not (class attribute)"""
  122. shell = False
  123. """Execute the command with the shell (class attribute)"""
  124. color = 'GREEN'
  125. """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
  126. ext_in = []
  127. """File extensions that objects of this task class may use"""
  128. ext_out = []
  129. """File extensions that objects of this task class may create"""
  130. before = []
  131. """List of task class names to execute before instances of this class"""
  132. after = []
  133. """List of task class names to execute after instances of this class"""
  134. hcode = Utils.SIG_NIL
  135. """String representing an additional hash for the class representation"""
  136. keep_last_cmd = False
  137. """Whether to keep the last command executed on the instance after execution.
  138. This may be useful for certain extensions but it can a lot of memory.
  139. """
  140. weight = 0
  141. """Optional weight to tune the priority for task instances.
  142. The higher, the earlier. The weight only applies to single task objects."""
  143. tree_weight = 0
  144. """Optional weight to tune the priority of task instances and whole subtrees.
  145. The higher, the earlier."""
  146. prio_order = 0
  147. """Priority order set by the scheduler on instances during the build phase.
  148. You most likely do not need to set it.
  149. """
  150. __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
  151. def __init__(self, *k, **kw):
  152. self.hasrun = NOT_RUN
  153. try:
  154. self.generator = kw['generator']
  155. except KeyError:
  156. self.generator = self
  157. self.env = kw['env']
  158. """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
  159. self.inputs = []
  160. """List of input nodes, which represent the files used by the task instance"""
  161. self.outputs = []
  162. """List of output nodes, which represent the files created by the task instance"""
  163. self.dep_nodes = []
  164. """List of additional nodes to depend on"""
  165. self.run_after = set()
  166. """Set of tasks that must be executed before this one"""
  167. def __lt__(self, other):
  168. return self.priority() > other.priority()
  169. def __le__(self, other):
  170. return self.priority() >= other.priority()
  171. def __gt__(self, other):
  172. return self.priority() < other.priority()
  173. def __ge__(self, other):
  174. return self.priority() <= other.priority()
  175. def get_cwd(self):
  176. """
  177. :return: current working directory
  178. :rtype: :py:class:`waflib.Node.Node`
  179. """
  180. bld = self.generator.bld
  181. ret = getattr(self, 'cwd', None) or getattr(bld, 'cwd', bld.bldnode)
  182. if isinstance(ret, str):
  183. if os.path.isabs(ret):
  184. ret = bld.root.make_node(ret)
  185. else:
  186. ret = self.generator.path.make_node(ret)
  187. return ret
  188. def quote_flag(self, x):
  189. """
  190. Surround a process argument by quotes so that a list of arguments can be written to a file
  191. :param x: flag
  192. :type x: string
  193. :return: quoted flag
  194. :rtype: string
  195. """
  196. old = x
  197. if '\\' in x:
  198. x = x.replace('\\', '\\\\')
  199. if '"' in x:
  200. x = x.replace('"', '\\"')
  201. if old != x or ' ' in x or '\t' in x or "'" in x:
  202. x = '"%s"' % x
  203. return x
  204. def priority(self):
  205. """
  206. Priority of execution; the higher, the earlier
  207. :return: the priority value
  208. :rtype: a tuple of numeric values
  209. """
  210. return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
  211. def split_argfile(self, cmd):
  212. """
  213. Splits a list of process commands into the executable part and its list of arguments
  214. :return: a tuple containing the executable first and then the rest of arguments
  215. :rtype: tuple
  216. """
  217. return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
  218. def exec_command(self, cmd, **kw):
  219. """
  220. Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
  221. This version set the current working directory (``build.variant_dir``),
  222. applies PATH settings (if self.env.PATH is provided), and can run long
  223. commands through a temporary ``@argfile``.
  224. :param cmd: process command to execute
  225. :type cmd: list of string (best) or string (process will use a shell)
  226. :return: the return code
  227. :rtype: int
  228. Optional parameters:
  229. #. cwd: current working directory (Node or string)
  230. #. stdout: set to None to prevent waf from capturing the process standard output
  231. #. stderr: set to None to prevent waf from capturing the process standard error
  232. #. timeout: timeout value (Python 3)
  233. """
  234. if not 'cwd' in kw:
  235. kw['cwd'] = self.get_cwd()
  236. if hasattr(self, 'timeout'):
  237. kw['timeout'] = self.timeout
  238. if self.env.PATH:
  239. env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
  240. env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
  241. if hasattr(self, 'stdout'):
  242. kw['stdout'] = self.stdout
  243. if hasattr(self, 'stderr'):
  244. kw['stderr'] = self.stderr
  245. # workaround for command line length limit:
  246. # http://support.microsoft.com/kb/830473
  247. if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
  248. cmd, args = self.split_argfile(cmd)
  249. try:
  250. (fd, tmp) = tempfile.mkstemp()
  251. os.write(fd, '\r\n'.join(args).encode())
  252. os.close(fd)
  253. if Logs.verbose:
  254. Logs.debug('argfile: @%r -> %r', tmp, args)
  255. return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
  256. finally:
  257. try:
  258. os.remove(tmp)
  259. except OSError:
  260. # anti-virus and indexers can keep files open -_-
  261. pass
  262. else:
  263. return self.generator.bld.exec_command(cmd, **kw)
  264. def process(self):
  265. """
  266. Runs the task and handles errors
  267. :return: 0 or None if everything is fine
  268. :rtype: integer
  269. """
  270. # remove the task signature immediately before it is executed
  271. # so that the task will be executed again in case of failure
  272. try:
  273. del self.generator.bld.task_sigs[self.uid()]
  274. except KeyError:
  275. pass
  276. try:
  277. ret = self.run()
  278. except Exception:
  279. self.err_msg = traceback.format_exc()
  280. self.hasrun = EXCEPTION
  281. else:
  282. if ret:
  283. self.err_code = ret
  284. self.hasrun = CRASHED
  285. else:
  286. try:
  287. self.post_run()
  288. except Errors.WafError:
  289. pass
  290. except Exception:
  291. self.err_msg = traceback.format_exc()
  292. self.hasrun = EXCEPTION
  293. else:
  294. self.hasrun = SUCCESS
  295. if self.hasrun != SUCCESS and self.scan:
  296. # rescan dependencies on next run
  297. try:
  298. del self.generator.bld.imp_sigs[self.uid()]
  299. except KeyError:
  300. pass
  301. def log_display(self, bld):
  302. "Writes the execution status on the context logger"
  303. if self.generator.bld.progress_bar == 3:
  304. return
  305. s = self.display()
  306. if s:
  307. if bld.logger:
  308. logger = bld.logger
  309. else:
  310. logger = Logs
  311. if self.generator.bld.progress_bar == 1:
  312. c1 = Logs.colors.cursor_off
  313. c2 = Logs.colors.cursor_on
  314. logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
  315. else:
  316. logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
  317. def display(self):
  318. """
  319. Returns an execution status for the console, the progress bar, or the IDE output.
  320. :rtype: string
  321. """
  322. col1 = Logs.colors(self.color)
  323. col2 = Logs.colors.NORMAL
  324. master = self.generator.bld.producer
  325. def cur():
  326. # the current task position, computed as late as possible
  327. return master.processed - master.ready.qsize()
  328. if self.generator.bld.progress_bar == 1:
  329. return self.generator.bld.progress_line(cur(), master.total, col1, col2)
  330. if self.generator.bld.progress_bar == 2:
  331. ela = str(self.generator.bld.timer)
  332. try:
  333. ins = ','.join([n.name for n in self.inputs])
  334. except AttributeError:
  335. ins = ''
  336. try:
  337. outs = ','.join([n.name for n in self.outputs])
  338. except AttributeError:
  339. outs = ''
  340. return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
  341. s = str(self)
  342. if not s:
  343. return None
  344. total = master.total
  345. n = len(str(total))
  346. fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
  347. kw = self.keyword()
  348. if kw:
  349. kw += ' '
  350. return fs % (cur(), total, kw, col1, s, col2)
  351. def hash_constraints(self):
  352. """
  353. Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
  354. :return: a hash value
  355. :rtype: string
  356. """
  357. return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
  358. def format_error(self):
  359. """
  360. Returns an error message to display the build failure reasons
  361. :rtype: string
  362. """
  363. if Logs.verbose:
  364. msg = ': %r\n%r' % (self, getattr(self, 'last_cmd', ''))
  365. else:
  366. msg = ' (run with -v to display more information)'
  367. name = getattr(self.generator, 'name', '')
  368. if getattr(self, "err_msg", None):
  369. return self.err_msg
  370. elif not self.hasrun:
  371. return 'task in %r was not executed for some reason: %r' % (name, self)
  372. elif self.hasrun == CRASHED:
  373. try:
  374. return ' -> task in %r failed with exit status %r%s' % (name, self.err_code, msg)
  375. except AttributeError:
  376. return ' -> task in %r failed%s' % (name, msg)
  377. elif self.hasrun == MISSING:
  378. return ' -> missing files in %r%s' % (name, msg)
  379. elif self.hasrun == CANCELED:
  380. return ' -> %r canceled because of missing dependencies' % name
  381. else:
  382. return 'invalid status for task in %r: %r' % (name, self.hasrun)
  383. def colon(self, var1, var2):
  384. """
  385. Enable scriptlet expressions of the form ${FOO_ST:FOO}
  386. If the first variable (FOO_ST) is empty, then an empty list is returned
  387. The results will be slightly different if FOO_ST is a list, for example::
  388. env.FOO = ['p1', 'p2']
  389. env.FOO_ST = '-I%s'
  390. # ${FOO_ST:FOO} returns
  391. ['-Ip1', '-Ip2']
  392. env.FOO_ST = ['-a', '-b']
  393. # ${FOO_ST:FOO} returns
  394. ['-a', '-b', 'p1', '-a', '-b', 'p2']
  395. """
  396. tmp = self.env[var1]
  397. if not tmp:
  398. return []
  399. if isinstance(var2, str):
  400. it = self.env[var2]
  401. else:
  402. it = var2
  403. if isinstance(tmp, str):
  404. return [tmp % x for x in it]
  405. else:
  406. lst = []
  407. for y in it:
  408. lst.extend(tmp)
  409. lst.append(y)
  410. return lst
  411. def __str__(self):
  412. "string to display to the user"
  413. name = self.__class__.__name__
  414. if self.outputs:
  415. if name.endswith(('lib', 'program')) or not self.inputs:
  416. node = self.outputs[0]
  417. return node.path_from(node.ctx.launch_node())
  418. if not (self.inputs or self.outputs):
  419. return self.__class__.__name__
  420. if len(self.inputs) == 1:
  421. node = self.inputs[0]
  422. return node.path_from(node.ctx.launch_node())
  423. src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
  424. tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
  425. if self.outputs:
  426. sep = ' -> '
  427. else:
  428. sep = ''
  429. return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
  430. def keyword(self):
  431. "Display keyword used to prettify the console outputs"
  432. name = self.__class__.__name__
  433. if name.endswith(('lib', 'program')):
  434. return 'Linking'
  435. if len(self.inputs) == 1 and len(self.outputs) == 1:
  436. return 'Compiling'
  437. if not self.inputs:
  438. if self.outputs:
  439. return 'Creating'
  440. else:
  441. return 'Running'
  442. return 'Processing'
  443. def __repr__(self):
  444. "for debugging purposes"
  445. try:
  446. ins = ",".join([x.name for x in self.inputs])
  447. outs = ",".join([x.name for x in self.outputs])
  448. except AttributeError:
  449. ins = ",".join([str(x) for x in self.inputs])
  450. outs = ",".join([str(x) for x in self.outputs])
  451. return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
  452. def uid(self):
  453. """
  454. Returns an identifier used to determine if tasks are up-to-date. Since the
  455. identifier will be stored between executions, it must be:
  456. - unique for a task: no two tasks return the same value (for a given build context)
  457. - the same for a given task instance
  458. By default, the node paths, the class name, and the function are used
  459. as inputs to compute a hash.
  460. The pointer to the object (python built-in 'id') will change between build executions,
  461. and must be avoided in such hashes.
  462. :return: hash value
  463. :rtype: string
  464. """
  465. try:
  466. return self.uid_
  467. except AttributeError:
  468. m = Utils.md5(self.__class__.__name__)
  469. up = m.update
  470. for x in self.inputs + self.outputs:
  471. up(x.abspath())
  472. self.uid_ = m.digest()
  473. return self.uid_
  474. def set_inputs(self, inp):
  475. """
  476. Appends the nodes to the *inputs* list
  477. :param inp: input nodes
  478. :type inp: node or list of nodes
  479. """
  480. if isinstance(inp, list):
  481. self.inputs += inp
  482. else:
  483. self.inputs.append(inp)
  484. def set_outputs(self, out):
  485. """
  486. Appends the nodes to the *outputs* list
  487. :param out: output nodes
  488. :type out: node or list of nodes
  489. """
  490. if isinstance(out, list):
  491. self.outputs += out
  492. else:
  493. self.outputs.append(out)
  494. def set_run_after(self, task):
  495. """
  496. Run this task only after the given *task*.
  497. Calling this method from :py:meth:`waflib.Task.Task.runnable_status` may cause
  498. build deadlocks; see :py:meth:`waflib.Tools.fc.fc.runnable_status` for details.
  499. :param task: task
  500. :type task: :py:class:`waflib.Task.Task`
  501. """
  502. assert isinstance(task, Task)
  503. self.run_after.add(task)
  504. def signature(self):
  505. """
  506. Task signatures are stored between build executions, they are use to track the changes
  507. made to the input nodes (not to the outputs!). The signature hashes data from various sources:
  508. * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
  509. * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
  510. * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
  511. If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
  512. from waflib import Task
  513. class cls(Task.Task):
  514. def signature(self):
  515. sig = super(Task.Task, self).signature()
  516. delattr(self, 'cache_sig')
  517. return super(Task.Task, self).signature()
  518. :return: the signature value
  519. :rtype: string or bytes
  520. """
  521. try:
  522. return self.cache_sig
  523. except AttributeError:
  524. pass
  525. self.m = Utils.md5(self.hcode)
  526. # explicit deps
  527. self.sig_explicit_deps()
  528. # env vars
  529. self.sig_vars()
  530. # implicit deps / scanner results
  531. if self.scan:
  532. try:
  533. self.sig_implicit_deps()
  534. except Errors.TaskRescan:
  535. return self.signature()
  536. ret = self.cache_sig = self.m.digest()
  537. return ret
  538. def runnable_status(self):
  539. """
  540. Returns the Task status
  541. :return: a task state in :py:const:`waflib.Task.RUN_ME`,
  542. :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
  543. :rtype: int
  544. """
  545. bld = self.generator.bld
  546. if bld.is_install < 0:
  547. return SKIP_ME
  548. for t in self.run_after:
  549. if not t.hasrun:
  550. return ASK_LATER
  551. elif t.hasrun < SKIPPED:
  552. # a dependency has an error
  553. return CANCEL_ME
  554. # first compute the signature
  555. try:
  556. new_sig = self.signature()
  557. except Errors.TaskNotReady:
  558. return ASK_LATER
  559. # compare the signature to a signature computed previously
  560. key = self.uid()
  561. try:
  562. prev_sig = bld.task_sigs[key]
  563. except KeyError:
  564. Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
  565. return RUN_ME
  566. if new_sig != prev_sig:
  567. Logs.debug('task: task %r must run: the task signature changed', self)
  568. return RUN_ME
  569. # compare the signatures of the outputs
  570. for node in self.outputs:
  571. sig = bld.node_sigs.get(node)
  572. if not sig:
  573. Logs.debug('task: task %r must run: an output node has no signature', self)
  574. return RUN_ME
  575. if sig != key:
  576. Logs.debug('task: task %r must run: an output node was produced by another task', self)
  577. return RUN_ME
  578. if not node.exists():
  579. Logs.debug('task: task %r must run: an output node does not exist', self)
  580. return RUN_ME
  581. return (self.always_run and RUN_ME) or SKIP_ME
  582. def post_run(self):
  583. """
  584. Called after successful execution to record that the task has run by
  585. updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
  586. """
  587. bld = self.generator.bld
  588. for node in self.outputs:
  589. if not node.exists():
  590. self.hasrun = MISSING
  591. self.err_msg = '-> missing file: %r' % node.abspath()
  592. raise Errors.WafError(self.err_msg)
  593. bld.node_sigs[node] = self.uid() # make sure this task produced the files in question
  594. bld.task_sigs[self.uid()] = self.signature()
  595. if not self.keep_last_cmd:
  596. try:
  597. del self.last_cmd
  598. except AttributeError:
  599. pass
  600. def sig_explicit_deps(self):
  601. """
  602. Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
  603. and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
  604. """
  605. bld = self.generator.bld
  606. upd = self.m.update
  607. # the inputs
  608. for x in self.inputs + self.dep_nodes:
  609. upd(x.get_bld_sig())
  610. # manual dependencies, they can slow down the builds
  611. if bld.deps_man:
  612. additional_deps = bld.deps_man
  613. for x in self.inputs + self.outputs:
  614. try:
  615. d = additional_deps[x]
  616. except KeyError:
  617. continue
  618. for v in d:
  619. try:
  620. v = v.get_bld_sig()
  621. except AttributeError:
  622. if hasattr(v, '__call__'):
  623. v = v() # dependency is a function, call it
  624. upd(v)
  625. def sig_deep_inputs(self):
  626. """
  627. Enable rebuilds on input files task signatures. Not used by default.
  628. Example: hashes of output programs can be unchanged after being re-linked,
  629. despite the libraries being different. This method can thus prevent stale unit test
  630. results (waf_unit_test.py).
  631. Hashing input file timestamps is another possibility for the implementation.
  632. This may cause unnecessary rebuilds when input tasks are frequently executed.
  633. Here is an implementation example::
  634. lst = []
  635. for node in self.inputs + self.dep_nodes:
  636. st = os.stat(node.abspath())
  637. lst.append(st.st_mtime)
  638. lst.append(st.st_size)
  639. self.m.update(Utils.h_list(lst))
  640. The downside of the implementation is that it absolutely requires all build directory
  641. files to be declared within the current build.
  642. """
  643. bld = self.generator.bld
  644. lst = [bld.task_sigs[bld.node_sigs[node]] for node in (self.inputs + self.dep_nodes) if node.is_bld()]
  645. self.m.update(Utils.h_list(lst))
  646. def sig_vars(self):
  647. """
  648. Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
  649. When overriding this method, and if scriptlet expressions are used, make sure to follow
  650. the code in :py:meth:`waflib.Task.Task.compile_sig_vars` to enable dependencies on scriptlet results.
  651. """
  652. sig = self.generator.bld.hash_env_vars(self.env, self.vars)
  653. self.m.update(sig)
  654. scan = None
  655. """
  656. This method, when provided, returns a tuple containing:
  657. * a list of nodes corresponding to real files
  658. * a list of names for files not found in path_lst
  659. For example::
  660. from waflib.Task import Task
  661. class mytask(Task):
  662. def scan(self, node):
  663. return ([], [])
  664. The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
  665. :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
  666. """
  667. def sig_implicit_deps(self):
  668. """
  669. Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
  670. obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
  671. The exception :py:class:`waflib.Errors.TaskRescan` is thrown
  672. when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
  673. once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
  674. """
  675. bld = self.generator.bld
  676. # get the task signatures from previous runs
  677. key = self.uid()
  678. prev = bld.imp_sigs.get(key, [])
  679. # for issue #379
  680. if prev:
  681. try:
  682. if prev == self.compute_sig_implicit_deps():
  683. return prev
  684. except Errors.TaskNotReady:
  685. raise
  686. except EnvironmentError:
  687. # when a file was renamed, remove the stale nodes (headers in folders without source files)
  688. # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
  689. # the behaviour will differ when top != out
  690. for x in bld.node_deps.get(self.uid(), []):
  691. if not x.is_bld() and not x.exists():
  692. try:
  693. del x.parent.children[x.name]
  694. except KeyError:
  695. pass
  696. del bld.imp_sigs[key]
  697. raise Errors.TaskRescan('rescan')
  698. # no previous run or the signature of the dependencies has changed, rescan the dependencies
  699. (bld.node_deps[key], bld.raw_deps[key]) = self.scan()
  700. if Logs.verbose:
  701. Logs.debug('deps: scanner for %s: %r; unresolved: %r', self, bld.node_deps[key], bld.raw_deps[key])
  702. # recompute the signature and return it
  703. try:
  704. bld.imp_sigs[key] = self.compute_sig_implicit_deps()
  705. except EnvironmentError:
  706. for k in bld.node_deps.get(self.uid(), []):
  707. if not k.exists():
  708. Logs.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k, self)
  709. raise
  710. def compute_sig_implicit_deps(self):
  711. """
  712. Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
  713. :py:class:`waflib.Node.Node` returned by the scanner.
  714. :return: a hash value for the implicit dependencies
  715. :rtype: string or bytes
  716. """
  717. upd = self.m.update
  718. self.are_implicit_nodes_ready()
  719. # scanner returns a node that does not have a signature
  720. # just *ignore* the error and let them figure out from the compiler output
  721. # waf -k behaviour
  722. for k in self.generator.bld.node_deps.get(self.uid(), []):
  723. upd(k.get_bld_sig())
  724. return self.m.digest()
  725. def are_implicit_nodes_ready(self):
  726. """
  727. For each node returned by the scanner, see if there is a task that creates it,
  728. and infer the build order
  729. This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
  730. """
  731. bld = self.generator.bld
  732. try:
  733. cache = bld.dct_implicit_nodes
  734. except AttributeError:
  735. bld.dct_implicit_nodes = cache = {}
  736. # one cache per build group
  737. try:
  738. dct = cache[bld.current_group]
  739. except KeyError:
  740. dct = cache[bld.current_group] = {}
  741. for tsk in bld.cur_tasks:
  742. for x in tsk.outputs:
  743. dct[x] = tsk
  744. modified = False
  745. for x in bld.node_deps.get(self.uid(), []):
  746. if x in dct:
  747. self.run_after.add(dct[x])
  748. modified = True
  749. if modified:
  750. for tsk in self.run_after:
  751. if not tsk.hasrun:
  752. #print "task is not ready..."
  753. raise Errors.TaskNotReady('not ready')
  754. if sys.hexversion > 0x3000000:
  755. def uid(self):
  756. try:
  757. return self.uid_
  758. except AttributeError:
  759. m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
  760. up = m.update
  761. for x in self.inputs + self.outputs:
  762. up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
  763. self.uid_ = m.digest()
  764. return self.uid_
  765. uid.__doc__ = Task.uid.__doc__
  766. Task.uid = uid
  767. def is_before(t1, t2):
  768. """
  769. Returns a non-zero value if task t1 is to be executed before task t2::
  770. t1.ext_out = '.h'
  771. t2.ext_in = '.h'
  772. t2.after = ['t1']
  773. t1.before = ['t2']
  774. waflib.Task.is_before(t1, t2) # True
  775. :param t1: Task object
  776. :type t1: :py:class:`waflib.Task.Task`
  777. :param t2: Task object
  778. :type t2: :py:class:`waflib.Task.Task`
  779. """
  780. to_list = Utils.to_list
  781. for k in to_list(t2.ext_in):
  782. if k in to_list(t1.ext_out):
  783. return 1
  784. if t1.__class__.__name__ in to_list(t2.after):
  785. return 1
  786. if t2.__class__.__name__ in to_list(t1.before):
  787. return 1
  788. return 0
  789. def set_file_constraints(tasks):
  790. """
  791. Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
  792. :param tasks: tasks
  793. :type tasks: list of :py:class:`waflib.Task.Task`
  794. """
  795. ins = Utils.defaultdict(set)
  796. outs = Utils.defaultdict(set)
  797. for x in tasks:
  798. for a in x.inputs:
  799. ins[a].add(x)
  800. for a in x.dep_nodes:
  801. ins[a].add(x)
  802. for a in x.outputs:
  803. outs[a].add(x)
  804. links = set(ins.keys()).intersection(outs.keys())
  805. for k in links:
  806. for a in ins[k]:
  807. a.run_after.update(outs[k])
  808. class TaskGroup(object):
  809. """
  810. Wrap nxm task order constraints into a single object
  811. to prevent the creation of large list/set objects
  812. This is an optimization
  813. """
  814. def __init__(self, prev, next):
  815. self.prev = prev
  816. self.next = next
  817. self.done = False
  818. def get_hasrun(self):
  819. for k in self.prev:
  820. if not k.hasrun:
  821. return NOT_RUN
  822. return SUCCESS
  823. hasrun = property(get_hasrun, None)
  824. def set_precedence_constraints(tasks):
  825. """
  826. Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
  827. :param tasks: tasks
  828. :type tasks: list of :py:class:`waflib.Task.Task`
  829. """
  830. cstr_groups = Utils.defaultdict(list)
  831. for x in tasks:
  832. h = x.hash_constraints()
  833. cstr_groups[h].append(x)
  834. keys = list(cstr_groups.keys())
  835. maxi = len(keys)
  836. # this list should be short
  837. for i in range(maxi):
  838. t1 = cstr_groups[keys[i]][0]
  839. for j in range(i + 1, maxi):
  840. t2 = cstr_groups[keys[j]][0]
  841. # add the constraints based on the comparisons
  842. if is_before(t1, t2):
  843. a = i
  844. b = j
  845. elif is_before(t2, t1):
  846. a = j
  847. b = i
  848. else:
  849. continue
  850. a = cstr_groups[keys[a]]
  851. b = cstr_groups[keys[b]]
  852. if len(a) < 2 or len(b) < 2:
  853. for x in b:
  854. x.run_after.update(a)
  855. else:
  856. group = TaskGroup(set(a), set(b))
  857. for x in b:
  858. x.run_after.add(group)
  859. def funex(c):
  860. """
  861. Compiles a scriptlet expression into a Python function
  862. :param c: function to compile
  863. :type c: string
  864. :return: the function 'f' declared in the input string
  865. :rtype: function
  866. """
  867. dc = {}
  868. exec(c, dc)
  869. return dc['f']
  870. re_cond = re.compile('(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
  871. re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
  872. reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
  873. def compile_fun_shell(line):
  874. """
  875. Creates a compiled function to execute a process through a sub-shell
  876. """
  877. extr = []
  878. def repl(match):
  879. g = match.group
  880. if g('dollar'):
  881. return "$"
  882. elif g('backslash'):
  883. return '\\\\'
  884. elif g('subst'):
  885. extr.append((g('var'), g('code')))
  886. return "%s"
  887. return None
  888. line = reg_act.sub(repl, line) or line
  889. dvars = []
  890. def add_dvar(x):
  891. if x not in dvars:
  892. dvars.append(x)
  893. def replc(m):
  894. # performs substitutions and populates dvars
  895. if m.group('and'):
  896. return ' and '
  897. elif m.group('or'):
  898. return ' or '
  899. else:
  900. x = m.group('var')
  901. add_dvar(x)
  902. return 'env[%r]' % x
  903. parm = []
  904. app = parm.append
  905. for (var, meth) in extr:
  906. if var == 'SRC':
  907. if meth:
  908. app('tsk.inputs%s' % meth)
  909. else:
  910. app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
  911. elif var == 'TGT':
  912. if meth:
  913. app('tsk.outputs%s' % meth)
  914. else:
  915. app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
  916. elif meth:
  917. if meth.startswith(':'):
  918. add_dvar(var)
  919. m = meth[1:]
  920. if m == 'SRC':
  921. m = '[a.path_from(cwdx) for a in tsk.inputs]'
  922. elif m == 'TGT':
  923. m = '[a.path_from(cwdx) for a in tsk.outputs]'
  924. elif re_novar.match(m):
  925. m = '[tsk.inputs%s]' % m[3:]
  926. elif re_novar.match(m):
  927. m = '[tsk.outputs%s]' % m[3:]
  928. else:
  929. add_dvar(m)
  930. if m[:3] not in ('tsk', 'gen', 'bld'):
  931. m = '%r' % m
  932. app('" ".join(tsk.colon(%r, %s))' % (var, m))
  933. elif meth.startswith('?'):
  934. # In A?B|C output env.A if one of env.B or env.C is non-empty
  935. expr = re_cond.sub(replc, meth[1:])
  936. app('p(%r) if (%s) else ""' % (var, expr))
  937. else:
  938. call = '%s%s' % (var, meth)
  939. add_dvar(call)
  940. app(call)
  941. else:
  942. add_dvar(var)
  943. app("p('%s')" % var)
  944. if parm:
  945. parm = "%% (%s) " % (',\n\t\t'.join(parm))
  946. else:
  947. parm = ''
  948. c = COMPILE_TEMPLATE_SHELL % (line, parm)
  949. Logs.debug('action: %s', c.strip().splitlines())
  950. return (funex(c), dvars)
  951. reg_act_noshell = re.compile(r"(?P<space>\s+)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})|(?P<text>([^$ \t\n\r\f\v]|\$\$)+)", re.M)
  952. def compile_fun_noshell(line):
  953. """
  954. Creates a compiled function to execute a process without a sub-shell
  955. """
  956. buf = []
  957. dvars = []
  958. merge = False
  959. app = buf.append
  960. def add_dvar(x):
  961. if x not in dvars:
  962. dvars.append(x)
  963. def replc(m):
  964. # performs substitutions and populates dvars
  965. if m.group('and'):
  966. return ' and '
  967. elif m.group('or'):
  968. return ' or '
  969. else:
  970. x = m.group('var')
  971. add_dvar(x)
  972. return 'env[%r]' % x
  973. for m in reg_act_noshell.finditer(line):
  974. if m.group('space'):
  975. merge = False
  976. continue
  977. elif m.group('text'):
  978. app('[%r]' % m.group('text').replace('$$', '$'))
  979. elif m.group('subst'):
  980. var = m.group('var')
  981. code = m.group('code')
  982. if var == 'SRC':
  983. if code:
  984. app('[tsk.inputs%s]' % code)
  985. else:
  986. app('[a.path_from(cwdx) for a in tsk.inputs]')
  987. elif var == 'TGT':
  988. if code:
  989. app('[tsk.outputs%s]' % code)
  990. else:
  991. app('[a.path_from(cwdx) for a in tsk.outputs]')
  992. elif code:
  993. if code.startswith(':'):
  994. # a composed variable ${FOO:OUT}
  995. add_dvar(var)
  996. m = code[1:]
  997. if m == 'SRC':
  998. m = '[a.path_from(cwdx) for a in tsk.inputs]'
  999. elif m == 'TGT':
  1000. m = '[a.path_from(cwdx) for a in tsk.outputs]'
  1001. elif re_novar.match(m):
  1002. m = '[tsk.inputs%s]' % m[3:]
  1003. elif re_novar.match(m):
  1004. m = '[tsk.outputs%s]' % m[3:]
  1005. else:
  1006. add_dvar(m)
  1007. if m[:3] not in ('tsk', 'gen', 'bld'):
  1008. m = '%r' % m
  1009. app('tsk.colon(%r, %s)' % (var, m))
  1010. elif code.startswith('?'):
  1011. # In A?B|C output env.A if one of env.B or env.C is non-empty
  1012. expr = re_cond.sub(replc, code[1:])
  1013. app('to_list(env[%r] if (%s) else [])' % (var, expr))
  1014. else:
  1015. # plain code such as ${tsk.inputs[0].abspath()}
  1016. call = '%s%s' % (var, code)
  1017. add_dvar(call)
  1018. app('gen.to_list(%s)' % call)
  1019. else:
  1020. # a plain variable such as # a plain variable like ${AR}
  1021. app('to_list(env[%r])' % var)
  1022. add_dvar(var)
  1023. if merge:
  1024. tmp = 'merge(%s, %s)' % (buf[-2], buf[-1])
  1025. del buf[-1]
  1026. buf[-1] = tmp
  1027. merge = True # next turn
  1028. buf = ['lst.extend(%s)' % x for x in buf]
  1029. fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
  1030. Logs.debug('action: %s', fun.strip().splitlines())
  1031. return (funex(fun), dvars)
  1032. def compile_fun(line, shell=False):
  1033. """
  1034. Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
  1035. * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
  1036. * The list of variables that must cause rebuilds when *env* data is modified
  1037. for example::
  1038. from waflib.Task import compile_fun
  1039. compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
  1040. def build(bld):
  1041. bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
  1042. The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
  1043. The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
  1044. """
  1045. if isinstance(line, str):
  1046. if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
  1047. shell = True
  1048. else:
  1049. dvars_lst = []
  1050. funs_lst = []
  1051. for x in line:
  1052. if isinstance(x, str):
  1053. fun, dvars = compile_fun(x, shell)
  1054. dvars_lst += dvars
  1055. funs_lst.append(fun)
  1056. else:
  1057. # assume a function to let through
  1058. funs_lst.append(x)
  1059. def composed_fun(task):
  1060. for x in funs_lst:
  1061. ret = x(task)
  1062. if ret:
  1063. return ret
  1064. return None
  1065. return composed_fun, dvars_lst
  1066. if shell:
  1067. return compile_fun_shell(line)
  1068. else:
  1069. return compile_fun_noshell(line)
  1070. def compile_sig_vars(vars):
  1071. """
  1072. This method produces a sig_vars method suitable for subclasses that provide
  1073. scriptlet code in their run_str code.
  1074. If no such method can be created, this method returns None.
  1075. The purpose of the sig_vars method returned is to ensures
  1076. that rebuilds occur whenever the contents of the expression changes.
  1077. This is the case B below::
  1078. import time
  1079. # case A: regular variables
  1080. tg = bld(rule='echo ${FOO}')
  1081. tg.env.FOO = '%s' % time.time()
  1082. # case B
  1083. bld(rule='echo ${gen.foo}', foo='%s' % time.time())
  1084. :param vars: env variables such as CXXFLAGS or gen.foo
  1085. :type vars: list of string
  1086. :return: A sig_vars method relevant for dependencies if adequate, else None
  1087. :rtype: A function, or None in most cases
  1088. """
  1089. buf = []
  1090. for x in sorted(vars):
  1091. if x[:3] in ('tsk', 'gen', 'bld'):
  1092. buf.append('buf.append(%s)' % x)
  1093. if buf:
  1094. return funex(COMPILE_TEMPLATE_SIG_VARS % '\n\t'.join(buf))
  1095. return None
  1096. def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
  1097. """
  1098. Returns a new task subclass with the function ``run`` compiled from the line given.
  1099. :param func: method run
  1100. :type func: string or function
  1101. :param vars: list of variables to hash
  1102. :type vars: list of string
  1103. :param color: color to use
  1104. :type color: string
  1105. :param shell: when *func* is a string, enable/disable the use of the shell
  1106. :type shell: bool
  1107. :param scan: method scan
  1108. :type scan: function
  1109. :rtype: :py:class:`waflib.Task.Task`
  1110. """
  1111. params = {
  1112. 'vars': vars or [], # function arguments are static, and this one may be modified by the class
  1113. 'color': color,
  1114. 'name': name,
  1115. 'shell': shell,
  1116. 'scan': scan,
  1117. }
  1118. if isinstance(func, str) or isinstance(func, tuple):
  1119. params['run_str'] = func
  1120. else:
  1121. params['run'] = func
  1122. cls = type(Task)(name, (Task,), params)
  1123. classes[name] = cls
  1124. if ext_in:
  1125. cls.ext_in = Utils.to_list(ext_in)
  1126. if ext_out:
  1127. cls.ext_out = Utils.to_list(ext_out)
  1128. if before:
  1129. cls.before = Utils.to_list(before)
  1130. if after:
  1131. cls.after = Utils.to_list(after)
  1132. return cls
  1133. def deep_inputs(cls):
  1134. """
  1135. Task class decorator to enable rebuilds on input files task signatures
  1136. """
  1137. def sig_explicit_deps(self):
  1138. Task.sig_explicit_deps(self)
  1139. Task.sig_deep_inputs(self)
  1140. cls.sig_explicit_deps = sig_explicit_deps
  1141. return cls
  1142. TaskBase = Task
  1143. "Provided for compatibility reasons, TaskBase should not be used"
  1144. class TaskSemaphore(object):
  1145. """
  1146. Task semaphores provide a simple and efficient way of throttling the amount of
  1147. a particular task to run concurrently. The throttling value is capped
  1148. by the amount of maximum jobs, so for example, a `TaskSemaphore(10)`
  1149. has no effect in a `-j2` build.
  1150. Task semaphores are typically specified on the task class level::
  1151. class compile(waflib.Task.Task):
  1152. semaphore = waflib.Task.TaskSemaphore(2)
  1153. run_str = 'touch ${TGT}'
  1154. Task semaphores are meant to be used by the build scheduler in the main
  1155. thread, so there are no guarantees of thread safety.
  1156. """
  1157. def __init__(self, num):
  1158. """
  1159. :param num: maximum value of concurrent tasks
  1160. :type num: int
  1161. """
  1162. self.num = num
  1163. self.locking = set()
  1164. self.waiting = set()
  1165. def is_locked(self):
  1166. """Returns True if this semaphore cannot be acquired by more tasks"""
  1167. return len(self.locking) >= self.num
  1168. def acquire(self, tsk):
  1169. """
  1170. Mark the semaphore as used by the given task (not re-entrant).
  1171. :param tsk: task object
  1172. :type tsk: :py:class:`waflib.Task.Task`
  1173. :raises: :py:class:`IndexError` in case the resource is already acquired
  1174. """
  1175. if self.is_locked():
  1176. raise IndexError('Cannot lock more %r' % self.locking)
  1177. self.locking.add(tsk)
  1178. def release(self, tsk):
  1179. """
  1180. Mark the semaphore as unused by the given task.
  1181. :param tsk: task object
  1182. :type tsk: :py:class:`waflib.Task.Task`
  1183. :raises: :py:class:`KeyError` in case the resource is not acquired by the task
  1184. """
  1185. self.locking.remove(tsk)