Repository: mesonbuild/meson

Issue:
Cannot use output of generator for custom_target?
Hello,

I am not sure if I am doing something wrong.

I have something like this:

```
          gen = generator(make_resource_header,
          output: '@BASENAME@.h',
          arguments : ['@INPUT@', '@OUTPUT@'])


          gen_src = card_gen.process(
          'images/res1.jpg',
          'images/res2.jpg')
```

I want gen_src to be able to be fed into a custom_target so that I can cat all the results,
but it seems that I cannot get the results of the generated files in a custom_target.
Seems reasonable to me:

```
all_headers_in_one = custom_target('all_headers',
    input : gen_src, <--------------------------------------------- This as input will fail
    output : all_resources.hpp',
    command : ['python3', cat_files, '@INPUT@',
               '@OUTPUT@'])
```

After this I would like to include `all_headers_in_one` in my executable as a dependency (source file). 

The error is below:

```
 File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/mesonmain.py", line 265, in run
    app.generate()
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/mesonmain.py", line 160, in generate
    g.generate(intr)
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/backend/ninjabackend.py", line 179, in generate
    [self.generate_target(t, outfile) for t in self.build.get_targets().values()]
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/backend/ninjabackend.py", line 179, in <listcomp>
    [self.generate_target(t, outfile) for t in self.build.get_targets().values()]
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/backend/ninjabackend.py", line 224, in generate_target
    self.generate_custom_target(target, outfile)
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/backend/ninjabackend.py", line 344, in generate_custom_target
    (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
  File "/usr/local/lib/python3.5/site-packages/meson-0.33.0.dev1-py3.5.egg/mesonbuild/backend/backends.py", line 480, in eval_custom_target_command
    fname = i.rel_to_builddir(self.build_to_src)
AttributeError: 'GeneratedList' object has no attribute 'rel_to_builddir'
```



Discussion:

Resolving patch:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -473,14 +473,16 @@ def eval_custom_target_command(self, target, absolute_paths=False):
             if hasattr(i, 'held_object'):
                 i = i.held_object
             if isinstance(i, str):
-                fname = os.path.join(self.build_to_src, target.subdir, i)
+                fname = [os.path.join(self.build_to_src, target.subdir, i)]
             elif isinstance(i, build.BuildTarget):
-                fname = self.get_target_filename(i)
+                fname = [self.get_target_filename(i)]
+            elif isinstance(i, build.GeneratedList):
+                fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outfilelist()]
             else:
-                fname = i.rel_to_builddir(self.build_to_src)
+                fname = [i.rel_to_builddir(self.build_to_src)]
             if absolute_paths:
-                fname = os.path.join(self.environment.get_build_dir(), fname)
-            srcs.append(fname)
+                fname =[os.path.join(self.environment.get_build_dir(), f) for f in fname]
+            srcs += fname
         cmd = []
         for i in target.command:
             if isinstance(i, build.Executable):
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -340,7 +340,15 @@ def process_target_dependencies(self, target, outfile):
             if not tname in self.processed_targets:
                 self.generate_target(t, outfile)
 
+    def custom_target_generator_inputs(self, target, outfile):
+        for s in target.sources:
+            if hasattr(s, 'held_object'):
+                s = s.held_object
+            if isinstance(s, build.GeneratedList):
+                self.generate_genlist_for_target(s, target, outfile)
+
     def generate_custom_target(self, target, outfile):
+        self.custom_target_generator_inputs(target, outfile)
         (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
         deps = []
         desc = 'Generating {0} with a {1} command.'
@@ -1303,40 +1311,43 @@ def generate_custom_generator_rules(self, target, outfile):
         for genlist in target.get_generated_sources():
             if isinstance(genlist, build.CustomTarget):
                 continue # Customtarget has already written its output rules
-            generator = genlist.get_generator()
-            exe = generator.get_exe()
-            exe_arr = self.exe_object_to_cmd_array(exe)
-            infilelist = genlist.get_infilelist()
-            outfilelist = genlist.get_outfilelist()
-            base_args = generator.get_arglist()
-            extra_dependencies = [os.path.join(self.build_to_src, i) for i in genlist.extra_depends]
-            for i in range(len(infilelist)):
-                if len(generator.outputs) == 1:
-                    sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i])
-                else:
-                    sole_output = ''
-                curfile = infilelist[i]
-                infilename = os.path.join(self.build_to_src, curfile)
-                outfiles = genlist.get_outputs_for(curfile)
-                outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles]
-                args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\
-                        for x in base_args]
-                args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist)
-                # We have consumed output files, so drop them from the list of remaining outputs.
-                if sole_output == '':
-                    outfilelist = outfilelist[len(generator.outputs):]
-                relout = self.get_target_private_dir(target)
-                args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout)
-                        for x in args]
-                cmdlist = exe_arr + self.replace_extra_args(args, genlist)
-                elem = NinjaBuildElement(self.all_outputs, outfiles, 'CUSTOM_COMMAND', infilename)
-                if len(extra_dependencies) > 0:
-                    elem.add_dep(extra_dependencies)
-                elem.add_item('DESC', 'Generating $out')
-                if isinstance(exe, build.BuildTarget):
-                    elem.add_dep(self.get_target_filename(exe))
-                elem.add_item('COMMAND', cmdlist)
-                elem.write(outfile)
+            self.generate_genlist_for_target(genlist, target, outfile)
+
+    def generate_genlist_for_target(self, genlist, target, outfile):
+        generator = genlist.get_generator()
+        exe = generator.get_exe()
+        exe_arr = self.exe_object_to_cmd_array(exe)
+        infilelist = genlist.get_infilelist()
+        outfilelist = genlist.get_outfilelist()
+        base_args = generator.get_arglist()
+        extra_dependencies = [os.path.join(self.build_to_src, i) for i in genlist.extra_depends]
+        for i in range(len(infilelist)):
+            if len(generator.outputs) == 1:
+                sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i])
+            else:
+                sole_output = ''
+            curfile = infilelist[i]
+            infilename = os.path.join(self.build_to_src, curfile)
+            outfiles = genlist.get_outputs_for(curfile)
+            outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles]
+            args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\
+                    for x in base_args]
+            args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist)
+            # We have consumed output files, so drop them from the list of remaining outputs.
+            if sole_output == '':
+                outfilelist = outfilelist[len(generator.outputs):]
+            relout = self.get_target_private_dir(target)
+            args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout)
+                    for x in args]
+            cmdlist = exe_arr + self.replace_extra_args(args, genlist)
+            elem = NinjaBuildElement(self.all_outputs, outfiles, 'CUSTOM_COMMAND', infilename)
+            if len(extra_dependencies) > 0:
+                elem.add_dep(extra_dependencies)
+            elem.add_item('DESC', 'Generating $out')
+            if isinstance(exe, build.BuildTarget):
+                elem.add_dep(self.get_target_filename(exe))
+            elem.add_item('COMMAND', cmdlist)
+            elem.write(outfile)
 
     def scan_fortran_module_outputs(self, target):
         compiler = None
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -824,7 +824,7 @@ def get_target_dependencies(self):
         for c in self.sources:
             if hasattr(c, 'held_object'):
                 c = c.held_object
-            if isinstance(c, BuildTarget) or isinstance(c, CustomTarget):
+            if isinstance(c, BuildTarget) or isinstance(c, CustomTarget) or isinstance(c, GeneratedList):
                 deps.append(c)
         return deps
 


Test patch:
diff --git a/test cases/common/113 generatorcustom/catter.py b/test cases/common/113 generatorcustom/catter.py
new file mode 100755
--- /dev/null
+++ b/test cases/common/113 generatorcustom/catter.py	
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+import sys, os
+
+output = sys.argv[-1]
+inputs = sys.argv[1:-1]
+
+with open(output, 'w') as ofile:
+    ofile.write('#pragma once\n')
+    for i in inputs:
+        content = open(i, 'r').read()
+        ofile.write(content)
+        ofile.write('\n')
diff --git a/test cases/common/113 generatorcustom/gen.py b/test cases/common/113 generatorcustom/gen.py
new file mode 100755
--- /dev/null
+++ b/test cases/common/113 generatorcustom/gen.py	
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+
+import sys, os
+
+ifile = sys.argv[1]
+ofile = sys.argv[2]
+
+resname = open(ifile, 'r').readline().strip()
+
+templ = 'const char %s[] = "%s";\n'
+open(ofile, 'w').write(templ % (resname, resname))
diff --git a/test cases/common/113 generatorcustom/main.c b/test cases/common/113 generatorcustom/main.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/113 generatorcustom/main.c	
@@ -0,0 +1,7 @@
+#include<stdio.h>
+
+#include"alltogether.h"
+
+int main(int argc, char **argv) {
+    return 0;
+}
diff --git a/test cases/common/113 generatorcustom/meson.build b/test cases/common/113 generatorcustom/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/113 generatorcustom/meson.build	
@@ -0,0 +1,17 @@
+project('generatorcustom', 'c')
+
+creator = find_program('gen.py')
+catter = find_program('catter.py')
+
+gen = generator(creator,
+  output: '@BASENAME@.h',
+  arguments : ['@INPUT@', '@OUTPUT@'])
+
+hs = gen.process('res1.txt', 'res2.txt')
+
+allinone = custom_target('alltogether',
+    input : hs,
+    output : 'alltogether.h',
+    command : [catter, '@INPUT@', '@OUTPUT@'])
+
+executable('proggie', 'main.c', allinone)
diff --git a/test cases/common/113 generatorcustom/res1.txt b/test cases/common/113 generatorcustom/res1.txt
new file mode 100644
--- /dev/null
+++ b/test cases/common/113 generatorcustom/res1.txt	
@@ -0,0 +1 @@
+res1
diff --git a/test cases/common/113 generatorcustom/res2.txt b/test cases/common/113 generatorcustom/res2.txt
new file mode 100644
--- /dev/null
+++ b/test cases/common/113 generatorcustom/res2.txt	
@@ -0,0 +1 @@
+res2