From 4f4bce5301ffd8c12aed1b108affa1a75feefb67 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Wed, 4 Jan 2012 00:31:27 +0100 Subject: Include waf as an extracted source directory, rather than as a one-in-a-file script. --- buildtools/wafadmin/TaskGen.py | 612 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 buildtools/wafadmin/TaskGen.py (limited to 'buildtools/wafadmin/TaskGen.py') 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 ("" + % (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) + -- cgit