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.

1193 lines
32KB

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