diff --git a/pbxproj/pbxextensions/ProjectFiles.py b/pbxproj/pbxextensions/ProjectFiles.py index 9057c8d..d27296f 100644 --- a/pbxproj/pbxextensions/ProjectFiles.py +++ b/pbxproj/pbxextensions/ProjectFiles.py @@ -145,7 +145,7 @@ class ProjectFiles: def __init__(self): raise EnvironmentError('This class cannot be instantiated directly, use XcodeProject instead') - def add_file(self, path, parent=None, tree=TreeType.SOURCE_ROOT, target_name=None, force=True, + def add_file(self, path, name=None, parent=None, tree=TreeType.SOURCE_ROOT, target_name=None, force=True, file_options=FileOptions()): """ Adds a file to the project, taking care of the type of the file and creating additional structures depending on @@ -153,6 +153,7 @@ def add_file(self, path, parent=None, tree=TreeType.SOURCE_ROOT, target_name=Non Header file will be added to the headers sections, but not compiled, whereas the source files will be added to the compilation phase. :param path: Path to the file to be added + :param name: Optional custom name of the file, if None is passed then "path" will be used. :param parent: Parent group to be added under :param tree: Tree where the path is relative to :param target_name: Target name or list of target names where the file should be added (none for every target) @@ -167,7 +168,7 @@ def add_file(self, path, parent=None, tree=TreeType.SOURCE_ROOT, target_name=Non if target_name.__len__() == 0: return [] - file_ref, abs_path, path, tree, expected_build_phase = self._add_file_reference(path, parent, tree, force, + file_ref, abs_path, path, tree, expected_build_phase = self._add_file_reference(path, name, parent, tree, force, file_options) if path is None or tree is None: return None @@ -199,7 +200,7 @@ def _filter_targets_without_path(self, path, target_name): build_phase = self.get_object(build_phase_id) for build_file_id in build_phase.files: build_file = self.get_object(build_file_id) - if build_file is None: + if build_file is None or not hasattr(build_file, 'fileRef'): continue file_ref = self.get_object(build_file.fileRef) @@ -236,7 +237,7 @@ def add_project(self, path, parent=None, tree=TreeType.GROUP, target_name=None, if not force and self._file_exists(path): return [] - file_ref, _, path, tree, expected_build_phase = self._add_file_reference(path, parent, tree, force, + file_ref, _, path, tree, expected_build_phase = self._add_file_reference(path, None, parent, tree, force, file_options) if path is None or tree is None: return None @@ -360,7 +361,8 @@ def remove_file_by_id(self, file_id, target_name=None): return True # remove the file from any groups if there is no reference from any target - for group in filter(lambda x: file_ref.get_id() in x.children, self.objects.get_objects_in_section('PBXGroup')): + for group in filter(lambda x: file_ref.get_id() in x.children, + self.objects.get_objects_in_section('PBXGroup', 'PBXVariantGroup')): group.remove_child(file_ref) # the file is not referenced in any build file, remove it @@ -412,8 +414,8 @@ def add_folder(self, path, parent=None, excludes=None, recursive=True, create_gr # add the top folder as a group, make it the new parent path = os.path.abspath(path) if not create_groups and os.path.splitext(path)[1] not in ProjectFiles._SPECIAL_FOLDERS: - return self.add_file(path, parent, target_name=target_name, force=False, tree=TreeType.GROUP, - file_options=file_options) + return self.add_file(path, parent=parent, target_name=target_name, + force=False, tree=TreeType.GROUP, file_options=file_options) parent = self.get_or_create_group(os.path.split(path)[1], path, parent, make_relative=file_options.add_groups_relative) @@ -428,8 +430,8 @@ def add_folder(self, path, parent=None, excludes=None, recursive=True, create_gr if os.path.isfile(full_path) or os.path.splitext(child)[1] in ProjectFiles._SPECIAL_FOLDERS or \ not create_groups: # check if the file exists already, if not add it - children = self.add_file(full_path, parent, target_name=target_name, force=False, tree=TreeType.GROUP, - file_options=file_options) + children = self.add_file(full_path, parent=parent, target_name=target_name, + force=False, tree=TreeType.GROUP, file_options=file_options) else: # if recursive is true, go deeper, otherwise create the group here. if recursive: @@ -572,14 +574,14 @@ def add_package_dependency(self, target_name, create_parameters=()): # miscellaneous functions, candidates to be extracted and decouple implementation - def _add_file_reference(self, path, parent, tree, force, file_options): + def _add_file_reference(self, path, name, parent, tree, force, file_options): # decide the proper tree and path to add abs_path, path, tree = ProjectFiles._get_path_and_tree(self._source_root, path, tree) if path is None or tree is None: return None, abs_path, path, tree, None # create a PBXFileReference for the new file - file_ref = PBXFileReference.create(path, tree) + file_ref = PBXFileReference.create(path, name, tree) # determine the type of the new file: file_type, expected_build_phase = ProjectFiles._determine_file_type(file_ref, file_options.ignore_unknown_type) @@ -637,7 +639,7 @@ def _create_build_products(self, product_ref, target_name): @classmethod def _determine_file_type(cls, file_ref, unknown_type_allowed): - ext = os.path.splitext(file_ref.get_name())[1] + ext = os.path.splitext(file_ref.path)[1] if os.path.isdir(os.path.abspath(file_ref.path)) and ext not in ProjectFiles._SPECIAL_FOLDERS: file_type = 'folder' build_phase = 'PBXResourcesBuildPhase' diff --git a/pbxproj/pbxextensions/ProjectGroups.py b/pbxproj/pbxextensions/ProjectGroups.py index aa72c6e..5202411 100644 --- a/pbxproj/pbxextensions/ProjectGroups.py +++ b/pbxproj/pbxextensions/ProjectGroups.py @@ -62,7 +62,7 @@ def remove_group_by_id(self, group_id, recursive=True): del self.objects[group.get_id()] # remove the reference from any other group object that could be containing it. - for other_group in self.objects.get_objects_in_section('PBXGroup'): + for other_group in self.objects.get_objects_in_section('PBXGroup', 'PBXVariantGroup'): other_group.remove_child(group) return True @@ -87,14 +87,14 @@ def remove_group_by_name(self, group_name, recursive=True): return True - def get_groups_by_name(self, name, parent=None): + def get_groups_by_name(self, name, parent=None, section='PBXGroup'): """ Retrieve all groups matching the given name and optionally filtered by the given parent node. :param name: The name of the group that has to be returned :param parent: A PBXGroup object where the object has to be retrieved from. If None all matching groups are returned :return: An list of all matching groups """ - groups = self.objects.get_objects_in_section('PBXGroup') + groups = self.objects.get_objects_in_section(section) groups = [group for group in groups if group.get_name() == name] if parent: @@ -110,7 +110,7 @@ def get_groups_by_path(self, path, parent=None): :param parent: A PBXGroup object where the object has to be retrieved from. If None all matching groups are returned :return: An list of all matching groups """ - groups = self.objects.get_objects_in_section('PBXGroup') + groups = self.objects.get_objects_in_section('PBXGroup', 'PBXVariantGroup') groups = [group for group in groups if group.get_path() == path] if parent: diff --git a/pbxproj/pbxsections/PBXFileReference.py b/pbxproj/pbxsections/PBXFileReference.py index a44e245..a30c05e 100644 --- a/pbxproj/pbxsections/PBXFileReference.py +++ b/pbxproj/pbxsections/PBXFileReference.py @@ -5,12 +5,12 @@ class PBXFileReference(PBXGenericObject): @classmethod - def create(cls, path, tree='SOURCE_ROOT'): + def create(cls, path, name=None, tree='SOURCE_ROOT'): return cls().parse({ '_id': cls._generate_id(), 'isa': cls.__name__, 'path': path, - 'name': os.path.split(path)[1], + 'name': name or os.path.split(path)[1], 'sourceTree': tree }) @@ -48,7 +48,7 @@ def remove(self, recursive=True): build_file.remove(recursive) # search for each group that has a reference to the build file and remove it from it. - for group in parent.get_objects_in_section('PBXGroup'): + for group in parent.get_objects_in_section('PBXGroup', 'PBXVariantGroup'): if self.get_id() in group.children: group.remove_child(self) diff --git a/pbxproj/pbxsections/PBXVariantGroup.py b/pbxproj/pbxsections/PBXVariantGroup.py new file mode 100644 index 0000000..e26480a --- /dev/null +++ b/pbxproj/pbxsections/PBXVariantGroup.py @@ -0,0 +1,13 @@ +from pbxproj.pbxsections import PBXFileReference, PBXGroup + + +class PBXVariantGroup(PBXGroup): + def add_child(self, child): + # Note: PBXVariantGroup is typically used for Localizable.strings + # If other children type is needed e.g. PBXFileReference, edit this + # function. + if not isinstance(child, PBXFileReference): + return False + + self.children.append(child.get_id()) + return True diff --git a/pbxproj/pbxsections/__init__.py b/pbxproj/pbxsections/__init__.py index ac16691..64e8679 100644 --- a/pbxproj/pbxsections/__init__.py +++ b/pbxproj/pbxsections/__init__.py @@ -16,6 +16,7 @@ from pbxproj.pbxsections.PBXLegacyTarget import * from pbxproj.pbxsections.PBXReferenceProxy import * from pbxproj.pbxsections.PBXGroup import * +from pbxproj.pbxsections.PBXVariantGroup import * from pbxproj.pbxsections.XCSwiftPackageProductDependency import * from pbxproj.pbxsections.XCRemoteSwiftPackageReference import * from pbxproj.pbxsections.XCLocalSwiftPackageReference import *