Xcode: fix generators that take custom targets as inputs.

pull/7079/head
Jussi Pakkanen 4 years ago
parent e422e9c634
commit 68dda2a241
  1. 201
      mesonbuild/backend/xcodebackend.py
  2. 10
      mesonbuild/build.py

@ -396,25 +396,36 @@ class XCodeBackend(backends.Backend):
for genlist in t.generated: for genlist in t.generated:
if not isinstance(genlist, build.GeneratedList): if not isinstance(genlist, build.GeneratedList):
continue continue
k = (tname, generator_id) self.gen_single_target_map(genlist, tname, t, generator_id)
assert(k not in self.shell_targets)
self.shell_targets[k] = self.gen_id()
ofile_abs = []
for i in genlist.get_inputs():
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))
assert(k not in self.generator_outputs)
self.generator_outputs[k] = ofile_abs
buildfile_ids = []
fileref_ids = []
for i in range(len(ofile_abs)):
buildfile_ids.append(self.gen_id())
fileref_ids.append(self.gen_id())
self.generator_buildfile_ids[k] = buildfile_ids
self.generator_fileref_ids[k] = fileref_ids
generator_id += 1 generator_id += 1
# FIXME add outputs. # FIXME add outputs.
for tname, t in self.custom_targets.items():
generator_id = 0
for genlist in t.sources:
if not isinstance(genlist, build.GeneratedList):
continue
self.gen_single_target_map(genlist, tname, t, generator_id)
generator_id += 1
def gen_single_target_map(self, genlist, tname, t, generator_id):
k = (tname, generator_id)
assert(k not in self.shell_targets)
self.shell_targets[k] = self.gen_id()
ofile_abs = []
for i in genlist.get_inputs():
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))
assert(k not in self.generator_outputs)
self.generator_outputs[k] = ofile_abs
buildfile_ids = []
fileref_ids = []
for i in range(len(ofile_abs)):
buildfile_ids.append(self.gen_id())
fileref_ids.append(self.gen_id())
self.generator_buildfile_ids[k] = buildfile_ids
self.generator_fileref_ids[k] = fileref_ids
def generate_native_frameworks_map(self): def generate_native_frameworks_map(self):
self.native_frameworks = {} self.native_frameworks = {}
@ -494,7 +505,18 @@ class XCodeBackend(backends.Backend):
for tname, t in self.build.get_custom_targets().items(): for tname, t in self.build.get_custom_targets().items():
ct_id = self.gen_id() ct_id = self.gen_id()
self.custom_aggregate_targets[tname] = ct_id self.custom_aggregate_targets[tname] = ct_id
aggregated_targets.append((ct_id, tname, self.buildconflistmap[tname], [self.shell_targets[tname]], [])) build_phases = []
dependencies = []
generator_id = 0
for s in t.sources:
if not isinstance(s, build.GeneratedList):
continue
build_phases.append(self.shell_targets[(tname, generator_id)])
for d in s.depends:
dependencies.append(self.pbx_custom_dep_map[d.get_id()])
generator_id += 1
build_phases.append(self.shell_targets[tname])
aggregated_targets.append((ct_id, tname, self.buildconflistmap[tname], build_phases, dependencies))
# Sort objects by ID before writing # Sort objects by ID before writing
sorted_aggregated_targets = sorted(aggregated_targets, key=operator.itemgetter(0)) sorted_aggregated_targets = sorted(aggregated_targets, key=operator.itemgetter(0))
@ -573,17 +595,7 @@ class XCodeBackend(backends.Backend):
for g in t.generated: for g in t.generated:
if not isinstance(g, build.GeneratedList): if not isinstance(g, build.GeneratedList):
continue continue
file_ids = self.generator_buildfile_ids[(tname, generator_id)] self.create_generator_shellphase(objects_dict, tname, generator_id)
ref_ids = self.generator_fileref_ids[tname, generator_id]
assert(len(ref_ids) == len(file_ids))
for i in range(len(file_ids)):
file_o = file_ids[i]
ref_id = ref_ids[i]
odict = PbxDict()
objects_dict.add_item(file_o, odict)
odict.add_item('isa', 'PBXBuildFile')
odict.add_item('fileRef', ref_id)
generator_id += 1 generator_id += 1
# Custom targets are shell build phases in Xcode terminology. # Custom targets are shell build phases in Xcode terminology.
@ -596,6 +608,24 @@ class XCodeBackend(backends.Backend):
objects_dict.add_item(self.custom_target_output_buildfile[o], custom_dict, f'/* {o} */') objects_dict.add_item(self.custom_target_output_buildfile[o], custom_dict, f'/* {o} */')
custom_dict.add_item('isa', 'PBXBuildFile') custom_dict.add_item('isa', 'PBXBuildFile')
custom_dict.add_item('fileRef', self.custom_target_output_fileref[o]) custom_dict.add_item('fileRef', self.custom_target_output_fileref[o])
generator_id = 0
for g in t.sources:
if not isinstance(g, build.GeneratedList):
continue
self.create_generator_shellphase(objects_dict, tname, generator_id)
generator_id += 1
def create_generator_shellphase(self, objects_dict, tname, generator_id):
file_ids = self.generator_buildfile_ids[(tname, generator_id)]
ref_ids = self.generator_fileref_ids[(tname, generator_id)]
assert(len(ref_ids) == len(file_ids))
for i in range(len(file_ids)):
file_o = file_ids[i]
ref_id = ref_ids[i]
odict = PbxDict()
objects_dict.add_item(file_o, odict)
odict.add_item('isa', 'PBXBuildFile')
odict.add_item('fileRef', ref_id)
def generate_pbx_build_style(self, objects_dict): def generate_pbx_build_style(self, objects_dict):
# FIXME: Xcode 9 and later does not uses PBXBuildStyle and it gets removed. Maybe we can remove this part. # FIXME: Xcode 9 and later does not uses PBXBuildStyle and it gets removed. Maybe we can remove this part.
@ -1033,59 +1063,68 @@ class XCodeBackend(backends.Backend):
generator_id = 0 generator_id = 0
for genlist in t.generated: for genlist in t.generated:
if isinstance(genlist, build.GeneratedList): if isinstance(genlist, build.GeneratedList):
generator = genlist.get_generator() self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict)
exe = generator.get_exe()
exe_arr = self.build_target_to_cmd_array(exe)
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())
gen_dict.add_item('name', '"Generator {}/{}"'.format(generator_id, tname))
commands = [["cd", workdir]] # Array of arrays, each one a single command, will get concatenated below.
k = (tname, generator_id)
ofile_abs = self.generator_outputs[k]
outarray = PbxArray()
gen_dict.add_item('outputPaths', outarray)
for of in ofile_abs:
outarray.add_item(of)
for i in infilelist:
# This might be needed to be added to inputPaths. It's not done yet as it is
# unclear whether it is necessary, what actually happens when it is defined
# and currently the build works without it.
#infile_abs = i.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
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)
args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', o).replace('@BUILD_DIR@', self.get_target_private_dir(t)) for x in base_args]
args = self.replace_outputs(args, self.get_target_private_dir(t), outfilelist)
args = self.replace_extra_args(args, genlist)
if generator.capture:
# When capturing, stdout is the output. Forward it with the shell.
full_command = ['('] + exe_arr + args + ['>', o, ')']
else:
full_command = exe_arr + args
commands.append(full_command)
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 generator_id += 1
for tname, t in self.custom_targets.items():
generator_id = 0
for genlist in t.sources:
if isinstance(genlist, build.GeneratedList):
self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict)
generator_id += 1
def generate_single_generator_phase(self, tname, t, genlist, generator_id, objects_dict):
generator = genlist.get_generator()
exe = generator.get_exe()
exe_arr = self.build_target_to_cmd_array(exe)
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())
gen_dict.add_item('name', '"Generator {}/{}"'.format(generator_id, tname))
commands = [["cd", workdir]] # Array of arrays, each one a single command, will get concatenated below.
k = (tname, generator_id)
ofile_abs = self.generator_outputs[k]
outarray = PbxArray()
gen_dict.add_item('outputPaths', outarray)
for of in ofile_abs:
outarray.add_item(of)
for i in infilelist:
# This might be needed to be added to inputPaths. It's not done yet as it is
# unclear whether it is necessary, what actually happens when it is defined
# and currently the build works without it.
#infile_abs = i.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
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)
args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', o).replace('@BUILD_DIR@', self.get_target_private_dir(t)) for x in base_args]
args = self.replace_outputs(args, self.get_target_private_dir(t), outfilelist)
args = self.replace_extra_args(args, genlist)
if generator.capture:
# When capturing, stdout is the output. Forward it with the shell.
full_command = ['('] + exe_arr + args + ['>', o, ')']
else:
full_command = exe_arr + args
commands.append(full_command)
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)
def generate_pbx_sources_build_phase(self, objects_dict): def generate_pbx_sources_build_phase(self, objects_dict):

@ -1580,10 +1580,15 @@ class Generator:
def process_files(self, name, files, state, preserve_path_from=None, extra_args=None): def process_files(self, name, files, state, preserve_path_from=None, extra_args=None):
new = False new = False
output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else [])
#XXX
for e in unholder(files): for e in unholder(files):
fs = [e] fs = [e]
if isinstance(e, CustomTarget):
output.depends.add(e)
if isinstance(e, CustomTargetIndex):
output.depends.add(e.target)
if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)): if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)):
self.depends.append(e) self.depends.append(e) # BUG: this should go in the GeneratedList object, not this object.
fs = [] fs = []
for f in e.get_outputs(): for f in e.get_outputs():
fs.append(File.from_built_file(state.subdir, f)) fs.append(File.from_built_file(state.subdir, f))
@ -1610,6 +1615,7 @@ class GeneratedList:
def __init__(self, generator, subdir, preserve_path_from=None, extra_args=None): def __init__(self, generator, subdir, preserve_path_from=None, extra_args=None):
self.generator = unholder(generator) self.generator = unholder(generator)
self.name = self.generator.exe self.name = self.generator.exe
self.depends = set() # Things this target depends on (because e.g. a custom target was used as input)
self.subdir = subdir self.subdir = subdir
self.infilelist = [] self.infilelist = []
self.outfilelist = [] self.outfilelist = []
@ -2545,7 +2551,7 @@ class Jar(BuildTarget):
class CustomTargetIndex: class CustomTargetIndex:
"""A special opaque object returned by indexing a CustomTarget. This object """A special opaque object returned by indexing a CustomTarget. This object
exists in meson, but acts as a proxy in the backends, making targets depend exists in Meson, but acts as a proxy in the backends, making targets depend
on the CustomTarget it's derived from, but only adding one source file to on the CustomTarget it's derived from, but only adding one source file to
the sources. the sources.
""" """

Loading…
Cancel
Save