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.

1197 lines
33KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Tasks represent atomic operations such as processes.
  6. """
  7. import os, re, sys
  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 occured in the task execution"""
  18. SKIPPED = 8
  19. """The task did not have to be executed"""
  20. SUCCESS = 9
  21. """The task was successfully executed"""
  22. ASK_LATER = -1
  23. """The task is not ready to be executed"""
  24. SKIP_ME = -2
  25. """The task does not need to be executed"""
  26. RUN_ME = -3
  27. """The task must be executed"""
  28. COMPILE_TEMPLATE_SHELL = '''
  29. def f(tsk):
  30. env = tsk.env
  31. gen = tsk.generator
  32. bld = gen.bld
  33. wd = getattr(tsk, 'cwd', None)
  34. p = env.get_flat
  35. tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
  36. return tsk.exec_command(cmd, cwd=wd, env=env.env or None)
  37. '''
  38. COMPILE_TEMPLATE_NOSHELL = '''
  39. def f(tsk):
  40. env = tsk.env
  41. gen = tsk.generator
  42. bld = gen.bld
  43. wd = getattr(tsk, 'cwd', None)
  44. def to_list(xx):
  45. if isinstance(xx, str): return [xx]
  46. return xx
  47. tsk.last_cmd = lst = []
  48. %s
  49. lst = [x for x in lst if x]
  50. return tsk.exec_command(lst, cwd=wd, env=env.env or None)
  51. '''
  52. classes = {}
  53. "Class tasks created by user scripts or Waf tools (maps names to class objects). Task classes defined in Waf tools are registered here through the metaclass :py:class:`waflib.Task.store_task_type`."
  54. class store_task_type(type):
  55. """
  56. Metaclass: store the task classes into :py:const:`waflib.Task.classes`, or to the dict pointed
  57. by the class attribute 'register'.
  58. The attribute 'run_str' will be processed to compute a method 'run' on the task class
  59. The decorator :py:func:`waflib.Task.cache_outputs` is also applied to the class
  60. """
  61. def __init__(cls, name, bases, dict):
  62. super(store_task_type, cls).__init__(name, bases, dict)
  63. name = cls.__name__
  64. if name.endswith('_task'):
  65. name = name.replace('_task', '')
  66. if name != 'evil' and name != 'TaskBase':
  67. global classes
  68. if getattr(cls, 'run_str', None):
  69. # if a string is provided, convert it to a method
  70. (f, dvars) = compile_fun(cls.run_str, cls.shell)
  71. cls.hcode = cls.run_str
  72. cls.orig_run_str = cls.run_str
  73. # change the name of run_str or it is impossible to subclass with a function
  74. cls.run_str = None
  75. cls.run = f
  76. cls.vars = list(set(cls.vars + dvars))
  77. cls.vars.sort()
  78. elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
  79. # getattr(cls, 'hcode') would look in the upper classes
  80. cls.hcode = Utils.h_fun(cls.run)
  81. if sys.hexversion > 0x3000000:
  82. cls.hcode = cls.hcode.encode('iso8859-1', 'xmlcharrefreplace')
  83. # be creative
  84. getattr(cls, 'register', classes)[name] = cls
  85. evil = store_task_type('evil', (object,), {})
  86. "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
  87. class TaskBase(evil):
  88. """
  89. Base class for all Waf tasks, which should be seen as an interface.
  90. For illustration purposes, instances of this class will execute the attribute
  91. 'fun' in :py:meth:`waflib.Task.TaskBase.run`. When in doubt, create
  92. subclasses of :py:class:`waflib.Task.Task` instead.
  93. Subclasses should override these methods:
  94. #. __str__: string to display to the user
  95. #. runnable_status: ask the task if it should be run, skipped, or if we have to ask later
  96. #. run: let threads execute the task
  97. #. post_run: let threads update the data regarding the task (cache)
  98. .. warning:: For backward compatibility reasons, the suffix "_task" is truncated in derived class names. This limitation will be removed in Waf 1.9.
  99. """
  100. color = 'GREEN'
  101. """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
  102. ext_in = []
  103. """File extensions that objects of this task class might use"""
  104. ext_out = []
  105. """File extensions that objects of this task class might create"""
  106. before = []
  107. """List of task class names to execute before instances of this class"""
  108. after = []
  109. """List of task class names to execute after instances of this class"""
  110. hcode = ''
  111. """String representing an additional hash for the class representation"""
  112. def __init__(self, *k, **kw):
  113. """
  114. The base task class requires a task generator, which will be itself if missing
  115. """
  116. self.hasrun = NOT_RUN
  117. try:
  118. self.generator = kw['generator']
  119. except KeyError:
  120. self.generator = self
  121. def __repr__(self):
  122. "for debugging purposes"
  123. return '\n\t{task %r: %s %s}' % (self.__class__.__name__, id(self), str(getattr(self, 'fun', '')))
  124. def __str__(self):
  125. "string to display to the user"
  126. if hasattr(self, 'fun'):
  127. return self.fun.__name__
  128. return self.__class__.__name__
  129. def __hash__(self):
  130. "Very fast hashing scheme but not persistent (replace/implement in subclasses and see :py:meth:`waflib.Task.Task.uid`)"
  131. return id(self)
  132. def keyword(self):
  133. if hasattr(self, 'fun'):
  134. return 'Function'
  135. return 'Processing'
  136. def exec_command(self, cmd, **kw):
  137. """
  138. Wrapper for :py:meth:`waflib.Context.Context.exec_command` which sets a current working directory to ``build.variant_dir``
  139. :return: the return code
  140. :rtype: int
  141. """
  142. bld = self.generator.bld
  143. try:
  144. if not kw.get('cwd', None):
  145. kw['cwd'] = bld.cwd
  146. except AttributeError:
  147. bld.cwd = kw['cwd'] = bld.variant_dir
  148. return bld.exec_command(cmd, **kw)
  149. def runnable_status(self):
  150. """
  151. State of the task
  152. :return: a task state in :py:const:`waflib.Task.RUN_ME`, :py:const:`waflib.Task.SKIP_ME` or :py:const:`waflib.Task.ASK_LATER`.
  153. :rtype: int
  154. """
  155. return RUN_ME
  156. def process(self):
  157. """
  158. Assume that the task has had a new attribute ``master`` which is an instance of :py:class:`waflib.Runner.Parallel`.
  159. Execute the task and then put it back in the queue :py:attr:`waflib.Runner.Parallel.out` (may be replaced by subclassing).
  160. """
  161. m = self.master
  162. if m.stop:
  163. m.out.put(self)
  164. return
  165. # remove the task signature immediately before it is executed
  166. # in case of failure the task will be executed again
  167. try:
  168. del self.generator.bld.task_sigs[self.uid()]
  169. except KeyError:
  170. pass
  171. try:
  172. self.generator.bld.returned_tasks.append(self)
  173. self.log_display(self.generator.bld)
  174. ret = self.run()
  175. except Exception:
  176. self.err_msg = Utils.ex_stack()
  177. self.hasrun = EXCEPTION
  178. # TODO cleanup
  179. m.error_handler(self)
  180. m.out.put(self)
  181. return
  182. if ret:
  183. self.err_code = ret
  184. self.hasrun = CRASHED
  185. else:
  186. try:
  187. self.post_run()
  188. except Errors.WafError:
  189. pass
  190. except Exception:
  191. self.err_msg = Utils.ex_stack()
  192. self.hasrun = EXCEPTION
  193. else:
  194. self.hasrun = SUCCESS
  195. if self.hasrun != SUCCESS:
  196. m.error_handler(self)
  197. m.out.put(self)
  198. def run(self):
  199. """
  200. Called by threads to execute the tasks. The default is empty and meant to be overridden in subclasses.
  201. It is a bad idea to create nodes in this method (so, no node.ant_glob)
  202. :rtype: int
  203. """
  204. if hasattr(self, 'fun'):
  205. return self.fun(self)
  206. return 0
  207. def post_run(self):
  208. "Update the cache files (executed by threads). Override in subclasses."
  209. pass
  210. def log_display(self, bld):
  211. "Write the execution status on the context logger"
  212. if self.generator.bld.progress_bar == 3:
  213. return
  214. s = self.display()
  215. if s:
  216. if bld.logger:
  217. logger = bld.logger
  218. else:
  219. logger = Logs
  220. if self.generator.bld.progress_bar == 1:
  221. c1 = Logs.colors.cursor_off
  222. c2 = Logs.colors.cursor_on
  223. logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
  224. else:
  225. logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
  226. def display(self):
  227. """
  228. Return an execution status for the console, the progress bar, or the IDE output.
  229. :rtype: string
  230. """
  231. col1 = Logs.colors(self.color)
  232. col2 = Logs.colors.NORMAL
  233. master = self.master
  234. def cur():
  235. # the current task position, computed as late as possible
  236. tmp = -1
  237. if hasattr(master, 'ready'):
  238. tmp -= master.ready.qsize()
  239. return master.processed + tmp
  240. if self.generator.bld.progress_bar == 1:
  241. return self.generator.bld.progress_line(cur(), master.total, col1, col2)
  242. if self.generator.bld.progress_bar == 2:
  243. ela = str(self.generator.bld.timer)
  244. try:
  245. ins = ','.join([n.name for n in self.inputs])
  246. except AttributeError:
  247. ins = ''
  248. try:
  249. outs = ','.join([n.name for n in self.outputs])
  250. except AttributeError:
  251. outs = ''
  252. return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
  253. s = str(self)
  254. if not s:
  255. return None
  256. total = master.total
  257. n = len(str(total))
  258. fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
  259. kw = self.keyword()
  260. if kw:
  261. kw += ' '
  262. return fs % (cur(), total, kw, col1, s, col2)
  263. def attr(self, att, default=None):
  264. """
  265. Retrieve an attribute from the instance or from the class.
  266. :param att: variable name
  267. :type att: string
  268. :param default: default value
  269. """
  270. ret = getattr(self, att, self)
  271. if ret is self: return getattr(self.__class__, att, default)
  272. return ret
  273. def hash_constraints(self):
  274. """
  275. Identify a task type for all the constraints relevant for the scheduler: precedence, file production
  276. :return: a hash value
  277. :rtype: string
  278. """
  279. cls = self.__class__
  280. tup = (str(cls.before), str(cls.after), str(cls.ext_in), str(cls.ext_out), cls.__name__, cls.hcode)
  281. h = hash(tup)
  282. return h
  283. def format_error(self):
  284. """
  285. Error message to display to the user when a build fails
  286. :rtype: string
  287. """
  288. msg = getattr(self, 'last_cmd', '')
  289. name = getattr(self.generator, 'name', '')
  290. if getattr(self, "err_msg", None):
  291. return self.err_msg
  292. elif not self.hasrun:
  293. return 'task in %r was not executed for some reason: %r' % (name, self)
  294. elif self.hasrun == CRASHED:
  295. try:
  296. return ' -> task in %r failed (exit status %r): %r\n%r' % (name, self.err_code, self, msg)
  297. except AttributeError:
  298. return ' -> task in %r failed: %r\n%r' % (name, self, msg)
  299. elif self.hasrun == MISSING:
  300. return ' -> missing files in %r: %r\n%r' % (name, self, msg)
  301. else:
  302. return 'invalid status for task in %r: %r' % (name, self.hasrun)
  303. def colon(self, var1, var2):
  304. """
  305. Support code for scriptlet expressions such as ${FOO_ST:FOO}
  306. If the first variable (FOO_ST) is empty, then an empty list is returned
  307. The results will be slightly different if FOO_ST is a list, for example::
  308. env.FOO_ST = ['-a', '-b']
  309. env.FOO_ST = '-I%s'
  310. # ${FOO_ST:FOO} returns
  311. ['-Ip1', '-Ip2']
  312. env.FOO = ['p1', 'p2']
  313. # ${FOO_ST:FOO} returns
  314. ['-a', '-b', 'p1', '-a', '-b', 'p2']
  315. """
  316. tmp = self.env[var1]
  317. if not tmp:
  318. return []
  319. if isinstance(var2, str):
  320. it = self.env[var2]
  321. else:
  322. it = var2
  323. if isinstance(tmp, str):
  324. return [tmp % x for x in it]
  325. else:
  326. lst = []
  327. for y in it:
  328. lst.extend(tmp)
  329. lst.append(y)
  330. return lst
  331. class Task(TaskBase):
  332. """
  333. This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
  334. uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
  335. the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
  336. nodes (if present).
  337. .. warning:: For backward compatibility reasons, the suffix "_task" is truncated in derived class names. This limitation will be removed in Waf 1.9.
  338. """
  339. vars = []
  340. """Variables to depend on (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
  341. shell = False
  342. """Execute the command with the shell (class attribute)"""
  343. def __init__(self, *k, **kw):
  344. TaskBase.__init__(self, *k, **kw)
  345. self.env = kw['env']
  346. """ConfigSet object (make sure to provide one)"""
  347. self.inputs = []
  348. """List of input nodes, which represent the files used by the task instance"""
  349. self.outputs = []
  350. """List of output nodes, which represent the files created by the task instance"""
  351. self.dep_nodes = []
  352. """List of additional nodes to depend on"""
  353. self.run_after = set([])
  354. """Set of tasks that must be executed before this one"""
  355. # Additionally, you may define the following
  356. #self.dep_vars = 'PREFIX DATADIR'
  357. def __str__(self):
  358. "string to display to the user"
  359. name = self.__class__.__name__
  360. if self.outputs:
  361. if (name.endswith('lib') or name.endswith('program')) or not self.inputs:
  362. node = self.outputs[0]
  363. return node.path_from(node.ctx.launch_node())
  364. if not (self.inputs or self.outputs):
  365. return self.__class__.__name__
  366. if len(self.inputs) == 1:
  367. node = self.inputs[0]
  368. return node.path_from(node.ctx.launch_node())
  369. src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
  370. tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
  371. if self.outputs: sep = ' -> '
  372. else: sep = ''
  373. return '%s: %s%s%s' % (self.__class__.__name__.replace('_task', ''), src_str, sep, tgt_str)
  374. def keyword(self):
  375. name = self.__class__.__name__
  376. if name.endswith('lib') or name.endswith('program'):
  377. return 'Linking'
  378. if len(self.inputs) == 1 and len(self.outputs) == 1:
  379. return 'Compiling'
  380. if not self.inputs:
  381. if self.outputs:
  382. return 'Creating'
  383. else:
  384. return 'Running'
  385. return 'Processing'
  386. def __repr__(self):
  387. "for debugging purposes"
  388. try:
  389. ins = ",".join([x.name for x in self.inputs])
  390. outs = ",".join([x.name for x in self.outputs])
  391. except AttributeError:
  392. ins = ",".join([str(x) for x in self.inputs])
  393. outs = ",".join([str(x) for x in self.outputs])
  394. return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
  395. def uid(self):
  396. """
  397. Return an identifier used to determine if tasks are up-to-date. Since the
  398. identifier will be stored between executions, it must be:
  399. - unique: no two tasks return the same value (for a given build context)
  400. - the same for a given task instance
  401. By default, the node paths, the class name, and the function are used
  402. as inputs to compute a hash.
  403. The pointer to the object (python built-in 'id') will change between build executions,
  404. and must be avoided in such hashes.
  405. :return: hash value
  406. :rtype: string
  407. """
  408. try:
  409. return self.uid_
  410. except AttributeError:
  411. m = Utils.md5()
  412. up = m.update
  413. up(self.__class__.__name__)
  414. for x in self.inputs + self.outputs:
  415. up(x.abspath())
  416. self.uid_ = m.digest()
  417. return self.uid_
  418. def set_inputs(self, inp):
  419. """
  420. Append the nodes to the *inputs*
  421. :param inp: input nodes
  422. :type inp: node or list of nodes
  423. """
  424. if isinstance(inp, list): self.inputs += inp
  425. else: self.inputs.append(inp)
  426. def set_outputs(self, out):
  427. """
  428. Append the nodes to the *outputs*
  429. :param out: output nodes
  430. :type out: node or list of nodes
  431. """
  432. if isinstance(out, list): self.outputs += out
  433. else: self.outputs.append(out)
  434. def set_run_after(self, task):
  435. """
  436. Run this task only after *task*. Affect :py:meth:`waflib.Task.runnable_status`
  437. You probably want to use tsk.run_after.add(task) directly
  438. :param task: task
  439. :type task: :py:class:`waflib.Task.Task`
  440. """
  441. assert isinstance(task, TaskBase)
  442. self.run_after.add(task)
  443. def signature(self):
  444. """
  445. Task signatures are stored between build executions, they are use to track the changes
  446. made to the input nodes (not to the outputs!). The signature hashes data from various sources:
  447. * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
  448. * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
  449. * hashed data: variables/values read from task.__class__.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
  450. If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
  451. from waflib import Task
  452. class cls(Task.Task):
  453. def signature(self):
  454. sig = super(Task.Task, self).signature()
  455. delattr(self, 'cache_sig')
  456. return super(Task.Task, self).signature()
  457. """
  458. try: return self.cache_sig
  459. except AttributeError: pass
  460. self.m = Utils.md5()
  461. self.m.update(self.hcode)
  462. # explicit deps
  463. self.sig_explicit_deps()
  464. # env vars
  465. self.sig_vars()
  466. # implicit deps / scanner results
  467. if self.scan:
  468. try:
  469. self.sig_implicit_deps()
  470. except Errors.TaskRescan:
  471. return self.signature()
  472. ret = self.cache_sig = self.m.digest()
  473. return ret
  474. def runnable_status(self):
  475. """
  476. Override :py:meth:`waflib.Task.TaskBase.runnable_status` to determine if the task is ready
  477. to be run (:py:attr:`waflib.Task.Task.run_after`)
  478. """
  479. #return 0 # benchmarking
  480. for t in self.run_after:
  481. if not t.hasrun:
  482. return ASK_LATER
  483. bld = self.generator.bld
  484. # first compute the signature
  485. try:
  486. new_sig = self.signature()
  487. except Errors.TaskNotReady:
  488. return ASK_LATER
  489. # compare the signature to a signature computed previously
  490. key = self.uid()
  491. try:
  492. prev_sig = bld.task_sigs[key]
  493. except KeyError:
  494. Logs.debug("task: task %r must run as it was never run before or the task code changed" % self)
  495. return RUN_ME
  496. # compare the signatures of the outputs
  497. for node in self.outputs:
  498. try:
  499. if node.sig != new_sig:
  500. return RUN_ME
  501. except AttributeError:
  502. Logs.debug("task: task %r must run as the output nodes do not exist" % self)
  503. return RUN_ME
  504. if new_sig != prev_sig:
  505. return RUN_ME
  506. return SKIP_ME
  507. def post_run(self):
  508. """
  509. Called after successful execution to update the cache data :py:class:`waflib.Node.Node` sigs
  510. and :py:attr:`waflib.Build.BuildContext.task_sigs`.
  511. The node signature is obtained from the task signature, but the output nodes may also get the signature
  512. of their contents. See the class decorator :py:func:`waflib.Task.update_outputs` if you need this behaviour.
  513. """
  514. bld = self.generator.bld
  515. sig = self.signature()
  516. for node in self.outputs:
  517. # check if the node exists ..
  518. try:
  519. os.stat(node.abspath())
  520. except OSError:
  521. self.hasrun = MISSING
  522. self.err_msg = '-> missing file: %r' % node.abspath()
  523. raise Errors.WafError(self.err_msg)
  524. # important, store the signature for the next run
  525. node.sig = node.cache_sig = sig
  526. bld.task_sigs[self.uid()] = self.cache_sig
  527. def sig_explicit_deps(self):
  528. """
  529. Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.inputs`
  530. and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
  531. :rtype: hash value
  532. """
  533. bld = self.generator.bld
  534. upd = self.m.update
  535. # the inputs
  536. for x in self.inputs + self.dep_nodes:
  537. try:
  538. upd(x.get_bld_sig())
  539. except (AttributeError, TypeError):
  540. raise Errors.WafError('Missing node signature for %r (required by %r)' % (x, self))
  541. # manual dependencies, they can slow down the builds
  542. if bld.deps_man:
  543. additional_deps = bld.deps_man
  544. for x in self.inputs + self.outputs:
  545. try:
  546. d = additional_deps[id(x)]
  547. except KeyError:
  548. continue
  549. for v in d:
  550. if isinstance(v, bld.root.__class__):
  551. try:
  552. v = v.get_bld_sig()
  553. except AttributeError:
  554. raise Errors.WafError('Missing node signature for %r (required by %r)' % (v, self))
  555. elif hasattr(v, '__call__'):
  556. v = v() # dependency is a function, call it
  557. upd(v)
  558. return self.m.digest()
  559. def sig_vars(self):
  560. """
  561. Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.env` variables/values
  562. :rtype: hash value
  563. """
  564. bld = self.generator.bld
  565. env = self.env
  566. upd = self.m.update
  567. # dependencies on the environment vars
  568. act_sig = bld.hash_env_vars(env, self.__class__.vars)
  569. upd(act_sig)
  570. # additional variable dependencies, if provided
  571. dep_vars = getattr(self, 'dep_vars', None)
  572. if dep_vars:
  573. upd(bld.hash_env_vars(env, dep_vars))
  574. return self.m.digest()
  575. scan = None
  576. """
  577. This method, when provided, returns a tuple containing:
  578. * a list of nodes corresponding to real files
  579. * a list of names for files not found in path_lst
  580. For example::
  581. from waflib.Task import Task
  582. class mytask(Task):
  583. def scan(self, node):
  584. return ((), ())
  585. The first and second lists are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
  586. :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
  587. """
  588. def sig_implicit_deps(self):
  589. """
  590. Used by :py:meth:`waflib.Task.Task.signature` hashes node signatures obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
  591. The exception :py:class:`waflib.Errors.TaskRescan` is thrown
  592. when a file has changed. When this occurs, :py:meth:`waflib.Task.Task.signature` is called
  593. once again, and this method will be executed once again, this time calling :py:meth:`waflib.Task.Task.scan`
  594. for searching the dependencies.
  595. :rtype: hash value
  596. """
  597. bld = self.generator.bld
  598. # get the task signatures from previous runs
  599. key = self.uid()
  600. prev = bld.task_sigs.get((key, 'imp'), [])
  601. # for issue #379
  602. if prev:
  603. try:
  604. if prev == self.compute_sig_implicit_deps():
  605. return prev
  606. except Errors.TaskNotReady:
  607. raise
  608. except EnvironmentError:
  609. # when a file was renamed (IOError usually), remove the stale nodes (headers in folders without source files)
  610. # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
  611. # the behaviour will differ when top != out
  612. for x in bld.node_deps.get(self.uid(), []):
  613. if not x.is_bld():
  614. try:
  615. os.stat(x.abspath())
  616. except OSError:
  617. try:
  618. del x.parent.children[x.name]
  619. except KeyError:
  620. pass
  621. del bld.task_sigs[(key, 'imp')]
  622. raise Errors.TaskRescan('rescan')
  623. # no previous run or the signature of the dependencies has changed, rescan the dependencies
  624. (nodes, names) = self.scan()
  625. if Logs.verbose:
  626. Logs.debug('deps: scanner for %s returned %s %s' % (str(self), str(nodes), str(names)))
  627. # store the dependencies in the cache
  628. bld.node_deps[key] = nodes
  629. bld.raw_deps[key] = names
  630. # might happen
  631. self.are_implicit_nodes_ready()
  632. # recompute the signature and return it
  633. try:
  634. bld.task_sigs[(key, 'imp')] = sig = self.compute_sig_implicit_deps()
  635. except Exception:
  636. if Logs.verbose:
  637. for k in bld.node_deps.get(self.uid(), []):
  638. try:
  639. k.get_bld_sig()
  640. except Exception:
  641. Logs.warn('Missing signature for node %r (may cause rebuilds)' % k)
  642. else:
  643. return sig
  644. def compute_sig_implicit_deps(self):
  645. """
  646. Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
  647. :py:class:`waflib.Node.Node` returned by the scanner.
  648. :return: hash value
  649. :rtype: string
  650. """
  651. upd = self.m.update
  652. bld = self.generator.bld
  653. self.are_implicit_nodes_ready()
  654. # scanner returns a node that does not have a signature
  655. # just *ignore* the error and let them figure out from the compiler output
  656. # waf -k behaviour
  657. for k in bld.node_deps.get(self.uid(), []):
  658. upd(k.get_bld_sig())
  659. return self.m.digest()
  660. def are_implicit_nodes_ready(self):
  661. """
  662. For each node returned by the scanner, see if there is a task behind it, and force the build order
  663. The performance impact on null builds is nearly invisible (1.66s->1.86s), but this is due to
  664. agressive caching (1.86s->28s)
  665. """
  666. bld = self.generator.bld
  667. try:
  668. cache = bld.dct_implicit_nodes
  669. except AttributeError:
  670. bld.dct_implicit_nodes = cache = {}
  671. try:
  672. dct = cache[bld.cur]
  673. except KeyError:
  674. dct = cache[bld.cur] = {}
  675. for tsk in bld.cur_tasks:
  676. for x in tsk.outputs:
  677. dct[x] = tsk
  678. modified = False
  679. for x in bld.node_deps.get(self.uid(), []):
  680. if x in dct:
  681. self.run_after.add(dct[x])
  682. modified = True
  683. if modified:
  684. for tsk in self.run_after:
  685. if not tsk.hasrun:
  686. #print "task is not ready..."
  687. raise Errors.TaskNotReady('not ready')
  688. if sys.hexversion > 0x3000000:
  689. def uid(self):
  690. try:
  691. return self.uid_
  692. except AttributeError:
  693. m = Utils.md5()
  694. up = m.update
  695. up(self.__class__.__name__.encode('iso8859-1', 'xmlcharrefreplace'))
  696. for x in self.inputs + self.outputs:
  697. up(x.abspath().encode('iso8859-1', 'xmlcharrefreplace'))
  698. self.uid_ = m.digest()
  699. return self.uid_
  700. uid.__doc__ = Task.uid.__doc__
  701. Task.uid = uid
  702. def is_before(t1, t2):
  703. """
  704. Return a non-zero value if task t1 is to be executed before task t2::
  705. t1.ext_out = '.h'
  706. t2.ext_in = '.h'
  707. t2.after = ['t1']
  708. t1.before = ['t2']
  709. waflib.Task.is_before(t1, t2) # True
  710. :param t1: task
  711. :type t1: :py:class:`waflib.Task.TaskBase`
  712. :param t2: task
  713. :type t2: :py:class:`waflib.Task.TaskBase`
  714. """
  715. to_list = Utils.to_list
  716. for k in to_list(t2.ext_in):
  717. if k in to_list(t1.ext_out):
  718. return 1
  719. if t1.__class__.__name__ in to_list(t2.after):
  720. return 1
  721. if t2.__class__.__name__ in to_list(t1.before):
  722. return 1
  723. return 0
  724. def set_file_constraints(tasks):
  725. """
  726. Adds tasks to the task 'run_after' attribute based on the task inputs and outputs
  727. :param tasks: tasks
  728. :type tasks: list of :py:class:`waflib.Task.TaskBase`
  729. """
  730. ins = Utils.defaultdict(set)
  731. outs = Utils.defaultdict(set)
  732. for x in tasks:
  733. for a in getattr(x, 'inputs', []) + getattr(x, 'dep_nodes', []):
  734. ins[id(a)].add(x)
  735. for a in getattr(x, 'outputs', []):
  736. outs[id(a)].add(x)
  737. links = set(ins.keys()).intersection(outs.keys())
  738. for k in links:
  739. for a in ins[k]:
  740. a.run_after.update(outs[k])
  741. def set_precedence_constraints(tasks):
  742. """
  743. Add tasks to the task 'run_after' attribute based on the after/before/ext_out/ext_in attributes
  744. :param tasks: tasks
  745. :type tasks: list of :py:class:`waflib.Task.TaskBase`
  746. """
  747. cstr_groups = Utils.defaultdict(list)
  748. for x in tasks:
  749. h = x.hash_constraints()
  750. cstr_groups[h].append(x)
  751. keys = list(cstr_groups.keys())
  752. maxi = len(keys)
  753. # this list should be short
  754. for i in range(maxi):
  755. t1 = cstr_groups[keys[i]][0]
  756. for j in range(i + 1, maxi):
  757. t2 = cstr_groups[keys[j]][0]
  758. # add the constraints based on the comparisons
  759. if is_before(t1, t2):
  760. a = i
  761. b = j
  762. elif is_before(t2, t1):
  763. a = j
  764. b = i
  765. else:
  766. continue
  767. aval = set(cstr_groups[keys[a]])
  768. for x in cstr_groups[keys[b]]:
  769. x.run_after.update(aval)
  770. def funex(c):
  771. """
  772. Compile a function by 'exec'
  773. :param c: function to compile
  774. :type c: string
  775. :return: the function 'f' declared in the input string
  776. :rtype: function
  777. """
  778. dc = {}
  779. exec(c, dc)
  780. return dc['f']
  781. reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})", re.M)
  782. def compile_fun_shell(line):
  783. """
  784. Create a compiled function to execute a process with the shell
  785. WARNING: this method may disappear anytime, so use compile_fun instead
  786. """
  787. extr = []
  788. def repl(match):
  789. g = match.group
  790. if g('dollar'): return "$"
  791. elif g('backslash'): return '\\\\'
  792. elif g('subst'): extr.append((g('var'), g('code'))); return "%s"
  793. return None
  794. line = reg_act.sub(repl, line) or line
  795. parm = []
  796. dvars = []
  797. app = parm.append
  798. for (var, meth) in extr:
  799. if var == 'SRC':
  800. if meth: app('tsk.inputs%s' % meth)
  801. else: app('" ".join([a.path_from(bld.bldnode) for a in tsk.inputs])')
  802. elif var == 'TGT':
  803. if meth: app('tsk.outputs%s' % meth)
  804. else: app('" ".join([a.path_from(bld.bldnode) for a in tsk.outputs])')
  805. elif meth:
  806. if meth.startswith(':'):
  807. m = meth[1:]
  808. if m == 'SRC':
  809. m = '[a.path_from(bld.bldnode) for a in tsk.inputs]'
  810. elif m == 'TGT':
  811. m = '[a.path_from(bld.bldnode) for a in tsk.outputs]'
  812. elif m[:3] not in ('tsk', 'gen', 'bld'):
  813. dvars.extend([var, meth[1:]])
  814. m = '%r' % m
  815. app('" ".join(tsk.colon(%r, %s))' % (var, m))
  816. else:
  817. app('%s%s' % (var, meth))
  818. else:
  819. if not var in dvars: dvars.append(var)
  820. app("p('%s')" % var)
  821. if parm: parm = "%% (%s) " % (',\n\t\t'.join(parm))
  822. else: parm = ''
  823. c = COMPILE_TEMPLATE_SHELL % (line, parm)
  824. Logs.debug('action: %s' % c.strip().splitlines())
  825. return (funex(c), dvars)
  826. def compile_fun_noshell(line):
  827. """
  828. Create a compiled function to execute a process without the shell
  829. WARNING: this method may disappear anytime, so use compile_fun instead
  830. """
  831. extr = []
  832. def repl(match):
  833. g = match.group
  834. if g('dollar'): return "$"
  835. elif g('backslash'): return '\\'
  836. elif g('subst'): extr.append((g('var'), g('code'))); return "<<|@|>>"
  837. return None
  838. line2 = reg_act.sub(repl, line)
  839. params = line2.split('<<|@|>>')
  840. assert(extr)
  841. buf = []
  842. dvars = []
  843. app = buf.append
  844. for x in range(len(extr)):
  845. params[x] = params[x].strip()
  846. if params[x]:
  847. app("lst.extend(%r)" % params[x].split())
  848. (var, meth) = extr[x]
  849. if var == 'SRC':
  850. if meth: app('lst.append(tsk.inputs%s)' % meth)
  851. else: app("lst.extend([a.path_from(bld.bldnode) for a in tsk.inputs])")
  852. elif var == 'TGT':
  853. if meth: app('lst.append(tsk.outputs%s)' % meth)
  854. else: app("lst.extend([a.path_from(bld.bldnode) for a in tsk.outputs])")
  855. elif meth:
  856. if meth.startswith(':'):
  857. m = meth[1:]
  858. if m == 'SRC':
  859. m = '[a.path_from(bld.bldnode) for a in tsk.inputs]'
  860. elif m == 'TGT':
  861. m = '[a.path_from(bld.bldnode) for a in tsk.outputs]'
  862. elif m[:3] not in ('tsk', 'gen', 'bld'):
  863. dvars.extend([var, m])
  864. m = '%r' % m
  865. app('lst.extend(tsk.colon(%r, %s))' % (var, m))
  866. else:
  867. app('lst.extend(gen.to_list(%s%s))' % (var, meth))
  868. else:
  869. app('lst.extend(to_list(env[%r]))' % var)
  870. if not var in dvars: dvars.append(var)
  871. if extr:
  872. if params[-1]:
  873. app("lst.extend(%r)" % params[-1].split())
  874. fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
  875. Logs.debug('action: %s' % fun.strip().splitlines())
  876. return (funex(fun), dvars)
  877. def compile_fun(line, shell=False):
  878. """
  879. Parse a string expression such as "${CC} ${SRC} -o ${TGT}" and return a pair containing:
  880. * the function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
  881. * the list of variables that imply a dependency from self.env
  882. for example::
  883. from waflib.Task import compile_fun
  884. compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
  885. def build(bld):
  886. bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
  887. The env variables (CXX, ..) on the task must not hold dicts (order)
  888. The reserved keywords *TGT* and *SRC* represent the task input and output nodes
  889. """
  890. if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
  891. shell = True
  892. if shell:
  893. return compile_fun_shell(line)
  894. else:
  895. return compile_fun_noshell(line)
  896. def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
  897. """
  898. Returns a new task subclass with the function ``run`` compiled from the line given.
  899. :param func: method run
  900. :type func: string or function
  901. :param vars: list of variables to hash
  902. :type vars: list of string
  903. :param color: color to use
  904. :type color: string
  905. :param shell: when *func* is a string, enable/disable the use of the shell
  906. :type shell: bool
  907. :param scan: method scan
  908. :type scan: function
  909. :rtype: :py:class:`waflib.Task.Task`
  910. """
  911. params = {
  912. 'vars': vars or [], # function arguments are static, and this one may be modified by the class
  913. 'color': color,
  914. 'name': name,
  915. 'ext_in': Utils.to_list(ext_in),
  916. 'ext_out': Utils.to_list(ext_out),
  917. 'before': Utils.to_list(before),
  918. 'after': Utils.to_list(after),
  919. 'shell': shell,
  920. 'scan': scan,
  921. }
  922. if isinstance(func, str):
  923. params['run_str'] = func
  924. else:
  925. params['run'] = func
  926. cls = type(Task)(name, (Task,), params)
  927. global classes
  928. classes[name] = cls
  929. return cls
  930. def always_run(cls):
  931. """
  932. Task class decorator
  933. Set all task instances of this class to be executed whenever a build is started
  934. The task signature is calculated, but the result of the comparation between
  935. task signatures is bypassed
  936. """
  937. old = cls.runnable_status
  938. def always(self):
  939. ret = old(self)
  940. if ret == SKIP_ME:
  941. ret = RUN_ME
  942. return ret
  943. cls.runnable_status = always
  944. return cls
  945. def update_outputs(cls):
  946. """
  947. Task class decorator
  948. If you want to create files in the source directory. For example, to keep *foo.txt* in the source
  949. directory, create it first and declare::
  950. def build(bld):
  951. bld(rule='cp ${SRC} ${TGT}', source='wscript', target='foo.txt', update_outputs=True)
  952. """
  953. old_post_run = cls.post_run
  954. def post_run(self):
  955. old_post_run(self)
  956. for node in self.outputs:
  957. node.sig = node.cache_sig = Utils.h_file(node.abspath())
  958. self.generator.bld.task_sigs[node.abspath()] = self.uid() # issue #1017
  959. cls.post_run = post_run
  960. old_runnable_status = cls.runnable_status
  961. def runnable_status(self):
  962. status = old_runnable_status(self)
  963. if status != RUN_ME:
  964. return status
  965. try:
  966. # by default, we check that the output nodes have the signature of the task
  967. # perform a second check, returning 'SKIP_ME' as we are expecting that
  968. # the signatures do not match
  969. bld = self.generator.bld
  970. prev_sig = bld.task_sigs[self.uid()]
  971. if prev_sig == self.signature():
  972. for x in self.outputs:
  973. if not x.is_child_of(bld.bldnode):
  974. # special case of files created in the source directory
  975. # hash them here for convenience -_-
  976. x.sig = Utils.h_file(x.abspath())
  977. if not x.sig or bld.task_sigs[x.abspath()] != self.uid():
  978. return RUN_ME
  979. return SKIP_ME
  980. except OSError:
  981. pass
  982. except IOError:
  983. pass
  984. except KeyError:
  985. pass
  986. except IndexError:
  987. pass
  988. except AttributeError:
  989. pass
  990. return RUN_ME
  991. cls.runnable_status = runnable_status
  992. return cls