summaryrefslogtreecommitdiff
path: root/third_party/waf/waflib/Task.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/waf/waflib/Task.py')
-rw-r--r--third_party/waf/waflib/Task.py379
1 files changed, 193 insertions, 186 deletions
diff --git a/third_party/waf/waflib/Task.py b/third_party/waf/waflib/Task.py
index 44db70c2a81..89a73258f92 100644
--- a/third_party/waf/waflib/Task.py
+++ b/third_party/waf/waflib/Task.py
@@ -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