waf

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

TaskGen.py (26525B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2005-2018 (ita)
      4 
      5 """
      6 Task generators
      7 
      8 The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code)
      9 The instances can have various parameters, but the creation of task nodes (Task.py)
     10 is deferred. To achieve this, various methods are called from the method "apply"
     11 """
     12 
     13 import copy, re, os, functools
     14 from waflib import Task, Utils, Logs, Errors, ConfigSet, Node
     15 
     16 feats = Utils.defaultdict(set)
     17 """remember the methods declaring features"""
     18 
     19 HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
     20 
     21 class task_gen(object):
     22 	"""
     23 	Instances of this class create :py:class:`waflib.Task.Task` when
     24 	calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
     25 	A few notes:
     26 
     27 	* The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..)
     28 	* The 'features' are used to add methods to self.meths and then execute them
     29 	* The attribute 'path' is a node representing the location of the task generator
     30 	* The tasks created are added to the attribute *tasks*
     31 	* The attribute 'idx' is a counter of task generators in the same path
     32 	"""
     33 
     34 	mappings = Utils.ordered_iter_dict()
     35 	"""Mappings are global file extension mappings that are retrieved in the order of definition"""
     36 
     37 	prec = Utils.defaultdict(set)
     38 	"""Dict that holds the precedence execution rules for task generator methods"""
     39 
     40 	def __init__(self, *k, **kw):
     41 		"""
     42 		Task generator objects predefine various attributes (source, target) for possible
     43 		processing by process_rule (make-like rules) or process_source (extensions, misc methods)
     44 
     45 		Tasks are stored on the attribute 'tasks'. They are created by calling methods
     46 		listed in ``self.meths`` or referenced in the attribute ``features``
     47 		A topological sort is performed to execute the methods in correct order.
     48 
     49 		The extra key/value elements passed in ``kw`` are set as attributes
     50 		"""
     51 		self.source = []
     52 		self.target = ''
     53 
     54 		self.meths = []
     55 		"""
     56 		List of method names to execute (internal)
     57 		"""
     58 
     59 		self.features = []
     60 		"""
     61 		List of feature names for bringing new methods in
     62 		"""
     63 
     64 		self.tasks = []
     65 		"""
     66 		Tasks created are added to this list
     67 		"""
     68 
     69 		if not 'bld' in kw:
     70 			# task generators without a build context :-/
     71 			self.env = ConfigSet.ConfigSet()
     72 			self.idx = 0
     73 			self.path = None
     74 		else:
     75 			self.bld = kw['bld']
     76 			self.env = self.bld.env.derive()
     77 			self.path = kw.get('path', self.bld.path) # by default, emulate chdir when reading scripts
     78 
     79 			# Provide a unique index per folder
     80 			# This is part of a measure to prevent output file name collisions
     81 			path = self.path.abspath()
     82 			try:
     83 				self.idx = self.bld.idx[path] = self.bld.idx.get(path, 0) + 1
     84 			except AttributeError:
     85 				self.bld.idx = {}
     86 				self.idx = self.bld.idx[path] = 1
     87 
     88 			# Record the global task generator count
     89 			try:
     90 				self.tg_idx_count = self.bld.tg_idx_count = self.bld.tg_idx_count + 1
     91 			except AttributeError:
     92 				self.tg_idx_count = self.bld.tg_idx_count = 1
     93 
     94 		for key, val in kw.items():
     95 			setattr(self, key, val)
     96 
     97 	def __str__(self):
     98 		"""Debugging helper"""
     99 		return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
    100 
    101 	def __repr__(self):
    102 		"""Debugging helper"""
    103 		lst = []
    104 		for x in self.__dict__:
    105 			if x not in ('env', 'bld', 'compiled_tasks', 'tasks'):
    106 				lst.append("%s=%s" % (x, repr(getattr(self, x))))
    107 		return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())
    108 
    109 	def get_cwd(self):
    110 		"""
    111 		Current working directory for the task generator, defaults to the build directory.
    112 		This is still used in a few places but it should disappear at some point as the classes
    113 		define their own working directory.
    114 
    115 		:rtype: :py:class:`waflib.Node.Node`
    116 		"""
    117 		return self.bld.bldnode
    118 
    119 	def get_name(self):
    120 		"""
    121 		If the attribute ``name`` is not set on the instance,
    122 		the name is computed from the target name::
    123 
    124 			def build(bld):
    125 				x = bld(name='foo')
    126 				x.get_name() # foo
    127 				y = bld(target='bar')
    128 				y.get_name() # bar
    129 
    130 		:rtype: string
    131 		:return: name of this task generator
    132 		"""
    133 		try:
    134 			return self._name
    135 		except AttributeError:
    136 			if isinstance(self.target, list):
    137 				lst = [str(x) for x in self.target]
    138 				name = self._name = ','.join(lst)
    139 			else:
    140 				name = self._name = str(self.target)
    141 			return name
    142 	def set_name(self, name):
    143 		self._name = name
    144 
    145 	name = property(get_name, set_name)
    146 
    147 	def to_list(self, val):
    148 		"""
    149 		Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list`
    150 
    151 		:type val: string or list of string
    152 		:param val: input to return as a list
    153 		:rtype: list
    154 		"""
    155 		if isinstance(val, str):
    156 			return val.split()
    157 		else:
    158 			return val
    159 
    160 	def post(self):
    161 		"""
    162 		Creates tasks for this task generators. The following operations are performed:
    163 
    164 		#. The body of this method is called only once and sets the attribute ``posted``
    165 		#. The attribute ``features`` is used to add more methods in ``self.meths``
    166 		#. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec`
    167 		#. The methods are then executed in order
    168 		#. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks`
    169 		"""
    170 		if getattr(self, 'posted', None):
    171 			return False
    172 		self.posted = True
    173 
    174 		keys = set(self.meths)
    175 		keys.update(feats['*'])
    176 
    177 		# add the methods listed in the features
    178 		self.features = Utils.to_list(self.features)
    179 		for x in self.features:
    180 			st = feats[x]
    181 			if st:
    182 				keys.update(st)
    183 			elif not x in Task.classes:
    184 				Logs.warn('feature %r does not exist - bind at least one method to it?', x)
    185 
    186 		# copy the precedence table
    187 		prec = {}
    188 		prec_tbl = self.prec
    189 		for x in prec_tbl:
    190 			if x in keys:
    191 				prec[x] = prec_tbl[x]
    192 
    193 		# elements disconnected
    194 		tmp = []
    195 		for a in keys:
    196 			for x in prec.values():
    197 				if a in x:
    198 					break
    199 			else:
    200 				tmp.append(a)
    201 
    202 		tmp.sort(reverse=True)
    203 
    204 		# topological sort
    205 		out = []
    206 		while tmp:
    207 			e = tmp.pop()
    208 			if e in keys:
    209 				out.append(e)
    210 			try:
    211 				nlst = prec[e]
    212 			except KeyError:
    213 				pass
    214 			else:
    215 				del prec[e]
    216 				for x in nlst:
    217 					for y in prec:
    218 						if x in prec[y]:
    219 							break
    220 					else:
    221 						tmp.append(x)
    222 						tmp.sort(reverse=True)
    223 
    224 		if prec:
    225 			buf = ['Cycle detected in the method execution:']
    226 			for k, v in prec.items():
    227 				buf.append('- %s after %s' % (k, [x for x in v if x in prec]))
    228 			raise Errors.WafError('\n'.join(buf))
    229 		self.meths = out
    230 
    231 		# then we run the methods in order
    232 		Logs.debug('task_gen: posting %s %d', self, id(self))
    233 		for x in out:
    234 			try:
    235 				v = getattr(self, x)
    236 			except AttributeError:
    237 				raise Errors.WafError('%r is not a valid task generator method' % x)
    238 			Logs.debug('task_gen: -> %s (%d)', x, id(self))
    239 			v()
    240 
    241 		Logs.debug('task_gen: posted %s', self.name)
    242 		return True
    243 
    244 	def get_hook(self, node):
    245 		"""
    246 		Returns the ``@extension`` method to call for a Node of a particular extension.
    247 
    248 		:param node: Input file to process
    249 		:type node: :py:class:`waflib.Tools.Node.Node`
    250 		:return: A method able to process the input node by looking at the extension
    251 		:rtype: function
    252 		"""
    253 		name = node.name
    254 		for k in self.mappings:
    255 			try:
    256 				if name.endswith(k):
    257 					return self.mappings[k]
    258 			except TypeError:
    259 				# regexps objects
    260 				if k.match(name):
    261 					return self.mappings[k]
    262 		keys = list(self.mappings.keys())
    263 		raise Errors.WafError("File %r has no mapping in %r (load a waf tool?)" % (node, keys))
    264 
    265 	def create_task(self, name, src=None, tgt=None, **kw):
    266 		"""
    267 		Creates task instances.
    268 
    269 		:param name: task class name
    270 		:type name: string
    271 		:param src: input nodes
    272 		:type src: list of :py:class:`waflib.Tools.Node.Node`
    273 		:param tgt: output nodes
    274 		:type tgt: list of :py:class:`waflib.Tools.Node.Node`
    275 		:return: A task object
    276 		:rtype: :py:class:`waflib.Task.Task`
    277 		"""
    278 		task = Task.classes[name](env=self.env.derive(), generator=self)
    279 		if src:
    280 			task.set_inputs(src)
    281 		if tgt:
    282 			task.set_outputs(tgt)
    283 		task.__dict__.update(kw)
    284 		self.tasks.append(task)
    285 		return task
    286 
    287 	def clone(self, env):
    288 		"""
    289 		Makes a copy of a task generator. Once the copy is made, it is necessary to ensure that the
    290 		it does not create the same output files as the original, or the same files may
    291 		be compiled several times.
    292 
    293 		:param env: A configuration set
    294 		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
    295 		:return: A copy
    296 		:rtype: :py:class:`waflib.TaskGen.task_gen`
    297 		"""
    298 		newobj = self.bld()
    299 		for x in self.__dict__:
    300 			if x in ('env', 'bld'):
    301 				continue
    302 			elif x in ('path', 'features'):
    303 				setattr(newobj, x, getattr(self, x))
    304 			else:
    305 				setattr(newobj, x, copy.copy(getattr(self, x)))
    306 
    307 		newobj.posted = False
    308 		if isinstance(env, str):
    309 			newobj.env = self.bld.all_envs[env].derive()
    310 		else:
    311 			newobj.env = env.derive()
    312 
    313 		return newobj
    314 
    315 def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
    316 	ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
    317 	"""
    318 	Creates a new mapping and a task class for processing files by extension.
    319 	See Tools/flex.py for an example.
    320 
    321 	:param name: name for the task class
    322 	:type name: string
    323 	:param rule: function to execute or string to be compiled in a function
    324 	:type rule: string or function
    325 	:param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
    326 	:type reentrant: int
    327 	:param color: color for the task output
    328 	:type color: string
    329 	:param ext_in: execute the task only after the files of such extensions are created
    330 	:type ext_in: list of string
    331 	:param ext_out: execute the task only before files of such extensions are processed
    332 	:type ext_out: list of string
    333 	:param before: execute instances of this task before classes of the given names
    334 	:type before: list of string
    335 	:param after: execute instances of this task after classes of the given names
    336 	:type after: list of string
    337 	:param decider: if present, function that returns a list of output file extensions (overrides ext_out for output files, but not for the build order)
    338 	:type decider: function
    339 	:param scan: scanner function for the task
    340 	:type scan: function
    341 	:param install_path: installation path for the output nodes
    342 	:type install_path: string
    343 	"""
    344 	ext_in = Utils.to_list(ext_in)
    345 	ext_out = Utils.to_list(ext_out)
    346 	if not name:
    347 		name = rule
    348 	cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)
    349 
    350 	def x_file(self, node):
    351 		if ext_in:
    352 			_ext_in = ext_in[0]
    353 
    354 		tsk = self.create_task(name, node)
    355 		cnt = 0
    356 
    357 		ext = decider(self, node) if decider else cls.ext_out
    358 		for x in ext:
    359 			k = node.change_ext(x, ext_in=_ext_in)
    360 			tsk.outputs.append(k)
    361 
    362 			if reentrant != None:
    363 				if cnt < int(reentrant):
    364 					self.source.append(k)
    365 			else:
    366 				# reinject downstream files into the build
    367 				for y in self.mappings: # ~ nfile * nextensions :-/
    368 					if k.name.endswith(y):
    369 						self.source.append(k)
    370 						break
    371 			cnt += 1
    372 
    373 		if install_path:
    374 			self.install_task = self.add_install_files(install_to=install_path, install_from=tsk.outputs)
    375 		return tsk
    376 
    377 	for x in cls.ext_in:
    378 		task_gen.mappings[x] = x_file
    379 	return x_file
    380 
    381 def taskgen_method(func):
    382 	"""
    383 	Decorator that registers method as a task generator method.
    384 	The function must accept a task generator as first parameter::
    385 
    386 		from waflib.TaskGen import taskgen_method
    387 		@taskgen_method
    388 		def mymethod(self):
    389 			pass
    390 
    391 	:param func: task generator method to add
    392 	:type func: function
    393 	:rtype: function
    394 	"""
    395 	setattr(task_gen, func.__name__, func)
    396 	return func
    397 
    398 def feature(*k):
    399 	"""
    400 	Decorator that registers a task generator method that will be executed when the
    401 	object attribute ``feature`` contains the corresponding key(s)::
    402 
    403 		from waflib.TaskGen import feature
    404 		@feature('myfeature')
    405 		def myfunction(self):
    406 			print('that is my feature!')
    407 		def build(bld):
    408 			bld(features='myfeature')
    409 
    410 	:param k: feature names
    411 	:type k: list of string
    412 	"""
    413 	def deco(func):
    414 		setattr(task_gen, func.__name__, func)
    415 		for name in k:
    416 			feats[name].update([func.__name__])
    417 		return func
    418 	return deco
    419 
    420 def before_method(*k):
    421 	"""
    422 	Decorator that registera task generator method which will be executed
    423 	before the functions of given name(s)::
    424 
    425 		from waflib.TaskGen import feature, before
    426 		@feature('myfeature')
    427 		@before_method('fun2')
    428 		def fun1(self):
    429 			print('feature 1!')
    430 		@feature('myfeature')
    431 		def fun2(self):
    432 			print('feature 2!')
    433 		def build(bld):
    434 			bld(features='myfeature')
    435 
    436 	:param k: method names
    437 	:type k: list of string
    438 	"""
    439 	def deco(func):
    440 		setattr(task_gen, func.__name__, func)
    441 		for fun_name in k:
    442 			task_gen.prec[func.__name__].add(fun_name)
    443 		return func
    444 	return deco
    445 before = before_method
    446 
    447 def after_method(*k):
    448 	"""
    449 	Decorator that registers a task generator method which will be executed
    450 	after the functions of given name(s)::
    451 
    452 		from waflib.TaskGen import feature, after
    453 		@feature('myfeature')
    454 		@after_method('fun2')
    455 		def fun1(self):
    456 			print('feature 1!')
    457 		@feature('myfeature')
    458 		def fun2(self):
    459 			print('feature 2!')
    460 		def build(bld):
    461 			bld(features='myfeature')
    462 
    463 	:param k: method names
    464 	:type k: list of string
    465 	"""
    466 	def deco(func):
    467 		setattr(task_gen, func.__name__, func)
    468 		for fun_name in k:
    469 			task_gen.prec[fun_name].add(func.__name__)
    470 		return func
    471 	return deco
    472 after = after_method
    473 
    474 def extension(*k):
    475 	"""
    476 	Decorator that registers a task generator method which will be invoked during
    477 	the processing of source files for the extension given::
    478 
    479 		from waflib import Task
    480 		class mytask(Task):
    481 			run_str = 'cp ${SRC} ${TGT}'
    482 		@extension('.moo')
    483 		def create_maa_file(self, node):
    484 			self.create_task('mytask', node, node.change_ext('.maa'))
    485 		def build(bld):
    486 			bld(source='foo.moo')
    487 	"""
    488 	def deco(func):
    489 		setattr(task_gen, func.__name__, func)
    490 		for x in k:
    491 			task_gen.mappings[x] = func
    492 		return func
    493 	return deco
    494 
    495 @taskgen_method
    496 def to_nodes(self, lst, path=None):
    497 	"""
    498 	Flatten the input list of string/nodes/lists into a list of nodes.
    499 
    500 	It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
    501 	It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
    502 
    503 	:param lst: input list
    504 	:type lst: list of string and nodes
    505 	:param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
    506 	:type path: :py:class:`waflib.Tools.Node.Node`
    507 	:rtype: list of :py:class:`waflib.Tools.Node.Node`
    508 	"""
    509 	tmp = []
    510 	path = path or self.path
    511 	find = path.find_resource
    512 
    513 	if isinstance(lst, Node.Node):
    514 		lst = [lst]
    515 
    516 	for x in Utils.to_list(lst):
    517 		if isinstance(x, str):
    518 			node = find(x)
    519 		elif hasattr(x, 'name'):
    520 			node = x
    521 		else:
    522 			tmp.extend(self.to_nodes(x))
    523 			continue
    524 		if not node:
    525 			raise Errors.WafError('source not found: %r in %r' % (x, self))
    526 		tmp.append(node)
    527 	return tmp
    528 
    529 @feature('*')
    530 def process_source(self):
    531 	"""
    532 	Processes each element in the attribute ``source`` by extension.
    533 
    534 	#. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
    535 	#. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
    536 	#. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
    537 	#. When called, the methods may modify self.source to append more source to process
    538 	#. The mappings can map an extension or a filename (see the code below)
    539 	"""
    540 	self.source = self.to_nodes(getattr(self, 'source', []))
    541 	for node in self.source:
    542 		self.get_hook(node)(self, node)
    543 
    544 @feature('*')
    545 @before_method('process_source')
    546 def process_rule(self):
    547 	"""
    548 	Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
    549 
    550 		def build(bld):
    551 			bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
    552 
    553 	Main attributes processed:
    554 
    555 	* rule: command to execute, it can be a tuple of strings for multiple commands
    556 	* chmod: permissions for the resulting files (integer value such as Utils.O755)
    557 	* shell: set to False to execute the command directly (default is True to use a shell)
    558 	* scan: scanner function
    559 	* vars: list of variables to trigger rebuilds, such as CFLAGS
    560 	* cls_str: string to display when executing the task
    561 	* cls_keyword: label to display when executing the task
    562 	* cache_rule: by default, try to re-use similar classes, set to False to disable
    563 	* source: list of Node or string objects representing the source files required by this task
    564 	* target: list of Node or string objects representing the files that this task creates
    565 	* cwd: current working directory (Node or string)
    566 	* stdout: standard output, set to None to prevent waf from capturing the text
    567 	* stderr: standard error, set to None to prevent waf from capturing the text
    568 	* timeout: timeout for command execution (Python 3)
    569 	* always: whether to always run the command (False by default)
    570 	* deep_inputs: whether the task must depend on the input file tasks too (False by default)
    571 	"""
    572 	if not getattr(self, 'rule', None):
    573 		return
    574 
    575 	# create the task class
    576 	name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
    577 
    578 	# or we can put the class in a cache for performance reasons
    579 	try:
    580 		cache = self.bld.cache_rule_attr
    581 	except AttributeError:
    582 		cache = self.bld.cache_rule_attr = {}
    583 
    584 	chmod = getattr(self, 'chmod', None)
    585 	shell = getattr(self, 'shell', True)
    586 	color = getattr(self, 'color', 'BLUE')
    587 	scan = getattr(self, 'scan', None)
    588 	_vars = getattr(self, 'vars', [])
    589 	cls_str = getattr(self, 'cls_str', None)
    590 	cls_keyword = getattr(self, 'cls_keyword', None)
    591 	use_cache = getattr(self, 'cache_rule', 'True')
    592 	deep_inputs = getattr(self, 'deep_inputs', False)
    593 
    594 	scan_val = has_deps = hasattr(self, 'deps')
    595 	if scan:
    596 		scan_val = id(scan)
    597 
    598 	key = Utils.h_list((name, self.rule, chmod, shell, color, cls_str, cls_keyword, scan_val, _vars, deep_inputs))
    599 
    600 	cls = None
    601 	if use_cache:
    602 		try:
    603 			cls = cache[key]
    604 		except KeyError:
    605 			pass
    606 	if not cls:
    607 		rule = self.rule
    608 		if chmod is not None:
    609 			def chmod_fun(tsk):
    610 				for x in tsk.outputs:
    611 					os.chmod(x.abspath(), tsk.generator.chmod)
    612 			if isinstance(rule, tuple):
    613 				rule = list(rule)
    614 				rule.append(chmod_fun)
    615 				rule = tuple(rule)
    616 			else:
    617 				rule = (rule, chmod_fun)
    618 
    619 		cls = Task.task_factory(name, rule, _vars, shell=shell, color=color)
    620 
    621 		if cls_str:
    622 			setattr(cls, '__str__', self.cls_str)
    623 
    624 		if cls_keyword:
    625 			setattr(cls, 'keyword', self.cls_keyword)
    626 
    627 		if deep_inputs:
    628 			Task.deep_inputs(cls)
    629 
    630 		if scan:
    631 			cls.scan = self.scan
    632 		elif has_deps:
    633 			def scan(self):
    634 				deps = getattr(self.generator, 'deps', None)
    635 				nodes = self.generator.to_nodes(deps)
    636 				return [nodes, []]
    637 			cls.scan = scan
    638 
    639 		if use_cache:
    640 			cache[key] = cls
    641 
    642 	# now create one instance
    643 	tsk = self.create_task(name)
    644 
    645 	for x in ('after', 'before', 'ext_in', 'ext_out'):
    646 		setattr(tsk, x, getattr(self, x, []))
    647 
    648 	if hasattr(self, 'stdout'):
    649 		tsk.stdout = self.stdout
    650 
    651 	if hasattr(self, 'stderr'):
    652 		tsk.stderr = self.stderr
    653 
    654 	if getattr(self, 'timeout', None):
    655 		tsk.timeout = self.timeout
    656 
    657 	if getattr(self, 'always', None):
    658 		tsk.always_run = True
    659 
    660 	if getattr(self, 'target', None):
    661 		if isinstance(self.target, str):
    662 			self.target = self.target.split()
    663 		if not isinstance(self.target, list):
    664 			self.target = [self.target]
    665 		for x in self.target:
    666 			if isinstance(x, str):
    667 				tsk.outputs.append(self.path.find_or_declare(x))
    668 			else:
    669 				x.parent.mkdir() # if a node was given, create the required folders
    670 				tsk.outputs.append(x)
    671 		if getattr(self, 'install_path', None):
    672 			self.install_task = self.add_install_files(install_to=self.install_path,
    673 				install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644))
    674 
    675 	if getattr(self, 'source', None):
    676 		tsk.inputs = self.to_nodes(self.source)
    677 		# bypass the execution of process_source by setting the source to an empty list
    678 		self.source = []
    679 
    680 	if getattr(self, 'cwd', None):
    681 		tsk.cwd = self.cwd
    682 
    683 	if isinstance(tsk.run, functools.partial):
    684 		# Python documentation says: "partial objects defined in classes
    685 		# behave like static methods and do not transform into bound
    686 		# methods during instance attribute look-up."
    687 		tsk.run = functools.partial(tsk.run, tsk)
    688 
    689 @feature('seq')
    690 def sequence_order(self):
    691 	"""
    692 	Adds a strict sequential constraint between the tasks generated by task generators.
    693 	It works because task generators are posted in order.
    694 	It will not post objects which belong to other folders.
    695 
    696 	Example::
    697 
    698 		bld(features='javac seq')
    699 		bld(features='jar seq')
    700 
    701 	To start a new sequence, set the attribute seq_start, for example::
    702 
    703 		obj = bld(features='seq')
    704 		obj.seq_start = True
    705 
    706 	Note that the method is executed in last position. This is more an
    707 	example than a widely-used solution.
    708 	"""
    709 	if self.meths and self.meths[-1] != 'sequence_order':
    710 		self.meths.append('sequence_order')
    711 		return
    712 
    713 	if getattr(self, 'seq_start', None):
    714 		return
    715 
    716 	# all the tasks previously declared must be run before these
    717 	if getattr(self.bld, 'prev', None):
    718 		self.bld.prev.post()
    719 		for x in self.bld.prev.tasks:
    720 			for y in self.tasks:
    721 				y.set_run_after(x)
    722 
    723 	self.bld.prev = self
    724 
    725 
    726 re_m4 = re.compile(r'@(\w+)@', re.M)
    727 
    728 class subst_pc(Task.Task):
    729 	"""
    730 	Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
    731 	in the substitution changes.
    732 	"""
    733 
    734 	def force_permissions(self):
    735 		"Private for the time being, we will probably refactor this into run_str=[run1,chmod]"
    736 		if getattr(self.generator, 'chmod', None):
    737 			for x in self.outputs:
    738 				os.chmod(x.abspath(), self.generator.chmod)
    739 
    740 	def run(self):
    741 		"Substitutes variables in a .in file"
    742 
    743 		if getattr(self.generator, 'is_copy', None):
    744 			for i, x in enumerate(self.outputs):
    745 				x.write(self.inputs[i].read('rb'), 'wb')
    746 				stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy
    747 				os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime))
    748 			self.force_permissions()
    749 			return None
    750 
    751 		if getattr(self.generator, 'fun', None):
    752 			ret = self.generator.fun(self)
    753 			if not ret:
    754 				self.force_permissions()
    755 			return ret
    756 
    757 		code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1'))
    758 		if getattr(self.generator, 'subst_fun', None):
    759 			code = self.generator.subst_fun(self, code)
    760 			if code is not None:
    761 				self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
    762 			self.force_permissions()
    763 			return None
    764 
    765 		# replace all % by %% to prevent errors by % signs
    766 		code = code.replace('%', '%%')
    767 
    768 		# extract the vars foo into lst and replace @foo@ by %(foo)s
    769 		lst = []
    770 		def repl(match):
    771 			g = match.group
    772 			if g(1):
    773 				lst.append(g(1))
    774 				return "%%(%s)s" % g(1)
    775 			return ''
    776 		code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
    777 
    778 		try:
    779 			d = self.generator.dct
    780 		except AttributeError:
    781 			d = {}
    782 			for x in lst:
    783 				tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
    784 				try:
    785 					tmp = ''.join(tmp)
    786 				except TypeError:
    787 					tmp = str(tmp)
    788 				d[x] = tmp
    789 
    790 		code = code % d
    791 		self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
    792 		self.generator.bld.raw_deps[self.uid()] = lst
    793 
    794 		# make sure the signature is updated
    795 		try:
    796 			delattr(self, 'cache_sig')
    797 		except AttributeError:
    798 			pass
    799 
    800 		self.force_permissions()
    801 
    802 	def sig_vars(self):
    803 		"""
    804 		Compute a hash (signature) of the variables used in the substitution
    805 		"""
    806 		bld = self.generator.bld
    807 		env = self.env
    808 		upd = self.m.update
    809 
    810 		if getattr(self.generator, 'fun', None):
    811 			upd(Utils.h_fun(self.generator.fun).encode())
    812 		if getattr(self.generator, 'subst_fun', None):
    813 			upd(Utils.h_fun(self.generator.subst_fun).encode())
    814 
    815 		# raw_deps: persistent custom values returned by the scanner
    816 		vars = self.generator.bld.raw_deps.get(self.uid(), [])
    817 
    818 		# hash both env vars and task generator attributes
    819 		act_sig = bld.hash_env_vars(env, vars)
    820 		upd(act_sig)
    821 
    822 		lst = [getattr(self.generator, x, '') for x in vars]
    823 		upd(Utils.h_list(lst))
    824 
    825 		return self.m.digest()
    826 
    827 @extension('.pc.in')
    828 def add_pcfile(self, node):
    829 	"""
    830 	Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
    831 
    832 		def build(bld):
    833 			bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
    834 	"""
    835 	tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
    836 	self.install_task = self.add_install_files(
    837 		install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs)
    838 
    839 class subst(subst_pc):
    840 	pass
    841 
    842 @feature('subst')
    843 @before_method('process_source', 'process_rule')
    844 def process_subst(self):
    845 	"""
    846 	Defines a transformation that substitutes the contents of *source* files to *target* files::
    847 
    848 		def build(bld):
    849 			bld(
    850 				features='subst',
    851 				source='foo.c.in',
    852 				target='foo.c',
    853 				install_path='${LIBDIR}/pkgconfig',
    854 				VAR = 'val'
    855 			)
    856 
    857 	The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
    858 	of the task generator object.
    859 
    860 	This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
    861 	"""
    862 
    863 	src = Utils.to_list(getattr(self, 'source', []))
    864 	if isinstance(src, Node.Node):
    865 		src = [src]
    866 	tgt = Utils.to_list(getattr(self, 'target', []))
    867 	if isinstance(tgt, Node.Node):
    868 		tgt = [tgt]
    869 	if len(src) != len(tgt):
    870 		raise Errors.WafError('invalid number of source/target for %r' % self)
    871 
    872 	for x, y in zip(src, tgt):
    873 		if not x or not y:
    874 			raise Errors.WafError('null source or target for %r' % self)
    875 		a, b = None, None
    876 
    877 		if isinstance(x, str) and isinstance(y, str) and x == y:
    878 			a = self.path.find_node(x)
    879 			b = self.path.get_bld().make_node(y)
    880 			if not os.path.isfile(b.abspath()):
    881 				b.parent.mkdir()
    882 		else:
    883 			if isinstance(x, str):
    884 				a = self.path.find_resource(x)
    885 			elif isinstance(x, Node.Node):
    886 				a = x
    887 			if isinstance(y, str):
    888 				b = self.path.find_or_declare(y)
    889 			elif isinstance(y, Node.Node):
    890 				b = y
    891 
    892 		if not a:
    893 			raise Errors.WafError('could not find %r for %r' % (x, self))
    894 
    895 		tsk = self.create_task('subst', a, b)
    896 		for k in ('after', 'before', 'ext_in', 'ext_out'):
    897 			val = getattr(self, k, None)
    898 			if val:
    899 				setattr(tsk, k, val)
    900 
    901 		# paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
    902 		for xt in HEADER_EXTS:
    903 			if b.name.endswith(xt):
    904 				tsk.ext_out = tsk.ext_out + ['.h']
    905 				break
    906 
    907 		inst_to = getattr(self, 'install_path', None)
    908 		if inst_to:
    909 			self.install_task = self.add_install_files(install_to=inst_to,
    910 				install_from=b, chmod=getattr(self, 'chmod', Utils.O644))
    911 
    912 	self.source = []
    913