third_party:waf: update to upstream 2.0.4 release
[samba.git] / third_party / waf / waflib / Task.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
4
5 #!/usr/bin/env python
6 # encoding: utf-8
7 # Thomas Nagy, 2005-2018 (ita)
8
9 """
10 Tasks represent atomic operations such as processes.
11 """
12
13 import os, re, sys, tempfile, traceback
14 from waflib import Utils, Logs, Errors
15
16 # task states
17 NOT_RUN = 0
18 """The task was not executed yet"""
19
20 MISSING = 1
21 """The task has been executed but the files have not been created"""
22
23 CRASHED = 2
24 """The task execution returned a non-zero exit status"""
25
26 EXCEPTION = 3
27 """An exception occurred in the task execution"""
28
29 CANCELED = 4
30 """A dependency for the task is missing so it was cancelled"""
31
32 SKIPPED = 8
33 """The task did not have to be executed"""
34
35 SUCCESS = 9
36 """The task was successfully executed"""
37
38 ASK_LATER = -1
39 """The task is not ready to be executed"""
40
41 SKIP_ME = -2
42 """The task does not need to be executed"""
43
44 RUN_ME = -3
45 """The task must be executed"""
46
47 CANCEL_ME = -4
48 """The task cannot be executed because of a dependency problem"""
49
50 COMPILE_TEMPLATE_SHELL = '''
51 def f(tsk):
52         env = tsk.env
53         gen = tsk.generator
54         bld = gen.bld
55         cwdx = tsk.get_cwd()
56         p = env.get_flat
57         tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
58         return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
59 '''
60
61 COMPILE_TEMPLATE_NOSHELL = '''
62 def f(tsk):
63         env = tsk.env
64         gen = tsk.generator
65         bld = gen.bld
66         cwdx = tsk.get_cwd()
67         def to_list(xx):
68                 if isinstance(xx, str): return [xx]
69                 return xx
70         def merge(lst1, lst2):
71                 if lst1 and lst2:
72                         return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
73                 return lst1 + lst2
74         lst = []
75         %s
76         if '' in lst:
77                 lst = [x for x in lst if x]
78         tsk.last_cmd = lst
79         return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
80 '''
81
82 classes = {}
83 """
84 The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
85 created by user scripts or Waf tools to this dict. It maps class names to class objects.
86 """
87
88 class store_task_type(type):
89         """
90         Metaclass: store the task classes into the dict pointed by the
91         class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
92
93         The attribute 'run_str' is compiled into a method 'run' bound to the task class.
94         """
95         def __init__(cls, name, bases, dict):
96                 super(store_task_type, cls).__init__(name, bases, dict)
97                 name = cls.__name__
98
99                 if name != 'evil' and name != 'Task':
100                         if getattr(cls, 'run_str', None):
101                                 # if a string is provided, convert it to a method
102                                 (f, dvars) = compile_fun(cls.run_str, cls.shell)
103                                 cls.hcode = Utils.h_cmd(cls.run_str)
104                                 cls.orig_run_str = cls.run_str
105                                 # change the name of run_str or it is impossible to subclass with a function
106                                 cls.run_str = None
107                                 cls.run = f
108                                 cls.vars = list(set(cls.vars + dvars))
109                                 cls.vars.sort()
110                         elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
111                                 # getattr(cls, 'hcode') would look in the upper classes
112                                 cls.hcode = Utils.h_cmd(cls.run)
113
114                         # be creative
115                         getattr(cls, 'register', classes)[name] = cls
116
117 evil = store_task_type('evil', (object,), {})
118 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
119
120 class Task(evil):
121         """
122         This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
123         uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
124         the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
125         nodes (if present).
126         """
127         vars = []
128         """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
129
130         always_run = False
131         """Specify whether task instances must always be executed or not (class attribute)"""
132
133         shell = False
134         """Execute the command with the shell (class attribute)"""
135
136         color = 'GREEN'
137         """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
138
139         ext_in = []
140         """File extensions that objects of this task class may use"""
141
142         ext_out = []
143         """File extensions that objects of this task class may create"""
144
145         before = []
146         """List of task class names to execute before instances of this class"""
147
148         after = []
149         """List of task class names to execute after instances of this class"""
150
151         hcode = Utils.SIG_NIL
152         """String representing an additional hash for the class representation"""
153
154         keep_last_cmd = False
155         """Whether to keep the last command executed on the instance after execution.
156         This may be useful for certain extensions but it can a lot of memory.
157         """
158
159         weight = 0
160         """Optional weight to tune the priority for task instances.
161         The higher, the earlier. The weight only applies to single task objects."""
162
163         tree_weight = 0
164         """Optional weight to tune the priority of task instances and whole subtrees.
165         The higher, the earlier."""
166
167         prio_order = 0
168         """Priority order set by the scheduler on instances during the build phase.
169         You most likely do not need to set it.
170         """
171
172         __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
173
174         def __init__(self, *k, **kw):
175                 self.hasrun = NOT_RUN
176                 try:
177                         self.generator = kw['generator']
178                 except KeyError:
179                         self.generator = self
180
181                 self.env = kw['env']
182                 """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
183
184                 self.inputs  = []
185                 """List of input nodes, which represent the files used by the task instance"""
186
187                 self.outputs = []
188                 """List of output nodes, which represent the files created by the task instance"""
189
190                 self.dep_nodes = []
191                 """List of additional nodes to depend on"""
192
193                 self.run_after = set()
194                 """Set of tasks that must be executed before this one"""
195
196         def __lt__(self, other):
197                 return self.priority() > other.priority()
198         def __le__(self, other):
199                 return self.priority() >= other.priority()
200         def __gt__(self, other):
201                 return self.priority() < other.priority()
202         def __ge__(self, other):
203                 return self.priority() <= other.priority()
204
205         def get_cwd(self):
206                 """
207                 :return: current working directory
208                 :rtype: :py:class:`waflib.Node.Node`
209                 """
210                 bld = self.generator.bld
211                 ret = getattr(self, 'cwd', None) or getattr(bld, 'cwd', bld.bldnode)
212                 if isinstance(ret, str):
213                         if os.path.isabs(ret):
214                                 ret = bld.root.make_node(ret)
215                         else:
216                                 ret = self.generator.path.make_node(ret)
217                 return ret
218
219         def quote_flag(self, x):
220                 """
221                 Surround a process argument by quotes so that a list of arguments can be written to a file
222
223                 :param x: flag
224                 :type x: string
225                 :return: quoted flag
226                 :rtype: string
227                 """
228                 old = x
229                 if '\\' in x:
230                         x = x.replace('\\', '\\\\')
231                 if '"' in x:
232                         x = x.replace('"', '\\"')
233                 if old != x or ' ' in x or '\t' in x or "'" in x:
234                         x = '"%s"' % x
235                 return x
236
237         def priority(self):
238                 """
239                 Priority of execution; the higher, the earlier
240
241                 :return: the priority value
242                 :rtype: a tuple of numeric values
243                 """
244                 return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
245
246         def split_argfile(self, cmd):
247                 """
248                 Splits a list of process commands into the executable part and its list of arguments
249
250                 :return: a tuple containing the executable first and then the rest of arguments
251                 :rtype: tuple
252                 """
253                 return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
254
255         def exec_command(self, cmd, **kw):
256                 """
257                 Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
258                 This version set the current working directory (``build.variant_dir``),
259                 applies PATH settings (if self.env.PATH is provided), and can run long
260                 commands through a temporary ``@argfile``.
261
262                 :param cmd: process command to execute
263                 :type cmd: list of string (best) or string (process will use a shell)
264                 :return: the return code
265                 :rtype: int
266
267                 Optional parameters:
268
269                 #. cwd: current working directory (Node or string)
270                 #. stdout: set to None to prevent waf from capturing the process standard output
271                 #. stderr: set to None to prevent waf from capturing the process standard error
272                 #. timeout: timeout value (Python 3)
273                 """
274                 if not 'cwd' in kw:
275                         kw['cwd'] = self.get_cwd()
276
277                 if hasattr(self, 'timeout'):
278                         kw['timeout'] = self.timeout
279
280                 if self.env.PATH:
281                         env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
282                         env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
283
284                 if hasattr(self, 'stdout'):
285                         kw['stdout'] = self.stdout
286                 if hasattr(self, 'stderr'):
287                         kw['stderr'] = self.stderr
288
289                 # workaround for command line length limit:
290                 # http://support.microsoft.com/kb/830473
291                 if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
292                         cmd, args = self.split_argfile(cmd)
293                         try:
294                                 (fd, tmp) = tempfile.mkstemp()
295                                 os.write(fd, '\r\n'.join(args).encode())
296                                 os.close(fd)
297                                 if Logs.verbose:
298                                         Logs.debug('argfile: @%r -> %r', tmp, args)
299                                 return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
300                         finally:
301                                 try:
302                                         os.remove(tmp)
303                                 except OSError:
304                                         # anti-virus and indexers can keep files open -_-
305                                         pass
306                 else:
307                         return self.generator.bld.exec_command(cmd, **kw)
308
309         def process(self):
310                 """
311                 Runs the task and handles errors
312
313                 :return: 0 or None if everything is fine
314                 :rtype: integer
315                 """
316                 # remove the task signature immediately before it is executed
317                 # so that the task will be executed again in case of failure
318                 try:
319                         del self.generator.bld.task_sigs[self.uid()]
320                 except KeyError:
321                         pass
322
323                 try:
324                         ret = self.run()
325                 except Exception:
326                         self.err_msg = traceback.format_exc()
327                         self.hasrun = EXCEPTION
328                 else:
329                         if ret:
330                                 self.err_code = ret
331                                 self.hasrun = CRASHED
332                         else:
333                                 try:
334                                         self.post_run()
335                                 except Errors.WafError:
336                                         pass
337                                 except Exception:
338                                         self.err_msg = traceback.format_exc()
339                                         self.hasrun = EXCEPTION
340                                 else:
341                                         self.hasrun = SUCCESS
342
343                 if self.hasrun != SUCCESS and self.scan:
344                         # rescan dependencies on next run
345                         try:
346                                 del self.generator.bld.imp_sigs[self.uid()]
347                         except KeyError:
348                                 pass
349
350         def log_display(self, bld):
351                 "Writes the execution status on the context logger"
352                 if self.generator.bld.progress_bar == 3:
353                         return
354
355                 s = self.display()
356                 if s:
357                         if bld.logger:
358                                 logger = bld.logger
359                         else:
360                                 logger = Logs
361
362                         if self.generator.bld.progress_bar == 1:
363                                 c1 = Logs.colors.cursor_off
364                                 c2 = Logs.colors.cursor_on
365                                 logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
366                         else:
367                                 logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
368
369         def display(self):
370                 """
371                 Returns an execution status for the console, the progress bar, or the IDE output.
372
373                 :rtype: string
374                 """
375                 col1 = Logs.colors(self.color)
376                 col2 = Logs.colors.NORMAL
377                 master = self.generator.bld.producer
378
379                 def cur():
380                         # the current task position, computed as late as possible
381                         return master.processed - master.ready.qsize()
382
383                 if self.generator.bld.progress_bar == 1:
384                         return self.generator.bld.progress_line(cur(), master.total, col1, col2)
385
386                 if self.generator.bld.progress_bar == 2:
387                         ela = str(self.generator.bld.timer)
388                         try:
389                                 ins  = ','.join([n.name for n in self.inputs])
390                         except AttributeError:
391                                 ins = ''
392                         try:
393                                 outs = ','.join([n.name for n in self.outputs])
394                         except AttributeError:
395                                 outs = ''
396                         return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
397
398                 s = str(self)
399                 if not s:
400                         return None
401
402                 total = master.total
403                 n = len(str(total))
404                 fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
405                 kw = self.keyword()
406                 if kw:
407                         kw += ' '
408                 return fs % (cur(), total, kw, col1, s, col2)
409
410         def hash_constraints(self):
411                 """
412                 Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
413
414                 :return: a hash value
415                 :rtype: string
416                 """
417                 return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
418
419         def format_error(self):
420                 """
421                 Returns an error message to display the build failure reasons
422
423                 :rtype: string
424                 """
425                 if Logs.verbose:
426                         msg = ': %r\n%r' % (self, getattr(self, 'last_cmd', ''))
427                 else:
428                         msg = ' (run with -v to display more information)'
429                 name = getattr(self.generator, 'name', '')
430                 if getattr(self, "err_msg", None):
431                         return self.err_msg
432                 elif not self.hasrun:
433                         return 'task in %r was not executed for some reason: %r' % (name, self)
434                 elif self.hasrun == CRASHED:
435                         try:
436                                 return ' -> task in %r failed with exit status %r%s' % (name, self.err_code, msg)
437                         except AttributeError:
438                                 return ' -> task in %r failed%s' % (name, msg)
439                 elif self.hasrun == MISSING:
440                         return ' -> missing files in %r%s' % (name, msg)
441                 elif self.hasrun == CANCELED:
442                         return ' -> %r canceled because of missing dependencies' % name
443                 else:
444                         return 'invalid status for task in %r: %r' % (name, self.hasrun)
445
446         def colon(self, var1, var2):
447                 """
448                 Enable scriptlet expressions of the form ${FOO_ST:FOO}
449                 If the first variable (FOO_ST) is empty, then an empty list is returned
450
451                 The results will be slightly different if FOO_ST is a list, for example::
452
453                         env.FOO    = ['p1', 'p2']
454                         env.FOO_ST = '-I%s'
455                         # ${FOO_ST:FOO} returns
456                         ['-Ip1', '-Ip2']
457
458                         env.FOO_ST = ['-a', '-b']
459                         # ${FOO_ST:FOO} returns
460                         ['-a', '-b', 'p1', '-a', '-b', 'p2']
461                 """
462                 tmp = self.env[var1]
463                 if not tmp:
464                         return []
465
466                 if isinstance(var2, str):
467                         it = self.env[var2]
468                 else:
469                         it = var2
470                 if isinstance(tmp, str):
471                         return [tmp % x for x in it]
472                 else:
473                         lst = []
474                         for y in it:
475                                 lst.extend(tmp)
476                                 lst.append(y)
477                         return lst
478
479         def __str__(self):
480                 "string to display to the user"
481                 name = self.__class__.__name__
482                 if self.outputs:
483                         if name.endswith(('lib', 'program')) or not self.inputs:
484                                 node = self.outputs[0]
485                                 return node.path_from(node.ctx.launch_node())
486                 if not (self.inputs or self.outputs):
487                         return self.__class__.__name__
488                 if len(self.inputs) == 1:
489                         node = self.inputs[0]
490                         return node.path_from(node.ctx.launch_node())
491
492                 src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
493                 tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
494                 if self.outputs:
495                         sep = ' -> '
496                 else:
497                         sep = ''
498                 return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
499
500         def keyword(self):
501                 "Display keyword used to prettify the console outputs"
502                 name = self.__class__.__name__
503                 if name.endswith(('lib', 'program')):
504                         return 'Linking'
505                 if len(self.inputs) == 1 and len(self.outputs) == 1:
506                         return 'Compiling'
507                 if not self.inputs:
508                         if self.outputs:
509                                 return 'Creating'
510                         else:
511                                 return 'Running'
512                 return 'Processing'
513
514         def __repr__(self):
515                 "for debugging purposes"
516                 try:
517                         ins = ",".join([x.name for x in self.inputs])
518                         outs = ",".join([x.name for x in self.outputs])
519                 except AttributeError:
520                         ins = ",".join([str(x) for x in self.inputs])
521                         outs = ",".join([str(x) for x in self.outputs])
522                 return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
523
524         def uid(self):
525                 """
526                 Returns an identifier used to determine if tasks are up-to-date. Since the
527                 identifier will be stored between executions, it must be:
528
529                         - unique for a task: no two tasks return the same value (for a given build context)
530                         - the same for a given task instance
531
532                 By default, the node paths, the class name, and the function are used
533                 as inputs to compute a hash.
534
535                 The pointer to the object (python built-in 'id') will change between build executions,
536                 and must be avoided in such hashes.
537
538                 :return: hash value
539                 :rtype: string
540                 """
541                 try:
542                         return self.uid_
543                 except AttributeError:
544                         m = Utils.md5(self.__class__.__name__)
545                         up = m.update
546                         for x in self.inputs + self.outputs:
547                                 up(x.abspath())
548                         self.uid_ = m.digest()
549                         return self.uid_
550
551         def set_inputs(self, inp):
552                 """
553                 Appends the nodes to the *inputs* list
554
555                 :param inp: input nodes
556                 :type inp: node or list of nodes
557                 """
558                 if isinstance(inp, list):
559                         self.inputs += inp
560                 else:
561                         self.inputs.append(inp)
562
563         def set_outputs(self, out):
564                 """
565                 Appends the nodes to the *outputs* list
566
567                 :param out: output nodes
568                 :type out: node or list of nodes
569                 """
570                 if isinstance(out, list):
571                         self.outputs += out
572                 else:
573                         self.outputs.append(out)
574
575         def set_run_after(self, task):
576                 """
577                 Run this task only after the given *task*.
578
579                 :param task: task
580                 :type task: :py:class:`waflib.Task.Task`
581                 """
582                 assert isinstance(task, Task)
583                 self.run_after.add(task)
584
585         def signature(self):
586                 """
587                 Task signatures are stored between build executions, they are use to track the changes
588                 made to the input nodes (not to the outputs!). The signature hashes data from various sources:
589
590                 * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
591                 * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
592                 * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
593
594                 If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
595
596                         from waflib import Task
597                         class cls(Task.Task):
598                                 def signature(self):
599                                         sig = super(Task.Task, self).signature()
600                                         delattr(self, 'cache_sig')
601                                         return super(Task.Task, self).signature()
602
603                 :return: the signature value
604                 :rtype: string or bytes
605                 """
606                 try:
607                         return self.cache_sig
608                 except AttributeError:
609                         pass
610
611                 self.m = Utils.md5(self.hcode)
612
613                 # explicit deps
614                 self.sig_explicit_deps()
615
616                 # env vars
617                 self.sig_vars()
618
619                 # implicit deps / scanner results
620                 if self.scan:
621                         try:
622                                 self.sig_implicit_deps()
623                         except Errors.TaskRescan:
624                                 return self.signature()
625
626                 ret = self.cache_sig = self.m.digest()
627                 return ret
628
629         def runnable_status(self):
630                 """
631                 Returns the Task status
632
633                 :return: a task state in :py:const:`waflib.Task.RUN_ME`,
634                         :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
635                 :rtype: int
636                 """
637                 bld = self.generator.bld
638                 if bld.is_install < 0:
639                         return SKIP_ME
640
641                 for t in self.run_after:
642                         if not t.hasrun:
643                                 return ASK_LATER
644                         elif t.hasrun < SKIPPED:
645                                 # a dependency has an error
646                                 return CANCEL_ME
647
648                 # first compute the signature
649                 try:
650                         new_sig = self.signature()
651                 except Errors.TaskNotReady:
652                         return ASK_LATER
653
654                 # compare the signature to a signature computed previously
655                 key = self.uid()
656                 try:
657                         prev_sig = bld.task_sigs[key]
658                 except KeyError:
659                         Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
660                         return RUN_ME
661
662                 if new_sig != prev_sig:
663                         Logs.debug('task: task %r must run: the task signature changed', self)
664                         return RUN_ME
665
666                 # compare the signatures of the outputs
667                 for node in self.outputs:
668                         sig = bld.node_sigs.get(node)
669                         if not sig:
670                                 Logs.debug('task: task %r must run: an output node has no signature', self)
671                                 return RUN_ME
672                         if sig != key:
673                                 Logs.debug('task: task %r must run: an output node was produced by another task', self)
674                                 return RUN_ME
675                         if not node.exists():
676                                 Logs.debug('task: task %r must run: an output node does not exist', self)
677                                 return RUN_ME
678
679                 return (self.always_run and RUN_ME) or SKIP_ME
680
681         def post_run(self):
682                 """
683                 Called after successful execution to record that the task has run by
684                 updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
685                 """
686                 bld = self.generator.bld
687                 for node in self.outputs:
688                         if not node.exists():
689                                 self.hasrun = MISSING
690                                 self.err_msg = '-> missing file: %r' % node.abspath()
691                                 raise Errors.WafError(self.err_msg)
692                         bld.node_sigs[node] = self.uid() # make sure this task produced the files in question
693                 bld.task_sigs[self.uid()] = self.signature()
694                 if not self.keep_last_cmd:
695                         try:
696                                 del self.last_cmd
697                         except AttributeError:
698                                 pass
699
700         def sig_explicit_deps(self):
701                 """
702                 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
703                 and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
704                 """
705                 bld = self.generator.bld
706                 upd = self.m.update
707
708                 # the inputs
709                 for x in self.inputs + self.dep_nodes:
710                         upd(x.get_bld_sig())
711
712                 # manual dependencies, they can slow down the builds
713                 if bld.deps_man:
714                         additional_deps = bld.deps_man
715                         for x in self.inputs + self.outputs:
716                                 try:
717                                         d = additional_deps[x]
718                                 except KeyError:
719                                         continue
720
721                                 for v in d:
722                                         try:
723                                                 v = v.get_bld_sig()
724                                         except AttributeError:
725                                                 if hasattr(v, '__call__'):
726                                                         v = v() # dependency is a function, call it
727                                         upd(v)
728
729         def sig_vars(self):
730                 """
731                 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
732                 """
733                 sig = self.generator.bld.hash_env_vars(self.env, self.vars)
734                 self.m.update(sig)
735
736         scan = None
737         """
738         This method, when provided, returns a tuple containing:
739
740         * a list of nodes corresponding to real files
741         * a list of names for files not found in path_lst
742
743         For example::
744
745                 from waflib.Task import Task
746                 class mytask(Task):
747                         def scan(self, node):
748                                 return ([], [])
749
750         The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
751         :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
752         """
753
754         def sig_implicit_deps(self):
755                 """
756                 Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
757                 obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
758
759                 The exception :py:class:`waflib.Errors.TaskRescan` is thrown
760                 when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
761                 once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
762                 """
763                 bld = self.generator.bld
764
765                 # get the task signatures from previous runs
766                 key = self.uid()
767                 prev = bld.imp_sigs.get(key, [])
768
769                 # for issue #379
770                 if prev:
771                         try:
772                                 if prev == self.compute_sig_implicit_deps():
773                                         return prev
774                         except Errors.TaskNotReady:
775                                 raise
776                         except EnvironmentError:
777                                 # when a file was renamed, remove the stale nodes (headers in folders without source files)
778                                 # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
779                                 # the behaviour will differ when top != out
780                                 for x in bld.node_deps.get(self.uid(), []):
781                                         if not x.is_bld() and not x.exists():
782                                                 try:
783                                                         del x.parent.children[x.name]
784                                                 except KeyError:
785                                                         pass
786                         del bld.imp_sigs[key]
787                         raise Errors.TaskRescan('rescan')
788
789                 # no previous run or the signature of the dependencies has changed, rescan the dependencies
790                 (bld.node_deps[key], bld.raw_deps[key]) = self.scan()
791                 if Logs.verbose:
792                         Logs.debug('deps: scanner for %s: %r; unresolved: %r', self, bld.node_deps[key], bld.raw_deps[key])
793
794                 # recompute the signature and return it
795                 try:
796                         bld.imp_sigs[key] = self.compute_sig_implicit_deps()
797                 except EnvironmentError:
798                         for k in bld.node_deps.get(self.uid(), []):
799                                 if not k.exists():
800                                         Logs.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k, self)
801                         raise
802
803         def compute_sig_implicit_deps(self):
804                 """
805                 Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
806                 :py:class:`waflib.Node.Node` returned by the scanner.
807
808                 :return: a hash value for the implicit dependencies
809                 :rtype: string or bytes
810                 """
811                 upd = self.m.update
812                 self.are_implicit_nodes_ready()
813
814                 # scanner returns a node that does not have a signature
815                 # just *ignore* the error and let them figure out from the compiler output
816                 # waf -k behaviour
817                 for k in self.generator.bld.node_deps.get(self.uid(), []):
818                         upd(k.get_bld_sig())
819                 return self.m.digest()
820
821         def are_implicit_nodes_ready(self):
822                 """
823                 For each node returned by the scanner, see if there is a task that creates it,
824                 and infer the build order
825
826                 This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
827                 """
828                 bld = self.generator.bld
829                 try:
830                         cache = bld.dct_implicit_nodes
831                 except AttributeError:
832                         bld.dct_implicit_nodes = cache = {}
833
834                 # one cache per build group
835                 try:
836                         dct = cache[bld.current_group]
837                 except KeyError:
838                         dct = cache[bld.current_group] = {}
839                         for tsk in bld.cur_tasks:
840                                 for x in tsk.outputs:
841                                         dct[x] = tsk
842
843                 modified = False
844                 for x in bld.node_deps.get(self.uid(), []):
845                         if x in dct:
846                                 self.run_after.add(dct[x])
847                                 modified = True
848
849                 if modified:
850                         for tsk in self.run_after:
851                                 if not tsk.hasrun:
852                                         #print "task is not ready..."
853                                         raise Errors.TaskNotReady('not ready')
854 if sys.hexversion > 0x3000000:
855         def uid(self):
856                 try:
857                         return self.uid_
858                 except AttributeError:
859                         m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
860                         up = m.update
861                         for x in self.inputs + self.outputs:
862                                 up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
863                         self.uid_ = m.digest()
864                         return self.uid_
865         uid.__doc__ = Task.uid.__doc__
866         Task.uid = uid
867
868 def is_before(t1, t2):
869         """
870         Returns a non-zero value if task t1 is to be executed before task t2::
871
872                 t1.ext_out = '.h'
873                 t2.ext_in = '.h'
874                 t2.after = ['t1']
875                 t1.before = ['t2']
876                 waflib.Task.is_before(t1, t2) # True
877
878         :param t1: Task object
879         :type t1: :py:class:`waflib.Task.Task`
880         :param t2: Task object
881         :type t2: :py:class:`waflib.Task.Task`
882         """
883         to_list = Utils.to_list
884         for k in to_list(t2.ext_in):
885                 if k in to_list(t1.ext_out):
886                         return 1
887
888         if t1.__class__.__name__ in to_list(t2.after):
889                 return 1
890
891         if t2.__class__.__name__ in to_list(t1.before):
892                 return 1
893
894         return 0
895
896 def set_file_constraints(tasks):
897         """
898         Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
899
900         :param tasks: tasks
901         :type tasks: list of :py:class:`waflib.Task.Task`
902         """
903         ins = Utils.defaultdict(set)
904         outs = Utils.defaultdict(set)
905         for x in tasks:
906                 for a in x.inputs:
907                         ins[a].add(x)
908                 for a in x.dep_nodes:
909                         ins[a].add(x)
910                 for a in x.outputs:
911                         outs[a].add(x)
912
913         links = set(ins.keys()).intersection(outs.keys())
914         for k in links:
915                 for a in ins[k]:
916                         a.run_after.update(outs[k])
917
918
919 class TaskGroup(object):
920         """
921         Wrap nxm task order constraints into a single object
922         to prevent the creation of large list/set objects
923
924         This is an optimization
925         """
926         def __init__(self, prev, next):
927                 self.prev = prev
928                 self.next = next
929                 self.done = False
930
931         def get_hasrun(self):
932                 for k in self.prev:
933                         if not k.hasrun:
934                                 return NOT_RUN
935                 return SUCCESS
936
937         hasrun = property(get_hasrun, None)
938
939 def set_precedence_constraints(tasks):
940         """
941         Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
942
943         :param tasks: tasks
944         :type tasks: list of :py:class:`waflib.Task.Task`
945         """
946         cstr_groups = Utils.defaultdict(list)
947         for x in tasks:
948                 h = x.hash_constraints()
949                 cstr_groups[h].append(x)
950
951         keys = list(cstr_groups.keys())
952         maxi = len(keys)
953
954         # this list should be short
955         for i in range(maxi):
956                 t1 = cstr_groups[keys[i]][0]
957                 for j in range(i + 1, maxi):
958                         t2 = cstr_groups[keys[j]][0]
959
960                         # add the constraints based on the comparisons
961                         if is_before(t1, t2):
962                                 a = i
963                                 b = j
964                         elif is_before(t2, t1):
965                                 a = j
966                                 b = i
967                         else:
968                                 continue
969
970                         a = cstr_groups[keys[a]]
971                         b = cstr_groups[keys[b]]
972
973                         if len(a) < 2 or len(b) < 2:
974                                 for x in b:
975                                         x.run_after.update(a)
976                         else:
977                                 group = TaskGroup(set(a), set(b))
978                                 for x in b:
979                                         x.run_after.add(group)
980
981 def funex(c):
982         """
983         Compiles a scriptlet expression into a Python function
984
985         :param c: function to compile
986         :type c: string
987         :return: the function 'f' declared in the input string
988         :rtype: function
989         """
990         dc = {}
991         exec(c, dc)
992         return dc['f']
993
994 re_cond = re.compile('(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
995 re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
996 reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
997 def compile_fun_shell(line):
998         """
999         Creates a compiled function to execute a process through a sub-shell
1000         """
1001         extr = []
1002         def repl(match):
1003                 g = match.group
1004                 if g('dollar'):
1005                         return "$"
1006                 elif g('backslash'):
1007                         return '\\\\'
1008                 elif g('subst'):
1009                         extr.append((g('var'), g('code')))
1010                         return "%s"
1011                 return None
1012         line = reg_act.sub(repl, line) or line
1013         dvars = []
1014
1015         def replc(m):
1016                 # performs substitutions and populates dvars
1017                 if m.group('and'):
1018                         return ' and '
1019                 elif m.group('or'):
1020                         return ' or '
1021                 else:
1022                         x = m.group('var')
1023                         if x not in dvars:
1024                                 dvars.append(x)
1025                         return 'env[%r]' % x
1026
1027         parm = []
1028         app = parm.append
1029         for (var, meth) in extr:
1030                 if var == 'SRC':
1031                         if meth:
1032                                 app('tsk.inputs%s' % meth)
1033                         else:
1034                                 app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
1035                 elif var == 'TGT':
1036                         if meth:
1037                                 app('tsk.outputs%s' % meth)
1038                         else:
1039                                 app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
1040                 elif meth:
1041                         if meth.startswith(':'):
1042                                 if var not in dvars:
1043                                         dvars.append(var)
1044                                 m = meth[1:]
1045                                 if m == 'SRC':
1046                                         m = '[a.path_from(cwdx) for a in tsk.inputs]'
1047                                 elif m == 'TGT':
1048                                         m = '[a.path_from(cwdx) for a in tsk.outputs]'
1049                                 elif re_novar.match(m):
1050                                         m = '[tsk.inputs%s]' % m[3:]
1051                                 elif re_novar.match(m):
1052                                         m = '[tsk.outputs%s]' % m[3:]
1053                                 elif m[:3] not in ('tsk', 'gen', 'bld'):
1054                                         dvars.append(meth[1:])
1055                                         m = '%r' % m
1056                                 app('" ".join(tsk.colon(%r, %s))' % (var, m))
1057                         elif meth.startswith('?'):
1058                                 # In A?B|C output env.A if one of env.B or env.C is non-empty
1059                                 expr = re_cond.sub(replc, meth[1:])
1060                                 app('p(%r) if (%s) else ""' % (var, expr))
1061                         else:
1062                                 app('%s%s' % (var, meth))
1063                 else:
1064                         if var not in dvars:
1065                                 dvars.append(var)
1066                         app("p('%s')" % var)
1067         if parm:
1068                 parm = "%% (%s) " % (',\n\t\t'.join(parm))
1069         else:
1070                 parm = ''
1071
1072         c = COMPILE_TEMPLATE_SHELL % (line, parm)
1073         Logs.debug('action: %s', c.strip().splitlines())
1074         return (funex(c), dvars)
1075
1076 reg_act_noshell = re.compile(r"(?P<space>\s+)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})|(?P<text>([^$ \t\n\r\f\v]|\$\$)+)", re.M)
1077 def compile_fun_noshell(line):
1078         """
1079         Creates a compiled function to execute a process without a sub-shell
1080         """
1081         buf = []
1082         dvars = []
1083         merge = False
1084         app = buf.append
1085
1086         def replc(m):
1087                 # performs substitutions and populates dvars
1088                 if m.group('and'):
1089                         return ' and '
1090                 elif m.group('or'):
1091                         return ' or '
1092                 else:
1093                         x = m.group('var')
1094                         if x not in dvars:
1095                                 dvars.append(x)
1096                         return 'env[%r]' % x
1097
1098         for m in reg_act_noshell.finditer(line):
1099                 if m.group('space'):
1100                         merge = False
1101                         continue
1102                 elif m.group('text'):
1103                         app('[%r]' % m.group('text').replace('$$', '$'))
1104                 elif m.group('subst'):
1105                         var = m.group('var')
1106                         code = m.group('code')
1107                         if var == 'SRC':
1108                                 if code:
1109                                         app('[tsk.inputs%s]' % code)
1110                                 else:
1111                                         app('[a.path_from(cwdx) for a in tsk.inputs]')
1112                         elif var == 'TGT':
1113                                 if code:
1114                                         app('[tsk.outputs%s]' % code)
1115                                 else:
1116                                         app('[a.path_from(cwdx) for a in tsk.outputs]')
1117                         elif code:
1118                                 if code.startswith(':'):
1119                                         # a composed variable ${FOO:OUT}
1120                                         if not var in dvars:
1121                                                 dvars.append(var)
1122                                         m = code[1:]
1123                                         if m == 'SRC':
1124                                                 m = '[a.path_from(cwdx) for a in tsk.inputs]'
1125                                         elif m == 'TGT':
1126                                                 m = '[a.path_from(cwdx) for a in tsk.outputs]'
1127                                         elif re_novar.match(m):
1128                                                 m = '[tsk.inputs%s]' % m[3:]
1129                                         elif re_novar.match(m):
1130                                                 m = '[tsk.outputs%s]' % m[3:]
1131                                         elif m[:3] not in ('tsk', 'gen', 'bld'):
1132                                                 dvars.append(m)
1133                                                 m = '%r' % m
1134                                         app('tsk.colon(%r, %s)' % (var, m))
1135                                 elif code.startswith('?'):
1136                                         # In A?B|C output env.A if one of env.B or env.C is non-empty
1137                                         expr = re_cond.sub(replc, code[1:])
1138                                         app('to_list(env[%r] if (%s) else [])' % (var, expr))
1139                                 else:
1140                                         # plain code such as ${tsk.inputs[0].abspath()}
1141                                         app('gen.to_list(%s%s)' % (var, code))
1142                         else:
1143                                 # a plain variable such as # a plain variable like ${AR}
1144                                 app('to_list(env[%r])' % var)
1145                                 if not var in dvars:
1146                                         dvars.append(var)
1147                 if merge:
1148                         tmp = 'merge(%s, %s)' % (buf[-2], buf[-1])
1149                         del buf[-1]
1150                         buf[-1] = tmp
1151                 merge = True # next turn
1152
1153         buf = ['lst.extend(%s)' % x for x in buf]
1154         fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
1155         Logs.debug('action: %s', fun.strip().splitlines())
1156         return (funex(fun), dvars)
1157
1158 def compile_fun(line, shell=False):
1159         """
1160         Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
1161
1162         * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
1163         * The list of variables that must cause rebuilds when *env* data is modified
1164
1165         for example::
1166
1167                 from waflib.Task import compile_fun
1168                 compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
1169
1170                 def build(bld):
1171                         bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
1172
1173         The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
1174         The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
1175
1176         """
1177         if isinstance(line, str):
1178                 if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
1179                         shell = True
1180         else:
1181                 dvars_lst = []
1182                 funs_lst = []
1183                 for x in line:
1184                         if isinstance(x, str):
1185                                 fun, dvars = compile_fun(x, shell)
1186                                 dvars_lst += dvars
1187                                 funs_lst.append(fun)
1188                         else:
1189                                 # assume a function to let through
1190                                 funs_lst.append(x)
1191                 def composed_fun(task):
1192                         for x in funs_lst:
1193                                 ret = x(task)
1194                                 if ret:
1195                                         return ret
1196                         return None
1197                 return composed_fun, dvars_lst
1198         if shell:
1199                 return compile_fun_shell(line)
1200         else:
1201                 return compile_fun_noshell(line)
1202
1203 def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
1204         """
1205         Returns a new task subclass with the function ``run`` compiled from the line given.
1206
1207         :param func: method run
1208         :type func: string or function
1209         :param vars: list of variables to hash
1210         :type vars: list of string
1211         :param color: color to use
1212         :type color: string
1213         :param shell: when *func* is a string, enable/disable the use of the shell
1214         :type shell: bool
1215         :param scan: method scan
1216         :type scan: function
1217         :rtype: :py:class:`waflib.Task.Task`
1218         """
1219
1220         params = {
1221                 'vars': vars or [], # function arguments are static, and this one may be modified by the class
1222                 'color': color,
1223                 'name': name,
1224                 'shell': shell,
1225                 'scan': scan,
1226         }
1227
1228         if isinstance(func, str) or isinstance(func, tuple):
1229                 params['run_str'] = func
1230         else:
1231                 params['run'] = func
1232
1233         cls = type(Task)(name, (Task,), params)
1234         classes[name] = cls
1235
1236         if ext_in:
1237                 cls.ext_in = Utils.to_list(ext_in)
1238         if ext_out:
1239                 cls.ext_out = Utils.to_list(ext_out)
1240         if before:
1241                 cls.before = Utils.to_list(before)
1242         if after:
1243                 cls.after = Utils.to_list(after)
1244
1245         return cls
1246
1247 TaskBase = Task
1248 "Provided for compatibility reasons, TaskBase should not be used"
1249