3

I am evaluating waf build for an existing project that has tasks similar to that:

1. preprocessing phase: datafile => TransformationTask => library name list
2. for each library name: 
  2.1 import files from repository
  2.2 build library

The library list depends on the preprocessing task and is naturally not known in advance.

How can this be achieved with waf?

maver
  • 41
  • 3

1 Answers1

2

You have to generate a file with the library list with a first task. Another task will have the output of the first as an input and will process the corresponding file if needed to generate what you need.

It is somehow the example given in §11.4.2 of the waf book. You have to replace the compiler output parsing with your library description file parsing. You need to copy the example and change the run method in mytool.py like:

class src2c(Task.Task):
    color = 'PINK'
    quiet = True
    before = ['cstlib']

    def run(self):

        libnode = self.inputs[0]
        libinfo = libnode.read_json()

        name = libinfo['name']
        files = [f"repo/{file}" for file in libinfo['files']]
        taskgen = self.generator
        
        # library name
        taskgen.link_task.outputs = []
        taskgen.link_task.add_target(name)
        # library sources files
        nodes = [taskgen.path.make_node(f) for f in files]
        # update discovered dependancies
        taskgen.bld.raw_deps[self.uid()] = [self.signature()] + nodes

        with g_lock:
            self.add_c_tasks(nodes)

    # cf waf book § 11.4.2
    def add_c_tasks(self, lst):
        ...
        
    # cf waf book § 11.4.2
    def runnable_status(self):
        ...

In the wscript, I simulate the datafile transformation with a copy.:

def options(opt):
    opt.load("compiler_c")

def configure(cnf):
    cnf.load("compiler_c")
    cnf.load("mytool", tooldir=".")

def build(bld):
    bld(source = "libs.json", target = "libs.src", features = "subst")
    bld(source = "libs.src", features = ["c", "cstlib"])

With a simple my_lib.json:

{
    "name": "mylib2",
    "files": ["f1.c", "f2.c"]
}

And files repo/f1.c and repo/f2.c like void f1(){} and void f2(){}

neuro
  • 14,948
  • 3
  • 36
  • 59
  • Thank you for responding. I tried this with create_lib being something like this: `def create_lib(taskgen, libnode): libinfo = libnode.read_json() name = libinfo['name'] files = [f"repo/{file}" for file in libinfo['files']] subtask = taskgen.create_task('cstlib', src=files, tgt=name, features='c cstlib') return subtask` The problem is: the libs are processed but without any source files. I feel I am still a beginner. – maver Jan 18 '22 at 12:27
  • Your hint to look for 'Task.more_tasks' leads to section 11.4.2. The function create_lib in your example has a task_gen parameter. How can I get a Task from a task_gen? – maver Jan 19 '22 at 06:28
  • Well, the problem is probably that dependencies are hidden from waf. The files needed to build libraries should be setup as dependencies. You can use a scanner or set the relevant tasks attribute as in 11.4.2. Your scenario is one of the most complicated. Maybe there's a simpler solution If you restate your what you need to do? – neuro Jan 19 '22 at 09:33
  • I'll try to write a working example if I have some time – neuro Jan 19 '22 at 09:34
  • That would be great. Then I won't post my second non-working example. – maver Jan 19 '22 at 16:45
  • I'll try. But can you elaborate on the first step? The simpler way would be to describe in wscript all the libraries you need to build, directly. – neuro Jan 20 '22 at 10:19
  • I added a working example but, I just realised that it will not work il my_lib.json is generated by another task. In that case you have to adapt the exact 11.4.2 example, replacing the parsing of the output of the compiler by the json parsing – neuro Jan 20 '22 at 11:41
  • So I modified my answer with the correct one – neuro Jan 20 '22 at 12:13
  • Thanks a lot, it works an generates the library that is described in my_lib.json. I still have a problem that might not be related to this: if I add a line `#bld.program(target="myprogram", source="main.c", use=["mylib2"])`, to the build(bld) definition, where main.c uses the function defined in f1.c or f2.c, apparently mylib2 is not linked at all. This leads to the linker error message with exresolved externals if the functions f1 or f2 are used in main.c – maver Jan 21 '22 at 11:47
  • That's expected :). waf can not guess what "mylib2" is. – neuro Jan 24 '22 at 12:20
  • The `use` attribute needs to point to a task generator name, so you need to have a task generator with the name "mylib2" for it work like that. Another thing that you can do is take advantage of how env variables are processed. Section 10.3.3 of the Waf book explains this, with a handy table of the variables you can define. For example, defining `env.STLIB_mylib2 = "mylib2"` will cause waf to link with libmylib2.a when you write `use = ["mylib2"]` in a c/c++ task. However, you also need to define `STLIBPATH_mylib2` so it knows where to find it. – user16978892 Jan 24 '22 at 16:15
  • If you post a working sample of what you have, I can help you debug it. Also, if you're curious, the logic implementing all of this is implemented in `waflib/tools/ccroot.py` in the function called `process_use` – user16978892 Jan 24 '22 at 16:19
  • @user : the problem is that the name is dynamically defined by the task, so process_use cannot have it :) – neuro Jan 24 '22 at 16:34
  • Yeah but (IIRC) if you ensure the link task doesn't run until after the dynamic tasks, you can call `process_use()` manually to update the environment variables the same way. This works because when the link task finally runs, it uses environment variables. I.e. it doesn't matter what the environment looked like at the time the link taskgen `post()`ed. You just gotta find the taskgen instance that created the link task, and call `process_use()` on it and it should work as expected. – user16978892 Jan 24 '22 at 18:15
  • Well sort of. The problem is that process_use "post" used TG. It seems a bit of a mess, even if already posted TG will not be re-posted. But It can work. I would preferably set the right env vars in mytool.py. Anyway this seems a bit complicated, partly recoding waf. Stating the real initial problem could help to avoid [XY problem](https://xyproblem.info/). – neuro Jan 25 '22 at 08:34
  • @user: By the way, you know waf well. Always a pleasure to meet someone who know this great tool :) – neuro Jan 25 '22 at 08:35
  • Yeah this is a very complicated approach. I figured this out when I did something similar while adding Android app support for a project I'm working on (involves downloading and linking with mixed Java/C++ libs from maven). It works well IME, but it's messy. A better solution for remote deps is to calculate dependencies up front, since you avoid these dynamic task problems, and it's just a better idea overall (Conan.io is a good option) And yeah, waf is great :D – user16978892 Jan 25 '22 at 16:09
  • Thanks for your laudatio, but I'm just evaluating waf for chances to replace gnu make in a large embedded project with many variants. Now i'm busy wit hother task, but i will return to this. – maver Jan 26 '22 at 06:05
  • What i don't understand is the difference between tasks and task generators. In the waf book examples, both are mentioned – maver Jan 26 '22 at 06:06
  • @maver: You have 3 main phases in waf processing : 1) wscript processing, in that phase you create TGs, 2) task generator posting -> that phase creates tasks with their dependencies, 3) task execution according to dependencies – neuro Jan 26 '22 at 08:32
  • @maver: task generators can create many tasks and manage their dependencies. The best example is a TG "program" that can create all your objects (.o) from your sources and then link them. For n sources files, it creates n+1 tasks with correct dependencies – neuro Jan 26 '22 at 08:37
  • @maver: NB: the laudatio was for user16978892 ;-) – neuro Jan 26 '22 at 08:38
  • @neuro: I noticed that after loading and reading all comments again ;-) – maver Jan 27 '22 at 11:24