ctdb-scripts: Support storing statd-callout state in cluster filesystem
[samba4-gss.git] / third_party / waf / waflib / TaskGen.py
blob32468f03d3c765952064adf198fd000b3e9899ed
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 = kw.get('path', self.bld.path) # by default, 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.TaskGen 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 deps = getattr(self.generator, 'deps', None)
635 nodes = self.generator.to_nodes(deps)
636 return [nodes, []]
637 cls.scan = scan
639 if use_cache:
640 cache[key] = cls
642 # now create one instance
643 tsk = self.create_task(name)
645 for x in ('after', 'before', 'ext_in', 'ext_out'):
646 setattr(tsk, x, getattr(self, x, []))
648 if hasattr(self, 'stdout'):
649 tsk.stdout = self.stdout
651 if hasattr(self, 'stderr'):
652 tsk.stderr = self.stderr
654 if getattr(self, 'timeout', None):
655 tsk.timeout = self.timeout
657 if getattr(self, 'always', None):
658 tsk.always_run = True
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))
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 = []
680 if getattr(self, 'cwd', None):
681 tsk.cwd = self.cwd
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)
689 @feature('seq')
690 def sequence_order(self):
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.
696 Example::
698 bld(features='javac seq')
699 bld(features='jar seq')
701 To start a new sequence, set the attribute seq_start, for example::
703 obj = bld(features='seq')
704 obj.seq_start = True
706 Note that the method is executed in last position. This is more an
707 example than a widely-used solution.
709 if self.meths and self.meths[-1] != 'sequence_order':
710 self.meths.append('sequence_order')
711 return
713 if getattr(self, 'seq_start', None):
714 return
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)
723 self.bld.prev = self
726 re_m4 = re.compile(r'@(\w+)@', re.M)
728 class subst_pc(Task.Task):
730 Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
731 in the substitution changes.
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)
740 def run(self):
741 "Substitutes variables in a .in file"
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
751 if getattr(self.generator, 'fun', None):
752 ret = self.generator.fun(self)
753 if not ret:
754 self.force_permissions()
755 return ret
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
765 # replace all % by %% to prevent errors by % signs
766 code = code.replace('%', '%%')
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)
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
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
794 # make sure the signature is updated
795 try:
796 delattr(self, 'cache_sig')
797 except AttributeError:
798 pass
800 self.force_permissions()
802 def sig_vars(self):
804 Compute a hash (signature) of the variables used in the substitution
806 bld = self.generator.bld
807 env = self.env
808 upd = self.m.update
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())
815 # raw_deps: persistent custom values returned by the scanner
816 vars = self.generator.bld.raw_deps.get(self.uid(), [])
818 # hash both env vars and task generator attributes
819 act_sig = bld.hash_env_vars(env, vars)
820 upd(act_sig)
822 lst = [getattr(self.generator, x, '') for x in vars]
823 upd(Utils.h_list(lst))
825 return self.m.digest()
827 @extension('.pc.in')
828 def add_pcfile(self, node):
830 Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
832 def build(bld):
833 bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
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)
839 class subst(subst_pc):
840 pass
842 @feature('subst')
843 @before_method('process_source', 'process_rule')
844 def process_subst(self):
846 Defines a transformation that substitutes the contents of *source* files to *target* files::
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'
857 The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
858 of the task generator object.
860 This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
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)
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
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
892 if not a:
893 raise Errors.WafError('could not find %r for %r' % (x, self))
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)
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
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))
912 self.source = []