summaryrefslogtreecommitdiff
path: root/buildtools/wafadmin/TaskGen.py
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@samba.org>2012-01-04 00:31:27 +0100
committerJelmer Vernooij <jelmer@samba.org>2012-01-04 22:34:20 +0100
commit4f4bce5301ffd8c12aed1b108affa1a75feefb67 (patch)
tree1607accd70a2c37a9b996f7b42ec926b014cfe5b /buildtools/wafadmin/TaskGen.py
parent1b45f2aed86dda9fda6e6bcf1c9c7cbdc471c18d (diff)
downloadsamba-4f4bce5301ffd8c12aed1b108affa1a75feefb67.tar.gz
samba-4f4bce5301ffd8c12aed1b108affa1a75feefb67.tar.bz2
samba-4f4bce5301ffd8c12aed1b108affa1a75feefb67.zip
Include waf as an extracted source directory, rather than as a one-in-a-file script.
Diffstat (limited to 'buildtools/wafadmin/TaskGen.py')
-rw-r--r--buildtools/wafadmin/TaskGen.py612
1 files changed, 612 insertions, 0 deletions
diff --git a/buildtools/wafadmin/TaskGen.py b/buildtools/wafadmin/TaskGen.py
new file mode 100644
index 0000000000..ae1834a10a
--- /dev/null
+++ b/buildtools/wafadmin/TaskGen.py
@@ -0,0 +1,612 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2008 (ita)
+
+"""
+The class task_gen encapsulates the creation of task objects (low-level code)
+The instances can have various parameters, but the creation of task nodes (Task.py)
+is delayed. To achieve this, various methods are called from the method "apply"
+
+The class task_gen contains lots of methods, and a configuration table:
+* the methods to call (self.meths) can be specified dynamically (removing, adding, ..)
+* the order of the methods (self.prec or by default task_gen.prec) is configurable
+* new methods can be inserted dynamically without pasting old code
+
+Additionally, task_gen provides the method apply_core
+* file extensions are mapped to methods: def meth(self, name_or_node)
+* if a mapping is not found in self.mappings, it is searched in task_gen.mappings
+* when called, the functions may modify self.allnodes to re-add source to process
+* the mappings can map an extension or a filename (see the code below)
+
+WARNING: subclasses must reimplement the clone method
+"""
+
+import os, traceback, copy
+import Build, Task, Utils, Logs, Options
+from Logs import debug, error, warn
+from Constants import *
+
+typos = {
+'sources':'source',
+'targets':'target',
+'include':'includes',
+'define':'defines',
+'importpath':'importpaths',
+'install_var':'install_path',
+'install_subdir':'install_path',
+'inst_var':'install_path',
+'inst_dir':'install_path',
+'feature':'features',
+}
+
+class register_obj(type):
+ """no decorators for classes, so we use a metaclass
+ we store into task_gen.classes the classes that inherit task_gen
+ and whose names end in '_taskgen'
+ """
+ def __init__(cls, name, bases, dict):
+ super(register_obj, cls).__init__(name, bases, dict)
+ name = cls.__name__
+ suffix = '_taskgen'
+ if name.endswith(suffix):
+ task_gen.classes[name.replace(suffix, '')] = cls
+
+class task_gen(object):
+ """
+ Most methods are of the form 'def meth(self):' without any parameters
+ there are many of them, and they do many different things:
+ * task creation
+ * task results installation
+ * environment modification
+ * attribute addition/removal
+
+ The inheritance approach is complicated
+ * mixing several languages at once
+ * subclassing is needed even for small changes
+ * inserting new methods is complicated
+
+ This new class uses a configuration table:
+ * adding new methods easily
+ * obtaining the order in which to call the methods
+ * postponing the method calls (post() -> apply)
+
+ Additionally, a 'traits' static attribute is provided:
+ * this list contains methods
+ * the methods can remove or add methods from self.meths
+ Example1: the attribute 'staticlib' is set on an instance
+ a method set in the list of traits is executed when the
+ instance is posted, it finds that flag and adds another method for execution
+ Example2: a method set in the list of traits finds the msvc
+ compiler (from self.env['MSVC']==1); more methods are added to self.meths
+ """
+
+ __metaclass__ = register_obj
+ mappings = {}
+ mapped = {}
+ prec = Utils.DefaultDict(list)
+ traits = Utils.DefaultDict(set)
+ classes = {}
+
+ def __init__(self, *kw, **kwargs):
+ self.prec = Utils.DefaultDict(list)
+ "map precedence of function names to call"
+ # so we will have to play with directed acyclic graphs
+ # detect cycles, etc
+
+ self.source = ''
+ self.target = ''
+
+ # list of methods to execute - does not touch it by hand unless you know
+ self.meths = []
+
+ # list of mappings extension -> function
+ self.mappings = {}
+
+ # list of features (see the documentation on traits)
+ self.features = list(kw)
+
+ # not always a good idea
+ self.tasks = []
+
+ self.default_chmod = O644
+ self.default_install_path = None
+
+ # kind of private, beware of what you put in it, also, the contents are consumed
+ self.allnodes = []
+
+ self.bld = kwargs.get('bld', Build.bld)
+ self.env = self.bld.env.copy()
+
+ self.path = self.bld.path # emulate chdir when reading scripts
+ self.name = '' # give a name to the target (static+shlib with the same targetname ambiguity)
+
+ # provide a unique id
+ self.idx = self.bld.idx[self.path.id] = self.bld.idx.get(self.path.id, 0) + 1
+
+ for key, val in kwargs.iteritems():
+ setattr(self, key, val)
+
+ self.bld.task_manager.add_task_gen(self)
+ self.bld.all_task_gen.append(self)
+
+ def __str__(self):
+ return ("<task_gen '%s' of type %s defined in %s>"
+ % (self.name or self.target, self.__class__.__name__, str(self.path)))
+
+ def __setattr__(self, name, attr):
+ real = typos.get(name, name)
+ if real != name:
+ warn('typo %s -> %s' % (name, real))
+ if Logs.verbose > 0:
+ traceback.print_stack()
+ object.__setattr__(self, real, attr)
+
+ def to_list(self, value):
+ "helper: returns a list"
+ if isinstance(value, str): return value.split()
+ else: return value
+
+ def apply(self):
+ "order the methods to execute using self.prec or task_gen.prec"
+ keys = set(self.meths)
+
+ # add the methods listed in the features
+ self.features = Utils.to_list(self.features)
+ for x in self.features + ['*']:
+ st = task_gen.traits[x]
+ if not st:
+ warn('feature %r does not exist - bind at least one method to it' % x)
+ keys.update(st)
+
+ # copy the precedence table
+ prec = {}
+ prec_tbl = self.prec or task_gen.prec
+ for x in prec_tbl:
+ if x in keys:
+ prec[x] = prec_tbl[x]
+
+ # elements disconnected
+ tmp = []
+ for a in keys:
+ for x in prec.values():
+ if a in x: break
+ else:
+ tmp.append(a)
+
+ # topological sort
+ out = []
+ while tmp:
+ e = tmp.pop()
+ if e in keys: out.append(e)
+ try:
+ nlst = prec[e]
+ except KeyError:
+ pass
+ else:
+ del prec[e]
+ for x in nlst:
+ for y in prec:
+ if x in prec[y]:
+ break
+ else:
+ tmp.append(x)
+
+ if prec: raise Utils.WafError("graph has a cycle %s" % str(prec))
+ out.reverse()
+ self.meths = out
+
+ # then we run the methods in order
+ debug('task_gen: posting %s %d', self, id(self))
+ for x in out:
+ try:
+ v = getattr(self, x)
+ except AttributeError:
+ raise Utils.WafError("tried to retrieve %s which is not a valid method" % x)
+ debug('task_gen: -> %s (%d)', x, id(self))
+ v()
+
+ def post(self):
+ "runs the code to create the tasks, do not subclass"
+ if not self.name:
+ if isinstance(self.target, list):
+ self.name = ' '.join(self.target)
+ else:
+ self.name = self.target
+
+ if getattr(self, 'posted', None):
+ #error("OBJECT ALREADY POSTED" + str( self))
+ return
+
+ self.apply()
+ self.posted = True
+ debug('task_gen: posted %s', self.name)
+
+ def get_hook(self, ext):
+ try: return self.mappings[ext]
+ except KeyError:
+ try: return task_gen.mappings[ext]
+ except KeyError: return None
+
+ # TODO waf 1.6: always set the environment
+ # TODO waf 1.6: create_task(self, name, inputs, outputs)
+ def create_task(self, name, src=None, tgt=None, env=None):
+ env = env or self.env
+ task = Task.TaskBase.classes[name](env.copy(), generator=self)
+ if src:
+ task.set_inputs(src)
+ if tgt:
+ task.set_outputs(tgt)
+ self.tasks.append(task)
+ return task
+
+ def name_to_obj(self, name):
+ return self.bld.name_to_obj(name, self.env)
+
+ def find_sources_in_dirs(self, dirnames, excludes=[], exts=[]):
+ """
+ The attributes "excludes" and "exts" must be lists to avoid the confusion
+ find_sources_in_dirs('a', 'b', 'c') <-> find_sources_in_dirs('a b c')
+
+ do not use absolute paths
+ do not use paths outside of the source tree
+ the files or folder beginning by . are not returned
+
+ # TODO: remove in Waf 1.6
+ """
+
+ err_msg = "'%s' attribute must be a list"
+ if not isinstance(excludes, list):
+ raise Utils.WscriptError(err_msg % 'excludes')
+ if not isinstance(exts, list):
+ raise Utils.WscriptError(err_msg % 'exts')
+
+ lst = []
+
+ #make sure dirnames is a list helps with dirnames with spaces
+ dirnames = self.to_list(dirnames)
+
+ ext_lst = exts or list(self.mappings.keys()) + list(task_gen.mappings.keys())
+
+ for name in dirnames:
+ anode = self.path.find_dir(name)
+
+ if not anode or not anode.is_child_of(self.bld.srcnode):
+ raise Utils.WscriptError("Unable to use '%s' - either because it's not a relative path" \
+ ", or it's not child of '%s'." % (name, self.bld.srcnode))
+
+ self.bld.rescan(anode)
+ for name in self.bld.cache_dir_contents[anode.id]:
+
+ # ignore hidden files
+ if name.startswith('.'):
+ continue
+
+ (base, ext) = os.path.splitext(name)
+ if ext in ext_lst and not name in lst and not name in excludes:
+ lst.append((anode.relpath_gen(self.path) or '.') + os.path.sep + name)
+
+ lst.sort()
+ self.source = self.to_list(self.source)
+ if not self.source: self.source = lst
+ else: self.source += lst
+
+ def clone(self, env):
+ """when creating a clone in a task generator method,
+ make sure to set posted=False on the clone
+ else the other task generator will not create its tasks"""
+ newobj = task_gen(bld=self.bld)
+ for x in self.__dict__:
+ if x in ['env', 'bld']:
+ continue
+ elif x in ["path", "features"]:
+ setattr(newobj, x, getattr(self, x))
+ else:
+ setattr(newobj, x, copy.copy(getattr(self, x)))
+
+ newobj.__class__ = self.__class__
+ if isinstance(env, str):
+ newobj.env = self.bld.all_envs[env].copy()
+ else:
+ newobj.env = env.copy()
+
+ return newobj
+
+ def get_inst_path(self):
+ return getattr(self, '_install_path', getattr(self, 'default_install_path', ''))
+
+ def set_inst_path(self, val):
+ self._install_path = val
+
+ install_path = property(get_inst_path, set_inst_path)
+
+
+ def get_chmod(self):
+ return getattr(self, '_chmod', getattr(self, 'default_chmod', O644))
+
+ def set_chmod(self, val):
+ self._chmod = val
+
+ chmod = property(get_chmod, set_chmod)
+
+def declare_extension(var, func):
+ try:
+ for x in Utils.to_list(var):
+ task_gen.mappings[x] = func
+ except:
+ raise Utils.WscriptError('declare_extension takes either a list or a string %r' % var)
+ task_gen.mapped[func.__name__] = func
+
+def declare_order(*k):
+ assert(len(k) > 1)
+ n = len(k) - 1
+ for i in xrange(n):
+ f1 = k[i]
+ f2 = k[i+1]
+ if not f1 in task_gen.prec[f2]:
+ task_gen.prec[f2].append(f1)
+
+def declare_chain(name='', action='', ext_in='', ext_out='', reentrant=True, color='BLUE',
+ install=0, before=[], after=[], decider=None, rule=None, scan=None):
+ """
+ see Tools/flex.py for an example
+ while i do not like such wrappers, some people really do
+ """
+
+ action = action or rule
+ if isinstance(action, str):
+ act = Task.simple_task_type(name, action, color=color)
+ else:
+ act = Task.task_type_from_func(name, action, color=color)
+ act.ext_in = tuple(Utils.to_list(ext_in))
+ act.ext_out = tuple(Utils.to_list(ext_out))
+ act.before = Utils.to_list(before)
+ act.after = Utils.to_list(after)
+ act.scan = scan
+
+ def x_file(self, node):
+ if decider:
+ ext = decider(self, node)
+ else:
+ ext = ext_out
+
+ if isinstance(ext, str):
+ out_source = node.change_ext(ext)
+ if reentrant:
+ self.allnodes.append(out_source)
+ elif isinstance(ext, list):
+ out_source = [node.change_ext(x) for x in ext]
+ if reentrant:
+ for i in xrange((reentrant is True) and len(out_source) or reentrant):
+ self.allnodes.append(out_source[i])
+ else:
+ # XXX: useless: it will fail on Utils.to_list above...
+ raise Utils.WafError("do not know how to process %s" % str(ext))
+
+ tsk = self.create_task(name, node, out_source)
+
+ if node.__class__.bld.is_install:
+ tsk.install = install
+
+ declare_extension(act.ext_in, x_file)
+ return x_file
+
+def bind_feature(name, methods):
+ lst = Utils.to_list(methods)
+ task_gen.traits[name].update(lst)
+
+"""
+All the following decorators are registration decorators, i.e add an attribute to current class
+ (task_gen and its derivatives), with same name as func, which points to func itself.
+For example:
+ @taskgen
+ def sayHi(self):
+ print("hi")
+Now taskgen.sayHi() may be called
+
+If python were really smart, it could infer itself the order of methods by looking at the
+attributes. A prerequisite for execution is to have the attribute set before.
+Intelligent compilers binding aspect-oriented programming and parallelization, what a nice topic for studies.
+"""
+def taskgen(func):
+ """
+ register a method as a task generator method
+ """
+ setattr(task_gen, func.__name__, func)
+ return func
+
+def feature(*k):
+ """
+ declare a task generator method that will be executed when the
+ object attribute 'feature' contains the corresponding key(s)
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for name in k:
+ task_gen.traits[name].update([func.__name__])
+ return func
+ return deco
+
+def before(*k):
+ """
+ declare a task generator method which will be executed
+ before the functions of given name(s)
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for fun_name in k:
+ if not func.__name__ in task_gen.prec[fun_name]:
+ task_gen.prec[fun_name].append(func.__name__)
+ return func
+ return deco
+
+def after(*k):
+ """
+ declare a task generator method which will be executed
+ after the functions of given name(s)
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for fun_name in k:
+ if not fun_name in task_gen.prec[func.__name__]:
+ task_gen.prec[func.__name__].append(fun_name)
+ return func
+ return deco
+
+def extension(var):
+ """
+ declare a task generator method which will be invoked during
+ the processing of source files for the extension given
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ try:
+ for x in Utils.to_list(var):
+ task_gen.mappings[x] = func
+ except:
+ raise Utils.WafError('extension takes either a list or a string %r' % var)
+ task_gen.mapped[func.__name__] = func
+ return func
+ return deco
+
+# TODO make certain the decorators may be used here
+
+def apply_core(self):
+ """Process the attribute source
+ transform the names into file nodes
+ try to process the files by name first, later by extension"""
+ # get the list of folders to use by the scanners
+ # all our objects share the same include paths anyway
+ find_resource = self.path.find_resource
+
+ for filename in self.to_list(self.source):
+ # if self.mappings or task_gen.mappings contains a file of the same name
+ x = self.get_hook(filename)
+ if x:
+ x(self, filename)
+ else:
+ node = find_resource(filename)
+ if not node: raise Utils.WafError("source not found: '%s' in '%s'" % (filename, str(self.path)))
+ self.allnodes.append(node)
+
+ for node in self.allnodes:
+ # self.mappings or task_gen.mappings map the file extension to a function
+ x = self.get_hook(node.suffix())
+
+ if not x:
+ raise Utils.WafError("Cannot guess how to process %s (got mappings %r in %r) -> try conf.check_tool(..)?" % \
+ (str(node), self.__class__.mappings.keys(), self.__class__))
+ x(self, node)
+feature('*')(apply_core)
+
+def exec_rule(self):
+ """Process the attribute rule, when provided the method apply_core will be disabled
+ """
+ if not getattr(self, 'rule', None):
+ return
+
+ # someone may have removed it already
+ try:
+ self.meths.remove('apply_core')
+ except ValueError:
+ pass
+
+ # get the function and the variables
+ func = self.rule
+
+ vars2 = []
+ if isinstance(func, str):
+ # use the shell by default for user-defined commands
+ (func, vars2) = Task.compile_fun('', self.rule, shell=getattr(self, 'shell', True))
+ func.code = self.rule
+
+ # create the task class
+ name = getattr(self, 'name', None) or self.target or self.rule
+ if not isinstance(name, str):
+ name = str(self.idx)
+ cls = Task.task_type_from_func(name, func, getattr(self, 'vars', vars2))
+ cls.color = getattr(self, 'color', 'BLUE')
+
+ # now create one instance
+ tsk = self.create_task(name)
+
+ dep_vars = getattr(self, 'dep_vars', ['ruledeps'])
+ if dep_vars:
+ tsk.dep_vars = dep_vars
+ if isinstance(self.rule, str):
+ tsk.env.ruledeps = self.rule
+ else:
+ # only works if the function is in a global module such as a waf tool
+ tsk.env.ruledeps = Utils.h_fun(self.rule)
+
+ # we assume that the user knows that without inputs or outputs
+ #if not getattr(self, 'target', None) and not getattr(self, 'source', None):
+ # cls.quiet = True
+
+ if getattr(self, 'target', None):
+ cls.quiet = True
+ tsk.outputs = [self.path.find_or_declare(x) for x in self.to_list(self.target)]
+
+ if getattr(self, 'source', None):
+ cls.quiet = True
+ tsk.inputs = []
+ for x in self.to_list(self.source):
+ y = self.path.find_resource(x)
+ if not y:
+ raise Utils.WafError('input file %r could not be found (%r)' % (x, self.path.abspath()))
+ tsk.inputs.append(y)
+
+ if self.allnodes:
+ tsk.inputs.extend(self.allnodes)
+
+ if getattr(self, 'scan', None):
+ cls.scan = self.scan
+
+ if getattr(self, 'install_path', None):
+ tsk.install_path = self.install_path
+
+ if getattr(self, 'cwd', None):
+ tsk.cwd = self.cwd
+
+ if getattr(self, 'on_results', None):
+ Task.update_outputs(cls)
+
+ if getattr(self, 'always', None):
+ Task.always_run(cls)
+
+ for x in ['after', 'before', 'ext_in', 'ext_out']:
+ setattr(cls, x, getattr(self, x, []))
+feature('*')(exec_rule)
+before('apply_core')(exec_rule)
+
+def sequence_order(self):
+ """
+ add a strict sequential constraint between the tasks generated by task generators
+ it uses the fact that task generators are posted in order
+ it will not post objects which belong to other folders
+ there is also an awesome trick for executing the method in last position
+
+ to use:
+ bld(features='javac seq')
+ bld(features='jar seq')
+
+ to start a new sequence, set the attribute seq_start, for example:
+ obj.seq_start = True
+ """
+ if self.meths and self.meths[-1] != 'sequence_order':
+ self.meths.append('sequence_order')
+ return
+
+ if getattr(self, 'seq_start', None):
+ return
+
+ # all the tasks previously declared must be run before these
+ if getattr(self.bld, 'prev', None):
+ self.bld.prev.post()
+ for x in self.bld.prev.tasks:
+ for y in self.tasks:
+ y.set_run_after(x)
+
+ self.bld.prev = self
+
+feature('seq')(sequence_order)
+