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.

1407 lines
39KB

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