Set "seq" alsa midi driver to maximum resolution possible
[jack2.git] / waflib / TaskGen.py
bloba74e6431d86ea67651d55fbf79eb22aa98775115
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2018 (ita)
5 """
6 Task generators
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 """
13 import copy, re, os, functools
14 from waflib import Task, Utils, Logs, Errors, ConfigSet, Node
16 feats = Utils.defaultdict(set)
17 """remember the methods declaring features"""
19 HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
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:
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 """
34 mappings = Utils.ordered_iter_dict()
35 """Mappings are global file extension mappings that are retrieved in the order of definition"""
37 prec = Utils.defaultdict(set)
38 """Dict that holds the precedence execution rules for task generator methods"""
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)
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.
49 The extra key/value elements passed in ``kw`` are set as attributes
50 """
51 self.source = []
52 self.target = ''
54 self.meths = []
55 """
56 List of method names to execute (internal)
57 """
59 self.features = []
60 """
61 List of feature names for bringing new methods in
62 """
64 self.tasks = []
65 """
66 Tasks created are added to this list
67 """
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 = self.bld.path # emulate chdir when reading scripts
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
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
94 for key, val in kw.items():
95 setattr(self, key, val)
97 def __str__(self):
98 """Debugging helper"""
99 return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
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())
109 def get_cwd(self):
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.
115 :rtype: :py:class:`waflib.Node.Node`
117 return self.bld.bldnode
119 def get_name(self):
121 If the attribute ``name`` is not set on the instance,
122 the name is computed from the target name::
124 def build(bld):
125 x = bld(name='foo')
126 x.get_name() # foo
127 y = bld(target='bar')
128 y.get_name() # bar
130 :rtype: string
131 :return: name of this task generator
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
145 name = property(get_name, set_name)
147 def to_list(self, val):
149 Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list`
151 :type val: string or list of string
152 :param val: input to return as a list
153 :rtype: list
155 if isinstance(val, str):
156 return val.split()
157 else:
158 return val
160 def post(self):
162 Creates tasks for this task generators. The following operations are performed:
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`
170 if getattr(self, 'posted', None):
171 return False
172 self.posted = True
174 keys = set(self.meths)
175 keys.update(feats['*'])
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)
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]
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)
202 tmp.sort(reverse=True)
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)
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
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))
241 Logs.debug('task_gen: posted %s', self.name)
242 return True
244 def get_hook(self, node):
246 Returns the ``@extension`` method to call for a Node of a particular extension.
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
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))
265 def create_task(self, name, src=None, tgt=None, **kw):
267 Creates task instances.
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`
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
287 def clone(self, env):
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.
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`
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)))
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()
313 return newobj
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):
318 Creates a new mapping and a task class for processing files by extension.
319 See Tools/flex.py for an example.
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
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)
350 def x_file(self, node):
351 if ext_in:
352 _ext_in = ext_in[0]
354 tsk = self.create_task(name, node)
355 cnt = 0
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)
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
373 if install_path:
374 self.install_task = self.add_install_files(install_to=install_path, install_from=tsk.outputs)
375 return tsk
377 for x in cls.ext_in:
378 task_gen.mappings[x] = x_file
379 return x_file
381 def taskgen_method(func):
383 Decorator that registers method as a task generator method.
384 The function must accept a task generator as first parameter::
386 from waflib.TaskGen import taskgen_method
387 @taskgen_method
388 def mymethod(self):
389 pass
391 :param func: task generator method to add
392 :type func: function
393 :rtype: function
395 setattr(task_gen, func.__name__, func)
396 return func
398 def feature(*k):
400 Decorator that registers a task generator method that will be executed when the
401 object attribute ``feature`` contains the corresponding key(s)::
403 from waflib.Task import feature
404 @feature('myfeature')
405 def myfunction(self):
406 print('that is my feature!')
407 def build(bld):
408 bld(features='myfeature')
410 :param k: feature names
411 :type k: list of string
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
420 def before_method(*k):
422 Decorator that registera task generator method which will be executed
423 before the functions of given name(s)::
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')
436 :param k: method names
437 :type k: list of string
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
447 def after_method(*k):
449 Decorator that registers a task generator method which will be executed
450 after the functions of given name(s)::
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')
463 :param k: method names
464 :type k: list of string
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
474 def extension(*k):
476 Decorator that registers a task generator method which will be invoked during
477 the processing of source files for the extension given::
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')
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
495 @taskgen_method
496 def to_nodes(self, lst, path=None):
498 Flatten the input list of string/nodes/lists into a list of nodes.
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`:
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`
509 tmp = []
510 path = path or self.path
511 find = path.find_resource
513 if isinstance(lst, Node.Node):
514 lst = [lst]
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
529 @feature('*')
530 def process_source(self):
532 Processes each element in the attribute ``source`` by extension.
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)
540 self.source = self.to_nodes(getattr(self, 'source', []))
541 for node in self.source:
542 self.get_hook(node)(self, node)
544 @feature('*')
545 @before_method('process_source')
546 def process_rule(self):
548 Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
550 def build(bld):
551 bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
553 Main attributes processed:
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)
572 if not getattr(self, 'rule', None):
573 return
575 # create the task class
576 name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
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 = {}
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)
594 scan_val = has_deps = hasattr(self, 'deps')
595 if scan:
596 scan_val = id(scan)
598 key = Utils.h_list((name, self.rule, chmod, shell, color, cls_str, cls_keyword, scan_val, _vars, deep_inputs))
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)
619 cls = Task.task_factory(name, rule, _vars, shell=shell, color=color)
621 if cls_str:
622 setattr(cls, '__str__', self.cls_str)
624 if cls_keyword:
625 setattr(cls, 'keyword', self.cls_keyword)
627 if deep_inputs:
628 Task.deep_inputs(cls)
630 if scan:
631 cls.scan = self.scan
632 elif has_deps:
633 def scan(self):
634 nodes = []
635 for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
636 node = self.generator.path.find_resource(x)
637 if not node:
638 self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
639 nodes.append(node)
640 return [nodes, []]
641 cls.scan = scan
643 if use_cache:
644 cache[key] = cls
646 # now create one instance
647 tsk = self.create_task(name)
649 for x in ('after', 'before', 'ext_in', 'ext_out'):
650 setattr(tsk, x, getattr(self, x, []))
652 if hasattr(self, 'stdout'):
653 tsk.stdout = self.stdout
655 if hasattr(self, 'stderr'):
656 tsk.stderr = self.stderr
658 if getattr(self, 'timeout', None):
659 tsk.timeout = self.timeout
661 if getattr(self, 'always', None):
662 tsk.always_run = True
664 if getattr(self, 'target', None):
665 if isinstance(self.target, str):
666 self.target = self.target.split()
667 if not isinstance(self.target, list):
668 self.target = [self.target]
669 for x in self.target:
670 if isinstance(x, str):
671 tsk.outputs.append(self.path.find_or_declare(x))
672 else:
673 x.parent.mkdir() # if a node was given, create the required folders
674 tsk.outputs.append(x)
675 if getattr(self, 'install_path', None):
676 self.install_task = self.add_install_files(install_to=self.install_path,
677 install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644))
679 if getattr(self, 'source', None):
680 tsk.inputs = self.to_nodes(self.source)
681 # bypass the execution of process_source by setting the source to an empty list
682 self.source = []
684 if getattr(self, 'cwd', None):
685 tsk.cwd = self.cwd
687 if isinstance(tsk.run, functools.partial):
688 # Python documentation says: "partial objects defined in classes
689 # behave like static methods and do not transform into bound
690 # methods during instance attribute look-up."
691 tsk.run = functools.partial(tsk.run, tsk)
693 @feature('seq')
694 def sequence_order(self):
696 Adds a strict sequential constraint between the tasks generated by task generators.
697 It works because task generators are posted in order.
698 It will not post objects which belong to other folders.
700 Example::
702 bld(features='javac seq')
703 bld(features='jar seq')
705 To start a new sequence, set the attribute seq_start, for example::
707 obj = bld(features='seq')
708 obj.seq_start = True
710 Note that the method is executed in last position. This is more an
711 example than a widely-used solution.
713 if self.meths and self.meths[-1] != 'sequence_order':
714 self.meths.append('sequence_order')
715 return
717 if getattr(self, 'seq_start', None):
718 return
720 # all the tasks previously declared must be run before these
721 if getattr(self.bld, 'prev', None):
722 self.bld.prev.post()
723 for x in self.bld.prev.tasks:
724 for y in self.tasks:
725 y.set_run_after(x)
727 self.bld.prev = self
730 re_m4 = re.compile('@(\w+)@', re.M)
732 class subst_pc(Task.Task):
734 Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
735 in the substitution changes.
738 def force_permissions(self):
739 "Private for the time being, we will probably refactor this into run_str=[run1,chmod]"
740 if getattr(self.generator, 'chmod', None):
741 for x in self.outputs:
742 os.chmod(x.abspath(), self.generator.chmod)
744 def run(self):
745 "Substitutes variables in a .in file"
747 if getattr(self.generator, 'is_copy', None):
748 for i, x in enumerate(self.outputs):
749 x.write(self.inputs[i].read('rb'), 'wb')
750 stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy
751 os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime))
752 self.force_permissions()
753 return None
755 if getattr(self.generator, 'fun', None):
756 ret = self.generator.fun(self)
757 if not ret:
758 self.force_permissions()
759 return ret
761 code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1'))
762 if getattr(self.generator, 'subst_fun', None):
763 code = self.generator.subst_fun(self, code)
764 if code is not None:
765 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
766 self.force_permissions()
767 return None
769 # replace all % by %% to prevent errors by % signs
770 code = code.replace('%', '%%')
772 # extract the vars foo into lst and replace @foo@ by %(foo)s
773 lst = []
774 def repl(match):
775 g = match.group
776 if g(1):
777 lst.append(g(1))
778 return "%%(%s)s" % g(1)
779 return ''
780 code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
782 try:
783 d = self.generator.dct
784 except AttributeError:
785 d = {}
786 for x in lst:
787 tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
788 try:
789 tmp = ''.join(tmp)
790 except TypeError:
791 tmp = str(tmp)
792 d[x] = tmp
794 code = code % d
795 self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
796 self.generator.bld.raw_deps[self.uid()] = lst
798 # make sure the signature is updated
799 try:
800 delattr(self, 'cache_sig')
801 except AttributeError:
802 pass
804 self.force_permissions()
806 def sig_vars(self):
808 Compute a hash (signature) of the variables used in the substitution
810 bld = self.generator.bld
811 env = self.env
812 upd = self.m.update
814 if getattr(self.generator, 'fun', None):
815 upd(Utils.h_fun(self.generator.fun).encode())
816 if getattr(self.generator, 'subst_fun', None):
817 upd(Utils.h_fun(self.generator.subst_fun).encode())
819 # raw_deps: persistent custom values returned by the scanner
820 vars = self.generator.bld.raw_deps.get(self.uid(), [])
822 # hash both env vars and task generator attributes
823 act_sig = bld.hash_env_vars(env, vars)
824 upd(act_sig)
826 lst = [getattr(self.generator, x, '') for x in vars]
827 upd(Utils.h_list(lst))
829 return self.m.digest()
831 @extension('.pc.in')
832 def add_pcfile(self, node):
834 Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
836 def build(bld):
837 bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
839 tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
840 self.install_task = self.add_install_files(
841 install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs)
843 class subst(subst_pc):
844 pass
846 @feature('subst')
847 @before_method('process_source', 'process_rule')
848 def process_subst(self):
850 Defines a transformation that substitutes the contents of *source* files to *target* files::
852 def build(bld):
853 bld(
854 features='subst',
855 source='foo.c.in',
856 target='foo.c',
857 install_path='${LIBDIR}/pkgconfig',
858 VAR = 'val'
861 The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
862 of the task generator object.
864 This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
867 src = Utils.to_list(getattr(self, 'source', []))
868 if isinstance(src, Node.Node):
869 src = [src]
870 tgt = Utils.to_list(getattr(self, 'target', []))
871 if isinstance(tgt, Node.Node):
872 tgt = [tgt]
873 if len(src) != len(tgt):
874 raise Errors.WafError('invalid number of source/target for %r' % self)
876 for x, y in zip(src, tgt):
877 if not x or not y:
878 raise Errors.WafError('null source or target for %r' % self)
879 a, b = None, None
881 if isinstance(x, str) and isinstance(y, str) and x == y:
882 a = self.path.find_node(x)
883 b = self.path.get_bld().make_node(y)
884 if not os.path.isfile(b.abspath()):
885 b.parent.mkdir()
886 else:
887 if isinstance(x, str):
888 a = self.path.find_resource(x)
889 elif isinstance(x, Node.Node):
890 a = x
891 if isinstance(y, str):
892 b = self.path.find_or_declare(y)
893 elif isinstance(y, Node.Node):
894 b = y
896 if not a:
897 raise Errors.WafError('could not find %r for %r' % (x, self))
899 tsk = self.create_task('subst', a, b)
900 for k in ('after', 'before', 'ext_in', 'ext_out'):
901 val = getattr(self, k, None)
902 if val:
903 setattr(tsk, k, val)
905 # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
906 for xt in HEADER_EXTS:
907 if b.name.endswith(xt):
908 tsk.ext_in = tsk.ext_in + ['.h']
909 break
911 inst_to = getattr(self, 'install_path', None)
912 if inst_to:
913 self.install_task = self.add_install_files(install_to=inst_to,
914 install_from=b, chmod=getattr(self, 'chmod', Utils.O644))
916 self.source = []