waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

Task.py (39486B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2005-2018 (ita)
      4 
      5 """
      6 Tasks represent atomic operations such as processes.
      7 """
      8 
      9 import os, re, sys, tempfile, traceback
     10 from waflib import Utils, Logs, Errors
     11 
     12 # task states
     13 NOT_RUN = 0
     14 """The task was not executed yet"""
     15 
     16 MISSING = 1
     17 """The task has been executed but the files have not been created"""
     18 
     19 CRASHED = 2
     20 """The task execution returned a non-zero exit status"""
     21 
     22 EXCEPTION = 3
     23 """An exception occurred in the task execution"""
     24 
     25 CANCELED = 4
     26 """A dependency for the task is missing so it was cancelled"""
     27 
     28 SKIPPED = 8
     29 """The task did not have to be executed"""
     30 
     31 SUCCESS = 9
     32 """The task was successfully executed"""
     33 
     34 ASK_LATER = -1
     35 """The task is not ready to be executed"""
     36 
     37 SKIP_ME = -2
     38 """The task does not need to be executed"""
     39 
     40 RUN_ME = -3
     41 """The task must be executed"""
     42 
     43 CANCEL_ME = -4
     44 """The task cannot be executed because of a dependency problem"""
     45 
     46 COMPILE_TEMPLATE_SHELL = '''
     47 def 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 
     60 COMPILE_TEMPLATE_NOSHELL = '''
     61 def 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 
     81 COMPILE_TEMPLATE_SIG_VARS = '''
     82 def 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 
     95 classes = {}
     96 """
     97 The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
     98 created by user scripts or Waf tools to this dict. It maps class names to class objects.
     99 """
    100 
    101 class 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 
    135 evil = 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 
    138 class 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')
    913 if 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 
    927 def 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 
    955 def 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 
    978 class 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 
    998 def 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 
   1040 def 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 
   1053 re_cond = re.compile(r'(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
   1054 re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
   1055 reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
   1056 def 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 
   1138 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)
   1139 def 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 
   1224 def 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 
   1269 def 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 
   1299 def 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 
   1343 def 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 
   1353 TaskBase = Task
   1354 "Provided for compatibility reasons, TaskBase should not be used"
   1355 
   1356 class 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