diff options
Diffstat (limited to 'mesonbuild/backend/xcodebackend.py')
-rw-r--r-- | mesonbuild/backend/xcodebackend.py | 1719 |
1 files changed, 1719 insertions, 0 deletions
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py new file mode 100644 index 0000000..605ee22 --- /dev/null +++ b/mesonbuild/backend/xcodebackend.py @@ -0,0 +1,1719 @@ +# Copyright 2014-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +import uuid, os, operator +import typing as T + +from . import backends +from .. import build +from .. import dependencies +from .. import mesonlib +from .. import mlog +from ..mesonlib import MesonException, OptionKey + +if T.TYPE_CHECKING: + from ..interpreter import Interpreter + +INDENT = '\t' +XCODETYPEMAP = {'c': 'sourcecode.c.c', + 'a': 'archive.ar', + 'cc': 'sourcecode.cpp.cpp', + 'cxx': 'sourcecode.cpp.cpp', + 'cpp': 'sourcecode.cpp.cpp', + 'c++': 'sourcecode.cpp.cpp', + 'm': 'sourcecode.c.objc', + 'mm': 'sourcecode.cpp.objcpp', + 'h': 'sourcecode.c.h', + 'hpp': 'sourcecode.cpp.h', + 'hxx': 'sourcecode.cpp.h', + 'hh': 'sourcecode.cpp.hh', + 'inc': 'sourcecode.c.h', + 'swift': 'sourcecode.swift', + 'dylib': 'compiled.mach-o.dylib', + 'o': 'compiled.mach-o.objfile', + 's': 'sourcecode.asm', + 'asm': 'sourcecode.asm', + } +LANGNAMEMAP = {'c': 'C', + 'cpp': 'CPLUSPLUS', + 'objc': 'OBJC', + 'objcpp': 'OBJCPLUSPLUS', + 'swift': 'SWIFT_' + } +OPT2XCODEOPT = {'plain': None, + '0': '0', + 'g': '0', + '1': '1', + '2': '2', + '3': '3', + 's': 's', + } +BOOL2XCODEBOOL = {True: 'YES', False: 'NO'} +LINKABLE_EXTENSIONS = {'.o', '.a', '.obj', '.so', '.dylib'} + +class FileTreeEntry: + + def __init__(self): + self.subdirs = {} + self.targets = [] + +class PbxItem: + def __init__(self, value, comment = ''): + self.value = value + self.comment = comment + +class PbxArray: + def __init__(self): + self.items = [] + + def add_item(self, item, comment=''): + if isinstance(item, PbxArrayItem): + self.items.append(item) + else: + self.items.append(PbxArrayItem(item, comment)) + + def write(self, ofile, indent_level): + ofile.write('(\n') + indent_level += 1 + for i in self.items: + if i.comment: + ofile.write(indent_level*INDENT + f'{i.value} {i.comment},\n') + else: + ofile.write(indent_level*INDENT + f'{i.value},\n') + indent_level -= 1 + ofile.write(indent_level*INDENT + ');\n') + +class PbxArrayItem: + def __init__(self, value, comment = ''): + self.value = value + if comment: + if '/*' in comment: + self.comment = comment + else: + self.comment = f'/* {comment} */' + else: + self.comment = comment + +class PbxComment: + def __init__(self, text): + assert isinstance(text, str) + assert '/*' not in text + self.text = f'/* {text} */' + + def write(self, ofile, indent_level): + ofile.write(f'\n{self.text}\n') + +class PbxDictItem: + def __init__(self, key, value, comment = ''): + self.key = key + self.value = value + if comment: + if '/*' in comment: + self.comment = comment + else: + self.comment = f'/* {comment} */' + else: + self.comment = comment + +class PbxDict: + def __init__(self): + # This class is a bit weird, because we want to write PBX dicts in + # defined order _and_ we want to write intermediate comments also in order. + self.keys = set() + self.items = [] + + def add_item(self, key, value, comment=''): + assert key not in self.keys + item = PbxDictItem(key, value, comment) + self.keys.add(key) + self.items.append(item) + + def has_item(self, key): + return key in self.keys + + def add_comment(self, comment): + if isinstance(comment, str): + self.items.append(PbxComment(str)) + else: + assert isinstance(comment, PbxComment) + self.items.append(comment) + + def write(self, ofile, indent_level): + ofile.write('{\n') + indent_level += 1 + for i in self.items: + if isinstance(i, PbxComment): + i.write(ofile, indent_level) + elif isinstance(i, PbxDictItem): + if isinstance(i.value, (str, int)): + if i.comment: + ofile.write(indent_level*INDENT + f'{i.key} = {i.value} {i.comment};\n') + else: + ofile.write(indent_level*INDENT + f'{i.key} = {i.value};\n') + elif isinstance(i.value, PbxDict): + if i.comment: + ofile.write(indent_level*INDENT + f'{i.key} {i.comment} = ') + else: + ofile.write(indent_level*INDENT + f'{i.key} = ') + i.value.write(ofile, indent_level) + elif isinstance(i.value, PbxArray): + if i.comment: + ofile.write(indent_level*INDENT + f'{i.key} {i.comment} = ') + else: + ofile.write(indent_level*INDENT + f'{i.key} = ') + i.value.write(ofile, indent_level) + else: + print(i) + print(i.key) + print(i.value) + raise RuntimeError('missing code') + else: + print(i) + raise RuntimeError('missing code2') + + indent_level -= 1 + ofile.write(indent_level*INDENT + '}') + if indent_level == 0: + ofile.write('\n') + else: + ofile.write(';\n') + +class XCodeBackend(backends.Backend): + def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]): + super().__init__(build, interpreter) + self.name = 'xcode' + self.project_uid = self.environment.coredata.lang_guids['default'].replace('-', '')[:24] + self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype')) + self.project_conflist = self.gen_id() + self.maingroup_id = self.gen_id() + self.all_id = self.gen_id() + self.all_buildconf_id = self.gen_id() + self.buildtypes = [self.buildtype] + self.test_id = self.gen_id() + self.test_command_id = self.gen_id() + self.test_buildconf_id = self.gen_id() + self.regen_id = self.gen_id() + self.regen_command_id = self.gen_id() + self.regen_buildconf_id = self.gen_id() + self.regen_dependency_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 + # UI will only show the file in one target but not the others. Thus they key is + # a tuple containing the target and filename. + self.buildfile_ids = {} + # That is not enough, though. Each target/file combination also gets a unique id + # in the file reference section. Because why not. This means that a source file + # that is used in two targets gets a total of four unique ID numbers. + self.fileref_ids = {} + + def write_pbxfile(self, top_level_dict, ofilename): + tmpname = ofilename + '.tmp' + with open(tmpname, 'w', encoding='utf-8') as ofile: + ofile.write('// !$*UTF8*$!\n') + top_level_dict.write(ofile, 0) + os.replace(tmpname, ofilename) + + def gen_id(self): + return str(uuid.uuid4()).upper().replace('-', '')[:24] + + def get_target_dir(self, target): + dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_option(OptionKey('buildtype'))) + #os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) + return dirname + + def get_custom_target_output_dir(self, target): + dirname = target.get_subdir() + os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) + return dirname + + def target_to_build_root(self, target): + if self.get_target_dir(target) == '': + return '' + directories = os.path.normpath(self.get_target_dir(target)).split(os.sep) + return os.sep.join(['..'] * len(directories)) + + def object_filename_from_source(self, target, source): + # Xcode has the following naming scheme: + # projectname.build/debug/prog@exe.build/Objects-normal/x86_64/func.o + project = self.build.project_name + buildtype = self.buildtype + tname = target.get_id() + arch = 'x86_64' + if isinstance(source, mesonlib.File): + source = source.fname + stem = os.path.splitext(os.path.basename(source))[0] + obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o' + return obj_path + + def generate(self): + self.serialize_tests() + # Cache the result as the method rebuilds the array every time it is called. + self.build_targets = self.build.get_build_targets() + self.custom_targets = self.build.get_custom_targets() + self.generate_filemap() + self.generate_buildstylemap() + self.generate_build_phase_map() + self.generate_build_configuration_map() + self.generate_build_configurationlist_map() + self.generate_project_configurations_map() + self.generate_buildall_configurations_map() + self.generate_test_configurations_map() + 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() + self.generate_containerproxy_map() + self.generate_target_file_maps() + self.generate_build_file_maps() + self.proj_dir = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.xcodeproj') + os.makedirs(self.proj_dir, exist_ok=True) + self.proj_file = os.path.join(self.proj_dir, 'project.pbxproj') + objects_dict = self.generate_prefix(self.top_level_dict) + objects_dict.add_comment(PbxComment('Begin PBXAggregateTarget section')) + self.generate_pbx_aggregate_target(objects_dict) + objects_dict.add_comment(PbxComment('End PBXAggregateTarget section')) + objects_dict.add_comment(PbxComment('Begin PBXBuildFile section')) + self.generate_pbx_build_file(objects_dict) + objects_dict.add_comment(PbxComment('End PBXBuildFile section')) + objects_dict.add_comment(PbxComment('Begin PBXBuildStyle section')) + self.generate_pbx_build_style(objects_dict) + objects_dict.add_comment(PbxComment('End PBXBuildStyle section')) + objects_dict.add_comment(PbxComment('Begin PBXContainerItemProxy section')) + self.generate_pbx_container_item_proxy(objects_dict) + objects_dict.add_comment(PbxComment('End PBXContainerItemProxy section')) + objects_dict.add_comment(PbxComment('Begin PBXFileReference section')) + self.generate_pbx_file_reference(objects_dict) + objects_dict.add_comment(PbxComment('End PBXFileReference section')) + objects_dict.add_comment(PbxComment('Begin PBXFrameworksBuildPhase section')) + self.generate_pbx_frameworks_buildphase(objects_dict) + objects_dict.add_comment(PbxComment('End PBXFrameworksBuildPhase section')) + objects_dict.add_comment(PbxComment('Begin PBXGroup section')) + self.generate_pbx_group(objects_dict) + objects_dict.add_comment(PbxComment('End PBXGroup section')) + objects_dict.add_comment(PbxComment('Begin PBXNativeTarget section')) + self.generate_pbx_native_target(objects_dict) + objects_dict.add_comment(PbxComment('End PBXNativeTarget section')) + objects_dict.add_comment(PbxComment('Begin PBXProject section')) + self.generate_pbx_project(objects_dict) + objects_dict.add_comment(PbxComment('End PBXProject section')) + objects_dict.add_comment(PbxComment('Begin PBXShellScriptBuildPhase section')) + self.generate_pbx_shell_build_phase(objects_dict) + objects_dict.add_comment(PbxComment('End PBXShellScriptBuildPhase section')) + objects_dict.add_comment(PbxComment('Begin PBXSourcesBuildPhase section')) + self.generate_pbx_sources_build_phase(objects_dict) + objects_dict.add_comment(PbxComment('End PBXSourcesBuildPhase section')) + objects_dict.add_comment(PbxComment('Begin PBXTargetDependency section')) + self.generate_pbx_target_dependency(objects_dict) + objects_dict.add_comment(PbxComment('End PBXTargetDependency section')) + objects_dict.add_comment(PbxComment('Begin XCBuildConfiguration section')) + self.generate_xc_build_configuration(objects_dict) + objects_dict.add_comment(PbxComment('End XCBuildConfiguration section')) + objects_dict.add_comment(PbxComment('Begin XCConfigurationList section')) + self.generate_xc_configurationList(objects_dict) + objects_dict.add_comment(PbxComment('End XCConfigurationList section')) + self.generate_suffix(self.top_level_dict) + self.write_pbxfile(self.top_level_dict, self.proj_file) + self.generate_regen_info() + + def get_xcodetype(self, fname): + extension = fname.split('.')[-1] + if extension == 'C': + extension = 'cpp' + xcodetype = XCODETYPEMAP.get(extension.lower()) + if not xcodetype: + xcodetype = 'sourcecode.unknown' + return xcodetype + + def generate_filemap(self): + self.filemap = {} # Key is source file relative to src root. + self.target_filemap = {} + for name, t in self.build_targets.items(): + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + self.filemap[s] = self.gen_id() + for o in t.objects: + if isinstance(o, str): + o = os.path.join(t.subdir, o) + self.filemap[o] = self.gen_id() + self.target_filemap[name] = self.gen_id() + + def generate_buildstylemap(self): + self.buildstylemap = {self.buildtype: self.gen_id()} + + def generate_build_phase_map(self): + for tname, t in self.build_targets.items(): + # generate id for our own target-name + t.buildphasemap = {} + t.buildphasemap[tname] = self.gen_id() + # each target can have it's own Frameworks/Sources/..., generate id's for those + t.buildphasemap['Frameworks'] = self.gen_id() + t.buildphasemap['Resources'] = self.gen_id() + t.buildphasemap['Sources'] = self.gen_id() + + def generate_build_configuration_map(self): + self.buildconfmap = {} + for t in self.build_targets: + bconfs = {self.buildtype: self.gen_id()} + self.buildconfmap[t] = bconfs + for t in self.custom_targets: + bconfs = {self.buildtype: self.gen_id()} + self.buildconfmap[t] = bconfs + + def generate_project_configurations_map(self): + self.project_configurations = {self.buildtype: self.gen_id()} + + def generate_buildall_configurations_map(self): + self.buildall_configurations = {self.buildtype: self.gen_id()} + + def generate_test_configurations_map(self): + self.test_configurations = {self.buildtype: self.gen_id()} + + def generate_build_configurationlist_map(self): + self.buildconflistmap = {} + for t in self.build_targets: + self.buildconflistmap[t] = self.gen_id() + for t in self.custom_targets: + self.buildconflistmap[t] = self.gen_id() + + def generate_native_target_map(self): + self.native_targets = {} + for t in self.build_targets: + self.native_targets[t] = self.gen_id() + + def generate_custom_target_map(self): + self.shell_targets = {} + self.custom_target_output_buildfile = {} + self.custom_target_output_fileref = {} + for tname, t in self.custom_targets.items(): + self.shell_targets[tname] = self.gen_id() + if not isinstance(t, build.CustomTarget): + continue + (srcs, ofilenames, cmd) = self.eval_custom_target_command(t) + for o in ofilenames: + 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. + self.generator_fileref_ids = {} + self.generator_buildfile_ids = {} + for tname, t in self.build_targets.items(): + generator_id = 0 + for genlist in t.generated: + if not isinstance(genlist, build.GeneratedList): + continue + self.gen_single_target_map(genlist, tname, t, generator_id) + generator_id += 1 + # 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): + self.native_frameworks = {} + self.native_frameworks_fileref = {} + for t in self.build_targets.values(): + for dep in t.get_external_deps(): + if isinstance(dep, dependencies.AppleFrameworks): + for f in dep.frameworks: + self.native_frameworks[f] = self.gen_id() + self.native_frameworks_fileref[f] = self.gen_id() + + def generate_target_dependency_map(self): + self.target_dependency_map = {} + for tname, t in self.build_targets.items(): + for target in t.link_targets: + if isinstance(target, build.CustomTargetIndex): + k = (tname, target.target.get_basename()) + if k in self.target_dependency_map: + continue + else: + k = (tname, target.get_basename()) + assert k not in self.target_dependency_map + self.target_dependency_map[k] = self.gen_id() + for tname, t in self.custom_targets.items(): + k = tname + assert k not in self.target_dependency_map + self.target_dependency_map[k] = self.gen_id() + + def generate_pbxdep_map(self): + self.pbx_dep_map = {} + self.pbx_custom_dep_map = {} + for t in self.build_targets: + self.pbx_dep_map[t] = self.gen_id() + for t in self.custom_targets: + self.pbx_custom_dep_map[t] = self.gen_id() + + def generate_containerproxy_map(self): + self.containerproxy_map = {} + for t in self.build_targets: + self.containerproxy_map[t] = self.gen_id() + + def generate_target_file_maps(self): + self.generate_target_file_maps_impl(self.build_targets) + self.generate_target_file_maps_impl(self.custom_targets) + + def generate_target_file_maps_impl(self, targets): + for tname, t in targets.items(): + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + if not isinstance(s, str): + continue + k = (tname, s) + assert k not in self.buildfile_ids + self.buildfile_ids[k] = self.gen_id() + assert k not in self.fileref_ids + self.fileref_ids[k] = self.gen_id() + if not hasattr(t, 'objects'): + continue + for o in t.objects: + if isinstance(o, build.ExtractedObjects): + # Extracted objects do not live in "the Xcode world". + continue + if isinstance(o, mesonlib.File): + o = os.path.join(o.subdir, o.fname) + if isinstance(o, str): + o = os.path.join(t.subdir, o) + k = (tname, o) + assert k not in self.buildfile_ids + self.buildfile_ids[k] = self.gen_id() + assert k not in self.fileref_ids + self.fileref_ids[k] = self.gen_id() + else: + raise RuntimeError('Unknown input type ' + str(o)) + + def generate_build_file_maps(self): + for buildfile in self.interpreter.get_build_def_files(): + assert isinstance(buildfile, str) + self.buildfile_ids[buildfile] = self.gen_id() + self.fileref_ids[buildfile] = self.gen_id() + + def generate_source_phase_map(self): + self.source_phase = {} + for t in self.build_targets: + self.source_phase[t] = self.gen_id() + + def generate_pbx_aggregate_target(self, objects_dict): + self.custom_aggregate_targets = {} + self.build_all_tdep_id = self.gen_id() + # FIXME: filter out targets that are not built by default. + target_dependencies = [self.pbx_dep_map[t] for t in self.build_targets] + custom_target_dependencies = [self.pbx_custom_dep_map[t] for t in self.custom_targets] + aggregated_targets = [] + aggregated_targets.append((self.all_id, 'ALL_BUILD', + self.all_buildconf_id, + [], + [self.regen_dependency_id] + target_dependencies + custom_target_dependencies)) + aggregated_targets.append((self.test_id, + 'RUN_TESTS', + self.test_buildconf_id, + [self.test_command_id], + [self.regen_dependency_id, self.build_all_tdep_id])) + aggregated_targets.append((self.regen_id, + 'REGENERATE', + self.regen_buildconf_id, + [self.regen_command_id], + [])) + for tname, t in self.build.get_custom_targets().items(): + ct_id = self.gen_id() + self.custom_aggregate_targets[tname] = ct_id + build_phases = [] + dependencies = [self.regen_dependency_id] + 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 + sorted_aggregated_targets = sorted(aggregated_targets, key=operator.itemgetter(0)) + for t in sorted_aggregated_targets: + agt_dict = PbxDict() + name = t[1] + buildconf_id = t[2] + build_phases = t[3] + dependencies = t[4] + agt_dict.add_item('isa', 'PBXAggregateTarget') + agt_dict.add_item('buildConfigurationList', buildconf_id, f'Build configuration list for PBXAggregateTarget "{name}"') + bp_arr = PbxArray() + agt_dict.add_item('buildPhases', bp_arr) + for bp in build_phases: + bp_arr.add_item(bp, 'ShellScript') + dep_arr = PbxArray() + agt_dict.add_item('dependencies', dep_arr) + for td in dependencies: + dep_arr.add_item(td, 'PBXTargetDependency') + agt_dict.add_item('name', f'"{name}"') + agt_dict.add_item('productName', f'"{name}"') + objects_dict.add_item(t[0], agt_dict, name) + + def generate_pbx_build_file(self, objects_dict): + for tname, t in self.build_targets.items(): + for dep in t.get_external_deps(): + if isinstance(dep, dependencies.AppleFrameworks): + for f in dep.frameworks: + fw_dict = PbxDict() + fwkey = self.native_frameworks[f] + if fwkey not in objects_dict.keys: + objects_dict.add_item(fwkey, fw_dict, f'{f}.framework in Frameworks') + fw_dict.add_item('isa', 'PBXBuildFile') + fw_dict.add_item('fileRef', self.native_frameworks_fileref[f], f) + + for s in t.sources: + in_build_dir = False + if isinstance(s, mesonlib.File): + if s.is_built: + in_build_dir = True + s = os.path.join(s.subdir, s.fname) + + if not isinstance(s, str): + continue + sdict = PbxDict() + k = (tname, s) + idval = self.buildfile_ids[k] + fileref = self.fileref_ids[k] + if in_build_dir: + fullpath = os.path.join(self.environment.get_build_dir(), s) + else: + fullpath = os.path.join(self.environment.get_source_dir(), s) + sdict.add_item('isa', 'PBXBuildFile') + sdict.add_item('fileRef', fileref, fullpath) + objects_dict.add_item(idval, sdict) + + for o in t.objects: + if isinstance(o, build.ExtractedObjects): + # Object files are not source files as such. We add them + # by hand in linker flags. It is also not particularly + # clear how to define build files in Xcode's file format. + continue + if isinstance(o, mesonlib.File): + o = os.path.join(o.subdir, o.fname) + elif isinstance(o, str): + o = os.path.join(t.subdir, o) + idval = self.buildfile_ids[(tname, o)] + k = (tname, o) + fileref = self.fileref_ids[k] + assert o not in self.filemap + self.filemap[o] = idval + fullpath = os.path.join(self.environment.get_source_dir(), o) + fullpath2 = fullpath + o_dict = PbxDict() + objects_dict.add_item(idval, o_dict, fullpath) + o_dict.add_item('isa', 'PBXBuildFile') + o_dict.add_item('fileRef', fileref, fullpath2) + + generator_id = 0 + for g in t.generated: + if not isinstance(g, build.GeneratedList): + continue + self.create_generator_shellphase(objects_dict, tname, generator_id) + generator_id += 1 + + # Custom targets are shell build phases in Xcode terminology. + for tname, t in self.custom_targets.items(): + if not isinstance(t, build.CustomTarget): + continue + (srcs, ofilenames, cmd) = self.eval_custom_target_command(t) + for o in ofilenames: + custom_dict = PbxDict() + objects_dict.add_item(self.custom_target_output_buildfile[o], custom_dict, f'/* {o} */') + custom_dict.add_item('isa', 'PBXBuildFile') + 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 file_o, ref_id in zip(file_ids, ref_ids): + 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): + # FIXME: Xcode 9 and later does not uses PBXBuildStyle and it gets removed. Maybe we can remove this part. + for name, idval in self.buildstylemap.items(): + styledict = PbxDict() + objects_dict.add_item(idval, styledict, name) + styledict.add_item('isa', 'PBXBuildStyle') + settings_dict = PbxDict() + styledict.add_item('buildSettings', settings_dict) + settings_dict.add_item('COPY_PHASE_STRIP', 'NO') + styledict.add_item('name', f'"{name}"') + + def generate_pbx_container_item_proxy(self, objects_dict): + for t in self.build_targets: + proxy_dict = PbxDict() + objects_dict.add_item(self.containerproxy_map[t], proxy_dict, 'PBXContainerItemProxy') + proxy_dict.add_item('isa', 'PBXContainerItemProxy') + proxy_dict.add_item('containerPortal', self.project_uid, 'Project object') + proxy_dict.add_item('proxyType', '1') + proxy_dict.add_item('remoteGlobalIDString', self.native_targets[t]) + proxy_dict.add_item('remoteInfo', '"' + t + '"') + + def generate_pbx_file_reference(self, objects_dict): + for tname, t in self.build_targets.items(): + for dep in t.get_external_deps(): + if isinstance(dep, dependencies.AppleFrameworks): + for f in dep.frameworks: + fw_dict = PbxDict() + framework_fileref = self.native_frameworks_fileref[f] + if objects_dict.has_item(framework_fileref): + continue + objects_dict.add_item(framework_fileref, fw_dict, f) + fw_dict.add_item('isa', 'PBXFileReference') + fw_dict.add_item('lastKnownFileType', 'wrapper.framework') + fw_dict.add_item('name', f'{f}.framework') + fw_dict.add_item('path', f'System/Library/Frameworks/{f}.framework') + fw_dict.add_item('sourceTree', 'SDKROOT') + for s in t.sources: + in_build_dir = False + if isinstance(s, mesonlib.File): + if s.is_built: + in_build_dir = True + s = os.path.join(s.subdir, s.fname) + if not isinstance(s, str): + continue + idval = self.fileref_ids[(tname, s)] + fullpath = os.path.join(self.environment.get_source_dir(), s) + src_dict = PbxDict() + xcodetype = self.get_xcodetype(s) + name = os.path.basename(s) + path = s + objects_dict.add_item(idval, src_dict, fullpath) + src_dict.add_item('isa', 'PBXFileReference') + src_dict.add_item('explicitFileType', '"' + xcodetype + '"') + src_dict.add_item('fileEncoding', '4') + if in_build_dir: + src_dict.add_item('name', '"' + name + '"') + # This makes no sense. This should say path instead of name + # but then the path gets added twice. + src_dict.add_item('path', '"' + name + '"') + src_dict.add_item('sourceTree', 'BUILD_ROOT') + else: + src_dict.add_item('name', '"' + name + '"') + src_dict.add_item('path', '"' + path + '"') + src_dict.add_item('sourceTree', 'SOURCE_ROOT') + + generator_id = 0 + for g in t.generated: + if not isinstance(g, build.GeneratedList): + continue + outputs = self.generator_outputs[(tname, generator_id)] + ref_ids = self.generator_fileref_ids[tname, generator_id] + assert len(ref_ids) == len(outputs) + for o, ref_id in zip(outputs, ref_ids): + odict = PbxDict() + name = os.path.basename(o) + objects_dict.add_item(ref_id, odict, o) + xcodetype = self.get_xcodetype(o) + rel_name = mesonlib.relpath(o, self.environment.get_source_dir()) + odict.add_item('isa', 'PBXFileReference') + odict.add_item('explicitFileType', '"' + xcodetype + '"') + odict.add_item('fileEncoding', '4') + odict.add_item('name', f'"{name}"') + odict.add_item('path', f'"{rel_name}"') + odict.add_item('sourceTree', 'SOURCE_ROOT') + + generator_id += 1 + + for o in t.objects: + if isinstance(o, build.ExtractedObjects): + # Same as with pbxbuildfile. + continue + if isinstance(o, mesonlib.File): + fullpath = o.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) + o = os.path.join(o.subdir, o.fname) + else: + o = os.path.join(t.subdir, o) + fullpath = os.path.join(self.environment.get_source_dir(), o) + idval = self.fileref_ids[(tname, o)] + rel_name = mesonlib.relpath(fullpath, self.environment.get_source_dir()) + o_dict = PbxDict() + name = os.path.basename(o) + objects_dict.add_item(idval, o_dict, fullpath) + o_dict.add_item('isa', 'PBXFileReference') + o_dict.add_item('explicitFileType', '"' + self.get_xcodetype(o) + '"') + o_dict.add_item('fileEncoding', '4') + o_dict.add_item('name', f'"{name}"') + o_dict.add_item('path', f'"{rel_name}"') + o_dict.add_item('sourceTree', 'SOURCE_ROOT') + for tname, idval in self.target_filemap.items(): + target_dict = PbxDict() + objects_dict.add_item(idval, target_dict, tname) + t = self.build_targets[tname] + fname = t.get_filename() + reftype = 0 + if isinstance(t, build.Executable): + typestr = 'compiled.mach-o.executable' + path = fname + elif isinstance(t, build.SharedLibrary): + typestr = self.get_xcodetype('dummy.dylib') + path = fname + else: + typestr = self.get_xcodetype(fname) + path = '"%s"' % t.get_filename() + target_dict.add_item('isa', 'PBXFileReference') + target_dict.add_item('explicitFileType', '"' + typestr + '"') + if ' ' in path and path[0] != '"': + target_dict.add_item('path', f'"{path}"') + else: + target_dict.add_item('path', path) + target_dict.add_item('refType', reftype) + target_dict.add_item('sourceTree', 'BUILT_PRODUCTS_DIR') + + for tname, t in self.custom_targets.items(): + if not isinstance(t, build.CustomTarget): + continue + (srcs, ofilenames, cmd) = self.eval_custom_target_command(t) + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + elif isinstance(s, str): + s = os.path.join(t.subdir, s) + else: + continue + custom_dict = PbxDict() + typestr = self.get_xcodetype(s) + custom_dict.add_item('isa', 'PBXFileReference') + custom_dict.add_item('explicitFileType', '"' + typestr + '"') + custom_dict.add_item('name', f'"{s}"') + custom_dict.add_item('path', f'"{s}"') + custom_dict.add_item('refType', 0) + custom_dict.add_item('sourceTree', 'SOURCE_ROOT') + objects_dict.add_item(self.fileref_ids[(tname, s)], custom_dict) + for o in ofilenames: + custom_dict = PbxDict() + typestr = self.get_xcodetype(o) + custom_dict.add_item('isa', 'PBXFileReference') + custom_dict.add_item('explicitFileType', '"' + typestr + '"') + custom_dict.add_item('name', o) + custom_dict.add_item('path', os.path.join(self.src_to_build, o)) + custom_dict.add_item('refType', 0) + custom_dict.add_item('sourceTree', 'SOURCE_ROOT') + objects_dict.add_item(self.custom_target_output_fileref[o], custom_dict) + + for buildfile in self.interpreter.get_build_def_files(): + basename = os.path.split(buildfile)[1] + buildfile_dict = PbxDict() + typestr = self.get_xcodetype(buildfile) + buildfile_dict.add_item('isa', 'PBXFileReference') + buildfile_dict.add_item('explicitFileType', '"' + typestr + '"') + buildfile_dict.add_item('name', f'"{basename}"') + buildfile_dict.add_item('path', f'"{buildfile}"') + buildfile_dict.add_item('refType', 0) + buildfile_dict.add_item('sourceTree', 'SOURCE_ROOT') + objects_dict.add_item(self.fileref_ids[buildfile], buildfile_dict) + + def generate_pbx_frameworks_buildphase(self, objects_dict): + for t in self.build_targets.values(): + bt_dict = PbxDict() + objects_dict.add_item(t.buildphasemap['Frameworks'], bt_dict, 'Frameworks') + bt_dict.add_item('isa', 'PBXFrameworksBuildPhase') + bt_dict.add_item('buildActionMask', 2147483647) + file_list = PbxArray() + bt_dict.add_item('files', file_list) + for dep in t.get_external_deps(): + if isinstance(dep, dependencies.AppleFrameworks): + for f in dep.frameworks: + file_list.add_item(self.native_frameworks[f], f'{f}.framework in Frameworks') + bt_dict.add_item('runOnlyForDeploymentPostprocessing', 0) + + def generate_pbx_group(self, objects_dict): + groupmap = {} + target_src_map = {} + for t in self.build_targets: + groupmap[t] = self.gen_id() + target_src_map[t] = self.gen_id() + for t in self.custom_targets: + groupmap[t] = self.gen_id() + target_src_map[t] = self.gen_id() + projecttree_id = self.gen_id() + resources_id = self.gen_id() + products_id = self.gen_id() + frameworks_id = self.gen_id() + main_dict = PbxDict() + objects_dict.add_item(self.maingroup_id, main_dict) + main_dict.add_item('isa', 'PBXGroup') + main_children = PbxArray() + main_dict.add_item('children', main_children) + main_children.add_item(projecttree_id, 'Project tree') + main_children.add_item(resources_id, 'Resources') + main_children.add_item(products_id, 'Products') + main_children.add_item(frameworks_id, 'Frameworks') + main_dict.add_item('sourceTree', '"<group>"') + + self.add_projecttree(objects_dict, projecttree_id) + + resource_dict = PbxDict() + objects_dict.add_item(resources_id, resource_dict, 'Resources') + resource_dict.add_item('isa', 'PBXGroup') + resource_children = PbxArray() + resource_dict.add_item('children', resource_children) + resource_dict.add_item('name', 'Resources') + resource_dict.add_item('sourceTree', '"<group>"') + + frameworks_dict = PbxDict() + objects_dict.add_item(frameworks_id, frameworks_dict, 'Frameworks') + frameworks_dict.add_item('isa', 'PBXGroup') + frameworks_children = PbxArray() + frameworks_dict.add_item('children', frameworks_children) + # write frameworks + + for t in self.build_targets.values(): + for dep in t.get_external_deps(): + if isinstance(dep, dependencies.AppleFrameworks): + for f in dep.frameworks: + frameworks_children.add_item(self.native_frameworks_fileref[f], f) + + frameworks_dict.add_item('name', 'Frameworks') + frameworks_dict.add_item('sourceTree', '"<group>"') + + for tname, t in self.custom_targets.items(): + target_dict = PbxDict() + objects_dict.add_item(groupmap[tname], target_dict, tname) + target_dict.add_item('isa', 'PBXGroup') + target_children = PbxArray() + target_dict.add_item('children', target_children) + target_children.add_item(target_src_map[tname], 'Source files') + if t.subproject: + target_dict.add_item('name', f'"{t.subproject} • {t.name}"') + else: + target_dict.add_item('name', f'"{t.name}"') + target_dict.add_item('sourceTree', '"<group>"') + source_files_dict = PbxDict() + objects_dict.add_item(target_src_map[tname], source_files_dict, 'Source files') + source_files_dict.add_item('isa', 'PBXGroup') + source_file_children = PbxArray() + source_files_dict.add_item('children', source_file_children) + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + elif isinstance(s, str): + s = os.path.join(t.subdir, s) + else: + continue + source_file_children.add_item(self.fileref_ids[(tname, s)], s) + source_files_dict.add_item('name', '"Source files"') + source_files_dict.add_item('sourceTree', '"<group>"') + + # And finally products + product_dict = PbxDict() + objects_dict.add_item(products_id, product_dict, 'Products') + product_dict.add_item('isa', 'PBXGroup') + product_children = PbxArray() + product_dict.add_item('children', product_children) + for t in self.build_targets: + product_children.add_item(self.target_filemap[t], t) + product_dict.add_item('name', 'Products') + product_dict.add_item('sourceTree', '"<group>"') + + def write_group_target_entry(self, objects_dict, t): + tid = t.get_id() + group_id = self.gen_id() + target_dict = PbxDict() + objects_dict.add_item(group_id, target_dict, tid) + target_dict.add_item('isa', 'PBXGroup') + target_children = PbxArray() + target_dict.add_item('children', target_children) + target_dict.add_item('name', f'"{t} · target"') + target_dict.add_item('sourceTree', '"<group>"') + source_files_dict = PbxDict() + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + elif isinstance(s, str): + s = os.path.join(t.subdir, s) + else: + continue + target_children.add_item(self.fileref_ids[(tid, s)], s) + for o in t.objects: + if isinstance(o, build.ExtractedObjects): + # Do not show built object files in the project tree. + continue + if isinstance(o, mesonlib.File): + o = os.path.join(o.subdir, o.fname) + else: + o = os.path.join(t.subdir, o) + target_children.add_item(self.fileref_ids[(tid, o)], o) + source_files_dict.add_item('name', '"Source files"') + source_files_dict.add_item('sourceTree', '"<group>"') + return group_id + + def add_projecttree(self, objects_dict, projecttree_id): + root_dict = PbxDict() + objects_dict.add_item(projecttree_id, root_dict, "Root of project tree") + root_dict.add_item('isa', 'PBXGroup') + target_children = PbxArray() + root_dict.add_item('children', target_children) + root_dict.add_item('name', '"Project root"') + root_dict.add_item('sourceTree', '"<group>"') + + project_tree = self.generate_project_tree() + self.write_tree(objects_dict, project_tree, target_children, '') + + def write_tree(self, objects_dict, tree_node, children_array, current_subdir): + for subdir_name, subdir_node in tree_node.subdirs.items(): + subdir_dict = PbxDict() + subdir_children = PbxArray() + subdir_id = self.gen_id() + objects_dict.add_item(subdir_id, subdir_dict) + children_array.add_item(subdir_id) + subdir_dict.add_item('isa', 'PBXGroup') + subdir_dict.add_item('children', subdir_children) + subdir_dict.add_item('name', f'"{subdir_name}"') + subdir_dict.add_item('sourceTree', '"<group>"') + self.write_tree(objects_dict, subdir_node, subdir_children, os.path.join(current_subdir, subdir_name)) + for target in tree_node.targets: + group_id = self.write_group_target_entry(objects_dict, target) + children_array.add_item(group_id) + potentials = [os.path.join(current_subdir, 'meson.build'), + os.path.join(current_subdir, 'meson_options.txt')] + for bf in potentials: + i = self.fileref_ids.get(bf, None) + if i: + children_array.add_item(i) + + def generate_project_tree(self): + tree_info = FileTreeEntry() + for tname, t in self.build_targets.items(): + self.add_target_to_tree(tree_info, t) + return tree_info + + def add_target_to_tree(self, tree_root, t): + current_node = tree_root + path_segments = t.subdir.split('/') + for s in path_segments: + if not s: + continue + if s not in current_node.subdirs: + current_node.subdirs[s] = FileTreeEntry() + current_node = current_node.subdirs[s] + current_node.targets.append(t) + + def generate_pbx_native_target(self, objects_dict): + for tname, idval in self.native_targets.items(): + ntarget_dict = PbxDict() + t = self.build_targets[tname] + objects_dict.add_item(idval, ntarget_dict, tname) + ntarget_dict.add_item('isa', 'PBXNativeTarget') + 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: + # Custom target are handled via inter-target dependencies. + # Generators are built as a shellscriptbuildphase. + if isinstance(g, build.GeneratedList): + buildphases_array.add_item(self.shell_targets[(tname, generator_id)], f'Generator {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()) + dep_array = PbxArray() + ntarget_dict.add_item('dependencies', dep_array) + dep_array.add_item(self.regen_dependency_id) + # These dependencies only tell Xcode that the deps must be built + # before this one. They don't set up linkage or anything + # like that. Those are set up in the XCBuildConfiguration. + for lt in self.build_targets[tname].link_targets: + # NOT DOCUMENTED, may need to make different links + # to same target have different targetdependency item. + if isinstance(lt, build.CustomTarget): + dep_array.add_item(self.pbx_custom_dep_map[lt.get_id()], lt.name) + elif isinstance(lt, build.CustomTargetIndex): + dep_array.add_item(self.pbx_custom_dep_map[lt.target.get_id()], lt.target.name) + else: + idval = self.pbx_dep_map[lt.get_id()] + dep_array.add_item(idval, 'PBXTargetDependency') + for o in t.objects: + if isinstance(o, build.ExtractedObjects): + source_target_id = o.target.get_id() + idval = self.pbx_dep_map[source_target_id] + dep_array.add_item(idval, 'PBXTargetDependency') + generator_id = 0 + for o in t.generated: + if isinstance(o, build.CustomTarget): + dep_array.add_item(self.pbx_custom_dep_map[o.get_id()], o.name) + elif isinstance(o, build.CustomTargetIndex): + dep_array.add_item(self.pbx_custom_dep_map[o.target.get_id()], o.target.name) + + generator_id += 1 + + ntarget_dict.add_item('name', f'"{tname}"') + ntarget_dict.add_item('productName', f'"{tname}"') + ntarget_dict.add_item('productReference', self.target_filemap[tname], tname) + if isinstance(t, build.Executable): + typestr = 'com.apple.product-type.tool' + elif isinstance(t, build.StaticLibrary): + typestr = 'com.apple.product-type.library.static' + elif isinstance(t, build.SharedLibrary): + typestr = 'com.apple.product-type.library.dynamic' + else: + raise MesonException('Unknown target type for %s' % tname) + ntarget_dict.add_item('productType', f'"{typestr}"') + + def generate_pbx_project(self, objects_dict): + project_dict = PbxDict() + objects_dict.add_item(self.project_uid, project_dict, 'Project object') + project_dict.add_item('isa', 'PBXProject') + attr_dict = PbxDict() + project_dict.add_item('attributes', attr_dict) + attr_dict.add_item('BuildIndependentTargetsInParallel', 'YES') + project_dict.add_item('buildConfigurationList', self.project_conflist, f'Build configuration list for PBXProject "{self.build.project_name}"') + project_dict.add_item('buildSettings', PbxDict()) + style_arr = PbxArray() + project_dict.add_item('buildStyles', style_arr) + for name, idval in self.buildstylemap.items(): + style_arr.add_item(idval, name) + project_dict.add_item('compatibilityVersion', '"Xcode 3.2"') + project_dict.add_item('hasScannedForEncodings', 0) + project_dict.add_item('mainGroup', self.maingroup_id) + project_dict.add_item('projectDirPath', '"' + self.environment.get_source_dir() + '"') + project_dict.add_item('projectRoot', '""') + targets_arr = PbxArray() + project_dict.add_item('targets', targets_arr) + targets_arr.add_item(self.all_id, 'ALL_BUILD') + targets_arr.add_item(self.test_id, 'RUN_TESTS') + targets_arr.add_item(self.regen_id, 'REGENERATE') + for t in self.build_targets: + targets_arr.add_item(self.native_targets[t], t) + for t in self.custom_targets: + targets_arr.add_item(self.custom_aggregate_targets[t], t) + + def generate_pbx_shell_build_phase(self, objects_dict): + self.generate_test_shell_build_phase(objects_dict) + self.generate_regen_shell_build_phase(objects_dict) + self.generate_custom_target_shell_build_phases(objects_dict) + self.generate_generator_target_shell_build_phases(objects_dict) + + def generate_test_shell_build_phase(self, objects_dict): + shell_dict = PbxDict() + objects_dict.add_item(self.test_command_id, shell_dict, 'ShellScript') + shell_dict.add_item('isa', 'PBXShellScriptBuildPhase') + shell_dict.add_item('buildActionMask', 2147483647) + shell_dict.add_item('files', PbxArray()) + shell_dict.add_item('inputPaths', PbxArray()) + shell_dict.add_item('outputPaths', PbxArray()) + shell_dict.add_item('runOnlyForDeploymentPostprocessing', 0) + shell_dict.add_item('shellPath', '/bin/sh') + cmd = mesonlib.get_meson_command() + ['test', '--no-rebuild', '-C', self.environment.get_build_dir()] + cmdstr = ' '.join(["'%s'" % i for i in cmd]) + shell_dict.add_item('shellScript', f'"{cmdstr}"') + shell_dict.add_item('showEnvVarsInLog', 0) + + def generate_regen_shell_build_phase(self, objects_dict): + shell_dict = PbxDict() + objects_dict.add_item(self.regen_command_id, shell_dict, 'ShellScript') + shell_dict.add_item('isa', 'PBXShellScriptBuildPhase') + shell_dict.add_item('buildActionMask', 2147483647) + shell_dict.add_item('files', PbxArray()) + shell_dict.add_item('inputPaths', PbxArray()) + shell_dict.add_item('outputPaths', PbxArray()) + shell_dict.add_item('runOnlyForDeploymentPostprocessing', 0) + shell_dict.add_item('shellPath', '/bin/sh') + cmd = mesonlib.get_meson_command() + ['--internal', 'regencheck', os.path.join(self.environment.get_build_dir(), 'meson-private')] + cmdstr = ' '.join(["'%s'" % i for i in cmd]) + shell_dict.add_item('shellScript', f'"{cmdstr}"') + shell_dict.add_item('showEnvVarsInLog', 0) + + 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): + continue + (srcs, ofilenames, cmd) = self.eval_custom_target_command(t, absolute_outputs=True) + fixed_cmd, _ = self.as_meson_exe_cmdline(cmd[0], + cmd[1:], + capture=ofilenames[0] if t.capture else None, + feed=srcs[0] if t.feed else None, + env=t.env) + custom_dict = PbxDict() + objects_dict.add_item(self.shell_targets[tname], custom_dict, f'/* Custom target {tname} */') + custom_dict.add_item('isa', 'PBXShellScriptBuildPhase') + custom_dict.add_item('buildActionMask', 2147483647) + custom_dict.add_item('files', PbxArray()) + custom_dict.add_item('inputPaths', PbxArray()) + outarray = PbxArray() + custom_dict.add_item('name', '"Generate {}."'.format(ofilenames[0])) + custom_dict.add_item('outputPaths', outarray) + for o in ofilenames: + outarray.add_item(os.path.join(self.environment.get_build_dir(), o)) + custom_dict.add_item('runOnlyForDeploymentPostprocessing', 0) + custom_dict.add_item('shellPath', '/bin/sh') + workdir = self.environment.get_build_dir() + quoted_cmd = [] + for c in fixed_cmd: + quoted_cmd.append(c.replace('"', chr(92) + '"')) + cmdstr = ' '.join([f"\\'{x}\\'" for x in quoted_cmd]) + 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): + self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict) + 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): + # TODO: this should be rewritten to use the meson wrapper, like the other generators do + # Currently it doesn't handle a host binary that requires an exe wrapper correctly. + 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, f'"Generator {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', f'"Generator {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 = [] + for arg in base_args: + arg = arg.replace("@INPUT@", infilename) + arg = arg.replace('@OUTPUT@', o).replace('@BUILD_DIR@', self.get_target_private_dir(t)) + arg = arg.replace("@CURRENT_SOURCE_DIR@", os.path.join(self.build_to_src, t.subdir)) + args.append(arg) + 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): + for name in self.source_phase: + phase_dict = PbxDict() + t = self.build_targets[name] + objects_dict.add_item(t.buildphasemap[name], phase_dict, 'Sources') + phase_dict.add_item('isa', 'PBXSourcesBuildPhase') + phase_dict.add_item('buildActionMask', 2147483647) + file_arr = PbxArray() + phase_dict.add_item('files', file_arr) + for s in self.build_targets[name].sources: + s = os.path.join(s.subdir, s.fname) + if not self.environment.is_header(s): + file_arr.add_item(self.buildfile_ids[(name, s)], os.path.join(self.environment.get_source_dir(), s)) + generator_id = 0 + for gt in t.generated: + if isinstance(gt, build.CustomTarget): + (srcs, ofilenames, cmd) = self.eval_custom_target_command(gt) + for o in ofilenames: + file_arr.add_item(self.custom_target_output_buildfile[o], + os.path.join(self.environment.get_build_dir(), o)) + elif isinstance(gt, build.CustomTargetIndex): + for o in gt.get_outputs(): + file_arr.add_item(self.custom_target_output_buildfile[o], + os.path.join(self.environment.get_build_dir(), o)) + elif isinstance(gt, build.GeneratedList): + genfiles = self.generator_buildfile_ids[(name, generator_id)] + generator_id += 1 + for o in genfiles: + file_arr.add_item(o) + else: + raise RuntimeError('Unknown input type: ' + str(gt)) + phase_dict.add_item('runOnlyForDeploymentPostprocessing', 0) + + def generate_pbx_target_dependency(self, objects_dict): + all_dict = PbxDict() + objects_dict.add_item(self.build_all_tdep_id, all_dict, 'ALL_BUILD') + all_dict.add_item('isa', 'PBXTargetDependency') + all_dict.add_item('target', self.all_id) + targets = [] + targets.append((self.regen_dependency_id, self.regen_id, 'REGEN', None)) + for t in self.build_targets: + idval = self.pbx_dep_map[t] # VERIFY: is this correct? + targets.append((idval, self.native_targets[t], t, self.containerproxy_map[t])) + + for t in self.custom_targets: + idval = self.pbx_custom_dep_map[t] + targets.append((idval, self.custom_aggregate_targets[t], t, None)) # self.containerproxy_map[t])) + + # Sort object by ID + sorted_targets = sorted(targets, key=operator.itemgetter(0)) + for t in sorted_targets: + t_dict = PbxDict() + objects_dict.add_item(t[0], t_dict, 'PBXTargetDependency') + t_dict.add_item('isa', 'PBXTargetDependency') + t_dict.add_item('target', t[1], t[2]) + if t[3] is not None: + t_dict.add_item('targetProxy', t[3], 'PBXContainerItemProxy') + + def generate_xc_build_configuration(self, objects_dict): + # First the setup for the toplevel project. + for buildtype in self.buildtypes: + bt_dict = PbxDict() + objects_dict.add_item(self.project_configurations[buildtype], bt_dict, buildtype) + bt_dict.add_item('isa', 'XCBuildConfiguration') + settings_dict = PbxDict() + bt_dict.add_item('buildSettings', settings_dict) + settings_dict.add_item('ARCHS', '"$(NATIVE_ARCH_ACTUAL)"') + settings_dict.add_item('ONLY_ACTIVE_ARCH', 'YES') + settings_dict.add_item('SWIFT_VERSION', '5.0') + settings_dict.add_item('SDKROOT', '"macosx"') + settings_dict.add_item('SYMROOT', '"%s/build"' % self.environment.get_build_dir()) + bt_dict.add_item('name', f'"{buildtype}"') + + # Then the all target. + for buildtype in self.buildtypes: + bt_dict = PbxDict() + objects_dict.add_item(self.buildall_configurations[buildtype], bt_dict, buildtype) + bt_dict.add_item('isa', 'XCBuildConfiguration') + settings_dict = PbxDict() + bt_dict.add_item('buildSettings', settings_dict) + settings_dict.add_item('SYMROOT', '"%s"' % self.environment.get_build_dir()) + warn_array = PbxArray() + warn_array.add_item('"$(inherited)"') + settings_dict.add_item('WARNING_CFLAGS', warn_array) + + bt_dict.add_item('name', f'"{buildtype}"') + + # Then the test target. + for buildtype in self.buildtypes: + bt_dict = PbxDict() + objects_dict.add_item(self.test_configurations[buildtype], bt_dict, buildtype) + bt_dict.add_item('isa', 'XCBuildConfiguration') + settings_dict = PbxDict() + bt_dict.add_item('buildSettings', settings_dict) + settings_dict.add_item('SYMROOT', '"%s"' % self.environment.get_build_dir()) + warn_array = PbxArray() + settings_dict.add_item('WARNING_CFLAGS', warn_array) + warn_array.add_item('"$(inherited)"') + bt_dict.add_item('name', f'"{buildtype}"') + + # Now finally targets. + for target_name, target in self.build_targets.items(): + self.generate_single_build_target(objects_dict, target_name, target) + + for target_name, target in self.custom_targets.items(): + bt_dict = PbxDict() + objects_dict.add_item(self.buildconfmap[target_name][buildtype], bt_dict, buildtype) + bt_dict.add_item('isa', 'XCBuildConfiguration') + settings_dict = PbxDict() + bt_dict.add_item('buildSettings', settings_dict) + settings_dict.add_item('ARCHS', '"$(NATIVE_ARCH_ACTUAL)"') + settings_dict.add_item('ONLY_ACTIVE_ARCH', 'YES') + settings_dict.add_item('SDKROOT', '"macosx"') + settings_dict.add_item('SYMROOT', '"%s/build"' % self.environment.get_build_dir()) + bt_dict.add_item('name', f'"{buildtype}"') + + def determine_internal_dep_link_args(self, target, buildtype): + links_dylib = False + dep_libs = [] + for l in target.link_targets: + if isinstance(target, build.SharedModule) and isinstance(l, build.Executable): + continue + if isinstance(l, build.CustomTargetIndex): + rel_dir = self.get_custom_target_output_dir(l.target) + libname = l.get_filename() + elif isinstance(l, build.CustomTarget): + rel_dir = self.get_custom_target_output_dir(l) + libname = l.get_filename() + else: + rel_dir = self.get_target_dir(l) + libname = l.get_filename() + abs_path = os.path.join(self.environment.get_build_dir(), rel_dir, libname) + dep_libs.append("'%s'" % abs_path) + if isinstance(l, build.SharedLibrary): + links_dylib = True + if isinstance(l, build.StaticLibrary): + (sub_libs, sub_links_dylib) = self.determine_internal_dep_link_args(l, buildtype) + dep_libs += sub_libs + links_dylib = links_dylib or sub_links_dylib + return (dep_libs, links_dylib) + + def generate_single_build_target(self, objects_dict, target_name, target): + for buildtype in self.buildtypes: + dep_libs = [] + links_dylib = False + headerdirs = [] + for d in target.include_dirs: + for sd in d.incdirs: + cd = os.path.join(d.curdir, sd) + headerdirs.append(os.path.join(self.environment.get_source_dir(), cd)) + headerdirs.append(os.path.join(self.environment.get_build_dir(), cd)) + for extra in d.extra_build_dirs: + headerdirs.append(os.path.join(self.environment.get_build_dir(), extra)) + (dep_libs, links_dylib) = self.determine_internal_dep_link_args(target, buildtype) + if links_dylib: + dep_libs = ['-Wl,-search_paths_first', '-Wl,-headerpad_max_install_names'] + dep_libs + dylib_version = None + if isinstance(target, build.SharedLibrary): + if isinstance(target, build.SharedModule): + ldargs = [] + else: + ldargs = ['-dynamiclib'] + ldargs += ['-Wl,-headerpad_max_install_names'] + dep_libs + install_path = os.path.join(self.environment.get_build_dir(), target.subdir, buildtype) + dylib_version = target.soversion + else: + ldargs = dep_libs + install_path = '' + if dylib_version is not None: + product_name = target.get_basename() + '.' + dylib_version + else: + product_name = target.get_basename() + ldargs += target.link_args + # Swift is special. Again. You can't mix Swift with other languages + # in the same target. Thus for Swift we only use + if self.is_swift_target(target): + linker, stdlib_args = target.compilers['swift'], [] + else: + linker, stdlib_args = self.determine_linker_and_stdlib_args(target) + if not isinstance(target, build.StaticLibrary): + ldargs += self.build.get_project_link_args(linker, target.subproject, target.for_machine) + ldargs += self.build.get_global_link_args(linker, target.for_machine) + cargs = [] + for dep in target.get_external_deps(): + cargs += dep.get_compile_args() + ldargs += dep.get_link_args() + for o in target.objects: + # Add extracted objects to the link line by hand. + if isinstance(o, build.ExtractedObjects): + added_objs = set() + for objname_rel in self.determine_ext_objs(o): + objname_abs = os.path.join(self.environment.get_build_dir(), o.target.subdir, objname_rel) + 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'\"'] + else: + if isinstance(o, build.CustomTarget): + (srcs, ofilenames, cmd) = self.eval_custom_target_command(o) + for ofname in ofilenames: + if os.path.splitext(ofname)[-1] in LINKABLE_EXTENSIONS: + ldargs += [r'\"' + os.path.join(self.environment.get_build_dir(), ofname) + r'\"'] + elif isinstance(o, build.CustomTargetIndex): + for ofname in o.get_outputs(): + if os.path.splitext(ofname)[-1] in LINKABLE_EXTENSIONS: + ldargs += [r'\"' + os.path.join(self.environment.get_build_dir(), ofname) + r'\"'] + else: + raise RuntimeError(o) + if isinstance(target, build.SharedModule): + options = self.environment.coredata.options + ldargs += linker.get_std_shared_module_link_args(options) + elif isinstance(target, build.SharedLibrary): + ldargs += linker.get_std_shared_lib_link_args() + ldstr = ' '.join(ldargs) + valid = self.buildconfmap[target_name][buildtype] + langargs = {} + for lang in self.environment.coredata.compilers[target.for_machine]: + if lang not in LANGNAMEMAP: + continue + compiler = target.compilers.get(lang) + if compiler is None: + continue + # Start with warning args + warn_args = compiler.get_warn_args(target.get_option(OptionKey('warning_level'))) + copt_proxy = target.get_options() + std_args = compiler.get_option_compile_args(copt_proxy) + # Add compile args added using add_project_arguments() + pargs = self.build.projects_args[target.for_machine].get(target.subproject, {}).get(lang, []) + # Add compile args added using add_global_arguments() + # These override per-project arguments + gargs = self.build.global_args[target.for_machine].get(lang, []) + targs = target.get_extra_args(lang) + args = warn_args + std_args + pargs + gargs + targs + if lang == 'swift': + # For some reason putting Swift module dirs in HEADER_SEARCH_PATHS does not work, + # but adding -I/path to manual args does work. + swift_dep_dirs = self.determine_swift_dep_dirs(target) + for d in swift_dep_dirs: + args += compiler.get_include_args(d, False) + if args: + lang_cargs = cargs + if compiler and target.implicit_include_directories: + # It is unclear what is the cwd when xcode runs. -I. does not seem to + # add the root build dir to the search path. So add an absolute path instead. + # This may break reproducible builds, in which case patches are welcome. + lang_cargs += self.get_custom_target_dir_include_args(target, compiler, absolute_path=True) + # Xcode can not handle separate compilation flags for C and ObjectiveC. They are both + # put in OTHER_CFLAGS. Same with C++ and ObjectiveC++. + if lang == 'objc': + lang = 'c' + elif lang == 'objcpp': + lang = 'cpp' + langname = LANGNAMEMAP[lang] + if langname in langargs: + langargs[langname] += args + else: + langargs[langname] = args + langargs[langname] += lang_cargs + symroot = os.path.join(self.environment.get_build_dir(), target.subdir) + bt_dict = PbxDict() + objects_dict.add_item(valid, bt_dict, buildtype) + bt_dict.add_item('isa', 'XCBuildConfiguration') + settings_dict = PbxDict() + bt_dict.add_item('buildSettings', settings_dict) + settings_dict.add_item('COMBINE_HIDPI_IMAGES', 'YES') + if isinstance(target, build.SharedModule): + settings_dict.add_item('DYLIB_CURRENT_VERSION', '""') + settings_dict.add_item('DYLIB_COMPATIBILITY_VERSION', '""') + else: + if dylib_version is not None: + settings_dict.add_item('DYLIB_CURRENT_VERSION', f'"{dylib_version}"') + if target.prefix: + settings_dict.add_item('EXECUTABLE_PREFIX', target.prefix) + if target.suffix: + suffix = '.' + target.suffix + settings_dict.add_item('EXECUTABLE_SUFFIX', suffix) + settings_dict.add_item('GCC_GENERATE_DEBUGGING_SYMBOLS', BOOL2XCODEBOOL[target.get_option(OptionKey('debug'))]) + settings_dict.add_item('GCC_INLINES_ARE_PRIVATE_EXTERN', 'NO') + opt_flag = OPT2XCODEOPT[target.get_option(OptionKey('optimization'))] + if opt_flag is not None: + settings_dict.add_item('GCC_OPTIMIZATION_LEVEL', opt_flag) + if target.has_pch: + # Xcode uses GCC_PREFIX_HEADER which only allows one file per target/executable. Precompiling various header files and + # applying a particular pch to each source file will require custom scripts (as a build phase) and build flags per each + # file. Since Xcode itself already discourages precompiled headers in favor of modules we don't try much harder here. + pchs = target.get_pch('c') + target.get_pch('cpp') + target.get_pch('objc') + target.get_pch('objcpp') + # Make sure to use headers (other backends require implementation files like *.c *.cpp, etc; these should not be used here) + pchs = [pch for pch in pchs if pch.endswith('.h') or pch.endswith('.hh') or pch.endswith('hpp')] + if pchs: + if len(pchs) > 1: + mlog.warning(f'Unsupported Xcode configuration: More than 1 precompiled header found "{pchs!s}". Target "{target.name}" might not compile correctly.') + relative_pch_path = os.path.join(target.get_subdir(), pchs[0]) # Path relative to target so it can be used with "$(PROJECT_DIR)" + settings_dict.add_item('GCC_PRECOMPILE_PREFIX_HEADER', 'YES') + settings_dict.add_item('GCC_PREFIX_HEADER', f'"$(PROJECT_DIR)/{relative_pch_path}"') + settings_dict.add_item('GCC_PREPROCESSOR_DEFINITIONS', '""') + settings_dict.add_item('GCC_SYMBOLS_PRIVATE_EXTERN', 'NO') + header_arr = PbxArray() + unquoted_headers = [] + unquoted_headers.append(self.get_target_private_dir_abs(target)) + if target.implicit_include_directories: + unquoted_headers.append(os.path.join(self.environment.get_build_dir(), target.get_subdir())) + unquoted_headers.append(os.path.join(self.environment.get_source_dir(), target.get_subdir())) + if headerdirs: + for i in headerdirs: + i = os.path.normpath(i) + unquoted_headers.append(i) + for i in unquoted_headers: + header_arr.add_item(f'"\\"{i}\\""') + settings_dict.add_item('HEADER_SEARCH_PATHS', header_arr) + settings_dict.add_item('INSTALL_PATH', f'"{install_path}"') + settings_dict.add_item('LIBRARY_SEARCH_PATHS', '""') + if isinstance(target, build.SharedModule): + settings_dict.add_item('LIBRARY_STYLE', 'BUNDLE') + settings_dict.add_item('MACH_O_TYPE', 'mh_bundle') + elif isinstance(target, build.SharedLibrary): + settings_dict.add_item('LIBRARY_STYLE', 'DYNAMIC') + self.add_otherargs(settings_dict, langargs) + settings_dict.add_item('OTHER_LDFLAGS', f'"{ldstr}"') + settings_dict.add_item('OTHER_REZFLAGS', '""') + if ' ' in product_name: + settings_dict.add_item('PRODUCT_NAME', f'"{product_name}"') + else: + settings_dict.add_item('PRODUCT_NAME', product_name) + settings_dict.add_item('SECTORDER_FLAGS', '""') + settings_dict.add_item('SYMROOT', f'"{symroot}"') + sysheader_arr = PbxArray() + # XCode will change every -I flag that points inside these directories + # to an -isystem. Thus set nothing in it since we control our own + # include flags. + settings_dict.add_item('SYSTEM_HEADER_SEARCH_PATHS', sysheader_arr) + settings_dict.add_item('USE_HEADERMAP', 'NO') + warn_array = PbxArray() + settings_dict.add_item('WARNING_CFLAGS', warn_array) + warn_array.add_item('"$(inherited)"') + bt_dict.add_item('name', buildtype) + + def add_otherargs(self, settings_dict, langargs): + for langname, args in langargs.items(): + if args: + quoted_args = [] + for a in args: + # This works but + # a) it's ugly as sin + # b) I don't know why it works or why every backslash must be escaped into eight backslashes + a = a.replace(chr(92), 8*chr(92)) # chr(92) is backslash, this how we smuggle it in without Python's quoting grabbing it. + a = a.replace(r'"', r'\\\"') + if ' ' in a or "'" in a: + a = r'\"' + a + r'\"' + quoted_args.append(a) + settings_dict.add_item(f'OTHER_{langname}FLAGS', '"' + ' '.join(quoted_args) + '"') + + def generate_xc_configurationList(self, objects_dict): + # FIXME: sort items + conf_dict = PbxDict() + objects_dict.add_item(self.project_conflist, conf_dict, f'Build configuration list for PBXProject "{self.build.project_name}"') + conf_dict.add_item('isa', 'XCConfigurationList') + confs_arr = PbxArray() + conf_dict.add_item('buildConfigurations', confs_arr) + for buildtype in self.buildtypes: + confs_arr.add_item(self.project_configurations[buildtype], buildtype) + conf_dict.add_item('defaultConfigurationIsVisible', 0) + conf_dict.add_item('defaultConfigurationName', self.buildtype) + + # Now the all target + all_dict = PbxDict() + objects_dict.add_item(self.all_buildconf_id, all_dict, 'Build configuration list for PBXAggregateTarget "ALL_BUILD"') + all_dict.add_item('isa', 'XCConfigurationList') + conf_arr = PbxArray() + all_dict.add_item('buildConfigurations', conf_arr) + for buildtype in self.buildtypes: + conf_arr.add_item(self.buildall_configurations[buildtype], buildtype) + all_dict.add_item('defaultConfigurationIsVisible', 0) + all_dict.add_item('defaultConfigurationName', self.buildtype) + + # Test target + test_dict = PbxDict() + objects_dict.add_item(self.test_buildconf_id, test_dict, 'Build configuration list for PBXAggregateTarget "RUN_TEST"') + test_dict.add_item('isa', 'XCConfigurationList') + conf_arr = PbxArray() + test_dict.add_item('buildConfigurations', conf_arr) + for buildtype in self.buildtypes: + conf_arr.add_item(self.test_configurations[buildtype], buildtype) + test_dict.add_item('defaultConfigurationIsVisible', 0) + test_dict.add_item('defaultConfigurationName', self.buildtype) + + # Regen target + regen_dict = PbxDict() + objects_dict.add_item(self.regen_buildconf_id, test_dict, 'Build configuration list for PBXAggregateTarget "REGENERATE"') + regen_dict.add_item('isa', 'XCConfigurationList') + conf_arr = PbxArray() + regen_dict.add_item('buildConfigurations', conf_arr) + for buildtype in self.buildtypes: + conf_arr.add_item(self.test_configurations[buildtype], buildtype) + regen_dict.add_item('defaultConfigurationIsVisible', 0) + regen_dict.add_item('defaultConfigurationName', self.buildtype) + + for target_name in self.build_targets: + t_dict = PbxDict() + listid = self.buildconflistmap[target_name] + objects_dict.add_item(listid, t_dict, f'Build configuration list for PBXNativeTarget "{target_name}"') + t_dict.add_item('isa', 'XCConfigurationList') + conf_arr = PbxArray() + t_dict.add_item('buildConfigurations', conf_arr) + idval = self.buildconfmap[target_name][self.buildtype] + conf_arr.add_item(idval, self.buildtype) + t_dict.add_item('defaultConfigurationIsVisible', 0) + t_dict.add_item('defaultConfigurationName', self.buildtype) + + for target_name in self.custom_targets: + t_dict = PbxDict() + listid = self.buildconflistmap[target_name] + objects_dict.add_item(listid, t_dict, f'Build configuration list for PBXAggregateTarget "{target_name}"') + t_dict.add_item('isa', 'XCConfigurationList') + conf_arr = PbxArray() + t_dict.add_item('buildConfigurations', conf_arr) + idval = self.buildconfmap[target_name][self.buildtype] + conf_arr.add_item(idval, self.buildtype) + t_dict.add_item('defaultConfigurationIsVisible', 0) + t_dict.add_item('defaultConfigurationName', self.buildtype) + + def generate_prefix(self, pbxdict): + pbxdict.add_item('archiveVersion', '1') + pbxdict.add_item('classes', PbxDict()) + pbxdict.add_item('objectVersion', '46') + objects_dict = PbxDict() + pbxdict.add_item('objects', objects_dict) + + return objects_dict + + def generate_suffix(self, pbxdict): + pbxdict.add_item('rootObject', self.project_uid, 'Project object') |