Xcode: can generate object files with generators.
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index 2bd6acf..d94ffde 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -189,6 +189,7 @@
         self.test_command_id = self.gen_id()
         self.test_buildconf_id = self.gen_id()
         self.top_level_dict = PbxDict()
+        self.generator_outputs = {}
         # In Xcode files are not accessed via their file names, but rather every one of them
         # gets an unique id. More precisely they get one unique id per target they are used
         # in. If you generate only one id per file and use them, compilation will work but the
@@ -252,6 +253,7 @@
         self.generate_native_target_map()
         self.generate_native_frameworks_map()
         self.generate_custom_target_map()
+        self.generate_generator_target_map()
         self.generate_source_phase_map()
         self.generate_target_dependency_map()
         self.generate_pbxdep_map()
@@ -378,6 +380,17 @@
                 self.custom_target_output_buildfile[o] = self.gen_id()
                 self.custom_target_output_fileref[o] = self.gen_id()
 
+    def generate_generator_target_map(self):
+        # Generator objects do not have natural unique ids
+        # so use a counter.
+        for tname, t in self.build_targets.items():
+            generator_id = 0
+            for s in t.generated:
+                if isinstance(s, build.GeneratedList):
+                    self.shell_targets[(tname, generator_id)] = self.gen_id()
+                    generator_id += 1
+        # FIXME add outputs.
+
     def generate_native_frameworks_map(self):
         self.native_frameworks = {}
         self.native_frameworks_fileref = {}
@@ -745,9 +758,13 @@
             ntarget_dict.add_item('buildConfigurationList', self.buildconflistmap[tname], f'Build configuration list for PBXNativeTarget "{tname}"')
             buildphases_array = PbxArray()
             ntarget_dict.add_item('buildPhases', buildphases_array)
+            generator_id = 0
             for g in t.generated:
                 if isinstance(g, build.CustomTarget):
                     buildphases_array.add_item(self.shell_targets[g.get_id()], f'/* {g.name} */')
+                elif isinstance(g, build.GeneratedList):
+                    buildphases_array.add_item(self.shell_targets[(tname, generator_id)], 'Generator {}/{}'.format(generator_id, tname))
+                    generator_id += 1
             for bpname, bpval in t.buildphasemap.items():
                 buildphases_array.add_item(bpval, f'{bpname} yyy')
             ntarget_dict.add_item('buildRules', PbxArray())
@@ -819,6 +836,10 @@
         cmdstr = ' '.join(["'%s'" % i for i in cmd])
         shell_dict.add_item('shellScript', f'"{cmdstr}"')
         shell_dict.add_item('showEnvVarsInLog', 0)
+        self.generate_custom_target_shell_build_phases(objects_dict)
+        self.generate_generator_target_shell_build_phases(objects_dict)
+
+    def generate_custom_target_shell_build_phases(self, objects_dict):
         # Custom targets are shell build phases in Xcode terminology.
         for tname, t in self.custom_targets.items():
             if not isinstance(t, build.CustomTarget):
@@ -842,6 +863,61 @@
             custom_dict.add_item('shellScript', f'"cd {workdir}; {cmdstr}"')
             custom_dict.add_item('showEnvVarsInLog', 0)
 
+    def generate_generator_target_shell_build_phases(self, objects_dict):
+        for tname, t in self.build_targets.items():
+            generator_id = 0
+            for genlist in t.generated:
+                if isinstance(genlist, build.GeneratedList):
+                    generator = genlist.get_generator()
+                    exe = generator.get_exe()
+                    exe_arr = self.build_target_to_cmd_array(exe)
+                    self.shell_targets[(tname, id)] = self.gen_id()
+                    workdir = self.environment.get_build_dir()
+                    gen_dict = PbxDict()
+                    objects_dict.add_item(self.shell_targets[(tname, generator_id)], gen_dict, '"Generator {}/{}"'.format(generator_id, tname))
+                    infilelist = genlist.get_inputs()
+                    outfilelist = genlist.get_outputs()
+                    gen_dict.add_item('isa', 'PBXShellScriptBuildPhase')
+                    gen_dict.add_item('buildActionMask', 2147483647)
+                    gen_dict.add_item('files', PbxArray())
+                    gen_dict.add_item('inputPaths', PbxArray())
+                    outarray = PbxArray()
+                    gen_dict.add_item('name', '"Generator {}/{}"'.format(generator_id, tname))    
+                    gen_dict.add_item('outputPaths', outarray)
+                    ofilenames = []
+                    infiles_abs = []
+                    commands = [["cd", workdir]] # Array of arrays, each one a single command, will get concatenated below.
+                    ofile_abs = []
+                    for i in infilelist:
+                        infiles_abs = i.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) # FIXME, make it handle other input types.
+                        infilename = i.rel_to_builddir(self.build_to_src)
+                        base_args = generator.get_arglist(infilename)
+                        for o_base in genlist.get_outputs_for(i):
+                            o = os.path.join(self.get_target_private_dir(t), o_base)
+                            ofile_abs.append(os.path.join(self.environment.get_build_dir(), o))
+                            args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', o) for x in base_args]
+                            args = self.replace_outputs(args, self.get_target_private_dir(t), outfilelist)
+                            commands.append(exe_arr + args)
+                    for of in ofile_abs:
+                        outarray.add_item(of)
+                    self.generator_outputs[(tname, generator_id)] = ofile_abs
+                    gen_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+                    gen_dict.add_item('shellPath', '/bin/sh')
+                    quoted_cmds = []
+                    for cmnd in commands:
+                        q = []
+                        for c in cmnd:
+                            if ' ' in c:
+                                q.append(f'\\"{c}\\"')
+                            else:
+                                q.append(c)
+                        quoted_cmds.append(' '.join(q))
+                    cmdstr = '"'  + ' && '.join(quoted_cmds) + '"'
+                    gen_dict.add_item('shellScript', cmdstr)
+                    gen_dict.add_item('showEnvVarsInLog', 0)
+                    generator_id += 1
+
+
     def generate_pbx_sources_build_phase(self, objects_dict):
         for name in self.source_phase.keys():
             phase_dict = PbxDict()
@@ -1002,6 +1078,14 @@
                         if objname_abs not in added_objs:
                             added_objs.add(objname_abs)
                             ldargs += [r'\"' + objname_abs + r'\"']
+            generator_id = 0
+            for o in target.generated:
+                if isinstance(o, build.GeneratedList):
+                    outputs = self.generator_outputs[target_name, generator_id] 
+                    generator_id += 1
+                    for o_abs in outputs:
+                        if o_abs.endswith('.o') or o_abs.endswith('.obj'):
+                            ldargs += [r'\"' + o_abs + r'\"']
             ldstr = ' '.join(ldargs)
             valid = self.buildconfmap[target_name][buildtype]
             langargs = {}