third_party/waf: upgrade to waf 2.0.8
[samba.git] / third_party / waf / waflib / TaskGen.py
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 = self.bld.path # 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.Task 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 rebuilts, 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                                 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
642
643                 if use_cache:
644                         cache[key] = cls
645
646         # now create one instance
647         tsk = self.create_task(name)
648
649         for x in ('after', 'before', 'ext_in', 'ext_out'):
650                 setattr(tsk, x, getattr(self, x, []))
651
652         if hasattr(self, 'stdout'):
653                 tsk.stdout = self.stdout
654
655         if hasattr(self, 'stderr'):
656                 tsk.stderr = self.stderr
657
658         if getattr(self, 'timeout', None):
659                 tsk.timeout = self.timeout
660
661         if getattr(self, 'always', None):
662                 tsk.always_run = True
663
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))
678
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 = []
683
684         if getattr(self, 'cwd', None):
685                 tsk.cwd = self.cwd
686
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)
692
693 @feature('seq')
694 def sequence_order(self):
695         """
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.
699
700         Example::
701
702                 bld(features='javac seq')
703                 bld(features='jar seq')
704
705         To start a new sequence, set the attribute seq_start, for example::
706
707                 obj = bld(features='seq')
708                 obj.seq_start = True
709
710         Note that the method is executed in last position. This is more an
711         example than a widely-used solution.
712         """
713         if self.meths and self.meths[-1] != 'sequence_order':
714                 self.meths.append('sequence_order')
715                 return
716
717         if getattr(self, 'seq_start', None):
718                 return
719
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)
726
727         self.bld.prev = self
728
729
730 re_m4 = re.compile('@(\w+)@', re.M)
731
732 class subst_pc(Task.Task):
733         """
734         Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
735         in the substitution changes.
736         """
737
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)
743
744         def run(self):
745                 "Substitutes variables in a .in file"
746
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
754
755                 if getattr(self.generator, 'fun', None):
756                         ret = self.generator.fun(self)
757                         if not ret:
758                                 self.force_permissions()
759                         return ret
760
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
768
769                 # replace all % by %% to prevent errors by % signs
770                 code = code.replace('%', '%%')
771
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)
781
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
793
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
797
798                 # make sure the signature is updated
799                 try:
800                         delattr(self, 'cache_sig')
801                 except AttributeError:
802                         pass
803
804                 self.force_permissions()
805
806         def sig_vars(self):
807                 """
808                 Compute a hash (signature) of the variables used in the substitution
809                 """
810                 bld = self.generator.bld
811                 env = self.env
812                 upd = self.m.update
813
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())
818
819                 # raw_deps: persistent custom values returned by the scanner
820                 vars = self.generator.bld.raw_deps.get(self.uid(), [])
821
822                 # hash both env vars and task generator attributes
823                 act_sig = bld.hash_env_vars(env, vars)
824                 upd(act_sig)
825
826                 lst = [getattr(self.generator, x, '') for x in vars]
827                 upd(Utils.h_list(lst))
828
829                 return self.m.digest()
830
831 @extension('.pc.in')
832 def add_pcfile(self, node):
833         """
834         Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
835
836                 def build(bld):
837                         bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
838         """
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)
842
843 class subst(subst_pc):
844         pass
845
846 @feature('subst')
847 @before_method('process_source', 'process_rule')
848 def process_subst(self):
849         """
850         Defines a transformation that substitutes the contents of *source* files to *target* files::
851
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'
859                         )
860
861         The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
862         of the task generator object.
863
864         This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
865         """
866
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)
875
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
880
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
895
896                 if not a:
897                         raise Errors.WafError('could not find %r for %r' % (x, self))
898
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)
904
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
910
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))
915
916         self.source = []
917