| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2014-2021 The Meson development team | |
| from __future__ import annotations | |
| import functools, uuid, os, operator, re | |
| import typing as T | |
| from . import backends | |
| from .. import build | |
| from .. import compilers | |
| from .. import mesonlib | |
| from .. import mlog | |
| from ..arglist import CompilerArgs | |
| from ..dependencies.platform import AppleFrameworks | |
| from ..mesonlib import MesonBugException, MesonException | |
| from ..linkers import StaticLinker | |
| from ..options import OptionKey | |
| if T.TYPE_CHECKING: | |
| from ..build import BuildTarget | |
| from ..compilers import Compiler | |
| PbxDictItemTypes = T.TypeVar('PbxDictItemTypes', 'PbxArray', 'PbxDict', str, int) | |
| INDENT = '\t' | |
| XCODETYPEMAP: T.Mapping[str, str] = { | |
| '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.nasm', | |
| 'metal': 'sourcecode.metal', | |
| 'glsl': 'sourcecode.glsl', | |
| } | |
| NEEDS_CUSTOM_RULES: T.Mapping[str, str] = { | |
| 'nasm': 'sourcecode.nasm', | |
| } | |
| LANGNAMEMAP: T.Mapping[str, str] = { | |
| 'c': 'C', | |
| 'cpp': 'CPLUSPLUS', | |
| 'objc': 'OBJC', | |
| 'objcpp': 'OBJCPLUSPLUS', | |
| 'swift': 'SWIFT_' | |
| } | |
| OPT2XCODEOPT: T.Mapping[str, str | None] = { | |
| 'plain': None, | |
| '0': '0', | |
| 'g': '0', | |
| '1': '1', | |
| '2': '2', | |
| '3': '3', | |
| 's': 's', | |
| } | |
| BOOL2XCODEBOOL: T.Mapping[bool, str] = {True: 'YES', False: 'NO'} | |
| LINKABLE_EXTENSIONS: T.AbstractSet[str] = {'.o', '.a', '.obj', '.so', '.dylib'} | |
| XCODEVERSIONS: T.Mapping[str, tuple[str, int]] = { | |
| '1500': ('Xcode 15.0', 60), | |
| '1400': ('Xcode 14.0', 56), | |
| '1300': ('Xcode 13.0', 55), | |
| '1200': ('Xcode 12.0', 54), | |
| '1140': ('Xcode 11.4', 53), | |
| '1100': ('Xcode 11.0', 52), | |
| '1000': ('Xcode 10.0', 51), | |
| '930': ('Xcode 9.3', 50), | |
| '800': ('Xcode 8.0', 48), | |
| '630': ('Xcode 6.3', 47), | |
| '320': ('Xcode 3.2', 46), | |
| '310': ('Xcode 3.1', 45) | |
| } | |
| def autodetect_xcode_version() -> T.Tuple[str, int]: | |
| try: | |
| pc, stdout, stderr = mesonlib.Popen_safe(['xcodebuild', '-version']) | |
| except FileNotFoundError: | |
| raise MesonException('Could not detect Xcode. Please install it if you wish to use the Xcode backend.') | |
| if pc.returncode != 0: | |
| raise MesonException(f'An error occurred while detecting Xcode: {stderr}') | |
| version = int(''.join(re.search(r'\d*\.\d*\.*\d*', stdout).group(0).split('.'))) | |
| # If the version number does not have two decimal points, pretend it does. | |
| if stdout.count('.') < 2: | |
| version *= 10 | |
| for v, r in XCODEVERSIONS.items(): | |
| if int(v) <= version: | |
| return r | |
| raise MesonException('Your Xcode installation is too old and is not supported.') | |
| class FileTreeEntry: | |
| def __init__(self) -> None: | |
| self.subdirs: T.Dict[str, FileTreeEntry] = {} | |
| self.targets: T.List[build.BuildTarget] = [] | |
| class PbxArray: | |
| def __init__(self) -> None: | |
| self.items: T.List[PbxArrayItem] = [] | |
| def add_item(self, item: T.Union[PbxArrayItem, str], comment: str = '') -> None: | |
| if isinstance(item, PbxArrayItem): | |
| self.items.append(item) | |
| else: | |
| self.items.append(PbxArrayItem(item, comment)) | |
| def write(self, ofile: T.TextIO, indent_level: int) -> None: | |
| 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: str, comment: str = ''): | |
| 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: str): | |
| assert '/*' not in text | |
| self.text = f'/* {text} */' | |
| def write(self, ofile: T.TextIO, indent_level: int) -> None: | |
| ofile.write(f'\n{self.text}\n') | |
| class PbxDictItem: | |
| value: PbxArray | PbxDict | str | int | |
| def __init__(self, key: str, value: T.Union[PbxArray, PbxDict, str, int], comment: str = ''): | |
| self.key = key | |
| if isinstance(value, str): | |
| self.value = self.quote_value(value) | |
| else: | |
| self.value = value | |
| if comment: | |
| if '/*' in comment: | |
| self.comment = comment | |
| else: | |
| self.comment = f'/* {comment} */' | |
| else: | |
| self.comment = comment | |
| def quote_value(self, value: str) -> str: | |
| quoted = f'"{value}"' | |
| if not value: | |
| return quoted | |
| if set(' +@$<>/').isdisjoint(value) or value[0] == '"': | |
| return value | |
| return quoted | |
| class PbxDict: | |
| def __init__(self) -> None: | |
| # 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: T.Set[str] = set() | |
| self.items: T.List[T.Union[PbxDictItem, PbxComment]] = [] | |
| def add_item(self, key: str, value: T.Union[PbxArray, PbxDict, str, int], comment: str = '') -> None: | |
| assert key not in self.keys | |
| item = PbxDictItem(key, value, comment) | |
| self.keys.add(key) | |
| self.items.append(item) | |
| def get_item(self, key: str) -> PbxDictItem: | |
| assert key in self.keys | |
| for item in self.items: | |
| if not isinstance(item, PbxDictItem): | |
| continue | |
| if item.key == key: | |
| return item | |
| return None | |
| def get_item_value(self, key: str, type_: type[PbxDictItemTypes]) -> PbxDictItemTypes: | |
| i = self.get_item(key).value | |
| if not isinstance(i, type_): | |
| raise MesonBugException(f'Attempted to get the wrong type for key {key}, expected {type_} but got {type(i)}') | |
| return i | |
| def has_item(self, key: str) -> bool: | |
| return key in self.keys | |
| def add_comment(self, comment: PbxComment) -> None: | |
| assert isinstance(comment, PbxComment) | |
| self.items.append(comment) | |
| def write(self, ofile: T.TextIO, indent_level: int) -> None: | |
| 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): | |
| name = 'xcode' | |
| def __init__(self, build: T.Optional[build.Build]): | |
| super().__init__(build) | |
| self.project_uid = self.environment.coredata.lang_guids['default'].replace('-', '')[:24] | |
| self.buildtype = T.cast('str', self.environment.coredata.optstore.get_value_for(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: dict[tuple[str, int], list[str]] = {} | |
| self.set_arch() | |
| self.xcodeversion, self.objversion = autodetect_xcode_version() | |
| # 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: dict[tuple[str, str] | str, str] = {} | |
| # 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: dict[tuple[str, str] | str, str] = {} | |
| self.buildphase_map: dict[str, dict[str, str]] = {} | |
| def write_pbxfile(self, top_level_dict: PbxDict, ofilename: str) -> None: | |
| 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) -> str: | |
| return str(uuid.uuid4()).upper().replace('-', '')[:24] | |
| @functools.lru_cache(maxsize=None) | |
| def get_target_dir_cached(self, target: build.Target) -> str: | |
| dirname = os.path.join(target.get_subdir(), T.cast('str', self.environment.coredata.optstore.get_value_for(OptionKey('buildtype')))) | |
| return dirname | |
| def get_custom_target_output_dir(self, target: build.AnyTargetType) -> str: | |
| dirname = target.get_subdir() | |
| os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) | |
| return dirname | |
| def object_filename_from_source(self, target: build.BuildTarget, compiler: Compiler, | |
| source: mesonlib.File, targetdir: T.Optional[str] = None) -> str: | |
| # 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() | |
| stem = os.path.splitext(os.path.basename(source.fname))[0] | |
| # Append "build" before the actual object path to match OBJROOT | |
| obj_path = f'build/{project}.build/{buildtype}/{tname}.build/Objects-normal/{self.arch}/{stem}.o' | |
| return obj_path | |
| def determine_swift_dep_dirs(self, target: build.BuildTarget) -> T.List[str]: | |
| result: T.List[str] = [] | |
| for l in target.link_targets: | |
| # Xcode does not recognize our private directories, so we have to use its build directories instead. | |
| result.append(os.path.join(self.environment.get_build_dir(), self.get_target_dir(l))) | |
| return result | |
| def set_arch(self) -> None: | |
| self.arch = self.build.environment.machines.host.cpu | |
| if self.arch == 'aarch64': | |
| self.arch = 'arm64' | |
| def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) -> None: | |
| if self.arch is None: # The host machine arch may not be set when backend is created | |
| self.set_arch() | |
| # Check for (currently) unexpected capture arg use cases - | |
| if capture: | |
| raise MesonBugException('We do not expect the xcode backend to generate with \'capture = True\'') | |
| if vslite_ctx: | |
| raise MesonBugException('We do not expect the xcode backend to be given a valid \'vslite_ctx\'') | |
| 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() | |
| if self.objversion < 50: | |
| 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_native_target_build_rules_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 PBXBuildRule section')) | |
| self.generate_pbx_build_rule(objects_dict) | |
| objects_dict.add_comment(PbxComment('End PBXBuildRule section')) | |
| objects_dict.add_comment(PbxComment('Begin PBXBuildStyle section')) | |
| if self.objversion < 50: | |
| 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: str) -> str: | |
| 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) -> None: | |
| self.filemap: dict[str, str] = {} # Key is source file relative to src root. | |
| self.foldermap: dict[tuple[str, build.BuildTarget], str] = {} | |
| self.target_filemap: dict[str, str] = {} | |
| for name, t in self.build_targets.items(): | |
| for s in t.sources: | |
| if isinstance(s, mesonlib.File): | |
| if '/' in s.fname: | |
| # From the top level down, add the folders containing the source file. | |
| folder = os.path.split(os.path.dirname(s.fname)) | |
| while folder: | |
| fpath = os.path.join(*folder) | |
| # Multiple targets might use the same folders, so store their targets with them. | |
| # Otherwise, folders and their source files will appear in the wrong places in Xcode. | |
| if (fpath, t) not in self.foldermap: | |
| self.foldermap[(fpath, t)] = self.gen_id() | |
| else: | |
| break | |
| folder = folder[:-1] | |
| 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() | |
| for e in t.extra_files: | |
| if isinstance(e, mesonlib.File): | |
| e = os.path.join(e.subdir, e.fname) | |
| self.filemap[e] = self.gen_id() | |
| else: | |
| e = os.path.join(t.subdir, e) | |
| self.filemap[e] = self.gen_id() | |
| self.target_filemap[name] = self.gen_id() | |
| def generate_buildstylemap(self) -> None: | |
| self.buildstylemap = {self.buildtype: self.gen_id()} | |
| def generate_build_phase_map(self) -> None: | |
| for tname, t in self.build_targets.items(): | |
| # generate id for our own target-name | |
| self.buildphase_map[t.id] = { | |
| tname: self.gen_id(), | |
| # each target can have its own Frameworks/Sources/..., generate | |
| # id's for those | |
| 'Frameworks': self.gen_id(), | |
| 'Resources': self.gen_id(), | |
| 'Sources': self.gen_id(), | |
| } | |
| def generate_build_configuration_map(self) -> None: | |
| 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) -> None: | |
| self.project_configurations = {self.buildtype: self.gen_id()} | |
| def generate_buildall_configurations_map(self) -> None: | |
| self.buildall_configurations = {self.buildtype: self.gen_id()} | |
| def generate_test_configurations_map(self) -> None: | |
| self.test_configurations = {self.buildtype: self.gen_id()} | |
| def generate_build_configurationlist_map(self) -> None: | |
| 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) -> None: | |
| self.native_targets = {} | |
| for t in self.build_targets: | |
| self.native_targets[t] = self.gen_id() | |
| def generate_native_target_build_rules_map(self) -> None: | |
| self.build_rules = {} | |
| for name, target in self.build_targets.items(): | |
| languages = {} | |
| for language in target.compilers: | |
| if language not in NEEDS_CUSTOM_RULES: | |
| continue | |
| languages[language] = self.gen_id() | |
| self.build_rules[name] = languages | |
| def generate_custom_target_map(self) -> None: | |
| self.shell_targets: T.Dict[T.Union[str, T.Tuple[str, int]], str] = {} | |
| 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) -> None: | |
| # Generator objects do not have natural unique ids | |
| # so use a counter. | |
| self.generator_fileref_ids: dict[tuple[str, int], list[str]] = {} | |
| self.generator_buildfile_ids: dict[tuple[str, int], list[str]] = {} | |
| 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: build.GeneratedList, tname: str, | |
| t: build.AnyTargetType, generator_id: int) -> None: | |
| k = (tname, generator_id) | |
| assert k not in self.shell_targets | |
| if genlist.extra_depends: | |
| raise NotImplementedError('XCode backend does not support depends for generator.process') | |
| self.shell_targets[k] = self.gen_id() | |
| ofile_abs: list[str] = [] | |
| 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: list[str] = [] | |
| fileref_ids: list[str] = [] | |
| 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) -> None: | |
| self.native_frameworks = {} | |
| self.native_frameworks_fileref = {} | |
| for t in self.build_targets.values(): | |
| for dep in t.get_external_deps(): | |
| if dep.found() and isinstance(dep, 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) -> None: | |
| self.target_dependency_map: T.Dict[T.Union[str, T.Tuple[str, str]], str] = {} | |
| for tname, t in self.build_targets.items(): | |
| for target in t.link_targets: | |
| 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) -> None: | |
| 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) -> None: | |
| self.containerproxy_map = {} | |
| for t in self.build_targets: | |
| self.containerproxy_map[t] = self.gen_id() | |
| def generate_target_file_maps(self) -> None: | |
| 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: T.Mapping[str, build.BuildTarget | build.CustomTarget] | |
| ) -> None: | |
| for tname, t in targets.items(): | |
| for s in t.sources: | |
| if not isinstance(s, mesonlib.File): | |
| continue | |
| s = os.path.join(s.subdir, s.fname) | |
| 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 isinstance(t, build.CustomTarget): | |
| 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)) | |
| for e in t.extra_files: | |
| if isinstance(e, mesonlib.File): | |
| e = os.path.join(e.subdir, e.fname) | |
| if isinstance(e, str): | |
| e = os.path.join(t.subdir, e) | |
| k = (tname, e) | |
| 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() | |
| def generate_build_file_maps(self) -> None: | |
| for buildfile in self.build.def_files: | |
| self.buildfile_ids[buildfile] = self.gen_id() | |
| self.fileref_ids[buildfile] = self.gen_id() | |
| def generate_source_phase_map(self) -> None: | |
| self.source_phase = {} | |
| for t in self.build_targets: | |
| self.source_phase[t] = self.gen_id() | |
| def generate_pbx_aggregate_target(self, objects_dict: PbxDict) -> None: | |
| self.custom_aggregate_targets = {} | |
| self.build_all_tdep_id = self.gen_id() | |
| target_dependencies = [] | |
| custom_target_dependencies = [] | |
| for tname, t in self.get_build_by_default_targets().items(): | |
| if isinstance(t, build.CustomTarget): | |
| custom_target_dependencies.append(self.pbx_custom_dep_map[t.get_id()]) | |
| elif isinstance(t, build.BuildTarget): | |
| target_dependencies.append(self.pbx_dep_map[t.get_id()]) | |
| aggregated_targets: list[tuple[str, str, str, list[str], list[str]]] = [] | |
| 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 d in t.dependencies: | |
| if isinstance(d, build.CustomTarget): | |
| dependencies.append(self.pbx_custom_dep_map[d.get_id()]) | |
| elif isinstance(d, build.BuildTarget): | |
| dependencies.append(self.pbx_dep_map[d.get_id()]) | |
| for s in t.sources: | |
| if isinstance(s, build.GeneratedList): | |
| build_phases.append(self.shell_targets[(tname, generator_id)]) | |
| for d in s.depends: | |
| if isinstance(d, build.GeneratedList): | |
| # TODO: what to do about this | |
| raise MesonBugException('Not implemented, patches highly welcome') | |
| dependencies.append(self.pbx_custom_dep_map[d.get_id()]) | |
| generator_id += 1 | |
| elif isinstance(s, build.ExtractedObjects): | |
| source_target_id = self.pbx_dep_map[s.target.get_id()] | |
| if source_target_id not in dependencies: | |
| dependencies.append(source_target_id) | |
| 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', name) | |
| agt_dict.add_item('productName', name) | |
| objects_dict.add_item(t[0], agt_dict, name) | |
| def generate_pbx_build_file(self, objects_dict: PbxDict) -> None: | |
| for tname, t in self.build_targets.items(): | |
| for dep in t.get_external_deps(): | |
| if dep.found() and isinstance(dep, 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: | |
| if not isinstance(s, mesonlib.File): | |
| continue | |
| in_build_dir = s.is_built | |
| s = os.path.join(s.subdir, s.fname) | |
| 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) | |
| else: | |
| # TODO: handle CustomTarget | CustomTargetIndex | GeneratedList | |
| raise MesonBugException('Not implemented, patches highly welcome') | |
| 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: PbxDict, tname: str, generator_id: int) -> None: | |
| 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) | |
| # This is skipped if Xcode 9 or above is installed, as PBXBuildStyle was removed on that version. | |
| def generate_pbx_build_style(self, objects_dict: PbxDict) -> None: | |
| 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', name) | |
| def to_shell_script(self, args: CompilerArgs) -> str: | |
| quoted_cmd = [] | |
| for c in args: | |
| quoted_cmd.append(c.replace('"', chr(92) + '"')) | |
| cmd = ' '.join(quoted_cmd) | |
| return f"\"#!/bin/sh\\n{cmd}\\n\"" | |
| def generate_pbx_build_rule(self, objects_dict: PbxDict) -> None: | |
| for name, languages in self.build_rules.items(): | |
| target: BuildTarget = self.build_targets[name] | |
| for language, idval in languages.items(): | |
| compiler: Compiler = target.compilers[language] | |
| buildrule = PbxDict() | |
| buildrule.add_item('isa', 'PBXBuildRule') | |
| buildrule.add_item('compilerSpec', 'com.apple.compilers.proxy.script') | |
| if compiler.get_id() != 'yasm': | |
| # Yasm doesn't generate escaped build rules | |
| buildrule.add_item('dependencyFile', '$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).d') | |
| buildrule.add_item('fileType', NEEDS_CUSTOM_RULES[language]) | |
| inputfiles = PbxArray() | |
| buildrule.add_item('inputFiles', inputfiles) | |
| buildrule.add_item('isEditable', '0') | |
| outputfiles = PbxArray() | |
| outputfiles.add_item('$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).o') | |
| buildrule.add_item('outputFiles', outputfiles) | |
| # Do NOT use this parameter. Xcode will accept it from the UI, | |
| # but the parser will break down inconsistently upon next | |
| # opening. rdar://FB12144055 | |
| # outputargs = PbxArray() | |
| # args = self.generate_basic_compiler_args(target, compiler) | |
| # outputargs.add_item(self.to_shell_script(args)) | |
| # buildrule.add_item('outputFilesCompilerFlags', outputargs) | |
| commands = CompilerArgs(compiler) | |
| commands += compiler.get_exelist() | |
| if compiler.get_id() == 'yasm': | |
| # Yasm doesn't generate escaped build rules | |
| commands += self.compiler_to_generator_args(target, compiler, output='"$SCRIPT_OUTPUT_FILE_0"', input='"$SCRIPT_INPUT_FILE"', depfile=None) | |
| else: | |
| commands += self.compiler_to_generator_args(target, | |
| compiler, | |
| output='"$SCRIPT_OUTPUT_FILE_0"', | |
| input='"$SCRIPT_INPUT_FILE"', | |
| depfile='"$(dirname "$SCRIPT_OUTPUT_FILE_0")/$(basename "$SCRIPT_OUTPUT_FILE_0" .o).d"', | |
| extras=['$OTHER_INPUT_FILE_FLAGS']) | |
| buildrule.add_item('script', self.to_shell_script(commands)) | |
| objects_dict.add_item(idval, buildrule, 'PBXBuildRule') | |
| def generate_pbx_container_item_proxy(self, objects_dict: PbxDict) -> None: | |
| 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: PbxDict) -> None: | |
| for tname, t in self.build_targets.items(): | |
| for dep in t.get_external_deps(): | |
| if dep.found() and isinstance(dep, 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: | |
| if not isinstance(s, mesonlib.File): | |
| continue | |
| in_build_dir = s.is_built | |
| s = os.path.join(s.subdir, s.fname) | |
| 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', name) | |
| odict.add_item('path', 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) | |
| elif isinstance(o, str): | |
| o = os.path.join(t.subdir, o) | |
| fullpath = os.path.join(self.environment.get_source_dir(), o) | |
| else: | |
| # TODO: handle CustomTarget, CustomTargeIndex, and GeneratedList | |
| raise MesonBugException('Not implemented, patches highly welcome') | |
| 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', name) | |
| o_dict.add_item('path', rel_name) | |
| o_dict.add_item('sourceTree', 'SOURCE_ROOT') | |
| for e in t.extra_files: | |
| if isinstance(e, mesonlib.File): | |
| e = os.path.join(e.subdir, e.fname) | |
| else: | |
| e = os.path.join(t.subdir, e) | |
| idval = self.fileref_ids[(tname, e)] | |
| fullpath = os.path.join(self.environment.get_source_dir(), e) | |
| e_dict = PbxDict() | |
| xcodetype = self.get_xcodetype(e) | |
| name = os.path.basename(e) | |
| path = e | |
| objects_dict.add_item(idval, e_dict, fullpath) | |
| e_dict.add_item('isa', 'PBXFileReference') | |
| e_dict.add_item('explicitFileType', xcodetype) | |
| e_dict.add_item('name', name) | |
| e_dict.add_item('path', path) | |
| e_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) | |
| 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 not isinstance(s, mesonlib.File): | |
| continue | |
| s = os.path.join(s.subdir, s.fname) | |
| 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', s) | |
| custom_dict.add_item('path', 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.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', basename) | |
| buildfile_dict.add_item('path', 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: PbxDict) -> None: | |
| for t in self.build_targets.values(): | |
| bt_dict = PbxDict() | |
| objects_dict.add_item(self.buildphase_map[t.id]['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 dep.found() and isinstance(dep, 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: PbxDict) -> None: | |
| 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>') | |
| # Define each folder as a group in Xcode. That way, it can build the file tree correctly. | |
| # This must be done before the project tree group is generated, as source files are added during that phase. | |
| for (path, target), id in self.foldermap.items(): | |
| folder_dict = PbxDict() | |
| objects_dict.add_item(id, folder_dict, path) | |
| folder_dict.add_item('isa', 'PBXGroup') | |
| folder_children = PbxArray() | |
| folder_dict.add_item('children', folder_children) | |
| folder_dict.add_item('name', '"{}"'.format(path.rsplit('/', 1)[-1])) | |
| folder_dict.add_item('path', f'"{path}"') | |
| folder_dict.add_item('sourceTree', 'SOURCE_ROOT') | |
| # Add any detected subdirectories (not declared as subdir()) here, but only one level higher. | |
| # Example: In "root", add "root/sub", but not "root/sub/subtwo". | |
| for path_dep, target_dep in self.foldermap: | |
| if path_dep.startswith(path) and path_dep.split('/', 1)[0] == path.split('/', 1)[0] and path_dep != path and path_dep.count('/') == path.count('/') + 1 and target == target_dep: | |
| folder_children.add_item(self.foldermap[(path_dep, target)], path_dep) | |
| 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 dep.found() and isinstance(dep, 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', 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 not isinstance(s, mesonlib.File): | |
| continue | |
| s = os.path.join(s.subdir, s.fname) | |
| 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: PbxDict, t: build.BuildTarget) -> str: | |
| tid = t.get_id() | |
| group_id = self.gen_id() | |
| target_dict = PbxDict() | |
| folder_ids = set() | |
| 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 not isinstance(s, mesonlib.File): | |
| continue | |
| # If the file is in a folder, add it to the group representing that folder. | |
| if '/' in s.fname: | |
| folder = '/'.join(s.fname.split('/')[:-1]) | |
| folder_dict = objects_dict.get_item_value(self.foldermap[(folder, t)], PbxDict) | |
| folder_children = folder_dict.get_item_value('children', PbxArray) | |
| temp = os.path.join(s.subdir, s.fname) | |
| folder_children.add_item(self.fileref_ids[(tid, temp)], temp) | |
| if self.foldermap[(folder, t)] in folder_ids: | |
| continue | |
| if len(folder.split('/')) == 1: | |
| target_children.add_item(self.foldermap[(folder, t)], folder) | |
| folder_ids.add(self.foldermap[(folder, t)]) | |
| continue | |
| s = os.path.join(s.subdir, s.fname) | |
| 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) | |
| elif isinstance(o, str): | |
| o = os.path.join(t.subdir, o) | |
| else: | |
| # TODO: handle CustomTarget, CustomTargeIndex, and GeneratedList | |
| raise MesonBugException('Not implemented, patches highly welcome') | |
| target_children.add_item(self.fileref_ids[(tid, o)], o) | |
| for e in t.extra_files: | |
| if isinstance(e, mesonlib.File): | |
| e = os.path.join(e.subdir, e.fname) | |
| elif isinstance(e, str): | |
| e = os.path.join(t.subdir, e) | |
| else: | |
| continue | |
| target_children.add_item(self.fileref_ids[(tid, e)], e) | |
| source_files_dict.add_item('name', 'Source files') | |
| source_files_dict.add_item('sourceTree', '<group>') | |
| return group_id | |
| def add_projecttree(self, objects_dict: PbxDict, projecttree_id: str) -> None: | |
| 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: PbxDict, tree_node: FileTreeEntry, children_array: PbxArray, current_subdir: str) -> None: | |
| 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', 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'), | |
| 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) -> FileTreeEntry: | |
| 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: FileTreeEntry, t: build.BuildTarget) -> None: | |
| 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: PbxDict) -> None: | |
| 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 self.buildphase_map[t.id].items(): | |
| buildphases_array.add_item(bpval, f'{bpname} yyy') | |
| build_rules = PbxArray() | |
| for language, build_rule_idval in self.build_rules[tname].items(): | |
| build_rules.add_item(build_rule_idval, f'{language}') | |
| ntarget_dict.add_item('buildRules', build_rules) | |
| 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, build.CustomTargetIndex)): | |
| dep_array.add_item(self.pbx_custom_dep_map[lt.get_id()], lt.get_basename()) | |
| 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, build.CustomTargetIndex)): | |
| dep_array.add_item(self.pbx_custom_dep_map[o.get_id()], o.get_basename()) | |
| generator_id += 1 | |
| # xcode backend does not support link_early_args | |
| if isinstance(t, build.BuildTarget) and not isinstance(t, build.StaticLibrary) and t.link_early_args: | |
| raise MesonException(f'XCode backend does not support link_early_args for {t.get_basename()}') | |
| ntarget_dict.add_item('name', tname) | |
| ntarget_dict.add_item('productName', 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: PbxDict) -> None: | |
| 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()) | |
| if self.objversion < 50: | |
| 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', self.xcodeversion) | |
| 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: PbxDict) -> None: | |
| 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: PbxDict) -> None: | |
| 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', cmdstr) | |
| shell_dict.add_item('showEnvVarsInLog', 0) | |
| def generate_regen_shell_build_phase(self, objects_dict: PbxDict) -> None: | |
| 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', cmdstr) | |
| shell_dict.add_item('showEnvVarsInLog', 0) | |
| def generate_custom_target_shell_build_phases(self, objects_dict: PbxDict) -> None: | |
| # 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: PbxDict) -> None: | |
| 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: str, t: build.AnyTargetType, | |
| genlist: build.GeneratedList, generator_id: int, objects_dict: PbxDict) -> None: | |
| # 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() | |
| target_private_dir = self.relpath(self.get_target_private_dir(t), self.get_target_dir(t)) | |
| 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, target_private_dir) | |
| 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: PbxDict) -> None: | |
| for name in self.source_phase: | |
| phase_dict = PbxDict() | |
| t = self.build_targets[name] | |
| objects_dict.add_item(self.buildphase_map[t.id][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 compilers.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): | |
| output_dir = self.get_custom_target_output_dir(gt) | |
| for o in gt.get_outputs(): | |
| o = os.path.join(output_dir, o) | |
| 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: PbxDict) -> None: | |
| 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: list[tuple[str, str, str, str | None]] = [] | |
| 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: PbxDict) -> None: | |
| # 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', self.arch) | |
| settings_dict.add_item('BUILD_DIR', self.environment.get_build_dir()) | |
| settings_dict.add_item('BUILD_ROOT', '$(BUILD_DIR)') | |
| 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('OBJROOT', '$(BUILD_DIR)/build') | |
| bt_dict.add_item('name', 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) | |
| warn_array = PbxArray() | |
| warn_array.add_item('"$(inherited)"') | |
| settings_dict.add_item('WARNING_CFLAGS', warn_array) | |
| bt_dict.add_item('name', 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) | |
| warn_array = PbxArray() | |
| settings_dict.add_item('WARNING_CFLAGS', warn_array) | |
| warn_array.add_item('"$(inherited)"') | |
| bt_dict.add_item('name', 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', self.arch) | |
| settings_dict.add_item('ONLY_ACTIVE_ARCH', 'YES') | |
| settings_dict.add_item('SDKROOT', 'macosx') | |
| bt_dict.add_item('name', buildtype) | |
| def determine_internal_dep_link_args(self, target: build.BuildTarget, buildtype: str) -> T.Tuple[T.List[str], bool]: | |
| 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.CustomTarget, build.CustomTargetIndex)): | |
| rel_dir = self.get_custom_target_output_dir(l.get_target()) | |
| 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: PbxDict, target_name: str, target: build.BuildTarget) -> None: | |
| for buildtype in self.buildtypes: | |
| dep_libs: T.List[str] = [] | |
| links_dylib = False | |
| headerdirs = [] | |
| bridging_header = "" | |
| is_swift = self.is_swift_target(target) | |
| langs = set() | |
| for d in target.include_dirs: | |
| headerdirs.extend(d.abs_string_list(self.environment.get_source_dir(), self.environment.get_build_dir())) | |
| # Swift can import declarations from C-based code using bridging headers. | |
| # There can only be one header, and it must be included as a source file. | |
| for i in target.get_sources(): | |
| if compilers.is_header(i) and is_swift: | |
| relh = i.rel_to_builddir(self.build_to_src) | |
| bridging_header = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) | |
| break | |
| (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 | |
| # TODO: is this still true? | |
| stdlib_args: list[str] | |
| if is_swift: | |
| linker, stdlib_args = target.compilers['swift'], [] | |
| else: | |
| linker, stdlib_args = self.determine_linker_and_stdlib_args(target) | |
| if not isinstance(linker, StaticLinker): | |
| ldargs += linker.get_build_link_args(target, self.build) | |
| 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): | |
| assert not isinstance(linker, StaticLinker), 'for mypy' | |
| ldargs += linker.get_std_shared_module_link_args(target) | |
| elif isinstance(target, build.SharedLibrary): | |
| assert not isinstance(linker, StaticLinker), 'for mypy' | |
| ldargs += linker.get_std_shared_lib_link_args() | |
| ldstr = ' '.join(ldargs) | |
| valid = self.buildconfmap[target_name][buildtype] | |
| langargs: T.Dict[str, T.List[str]] = {} | |
| 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_level = self.get_target_option(target, 'warning_level') | |
| assert isinstance(_warn_level, str), 'for mypy' | |
| warn_args = compiler.get_warn_args(_warn_level) | |
| std_args = compiler.get_option_compile_args(target, target.subproject) | |
| std_args += compiler.get_option_std_args(target, target.subproject) | |
| # Add compile args added using add_project_arguments() | |
| pargs = self.build.get_project_args(compiler, target) | |
| # 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: | |
| cti_args = [] | |
| 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. | |
| cti_args = self.get_custom_target_dir_include_args(target, compiler, absolute_path=True) | |
| # Xcode cannot 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' | |
| langs.add(lang) | |
| langname = LANGNAMEMAP[lang] | |
| langargs.setdefault(langname, []) | |
| langargs[langname] = cargs + cti_args + args | |
| symroot = os.path.join(self.environment.get_build_dir(), target.subdir).rstrip('/') | |
| 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', str(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) | |
| debug = self.get_target_option(target, 'debug') | |
| assert isinstance(debug, bool), 'for mypy' | |
| settings_dict.add_item('GCC_GENERATE_DEBUGGING_SYMBOLS', BOOL2XCODEBOOL[debug]) | |
| settings_dict.add_item('GCC_INLINES_ARE_PRIVATE_EXTERN', 'NO') | |
| opt = self.get_target_option(target, 'optimization') | |
| assert isinstance(opt, str), 'for mypy' | |
| opt_flag = OPT2XCODEOPT[opt] | |
| 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 = [t[0] for t in [target.pch['c'], target.pch['cpp']] if t is not None] | |
| 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') | |
| unquoted_headers = [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_builddir())) | |
| unquoted_headers.append(os.path.join(self.environment.get_source_dir(), target.get_subdir())) | |
| unquoted_headers += headerdirs | |
| settings_dict.add_item('HEADER_SEARCH_PATHS', self.normalize_header_search_paths(unquoted_headers)) | |
| settings_dict.add_item('INSTALL_PATH', 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', ldstr) | |
| settings_dict.add_item('OTHER_REZFLAGS', '') | |
| settings_dict.add_item('PRODUCT_NAME', product_name) | |
| settings_dict.add_item('SECTORDER_FLAGS', '') | |
| if is_swift and bridging_header: | |
| settings_dict.add_item('SWIFT_OBJC_BRIDGING_HEADER', bridging_header) | |
| if self.objversion >= 60 and target.uses_swift_cpp_interop(): | |
| settings_dict.add_item('SWIFT_OBJC_INTEROP_MODE', 'objcxx') | |
| settings_dict.add_item('BUILD_DIR', symroot) | |
| settings_dict.add_item('OBJROOT', f'{symroot}/build') | |
| 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 normalize_header_search_paths(self, header_dirs: T.List[str]) -> PbxArray: | |
| header_arr = PbxArray() | |
| for i in header_dirs: | |
| np = os.path.normpath(i) | |
| # Make sure Xcode will not split single path into separate entries, escaping space with a slash is not enough | |
| item = f'"\\\"{np}\\\""' if ' ' in np else f'"{np}"' | |
| header_arr.add_item(item) | |
| return header_arr | |
| def add_otherargs(self, settings_dict: PbxDict, langargs: T.Dict[str, T.List[str]]) -> None: | |
| 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: PbxDict) -> None: | |
| # 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) -> PbxDict: | |
| pbxdict.add_item('archiveVersion', '1') | |
| pbxdict.add_item('classes', PbxDict()) | |
| pbxdict.add_item('objectVersion', self.objversion) | |
| objects_dict = PbxDict() | |
| pbxdict.add_item('objects', objects_dict) | |
| return objects_dict | |
| def generate_suffix(self, pbxdict: PbxDict) -> None: | |
| pbxdict.add_item('rootObject', self.project_uid, 'Project object') |