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