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.

1230 lines
34KB

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