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