Repository: mesonbuild/meson

Issue:
GeneratedList as input of CustomTarget is broken with VS backends
Can be seen in the `common\113 generatorcustom` test: https://ci.appveyor.com/project/jpakkane/meson/build/1.0.925/job/p69h84wd9rtvkdns

```
Traceback (most recent call last):
  File "C:\projects\meson\mesonbuild\mesonmain.py", line 279, in run
    app.generate()
  File "C:\projects\meson\mesonbuild\mesonmain.py", line 169, in generate
    g.generate(intr)
  File "C:\projects\meson\mesonbuild\backend\vs2010backend.py", line 168, in generate
    self.generate_solution(sln_filename, projlist)
  File "C:\projects\meson\mesonbuild\backend\vs2010backend.py", line 233, in generate_solution
    all_deps = self.determine_deps(p)
  File "C:\projects\meson\mesonbuild\backend\vs2010backend.py", line 203, in determine_deps
    all_deps[d.get_id()] = True
AttributeError: 'GeneratedList' object has no attribute 'get_id'
```



Discussion:
@nioncode I tried looking at the code for this and I thought you would be a much better person to tackle this. Do you think you can take a stab at fixing this? :)
I'm currently on vacation, but I'll have a look at it after Christmas / New Year if it is not resolved by then. 
Thanks!

Resolving patch:
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -93,9 +93,6 @@ def resolve_source_conflicts(self):
 
     def generate_custom_generator_commands(self, target, parent_node):
         generator_output_files = []
-        commands = []
-        inputs = []
-        outputs = []
         custom_target_include_dirs = []
         custom_target_output_files = []
         target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target))
@@ -116,6 +113,7 @@ def generate_custom_generator_commands(self, target, parent_node):
                 outfilelist = genlist.get_outputs()
                 exe_arr = self.exe_object_to_cmd_array(exe)
                 base_args = generator.get_arglist()
+                idgroup = ET.SubElement(parent_node, 'ItemGroup')
                 for i in range(len(infilelist)):
                     if len(infilelist) == len(outfilelist):
                         sole_output = os.path.join(target_private_dir, outfilelist[i])
@@ -131,19 +129,10 @@ def generate_custom_generator_commands(self, target, parent_node):
                     args = self.replace_outputs(args, target_private_dir, outfiles_rel)
                     args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()).replace("@BUILD_DIR@", target_private_dir)
                             for x in args]
-                    fullcmd = exe_arr + self.replace_extra_args(args, genlist)
-                    commands.append(' '.join(self.special_quote(fullcmd)))
-                    inputs.append(infilename)
-                    outputs.extend(outfiles)
-        if len(commands) > 0:
-            idgroup = ET.SubElement(parent_node, 'ItemDefinitionGroup')
-            cbs = ET.SubElement(idgroup, 'CustomBuildStep')
-            ET.SubElement(cbs, 'Command').text = '\r\n'.join(commands)
-            ET.SubElement(cbs, 'Inputs').text = ";".join(inputs)
-            ET.SubElement(cbs, 'Outputs').text = ';'.join(outputs)
-            ET.SubElement(cbs, 'Message').text = 'Generating custom sources.'
-            pg = ET.SubElement(parent_node, 'PropertyGroup')
-            ET.SubElement(pg, 'CustomBuildBeforeTargets').text = 'ClCompile'
+                    cmd = exe_arr + self.replace_extra_args(args, genlist)
+                    cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename)
+                    ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd))
+                    ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles)
         return generator_output_files, custom_target_output_files, custom_target_include_dirs
 
     def generate(self, interp):
@@ -205,8 +194,7 @@ def get_target_deps(self, t, recursive=False):
                 for d in [target.command] + target.args:
                     if isinstance(d, (build.BuildTarget, build.CustomTarget)):
                         all_deps[d.get_id()] = d
-            # BuildTarget
-            else:
+            elif isinstance(target, build.BuildTarget):
                 for ldep in target.link_targets:
                     all_deps[ldep.get_id()] = ldep
                 for obj_id, objdep in self.get_obj_target_deps(target.objects):
@@ -218,6 +206,8 @@ def get_target_deps(self, t, recursive=False):
                         gen_exe = gendep.generator.get_exe()
                         if isinstance(gen_exe, build.Executable):
                             all_deps[gen_exe.get_id()] = gen_exe
+            else:
+                raise MesonException('Unknown target type for target %s' % target)
         if not t or not recursive:
             return all_deps
         ret = self.get_target_deps(all_deps, recursive)
@@ -340,8 +330,8 @@ def target_to_build_root(self, target):
         directories = os.path.normpath(target.subdir).split(os.sep)
         return os.sep.join(['..'] * len(directories))
 
-    def special_quote(self, arr):
-        return ['&quot;%s&quot;' % i for i in arr]
+    def quote_arguments(self, arr):
+        return ['"%s"' % i for i in arr]
 
     def create_basic_crap(self, target):
         project_name = target.name
@@ -413,11 +403,11 @@ def gen_custom_target_vcxproj(self, target, ofname, guid):
         # from the target dir, not the build root.
         target.absolute_paths = True
         (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True)
-        cmd_templ = '''"%s" ''' * len(cmd)
-        ET.SubElement(customstep, 'Command').text = cmd_templ % tuple(cmd)
+        ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(cmd))
         ET.SubElement(customstep, 'Outputs').text = ';'.join(ofilenames)
         ET.SubElement(customstep, 'Inputs').text = ';'.join(srcs)
         ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
+        self.generate_custom_generator_commands(target, root)
         tree = ET.ElementTree(root)
         tree.write(ofname, encoding='utf-8', xml_declaration=True)
 
@@ -467,14 +457,14 @@ def add_include_dirs(self, lang, parent_node, file_inc_dirs):
 
     @staticmethod
     def has_objects(objects, additional_objects, generated_objects):
-        # Ignore generated objects, those are automatically used by MSBuild for VS2010, because they are part of
-        # the CustomBuildStep Outputs.
+        # Ignore generated objects, those are automatically used by MSBuild because they are part of
+        # the CustomBuild Outputs.
         return len(objects) + len(additional_objects) > 0
 
     @staticmethod
     def add_generated_objects(node, generated_objects):
-        # Do not add generated objects to project file. Those are automatically used by MSBuild for VS2010, because
-        # they are part of the CustomBuildStep Outputs.
+        # Do not add generated objects to project file. Those are automatically used by MSBuild, because
+        # they are part of the CustomBuild Outputs.
         return
 
     @staticmethod
diff --git a/mesonbuild/backend/vs2015backend.py b/mesonbuild/backend/vs2015backend.py
--- a/mesonbuild/backend/vs2015backend.py
+++ b/mesonbuild/backend/vs2015backend.py
@@ -23,14 +23,3 @@ def __init__(self, build):
         self.platform_toolset = 'v140'
         self.vs_version = '2015'
 
-    @staticmethod
-    def has_objects(objects, additional_objects, generated_objects):
-        # VS2015 requires generated objects to be added explicitly to the project file.
-        return len(objects) + len(additional_objects) + len(generated_objects) > 0
-
-    @staticmethod
-    def add_generated_objects(node, generated_objects):
-        # VS2015 requires generated objects to be added explicitly to the project file.
-        for s in generated_objects:
-            ET.SubElement(node, 'Object', Include=s)
-        return
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -1277,7 +1277,7 @@ def get_target_dependencies(self):
         for c in self.sources:
             if hasattr(c, 'held_object'):
                 c = c.held_object
-            if isinstance(c, (BuildTarget, CustomTarget, GeneratedList)):
+            if isinstance(c, (BuildTarget, CustomTarget)):
                 deps.append(c)
         return deps
 
@@ -1402,8 +1402,17 @@ def get_filename(self):
     def get_sources(self):
         return self.sources
 
+    def get_generated_lists(self):
+        genlists = []
+        for c in self.sources:
+            if hasattr(c, 'held_object'):
+                c = c.held_object
+            if isinstance(c, GeneratedList):
+                genlists.append(c)
+        return genlists
+
     def get_generated_sources(self):
-        return []
+        return self.get_generated_lists()
 
     def type_suffix(self):
         return "@cus"


Test patch:
diff --git a/test cases/common/113 generatorcustom/meson.build b/test cases/common/113 generatorcustom/meson.build
--- a/test cases/common/113 generatorcustom/meson.build	
+++ b/test cases/common/113 generatorcustom/meson.build	
@@ -1,21 +1,18 @@
 project('generatorcustom', 'c')
 
-if meson.get_compiler('c').get_id() != 'msvc'
-  creator = find_program('gen.py')
-  catter = find_program('catter.py')
+creator = find_program('gen.py')
+catter = find_program('catter.py')
 
-  gen = generator(creator,
-    output: '@BASENAME@.h',
-    arguments : ['@INPUT@', '@OUTPUT@'])
+gen = generator(creator,
+  output: '@BASENAME@.h',
+  arguments : ['@INPUT@', '@OUTPUT@'])
 
-  hs = gen.process('res1.txt', 'res2.txt')
+hs = gen.process('res1.txt', 'res2.txt')
 
-  allinone = custom_target('alltogether',
-      input : hs,
-      output : 'alltogether.h',
-      command : [catter, '@INPUT@', '@OUTPUT@'])
+allinone = custom_target('alltogether',
+    input : hs,
+    output : 'alltogether.h',
+    command : [catter, '@INPUT@', '@OUTPUT@'])
+
+executable('proggie', 'main.c', allinone)
 
-  executable('proggie', 'main.c', allinone)
-else
-  error('MESON_SKIP_TEST: Skipping test on VS backend; see: https://github.com/mesonbuild/meson/issues/1004')
-endif