The example below demonstrates how to tackle the following requirements:
The main difficulty in this scenario is to store the information on the source file produced and to create the corresponding tasks each time.
## A DEMO ## VERSION='0.0.1' APPNAME='unknown_outputs' srcdir = '.' blddir = 'build' def set_options(opt): pass def configure(conf): # used only when configured from the same folder conf.check_tool('gcc') conf.env["SHPIP_COMPILER"] = os.getcwd() + os.sep + "bad_compiler.py" def build(bld): staticlib = bld.new_task_gen() staticlib.features = 'cc cstaticlib' staticlib.source = 'x.c foo.shpip'staticlib.target='teststaticlib' staticlib.includes = '.' ## INTERNAL CODE BELOW ## import os import TaskGen, Task, Utils, Build from TaskGen import taskgen, feature, before, after, extension from logging import debug from Constants import * @taskgen @after('apply_link') @extension('.shpip') def process_shpip(self, node):
tsk = shpip_task(self.env, generator=self) tsk.task_gen = self tsk.set_inputs(node) class shpip_task(Task.Task):
""" A special task, which finds its outputs once it has run It outputs cpp files that must be compiled too """ color = 'PINK' quiet = 1
# important, no link before all shpip are done before = ['cc_link', 'cxx_link', 'ar_link_static'] def __init__(self, *k, **kw): Task.Task.__init__(self, *k, **kw) def run(self):
"runs a program that creates cpp files, capture the output to compile them" node = self.inputs[0] dir = self.generator.bld.srcnode.bldpath(self.env) cmd = 'cd %s && %s %s' % (dir, self.env['SHPIP_COMPILER'], node.abspath(self.env)) try: # read the contents of the file and create cpp files from it files = os.popen(cmd).read().strip() except: # comment the following line to disable debugging #raise return 1 # error # the variable lst should contain a list of paths to the files produced lst = Utils.to_list(files) # Waf does not know "magically" what files are produced # In the most general case it may be necessary to run os.listdir() to see them # In this demo the command outputs is giving us this list # the files exist in the build dir only so we do not use find_or_declare build_nodes = [node.parent.exclusive_build_node(x) for x in lst] self.outputs = build_nodes # create the cpp tasks, in the thread self.more_tasks = self.add_cpp_tasks(build_nodes)
# cache the file names and the task signature node = self.inputs[0] sig = self.signature() self.generator.bld.raw_deps[self.unique_id()] = [sig] + lst return 0 # no error def runnable_status(self): # look at the cache, if the shpip task was already run # and if the source has not changed, create the corresponding cpp tasks for t in self.run_after: if not t.hasrun: return ASK_LATER tree = self.generator.bld node = self.inputs[0] try:
sig = self.signature() key = self.unique_id() deps = tree.raw_deps[key] prev_sig = tree.task_sigs[key][0] except KeyError: pass else: # if the file has not changed, create the cpp tasks if prev_sig == sig: lst = [self.task_gen.path.exclusive_build_node(y) for y in deps[1:]] self.set_outputs(lst) lst = self.add_cpp_tasks(lst)
for tsk in lst: generator = self.generator.bld.generator generator.outstanding.append(tsk) if not self.outputs: return RUN_ME # this is a part of Task.Task:runnable_status: first node does not exist -> run # this is necessary after a clean env = self.env node = self.outputs[0] variant = node.variant(env) try: time = tree.node_sigs[variant][node.id] except KeyError: debug("run task #%d - the first node does not exist" % self.idx, 'task') try: new_sig = self.signature() except KeyError: return RUN_ME ret = self.can_retrieve_cache(new_sig) return ret and SKIP_ME or RUN_ME return SKIP_ME def add_cpp_tasks(self, lst):
"creates cpp tasks after the build has started" tgen = self.task_gen tsklst = [] for node in lst: TaskGen.task_gen.mapped['c_hook'](tgen, node) task = tgen.compiled_tasks[-1] task.set_run_after(self) # important, no link before compilations are all over try: self.generator.link_task.set_run_after(task) except AttributeError: pass tgen.link_task.inputs.append(task.outputs[0]) tsklst.append(task) # if headers are produced something like this can be done # to add the include paths dir = task.inputs[0].parent # include paths for c++ and c self.env.append_unique('_CXXINCFLAGS', '-I%s' % dir.abspath(self.env)) self.env.append_unique('_CCINCFLAGS', '-I%s' % dir.abspath(self.env)) self.env.append_value('INC_PATHS', dir) # for the waf preprocessor return tsklst
An example. The source line contains a directive foo.shpip which triggers the creation of a shpip task (it does not represent a real file) | |
This method is used to create the shpip task when a file ending in .shpip is found | |
Create the new task type | |
Disable the warnings raised because the task has no input and outputs | |
Execute the task | |
Retrieve the information on the source files created | |
Create the c++ tasks used for processing the source files found | |
If the tasks are created during a task execution (in an execution thread), the tasks must be re-injected by adding them to the attribute more_tasks | |
If the tasks are created during the task examination (runnable_status), the tasks can be injected directly in the build by using the attribute outstanding of the scheduler |