summaryrefslogtreecommitdiff
path: root/third_party/waf/wafadmin/Task.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/waf/wafadmin/Task.py')
-rw-r--r--third_party/waf/wafadmin/Task.py1199
1 files changed, 0 insertions, 1199 deletions
diff --git a/third_party/waf/wafadmin/Task.py b/third_party/waf/wafadmin/Task.py
deleted file mode 100644
index 59d10203a49..00000000000
--- a/third_party/waf/wafadmin/Task.py
+++ /dev/null
@@ -1,1199 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-# Thomas Nagy, 2005-2008 (ita)
-
-"""
-Running tasks in parallel is a simple problem, but in practice it is more complicated:
-* dependencies discovered during the build (dynamic task creation)
-* dependencies discovered after files are compiled
-* the amount of tasks and dependencies (graph size) can be huge
-
-This is why the dependency management is split on three different levels:
-1. groups of tasks that run all after another group of tasks
-2. groups of tasks that can be run in parallel
-3. tasks that can run in parallel, but with possible unknown ad-hoc dependencies
-
-The point #1 represents a strict sequential order between groups of tasks, for example a compiler is produced
-and used to compile the rest, whereas #2 and #3 represent partial order constraints where #2 applies to the kind of task
-and #3 applies to the task instances.
-
-#1 is held by the task manager: ordered list of TaskGroups (see bld.add_group)
-#2 is held by the task groups and the task types: precedence after/before (topological sort),
- and the constraints extracted from file extensions
-#3 is held by the tasks individually (attribute run_after),
- and the scheduler (Runner.py) use Task::runnable_status to reorder the tasks
-
---
-
-To try, use something like this in your code:
-import Constants, Task
-Task.algotype = Constants.MAXPARALLEL
-
---
-
-There are two concepts with the tasks (individual units of change):
-* dependency (if 1 is recompiled, recompile 2)
-* order (run 2 after 1)
-
-example 1: if t1 depends on t2 and t2 depends on t3 it is not necessary to make t1 depend on t3 (dependency is transitive)
-example 2: if t1 depends on a node produced by t2, it is not immediately obvious that t1 must run after t2 (order is not obvious)
-
-The role of the Task Manager is to give the tasks in order (groups of task that may be run in parallel one after the other)
-
-"""
-
-import os, shutil, sys, re, random, datetime, tempfile, shlex
-from Utils import md5
-import Build, Runner, Utils, Node, Logs, Options
-from Logs import debug, warn, error
-from Constants import *
-
-algotype = NORMAL
-#algotype = JOBCONTROL
-#algotype = MAXPARALLEL
-
-COMPILE_TEMPLATE_SHELL = '''
-def f(task):
- env = task.env
- wd = getattr(task, 'cwd', None)
- p = env.get_flat
- cmd = \'\'\' %s \'\'\' % s
- return task.exec_command(cmd, cwd=wd)
-'''
-
-COMPILE_TEMPLATE_NOSHELL = '''
-def f(task):
- env = task.env
- wd = getattr(task, 'cwd', None)
- def to_list(xx):
- if isinstance(xx, str): return [xx]
- return xx
- lst = []
- %s
- lst = [x for x in lst if x]
- return task.exec_command(lst, cwd=wd)
-'''
-
-
-"""
-Enable different kind of dependency algorithms:
-1 make groups: first compile all cpps and then compile all links (NORMAL)
-2 parallelize all (each link task run after its dependencies) (MAXPARALLEL)
-3 like 1 but provide additional constraints for the parallelization (MAXJOBS)
-
-In theory 1. will be faster than 2 for waf, but might be slower for builds
-The scheme 2 will not allow for running tasks one by one so it can cause disk thrashing on huge builds
-"""
-
-file_deps = Utils.nada
-"""
-Additional dependency pre-check may be added by replacing the function file_deps.
-e.g. extract_outputs, extract_deps below.
-"""
-
-class TaskManager(object):
- """The manager is attached to the build object, it holds a list of TaskGroup"""
- def __init__(self):
- self.groups = []
- self.tasks_done = []
- self.current_group = 0
- self.groups_names = {}
-
- def group_name(self, g):
- """name for the group g (utility)"""
- if not isinstance(g, TaskGroup):
- g = self.groups[g]
- for x in self.groups_names:
- if id(self.groups_names[x]) == id(g):
- return x
- return ''
-
- def group_idx(self, tg):
- """group the task generator tg is in"""
- se = id(tg)
- for i in range(len(self.groups)):
- g = self.groups[i]
- for t in g.tasks_gen:
- if id(t) == se:
- return i
- return None
-
- def get_next_set(self):
- """return the next set of tasks to execute
- the first parameter is the maximum amount of parallelization that may occur"""
- ret = None
- while not ret and self.current_group < len(self.groups):
- ret = self.groups[self.current_group].get_next_set()
- if ret: return ret
- else:
- self.groups[self.current_group].process_install()
- self.current_group += 1
- return (None, None)
-
- def add_group(self, name=None, set=True):
- #if self.groups and not self.groups[0].tasks:
- # error('add_group: an empty group is already present')
- g = TaskGroup()
-
- if name and name in self.groups_names:
- error('add_group: name %s already present' % name)
- self.groups_names[name] = g
- self.groups.append(g)
- if set:
- self.current_group = len(self.groups) - 1
-
- def set_group(self, idx):
- if isinstance(idx, str):
- g = self.groups_names[idx]
- for x in xrange(len(self.groups)):
- if id(g) == id(self.groups[x]):
- self.current_group = x
- else:
- self.current_group = idx
-
- def add_task_gen(self, tgen):
- if not self.groups: self.add_group()
- self.groups[self.current_group].tasks_gen.append(tgen)
-
- def add_task(self, task):
- if not self.groups: self.add_group()
- self.groups[self.current_group].tasks.append(task)
-
- def total(self):
- total = 0
- if not self.groups: return 0
- for group in self.groups:
- total += len(group.tasks)
- return total
-
- def add_finished(self, tsk):
- self.tasks_done.append(tsk)
- bld = tsk.generator.bld
- if bld.is_install:
- f = None
- if 'install' in tsk.__dict__:
- f = tsk.__dict__['install']
- # install=0 to prevent installation
- if f: f(tsk)
- else:
- tsk.install()
-
-class TaskGroup(object):
- "the compilation of one group does not begin until the previous group has finished (in the manager)"
- def __init__(self):
- self.tasks = [] # this list will be consumed
- self.tasks_gen = []
-
- self.cstr_groups = Utils.DefaultDict(list) # tasks having equivalent constraints
- self.cstr_order = Utils.DefaultDict(set) # partial order between the cstr groups
- self.temp_tasks = [] # tasks put on hold
- self.ready = 0
- self.post_funs = []
-
- def reset(self):
- "clears the state of the object (put back the tasks into self.tasks)"
- for x in self.cstr_groups:
- self.tasks += self.cstr_groups[x]
- self.tasks = self.temp_tasks + self.tasks
- self.temp_tasks = []
- self.cstr_groups = Utils.DefaultDict(list)
- self.cstr_order = Utils.DefaultDict(set)
- self.ready = 0
-
- def process_install(self):
- for (f, k, kw) in self.post_funs:
- f(*k, **kw)
-
- def prepare(self):
- "prepare the scheduling"
- self.ready = 1
- file_deps(self.tasks)
- self.make_cstr_groups()
- self.extract_constraints()
-
- def get_next_set(self):
- "next list of tasks to execute using max job settings, returns (maxjobs, task_list)"
- global algotype
- if algotype == NORMAL:
- tasks = self.tasks_in_parallel()
- maxj = MAXJOBS
- elif algotype == JOBCONTROL:
- (maxj, tasks) = self.tasks_by_max_jobs()
- elif algotype == MAXPARALLEL:
- tasks = self.tasks_with_inner_constraints()
- maxj = MAXJOBS
- else:
- raise Utils.WafError("unknown algorithm type %s" % (algotype))
-
- if not tasks: return ()
- return (maxj, tasks)
-
- def make_cstr_groups(self):
- "unite the tasks that have similar constraints"
- self.cstr_groups = Utils.DefaultDict(list)
- for x in self.tasks:
- h = x.hash_constraints()
- self.cstr_groups[h].append(x)
-
- def set_order(self, a, b):
- self.cstr_order[a].add(b)
-
- def compare_exts(self, t1, t2):
- "extension production"
- x = "ext_in"
- y = "ext_out"
- in_ = t1.attr(x, ())
- out_ = t2.attr(y, ())
- for k in in_:
- if k in out_:
- return -1
- in_ = t2.attr(x, ())
- out_ = t1.attr(y, ())
- for k in in_:
- if k in out_:
- return 1
- return 0
-
- def compare_partial(self, t1, t2):
- "partial relations after/before"
- m = "after"
- n = "before"
- name = t2.__class__.__name__
- if name in Utils.to_list(t1.attr(m, ())): return -1
- elif name in Utils.to_list(t1.attr(n, ())): return 1
- name = t1.__class__.__name__
- if name in Utils.to_list(t2.attr(m, ())): return 1
- elif name in Utils.to_list(t2.attr(n, ())): return -1
- return 0
-
- def extract_constraints(self):
- "extract the parallelization constraints from the tasks with different constraints"
- keys = self.cstr_groups.keys()
- max = len(keys)
- # hopefully the length of this list is short
- for i in xrange(max):
- t1 = self.cstr_groups[keys[i]][0]
- for j in xrange(i + 1, max):
- t2 = self.cstr_groups[keys[j]][0]
-
- # add the constraints based on the comparisons
- val = (self.compare_exts(t1, t2)
- or self.compare_partial(t1, t2)
- )
- if val > 0:
- self.set_order(keys[i], keys[j])
- elif val < 0:
- self.set_order(keys[j], keys[i])
-
- def tasks_in_parallel(self):
- "(NORMAL) next list of tasks that may be executed in parallel"
-
- if not self.ready: self.prepare()
-
- keys = self.cstr_groups.keys()
-
- unconnected = []
- remainder = []
-
- for u in keys:
- for k in self.cstr_order.values():
- if u in k:
- remainder.append(u)
- break
- else:
- unconnected.append(u)
-
- toreturn = []
- for y in unconnected:
- toreturn.extend(self.cstr_groups[y])
-
- # remove stuff only after
- for y in unconnected:
- try: self.cstr_order.__delitem__(y)
- except KeyError: pass
- self.cstr_groups.__delitem__(y)
-
- if not toreturn and remainder:
- raise Utils.WafError("circular order constraint detected %r" % remainder)
-
- return toreturn
-
- def tasks_by_max_jobs(self):
- "(JOBCONTROL) returns the tasks that can run in parallel with the max amount of jobs"
- if not self.ready: self.prepare()
- if not self.temp_tasks: self.temp_tasks = self.tasks_in_parallel()
- if not self.temp_tasks: return (None, None)
-
- maxjobs = MAXJOBS
- ret = []
- remaining = []
- for t in self.temp_tasks:
- m = getattr(t, "maxjobs", getattr(self.__class__, "maxjobs", MAXJOBS))
- if m > maxjobs:
- remaining.append(t)
- elif m < maxjobs:
- remaining += ret
- ret = [t]
- maxjobs = m
- else:
- ret.append(t)
- self.temp_tasks = remaining
- return (maxjobs, ret)
-
- def tasks_with_inner_constraints(self):
- """(MAXPARALLEL) returns all tasks in this group, but add the constraints on each task instance
- as an optimization, it might be desirable to discard the tasks which do not have to run"""
- if not self.ready: self.prepare()
-
- if getattr(self, "done", None): return None
-
- for p in self.cstr_order:
- for v in self.cstr_order[p]:
- for m in self.cstr_groups[p]:
- for n in self.cstr_groups[v]:
- n.set_run_after(m)
- self.cstr_order = Utils.DefaultDict(set)
- self.cstr_groups = Utils.DefaultDict(list)
- self.done = 1
- return self.tasks[:] # make a copy
-
-class store_task_type(type):
- "store the task types that have a name ending in _task into a map (remember the existing task types)"
- def __init__(cls, name, bases, dict):
- super(store_task_type, cls).__init__(name, bases, dict)
- name = cls.__name__
-
- if name.endswith('_task'):
- name = name.replace('_task', '')
- if name != 'TaskBase':
- TaskBase.classes[name] = cls
-
-class TaskBase(object):
- """Base class for all Waf tasks
-
- The most important methods are (by usual order of call):
- 1 runnable_status: ask the task if it should be run, skipped, or if we have to ask later
- 2 __str__: string to display to the user
- 3 run: execute the task
- 4 post_run: after the task is run, update the cache about the task
-
- This class should be seen as an interface, it provides the very minimum necessary for the scheduler
- so it does not do much.
-
- For illustration purposes, TaskBase instances try to execute self.fun (if provided)
- """
-
- __metaclass__ = store_task_type
-
- color = "GREEN"
- maxjobs = MAXJOBS
- classes = {}
- stat = None
-
- def __init__(self, *k, **kw):
- self.hasrun = NOT_RUN
-
- try:
- self.generator = kw['generator']
- except KeyError:
- self.generator = self
- self.bld = Build.bld
-
- if kw.get('normal', 1):
- self.generator.bld.task_manager.add_task(self)
-
- def __repr__(self):
- "used for debugging"
- return '\n\t{task: %s %s}' % (self.__class__.__name__, str(getattr(self, "fun", "")))
-
- def __str__(self):
- "string to display to the user"
- if hasattr(self, 'fun'):
- return 'executing: %s\n' % self.fun.__name__
- return self.__class__.__name__ + '\n'
-
- def exec_command(self, *k, **kw):
- "use this for executing commands from tasks"
- # TODO in waf 1.6, eliminate bld.exec_command, and move the cwd processing to here
- if self.env['env']:
- kw['env'] = self.env['env']
- return self.generator.bld.exec_command(*k, **kw)
-
- def runnable_status(self):
- "RUN_ME SKIP_ME or ASK_LATER"
- return RUN_ME
-
- def can_retrieve_cache(self):
- return False
-
- def call_run(self):
- if self.can_retrieve_cache():
- return 0
- return self.run()
-
- def run(self):
- "called if the task must run"
- if hasattr(self, 'fun'):
- return self.fun(self)
- return 0
-
- def post_run(self):
- "update the dependency tree (node stats)"
- pass
-
- def display(self):
- "print either the description (using __str__) or the progress bar or the ide output"
- col1 = Logs.colors(self.color)
- col2 = Logs.colors.NORMAL
-
- if Options.options.progress_bar == 1:
- return self.generator.bld.progress_line(self.position[0], self.position[1], col1, col2)
-
- if Options.options.progress_bar == 2:
- ela = Utils.get_elapsed_time(self.generator.bld.ini)
- try:
- ins = ','.join([n.name for n in self.inputs])
- except AttributeError:
- ins = ''
- try:
- outs = ','.join([n.name for n in self.outputs])
- except AttributeError:
- outs = ''
- return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (self.position[1], self.position[0], ins, outs, ela)
-
- total = self.position[1]
- n = len(str(total))
- fs = '[%%%dd/%%%dd] %%s%%s%%s' % (n, n)
- return fs % (self.position[0], self.position[1], col1, str(self), col2)
-
- def attr(self, att, default=None):
- "retrieve an attribute from the instance or from the class (microoptimization here)"
- ret = getattr(self, att, self)
- if ret is self: return getattr(self.__class__, att, default)
- return ret
-
- def hash_constraints(self):
- "identify a task type for all the constraints relevant for the scheduler: precedence, file production"
- a = self.attr
- sum = hash((self.__class__.__name__,
- str(a('before', '')),
- str(a('after', '')),
- str(a('ext_in', '')),
- str(a('ext_out', '')),
- self.__class__.maxjobs))
- return sum
-
- def format_error(self):
- "error message to display to the user (when a build fails)"
- if getattr(self, "err_msg", None):
- return self.err_msg
- elif self.hasrun == CRASHED:
- try:
- return " -> task failed (err #%d): %r" % (self.err_code, self)
- except AttributeError:
- return " -> task failed: %r" % self
- elif self.hasrun == MISSING:
- return " -> missing files: %r" % self
- else:
- return ''
-
- def install(self):
- """
- installation is performed by looking at the task attributes:
- * install_path: installation path like "${PREFIX}/bin"
- * filename: install the first node in the outputs as a file with a particular name, be certain to give os.sep
- * chmod: permissions
- """
- bld = self.generator.bld
- d = self.attr('install')
-
- if self.attr('install_path'):
- lst = [a.relpath_gen(bld.srcnode) for a in self.outputs]
- perm = self.attr('chmod', O644)
- if self.attr('src'):
- # if src is given, install the sources too
- lst += [a.relpath_gen(bld.srcnode) for a in self.inputs]
- if self.attr('filename'):
- dir = self.install_path.rstrip(os.sep) + os.sep + self.attr('filename')
- bld.install_as(dir, lst[0], self.env, perm)
- else:
- bld.install_files(self.install_path, lst, self.env, perm)
-
-class Task(TaskBase):
- """The parent class is quite limited, in this version:
- * file system interaction: input and output nodes
- * persistence: do not re-execute tasks that have already run
- * caching: same files can be saved and retrieved from a cache directory
- * dependencies:
- implicit, like .c files depending on .h files
- explicit, like the input nodes or the dep_nodes
- environment variables, like the CXXFLAGS in self.env
- """
- vars = []
- def __init__(self, env, **kw):
- TaskBase.__init__(self, **kw)
- self.env = env
-
- # inputs and outputs are nodes
- # use setters when possible
- self.inputs = []
- self.outputs = []
-
- self.dep_nodes = []
- self.run_after = []
-
- # Additionally, you may define the following
- #self.dep_vars = 'PREFIX DATADIR'
-
- def __str__(self):
- "string to display to the user"
- env = self.env
- src_str = ' '.join([a.nice_path(env) for a in self.inputs])
- tgt_str = ' '.join([a.nice_path(env) for a in self.outputs])
- if self.outputs: sep = ' -> '
- else: sep = ''
- return '%s: %s%s%s\n' % (self.__class__.__name__.replace('_task', ''), src_str, sep, tgt_str)
-
- def __repr__(self):
- return "".join(['\n\t{task: ', self.__class__.__name__, " ", ",".join([x.name for x in self.inputs]), " -> ", ",".join([x.name for x in self.outputs]), '}'])
-
- def unique_id(self):
- "get a unique id: hash the node paths, the variant, the class, the function"
- try:
- return self.uid
- except AttributeError:
- "this is not a real hot zone, but we want to avoid surprizes here"
- m = md5()
- up = m.update
- up(self.__class__.__name__)
- up(self.env.variant())
- p = None
- for x in self.inputs + self.outputs:
- if p != x.parent.id:
- p = x.parent.id
- up(x.parent.abspath())
- up(x.name)
- self.uid = m.digest()
- return self.uid
-
- def set_inputs(self, inp):
- if isinstance(inp, list): self.inputs += inp
- else: self.inputs.append(inp)
-
- def set_outputs(self, out):
- if isinstance(out, list): self.outputs += out
- else: self.outputs.append(out)
-
- def set_run_after(self, task):
- "set (scheduler) order on another task"
- # TODO: handle list or object
- assert isinstance(task, TaskBase)
- self.run_after.append(task)
-
- def add_file_dependency(self, filename):
- "TODO user-provided file dependencies"
- node = self.generator.bld.path.find_resource(filename)
- self.dep_nodes.append(node)
-
- def signature(self):
- # compute the result one time, and suppose the scan_signature will give the good result
- try: return self.cache_sig[0]
- except AttributeError: pass
-
- self.m = md5()
-
- # explicit deps
- exp_sig = self.sig_explicit_deps()
-
- # env vars
- var_sig = self.sig_vars()
-
- # implicit deps
-
- imp_sig = SIG_NIL
- if self.scan:
- try:
- imp_sig = self.sig_implicit_deps()
- except ValueError:
- return self.signature()
-
- # we now have the signature (first element) and the details (for debugging)
- ret = self.m.digest()
- self.cache_sig = (ret, exp_sig, imp_sig, var_sig)
- return ret
-
- def runnable_status(self):
- "SKIP_ME RUN_ME or ASK_LATER"
- #return 0 # benchmarking
-
- if self.inputs and (not self.outputs):
- if not getattr(self.__class__, 'quiet', None):
- warn("invalid task (no inputs OR outputs): override in a Task subclass or set the attribute 'quiet' %r" % self)
-
- for t in self.run_after:
- if not t.hasrun:
- return ASK_LATER
-
- env = self.env
- bld = self.generator.bld
-
- # first compute the signature
- new_sig = self.signature()
-
- # compare the signature to a signature computed previously
- key = self.unique_id()
- try:
- prev_sig = bld.task_sigs[key][0]
- except KeyError:
- debug("task: task %r must run as it was never run before or the task code changed", self)
- return RUN_ME
-
- # compare the signatures of the outputs
- for node in self.outputs:
- variant = node.variant(env)
- try:
- if bld.node_sigs[variant][node.id] != new_sig:
- return RUN_ME
- except KeyError:
- debug("task: task %r must run as the output nodes do not exist", self)
- return RUN_ME
-
- # debug if asked to
- if Logs.verbose: self.debug_why(bld.task_sigs[key])
-
- if new_sig != prev_sig:
- return RUN_ME
- return SKIP_ME
-
- def post_run(self):
- "called after a successful task run"
- bld = self.generator.bld
- env = self.env
- sig = self.signature()
- ssig = sig.encode('hex')
-
- variant = env.variant()
- for node in self.outputs:
- # check if the node exists ..
- try:
- os.stat(node.abspath(env))
- except OSError:
- self.hasrun = MISSING
- self.err_msg = '-> missing file: %r' % node.abspath(env)
- raise Utils.WafError
-
- # important, store the signature for the next run
- bld.node_sigs[variant][node.id] = sig
- bld.task_sigs[self.unique_id()] = self.cache_sig
-
- # file caching, if possible
- # try to avoid data corruption as much as possible
- if not Options.cache_global or Options.options.nocache or not self.outputs:
- return None
-
- if getattr(self, 'cached', None):
- return None
-
- dname = os.path.join(Options.cache_global, ssig)
- tmpdir = tempfile.mkdtemp(prefix=Options.cache_global + os.sep + 'waf')
-
- try:
- shutil.rmtree(dname)
- except:
- pass
-
- try:
- i = 0
- for node in self.outputs:
- variant = node.variant(env)
- dest = os.path.join(tmpdir, str(i) + node.name)
- shutil.copy2(node.abspath(env), dest)
- i += 1
- except (OSError, IOError):
- try:
- shutil.rmtree(tmpdir)
- except:
- pass
- else:
- try:
- os.rename(tmpdir, dname)
- except OSError:
- try:
- shutil.rmtree(tmpdir)
- except:
- pass
- else:
- try:
- os.chmod(dname, O755)
- except:
- pass
-
- def can_retrieve_cache(self):
- """
- Retrieve build nodes from the cache
- update the file timestamps to help cleaning the least used entries from the cache
- additionally, set an attribute 'cached' to avoid re-creating the same cache files
-
- suppose there are files in cache/dir1/file1 and cache/dir2/file2
- first, read the timestamp of dir1
- then try to copy the files
- then look at the timestamp again, if it has changed, the data may have been corrupt (cache update by another process)
- should an exception occur, ignore the data
- """
- if not Options.cache_global or Options.options.nocache or not self.outputs:
- return None
-
- env = self.env
- sig = self.signature()
- ssig = sig.encode('hex')
-
- # first try to access the cache folder for the task
- dname = os.path.join(Options.cache_global, ssig)
- try:
- t1 = os.stat(dname).st_mtime
- except OSError:
- return None
-
- i = 0
- for node in self.outputs:
- variant = node.variant(env)
-
- orig = os.path.join(dname, str(i) + node.name)
- try:
- shutil.copy2(orig, node.abspath(env))
- # mark the cache file as used recently (modified)
- os.utime(orig, None)
- except (OSError, IOError):
- debug('task: failed retrieving file')
- return None
- i += 1
-
- # is it the same folder?
- try:
- t2 = os.stat(dname).st_mtime
- except OSError:
- return None
-
- if t1 != t2:
- return None
-
- for node in self.outputs:
- self.generator.bld.node_sigs[variant][node.id] = sig
- if Options.options.progress_bar < 1:
- self.generator.bld.printout('restoring from cache %r\n' % node.bldpath(env))
-
- self.cached = True
- return 1
-
- def debug_why(self, old_sigs):
- "explains why a task is run"
-
- new_sigs = self.cache_sig
- def v(x):
- return x.encode('hex')
-
- debug("Task %r", self)
- msgs = ['Task must run', '* Source file or manual dependency', '* Implicit dependency', '* Environment variable']
- tmp = 'task: -> %s: %s %s'
- for x in xrange(len(msgs)):
- if (new_sigs[x] != old_sigs[x]):
- debug(tmp, msgs[x], v(old_sigs[x]), v(new_sigs[x]))
-
- def sig_explicit_deps(self):
- bld = self.generator.bld
- up = self.m.update
-
- # the inputs
- for x in self.inputs + getattr(self, 'dep_nodes', []):
- if not x.parent.id in bld.cache_scanned_folders:
- bld.rescan(x.parent)
-
- variant = x.variant(self.env)
- try:
- up(bld.node_sigs[variant][x.id])
- except KeyError:
- raise Utils.WafError('Missing node signature for %r (required by %r)' % (x, self))
-
- # manual dependencies, they can slow down the builds
- if bld.deps_man:
- additional_deps = bld.deps_man
- for x in self.inputs + self.outputs:
- try:
- d = additional_deps[x.id]
- except KeyError:
- continue
-
- for v in d:
- if isinstance(v, Node.Node):
- bld.rescan(v.parent)
- variant = v.variant(self.env)
- try:
- v = bld.node_sigs[variant][v.id]
- except KeyError:
- raise Utils.WafError('Missing node signature for %r (required by %r)' % (v, self))
- elif hasattr(v, '__call__'):
- v = v() # dependency is a function, call it
- up(v)
-
- for x in self.dep_nodes:
- v = bld.node_sigs[x.variant(self.env)][x.id]
- up(v)
-
- return self.m.digest()
-
- def sig_vars(self):
- bld = self.generator.bld
- env = self.env
-
- # dependencies on the environment vars
- act_sig = bld.hash_env_vars(env, self.__class__.vars)
- self.m.update(act_sig)
-
- # additional variable dependencies, if provided
- dep_vars = getattr(self, 'dep_vars', None)
- if dep_vars:
- self.m.update(bld.hash_env_vars(env, dep_vars))
-
- return self.m.digest()
-
- #def scan(self, node):
- # """this method returns a tuple containing:
- # * a list of nodes corresponding to real files
- # * a list of names for files not found in path_lst
- # the input parameters may have more parameters that the ones used below
- # """
- # return ((), ())
- scan = None
-
- # compute the signature, recompute it if there is no match in the cache
- def sig_implicit_deps(self):
- "the signature obtained may not be the one if the files have changed, we do it in two steps"
-
- bld = self.generator.bld
-
- # get the task signatures from previous runs
- key = self.unique_id()
- prev_sigs = bld.task_sigs.get(key, ())
- if prev_sigs:
- try:
- # for issue #379
- if prev_sigs[2] == self.compute_sig_implicit_deps():
- return prev_sigs[2]
- except (KeyError, OSError):
- pass
- del bld.task_sigs[key]
- raise ValueError('rescan')
-
- # no previous run or the signature of the dependencies has changed, rescan the dependencies
- (nodes, names) = self.scan()
- if Logs.verbose:
- debug('deps: scanner for %s returned %s %s', str(self), str(nodes), str(names))
-
- # store the dependencies in the cache
- bld.node_deps[key] = nodes
- bld.raw_deps[key] = names
-
- # recompute the signature and return it
- try:
- sig = self.compute_sig_implicit_deps()
- except KeyError:
- try:
- nodes = []
- for k in bld.node_deps.get(self.unique_id(), []):
- if k.id & 3 == 2: # Node.FILE:
- if not k.id in bld.node_sigs[0]:
- nodes.append(k)
- else:
- if not k.id in bld.node_sigs[self.env.variant()]:
- nodes.append(k)
- except:
- nodes = '?'
- raise Utils.WafError('Missing node signature for %r (for implicit dependencies %r)' % (nodes, self))
-
- return sig
-
- def compute_sig_implicit_deps(self):
- """it is intended for .cpp and inferred .h files
- there is a single list (no tree traversal)
- this is the hot spot so ... do not touch"""
- upd = self.m.update
-
- bld = self.generator.bld
- tstamp = bld.node_sigs
- env = self.env
-
- for k in bld.node_deps.get(self.unique_id(), []):
- # unlikely but necessary if it happens
- if not k.parent.id in bld.cache_scanned_folders:
- # if the parent folder is removed, an OSError may be thrown
- bld.rescan(k.parent)
-
- # if the parent folder is removed, a KeyError will be thrown
- if k.id & 3 == 2: # Node.FILE:
- upd(tstamp[0][k.id])
- else:
- upd(tstamp[env.variant()][k.id])
-
- return self.m.digest()
-
-def funex(c):
- dc = {}
- exec(c, dc)
- return dc['f']
-
-reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})", re.M)
-def compile_fun_shell(name, line):
- """Compiles a string (once) into a function, eg:
- simple_task_type('c++', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
-
- The env variables (CXX, ..) on the task must not hold dicts (order)
- The reserved keywords TGT and SRC represent the task input and output nodes
-
- quick test:
- bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
- """
-
- extr = []
- def repl(match):
- g = match.group
- if g('dollar'): return "$"
- elif g('backslash'): return '\\\\'
- elif g('subst'): extr.append((g('var'), g('code'))); return "%s"
- return None
-
- line = reg_act.sub(repl, line) or line
-
- parm = []
- dvars = []
- app = parm.append
- for (var, meth) in extr:
- if var == 'SRC':
- if meth: app('task.inputs%s' % meth)
- else: app('" ".join([a.srcpath(env) for a in task.inputs])')
- elif var == 'TGT':
- if meth: app('task.outputs%s' % meth)
- else: app('" ".join([a.bldpath(env) for a in task.outputs])')
- else:
- if not var in dvars: dvars.append(var)
- app("p('%s')" % var)
- if parm: parm = "%% (%s) " % (',\n\t\t'.join(parm))
- else: parm = ''
-
- c = COMPILE_TEMPLATE_SHELL % (line, parm)
-
- debug('action: %s', c)
- return (funex(c), dvars)
-
-def compile_fun_noshell(name, line):
-
- extr = []
- def repl(match):
- g = match.group
- if g('dollar'): return "$"
- elif g('subst'): extr.append((g('var'), g('code'))); return "<<|@|>>"
- return None
-
- line2 = reg_act.sub(repl, line)
- params = line2.split('<<|@|>>')
-
- buf = []
- dvars = []
- app = buf.append
- for x in xrange(len(extr)):
- params[x] = params[x].strip()
- if params[x]:
- app("lst.extend(%r)" % params[x].split())
- (var, meth) = extr[x]
- if var == 'SRC':
- if meth: app('lst.append(task.inputs%s)' % meth)
- else: app("lst.extend([a.srcpath(env) for a in task.inputs])")
- elif var == 'TGT':
- if meth: app('lst.append(task.outputs%s)' % meth)
- else: app("lst.extend([a.bldpath(env) for a in task.outputs])")
- else:
- app('lst.extend(to_list(env[%r]))' % var)
- if not var in dvars: dvars.append(var)
-
- if params[-1]:
- app("lst.extend(%r)" % shlex.split(params[-1]))
-
- fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
- debug('action: %s', fun)
- return (funex(fun), dvars)
-
-def compile_fun(name, line, shell=None):
- "commands can be launched by the shell or not"
- if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
- shell = True
- #else:
- # shell = False
-
- if shell is None:
- if sys.platform == 'win32':
- shell = False
- else:
- shell = True
-
- if shell:
- return compile_fun_shell(name, line)
- else:
- return compile_fun_noshell(name, line)
-
-def simple_task_type(name, line, color='GREEN', vars=[], ext_in=[], ext_out=[], before=[], after=[], shell=None):
- """return a new Task subclass with the function run compiled from the line given"""
- (fun, dvars) = compile_fun(name, line, shell)
- fun.code = line
- return task_type_from_func(name, fun, vars or dvars, color, ext_in, ext_out, before, after)
-
-def task_type_from_func(name, func, vars=[], color='GREEN', ext_in=[], ext_out=[], before=[], after=[]):
- """return a new Task subclass with the function run compiled from the line given"""
- params = {
- 'run': func,
- 'vars': vars,
- 'color': color,
- 'name': name,
- 'ext_in': Utils.to_list(ext_in),
- 'ext_out': Utils.to_list(ext_out),
- 'before': Utils.to_list(before),
- 'after': Utils.to_list(after),
- }
-
- cls = type(Task)(name, (Task,), params)
- TaskBase.classes[name] = cls
- return cls
-
-def always_run(cls):
- """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 comparation between
- task signatures is bypassed
- """
- old = cls.runnable_status
- def always(self):
- ret = old(self)
- if ret == SKIP_ME:
- return RUN_ME
- return ret
- cls.runnable_status = always
-
-def update_outputs(cls):
- """When a command is always run, it is possible that the output only change
- sometimes. By default the build node have as a hash the signature of the task
- which may not change. With this, the output nodes (produced) are hashed,
- and the hashes are set to the build nodes
-
- This may avoid unnecessary recompilations, but it uses more resources
- (hashing the output files) so it is not used by default
- """
- old_post_run = cls.post_run
- def post_run(self):
- old_post_run(self)
- bld = self.generator.bld
- for output in self.outputs:
- bld.node_sigs[self.env.variant()][output.id] = Utils.h_file(output.abspath(self.env))
- bld.task_sigs[output.id] = self.unique_id()
- cls.post_run = post_run
-
- old_runnable_status = cls.runnable_status
- def runnable_status(self):
- status = old_runnable_status(self)
- if status != RUN_ME:
- return status
-
- uid = self.unique_id()
- try:
- bld = self.outputs[0].__class__.bld
- new_sig = self.signature()
- prev_sig = bld.task_sigs[uid][0]
- if prev_sig == new_sig:
- for x in self.outputs:
- if not x.id in bld.node_sigs[self.env.variant()]:
- return RUN_ME
- if bld.task_sigs[x.id] != uid: # ensure the outputs are associated with *this* task
- return RUN_ME
- return SKIP_ME
- except KeyError:
- pass
- except IndexError:
- pass
- return RUN_ME
- cls.runnable_status = runnable_status
-
-def extract_outputs(tasks):
- """file_deps: Infer additional dependencies from task input and output nodes
- """
- v = {}
- for x in tasks:
- try:
- (ins, outs) = v[x.env.variant()]
- except KeyError:
- ins = {}
- outs = {}
- v[x.env.variant()] = (ins, outs)
-
- for a in getattr(x, 'inputs', []):
- try: ins[a.id].append(x)
- except KeyError: ins[a.id] = [x]
- for a in getattr(x, 'outputs', []):
- try: outs[a.id].append(x)
- except KeyError: outs[a.id] = [x]
-
- for (ins, outs) in v.values():
- links = set(ins.iterkeys()).intersection(outs.iterkeys())
- for k in links:
- for a in ins[k]:
- for b in outs[k]:
- a.set_run_after(b)
-
-def extract_deps(tasks):
- """file_deps: Infer additional dependencies from task input and output nodes and from implicit dependencies
- returned by the scanners - that will only work if all tasks are created
-
- this is aimed at people who have pathological builds and who do not care enough
- to implement the build dependencies properly
-
- with two loops over the list of tasks, do not expect this to be really fast
- """
-
- # first reuse the function above
- extract_outputs(tasks)
-
- # map the output nodes to the tasks producing them
- out_to_task = {}
- for x in tasks:
- v = x.env.variant()
- try:
- lst = x.outputs
- except AttributeError:
- pass
- else:
- for node in lst:
- out_to_task[(v, node.id)] = x
-
- # map the dependencies found to the tasks compiled
- dep_to_task = {}
- for x in tasks:
- try:
- x.signature()
- except: # this is on purpose
- pass
-
- v = x.env.variant()
- key = x.unique_id()
- for k in x.generator.bld.node_deps.get(x.unique_id(), []):
- try: dep_to_task[(v, k.id)].append(x)
- except KeyError: dep_to_task[(v, k.id)] = [x]
-
- # now get the intersection
- deps = set(dep_to_task.keys()).intersection(set(out_to_task.keys()))
-
- # and add the dependencies from task to task
- for idx in deps:
- for k in dep_to_task[idx]:
- k.set_run_after(out_to_task[idx])
-
- # cleanup, remove the signatures
- for x in tasks:
- try:
- delattr(x, 'cache_sig')
- except AttributeError:
- pass