third_party:waf: update to upstream 2.0.4 release
[samba.git] / third_party / waf / waflib / Task.py
index 44db70c2a8155da5fdab01374b2af7af7a5aeea6..89a73258f924aedb8609cf2cd03f1167cbb49cde 100644 (file)
@@ -4,13 +4,13 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Tasks represent atomic operations such as processes.
 """
 
-import os, re, sys, tempfile
+import os, re, sys, tempfile, traceback
 from waflib import Utils, Logs, Errors
 
 # task states
@@ -26,6 +26,9 @@ CRASHED = 2
 EXCEPTION = 3
 """An exception occurred in the task execution"""
 
+CANCELED = 4
+"""A dependency for the task is missing so it was cancelled"""
+
 SKIPPED = 8
 """The task did not have to be executed"""
 
@@ -41,6 +44,9 @@ SKIP_ME = -2
 RUN_ME = -3
 """The task must be executed"""
 
+CANCEL_ME = -4
+"""The task cannot be executed because of a dependency problem"""
+
 COMPILE_TEMPLATE_SHELL = '''
 def f(tsk):
        env = tsk.env
@@ -90,8 +96,7 @@ class store_task_type(type):
                super(store_task_type, cls).__init__(name, bases, dict)
                name = cls.__name__
 
-               if name != 'evil' and name != 'TaskBase':
-                       global classes
+               if name != 'evil' and name != 'Task':
                        if getattr(cls, 'run_str', None):
                                # if a string is provided, convert it to a method
                                (f, dvars) = compile_fun(cls.run_str, cls.shell)
@@ -112,20 +117,21 @@ class store_task_type(type):
 evil = store_task_type('evil', (object,), {})
 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
 
-class TaskBase(evil):
+class Task(evil):
        """
-       Base class for all Waf tasks, which should be seen as an interface.
-       For illustration purposes, instances of this class will execute the attribute
-       'fun' in :py:meth:`waflib.Task.TaskBase.run`. When in doubt, create
-       subclasses of :py:class:`waflib.Task.Task` instead.
+       This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
+       uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
+       the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
+       nodes (if present).
+       """
+       vars = []
+       """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
 
-       Subclasses must override these methods:
+       always_run = False
+       """Specify whether task instances must always be executed or not (class attribute)"""
 
-       #. __str__: string to display to the user
-       #. runnable_status: ask the task if it should be run, skipped, or if we have to ask later
-       #. run: what to do to execute the task
-       #. post_run: what to do after the task has been executed
-       """
+       shell = False
+       """Execute the command with the shell (class attribute)"""
 
        color = 'GREEN'
        """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
@@ -142,7 +148,7 @@ class TaskBase(evil):
        after = []
        """List of task class names to execute after instances of this class"""
 
-       hcode = ''
+       hcode = Utils.SIG_NIL
        """String representing an additional hash for the class representation"""
 
        keep_last_cmd = False
@@ -150,32 +156,51 @@ class TaskBase(evil):
        This may be useful for certain extensions but it can a lot of memory.
        """
 
-       __slots__ = ('hasrun', 'generator')
+       weight = 0
+       """Optional weight to tune the priority for task instances.
+       The higher, the earlier. The weight only applies to single task objects."""
+
+       tree_weight = 0
+       """Optional weight to tune the priority of task instances and whole subtrees.
+       The higher, the earlier."""
+
+       prio_order = 0
+       """Priority order set by the scheduler on instances during the build phase.
+       You most likely do not need to set it.
+       """
+
+       __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
 
        def __init__(self, *k, **kw):
-               """
-               The base task class requires a task generator (set to *self* if missing)
-               """
                self.hasrun = NOT_RUN
                try:
                        self.generator = kw['generator']
                except KeyError:
                        self.generator = self
 
-       def __repr__(self):
-               return '\n\t{task %r: %s %s}' % (self.__class__.__name__, id(self), str(getattr(self, 'fun', '')))
+               self.env = kw['env']
+               """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
 
-       def __str__(self):
-               "String to display to the user"
-               if hasattr(self, 'fun'):
-                       return self.fun.__name__
-               return self.__class__.__name__
+               self.inputs  = []
+               """List of input nodes, which represent the files used by the task instance"""
 
-       def keyword(self):
-               "Display keyword used to prettify the console outputs"
-               if hasattr(self, 'fun'):
-                       return 'Function'
-               return 'Processing'
+               self.outputs = []
+               """List of output nodes, which represent the files created by the task instance"""
+
+               self.dep_nodes = []
+               """List of additional nodes to depend on"""
+
+               self.run_after = set()
+               """Set of tasks that must be executed before this one"""
+
+       def __lt__(self, other):
+               return self.priority() > other.priority()
+       def __le__(self, other):
+               return self.priority() >= other.priority()
+       def __gt__(self, other):
+               return self.priority() < other.priority()
+       def __ge__(self, other):
+               return self.priority() <= other.priority()
 
        def get_cwd(self):
                """
@@ -209,6 +234,15 @@ class TaskBase(evil):
                        x = '"%s"' % x
                return x
 
+       def priority(self):
+               """
+               Priority of execution; the higher, the earlier
+
+               :return: the priority value
+               :rtype: a tuple of numeric values
+               """
+               return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
+
        def split_argfile(self, cmd):
                """
                Splits a list of process commands into the executable part and its list of arguments
@@ -229,6 +263,13 @@ class TaskBase(evil):
                :type cmd: list of string (best) or string (process will use a shell)
                :return: the return code
                :rtype: int
+
+               Optional parameters:
+
+               #. cwd: current working directory (Node or string)
+               #. stdout: set to None to prevent waf from capturing the process standard output
+               #. stderr: set to None to prevent waf from capturing the process standard error
+               #. timeout: timeout value (Python 3)
                """
                if not 'cwd' in kw:
                        kw['cwd'] = self.get_cwd()
@@ -240,13 +281,18 @@ class TaskBase(evil):
                        env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
                        env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
 
+               if hasattr(self, 'stdout'):
+                       kw['stdout'] = self.stdout
+               if hasattr(self, 'stderr'):
+                       kw['stderr'] = self.stderr
+
                # workaround for command line length limit:
                # http://support.microsoft.com/kb/830473
                if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
                        cmd, args = self.split_argfile(cmd)
                        try:
                                (fd, tmp) = tempfile.mkstemp()
-                               os.write(fd, '\r\n'.join(args))
+                               os.write(fd, '\r\n'.join(args).encode())
                                os.close(fd)
                                if Logs.verbose:
                                        Logs.debug('argfile: @%r -> %r', tmp, args)
@@ -260,36 +306,16 @@ class TaskBase(evil):
                else:
                        return self.generator.bld.exec_command(cmd, **kw)
 
-       def runnable_status(self):
-               """
-               Returns the Task status
-
-               :return: a task state in :py:const:`waflib.Task.RUN_ME`, :py:const:`waflib.Task.SKIP_ME` or :py:const:`waflib.Task.ASK_LATER`.
-               :rtype: int
-               """
-               return RUN_ME
-
-       def uid(self):
-               """
-               Computes a unique identifier for the task
-
-               :rtype: string or bytes
-               """
-               return Utils.SIG_NIL
-
        def process(self):
                """
-               Assume that the task has had a ``master`` which is an instance of :py:class:`waflib.Runner.Parallel`.
-               Execute the task and then put it back in the queue :py:attr:`waflib.Runner.Parallel.out` (may be replaced by subclassing).
+               Runs the task and handles errors
 
                :return: 0 or None if everything is fine
                :rtype: integer
                """
                # remove the task signature immediately before it is executed
-               # in case of failure the task will be executed again
-               m = self.generator.bld.producer
+               # so that the task will be executed again in case of failure
                try:
-                       # TODO another place for this?
                        del self.generator.bld.task_sigs[self.uid()]
                except KeyError:
                        pass
@@ -297,44 +323,29 @@ class TaskBase(evil):
                try:
                        ret = self.run()
                except Exception:
-                       self.err_msg = Utils.ex_stack()
+                       self.err_msg = traceback.format_exc()
                        self.hasrun = EXCEPTION
-
-                       # TODO cleanup
-                       m.error_handler(self)
-                       return
-
-               if ret:
-                       self.err_code = ret
-                       self.hasrun = CRASHED
                else:
-                       try:
-                               self.post_run()
-                       except Errors.WafError:
-                               pass
-                       except Exception:
-                               self.err_msg = Utils.ex_stack()
-                               self.hasrun = EXCEPTION
+                       if ret:
+                               self.err_code = ret
+                               self.hasrun = CRASHED
                        else:
-                               self.hasrun = SUCCESS
-               if self.hasrun != SUCCESS:
-                       m.error_handler(self)
-
-       def run(self):
-               """
-               Called by threads to execute the tasks. The default is empty and meant to be overridden in subclasses.
-
-               .. warning:: It is a bad idea to create nodes in this method, so avoid :py:meth:`waflib.Node.Node.ant_glob`
-
-               :rtype: int
-               """
-               if hasattr(self, 'fun'):
-                       return self.fun(self)
-               return 0
+                               try:
+                                       self.post_run()
+                               except Errors.WafError:
+                                       pass
+                               except Exception:
+                                       self.err_msg = traceback.format_exc()
+                                       self.hasrun = EXCEPTION
+                               else:
+                                       self.hasrun = SUCCESS
 
-       def post_run(self):
-               "Update build data after successful Task execution. Override in subclasses."
-               pass
+               if self.hasrun != SUCCESS and self.scan:
+                       # rescan dependencies on next run
+                       try:
+                               del self.generator.bld.imp_sigs[self.uid()]
+                       except KeyError:
+                               pass
 
        def log_display(self, bld):
                "Writes the execution status on the context logger"
@@ -367,10 +378,7 @@ class TaskBase(evil):
 
                def cur():
                        # the current task position, computed as late as possible
-                       tmp = -1
-                       if hasattr(master, 'ready'):
-                               tmp -= master.ready.qsize()
-                       return master.processed + tmp
+                       return master.processed - master.ready.qsize()
 
                if self.generator.bld.progress_bar == 1:
                        return self.generator.bld.progress_line(cur(), master.total, col1, col2)
@@ -406,9 +414,7 @@ class TaskBase(evil):
                :return: a hash value
                :rtype: string
                """
-               cls = self.__class__
-               tup = (str(cls.before), str(cls.after), str(cls.ext_in), str(cls.ext_out), cls.__name__, cls.hcode)
-               return hash(tup)
+               return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
 
        def format_error(self):
                """
@@ -432,6 +438,8 @@ class TaskBase(evil):
                                return ' -> task in %r failed%s' % (name, msg)
                elif self.hasrun == MISSING:
                        return ' -> missing files in %r%s' % (name, msg)
+               elif self.hasrun == CANCELED:
+                       return ' -> %r canceled because of missing dependencies' % name
                else:
                        return 'invalid status for task in %r: %r' % (name, self.hasrun)
 
@@ -442,12 +450,12 @@ class TaskBase(evil):
 
                The results will be slightly different if FOO_ST is a list, for example::
 
-                       env.FOO_ST = ['-a', '-b']
+                       env.FOO    = ['p1', 'p2']
                        env.FOO_ST = '-I%s'
                        # ${FOO_ST:FOO} returns
                        ['-Ip1', '-Ip2']
 
-                       env.FOO    = ['p1', 'p2']
+                       env.FOO_ST = ['-a', '-b']
                        # ${FOO_ST:FOO} returns
                        ['-a', '-b', 'p1', '-a', '-b', 'p2']
                """
@@ -468,40 +476,6 @@ class TaskBase(evil):
                                lst.append(y)
                        return lst
 
-class Task(TaskBase):
-       """
-       This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
-       uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
-       the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
-       nodes (if present).
-       """
-       vars = []
-       """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
-
-       always_run = False
-       """Specify whether task instances must always be executed or not (class attribute)"""
-
-       shell = False
-       """Execute the command with the shell (class attribute)"""
-
-       def __init__(self, *k, **kw):
-               TaskBase.__init__(self, *k, **kw)
-
-               self.env = kw['env']
-               """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
-
-               self.inputs  = []
-               """List of input nodes, which represent the files used by the task instance"""
-
-               self.outputs = []
-               """List of output nodes, which represent the files created by the task instance"""
-
-               self.dep_nodes = []
-               """List of additional nodes to depend on"""
-
-               self.run_after = set()
-               """Set of tasks that must be executed before this one"""
-
        def __str__(self):
                "string to display to the user"
                name = self.__class__.__name__
@@ -517,14 +491,14 @@ class Task(TaskBase):
 
                src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
                tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
-               if self.outputs: sep = ' -> '
-               else: sep = ''
+               if self.outputs:
+                       sep = ' -> '
+               else:
+                       sep = ''
                return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
 
        def keyword(self):
-               """
-               See :py:meth:`waflib.Task.TaskBase`
-               """
+               "Display keyword used to prettify the console outputs"
                name = self.__class__.__name__
                if name.endswith(('lib', 'program')):
                        return 'Linking'
@@ -581,8 +555,10 @@ class Task(TaskBase):
                :param inp: input nodes
                :type inp: node or list of nodes
                """
-               if isinstance(inp, list): self.inputs += inp
-               else: self.inputs.append(inp)
+               if isinstance(inp, list):
+                       self.inputs += inp
+               else:
+                       self.inputs.append(inp)
 
        def set_outputs(self, out):
                """
@@ -591,8 +567,10 @@ class Task(TaskBase):
                :param out: output nodes
                :type out: node or list of nodes
                """
-               if isinstance(out, list): self.outputs += out
-               else: self.outputs.append(out)
+               if isinstance(out, list):
+                       self.outputs += out
+               else:
+                       self.outputs.append(out)
 
        def set_run_after(self, task):
                """
@@ -601,7 +579,7 @@ class Task(TaskBase):
                :param task: task
                :type task: :py:class:`waflib.Task.Task`
                """
-               assert isinstance(task, TaskBase)
+               assert isinstance(task, Task)
                self.run_after.add(task)
 
        def signature(self):
@@ -650,13 +628,22 @@ class Task(TaskBase):
 
        def runnable_status(self):
                """
-               See :py:meth:`waflib.Task.TaskBase.runnable_status`
+               Returns the Task status
+
+               :return: a task state in :py:const:`waflib.Task.RUN_ME`,
+                       :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
+               :rtype: int
                """
-               #return 0 # benchmarking
+               bld = self.generator.bld
+               if bld.is_install < 0:
+                       return SKIP_ME
 
                for t in self.run_after:
                        if not t.hasrun:
                                return ASK_LATER
+                       elif t.hasrun < SKIPPED:
+                               # a dependency has an error
+                               return CANCEL_ME
 
                # first compute the signature
                try:
@@ -665,7 +652,6 @@ class Task(TaskBase):
                        return ASK_LATER
 
                # compare the signature to a signature computed previously
-               bld = self.generator.bld
                key = self.uid()
                try:
                        prev_sig = bld.task_sigs[key]
@@ -733,10 +719,11 @@ class Task(TaskBase):
                                        continue
 
                                for v in d:
-                                       if isinstance(v, bld.root.__class__):
+                                       try:
                                                v = v.get_bld_sig()
-                                       elif hasattr(v, '__call__'):
-                                               v = v() # dependency is a function, call it
+                                       except AttributeError:
+                                               if hasattr(v, '__call__'):
+                                                       v = v() # dependency is a function, call it
                                        upd(v)
 
        def sig_vars(self):
@@ -869,10 +856,10 @@ if sys.hexversion > 0x3000000:
                try:
                        return self.uid_
                except AttributeError:
-                       m = Utils.md5(self.__class__.__name__.encode('iso8859-1', 'xmlcharrefreplace'))
+                       m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
                        up = m.update
                        for x in self.inputs + self.outputs:
-                               up(x.abspath().encode('iso8859-1', 'xmlcharrefreplace'))
+                               up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
                        self.uid_ = m.digest()
                        return self.uid_
        uid.__doc__ = Task.uid.__doc__
@@ -889,9 +876,9 @@ def is_before(t1, t2):
                waflib.Task.is_before(t1, t2) # True
 
        :param t1: Task object
-       :type t1: :py:class:`waflib.Task.TaskBase`
+       :type t1: :py:class:`waflib.Task.Task`
        :param t2: Task object
-       :type t2: :py:class:`waflib.Task.TaskBase`
+       :type t2: :py:class:`waflib.Task.Task`
        """
        to_list = Utils.to_list
        for k in to_list(t2.ext_in):
@@ -911,27 +898,50 @@ def set_file_constraints(tasks):
        Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
 
        :param tasks: tasks
-       :type tasks: list of :py:class:`waflib.Task.TaskBase`
+       :type tasks: list of :py:class:`waflib.Task.Task`
        """
        ins = Utils.defaultdict(set)
        outs = Utils.defaultdict(set)
        for x in tasks:
-               for a in getattr(x, 'inputs', []) + getattr(x, 'dep_nodes', []):
-                       ins[id(a)].add(x)
-               for a in getattr(x, 'outputs', []):
-                       outs[id(a)].add(x)
+               for a in x.inputs:
+                       ins[a].add(x)
+               for a in x.dep_nodes:
+                       ins[a].add(x)
+               for a in x.outputs:
+                       outs[a].add(x)
 
        links = set(ins.keys()).intersection(outs.keys())
        for k in links:
                for a in ins[k]:
                        a.run_after.update(outs[k])
 
+
+class TaskGroup(object):
+       """
+       Wrap nxm task order constraints into a single object
+       to prevent the creation of large list/set objects
+
+       This is an optimization
+       """
+       def __init__(self, prev, next):
+               self.prev = prev
+               self.next = next
+               self.done = False
+
+       def get_hasrun(self):
+               for k in self.prev:
+                       if not k.hasrun:
+                               return NOT_RUN
+               return SUCCESS
+
+       hasrun = property(get_hasrun, None)
+
 def set_precedence_constraints(tasks):
        """
        Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
 
        :param tasks: tasks
-       :type tasks: list of :py:class:`waflib.Task.TaskBase`
+       :type tasks: list of :py:class:`waflib.Task.Task`
        """
        cstr_groups = Utils.defaultdict(list)
        for x in tasks:
@@ -957,9 +967,16 @@ def set_precedence_constraints(tasks):
                        else:
                                continue
 
-                       aval = set(cstr_groups[keys[a]])
-                       for x in cstr_groups[keys[b]]:
-                               x.run_after.update(aval)
+                       a = cstr_groups[keys[a]]
+                       b = cstr_groups[keys[b]]
+
+                       if len(a) < 2 or len(b) < 2:
+                               for x in b:
+                                       x.run_after.update(a)
+                       else:
+                               group = TaskGroup(set(a), set(b))
+                               for x in b:
+                                       x.run_after.add(group)
 
 def funex(c):
        """
@@ -1011,11 +1028,15 @@ def compile_fun_shell(line):
        app = parm.append
        for (var, meth) in extr:
                if var == 'SRC':
-                       if meth: app('tsk.inputs%s' % meth)
-                       else: app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
+                       if meth:
+                               app('tsk.inputs%s' % meth)
+                       else:
+                               app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
                elif var == 'TGT':
-                       if meth: app('tsk.outputs%s' % meth)
-                       else: app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
+                       if meth:
+                               app('tsk.outputs%s' % meth)
+                       else:
+                               app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
                elif meth:
                        if meth.startswith(':'):
                                if var not in dvars:
@@ -1043,8 +1064,10 @@ def compile_fun_shell(line):
                        if var not in dvars:
                                dvars.append(var)
                        app("p('%s')" % var)
-       if parm: parm = "%% (%s) " % (',\n\t\t'.join(parm))
-       else: parm = ''
+       if parm:
+               parm = "%% (%s) " % (',\n\t\t'.join(parm))
+       else:
+               parm = ''
 
        c = COMPILE_TEMPLATE_SHELL % (line, parm)
        Logs.debug('action: %s', c.strip().splitlines())
@@ -1136,7 +1159,7 @@ def compile_fun(line, shell=False):
        """
        Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
 
-       * The function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
+       * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
        * The list of variables that must cause rebuilds when *env* data is modified
 
        for example::
@@ -1208,7 +1231,6 @@ def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[
                params['run'] = func
 
        cls = type(Task)(name, (Task,), params)
-       global classes
        classes[name] = cls
 
        if ext_in:
@@ -1222,21 +1244,6 @@ def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[
 
        return cls
 
+TaskBase = Task
+"Provided for compatibility reasons, TaskBase should not be used"
 
-def always_run(cls):
-       """
-       Deprecated Task class decorator (to be removed in waf 2.0)
-
-       Set all task instances of this class to be executed whenever a build is started
-       The task signature is calculated, but the result of the comparison between
-       task signatures is bypassed
-       """
-       Logs.warn('This decorator is deprecated, set always_run on the task class instead!')
-       cls.always_run = True
-       return cls
-
-def update_outputs(cls):
-       """
-       Obsolete, to be removed in waf 2.0
-       """
-       return cls