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.

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