Imported Upstream version 4.0.0+dfsg1
[abartlet/samba-debian.git] / buildtools / wafadmin / TaskGen.py
diff --git a/buildtools/wafadmin/TaskGen.py b/buildtools/wafadmin/TaskGen.py
new file mode 100644 (file)
index 0000000..ae1834a
--- /dev/null
@@ -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)
+