Task.py 37 KB

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