From 523b19aedb66a0e09b793015aa335c5a8abca420 Mon Sep 17 00:00:00 2001 From: Yusuf Olokoba Date: Tue, 29 Oct 2024 13:18:46 -0400 Subject: [PATCH 1/2] Add support for reading `PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet` --- ...temSynchronizedBuildFileExceptionSet.swift | 2 +- ...BXFileSystemSynchronizedExceptionSet.swift | 3 + ...roupBuildPhaseMembershipExceptionSet.swift | 83 +++++++++++++++++++ .../PBXFileSystemSynchronizedRootGroup.swift | 10 +-- .../Objects/Project/PBXObjectParser.swift | 2 + .../Objects/Project/PBXObjects.swift | 16 +++- .../Objects/Project/PBXProjEncoder.swift | 6 ++ .../Objects/Sourcery/Equality.generated.swift | 9 ++ 8 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedExceptionSet.swift create mode 100644 Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.swift diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift index 4f363322a..c375f16d0 100644 --- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift @@ -1,7 +1,7 @@ import Foundation /// Class representing an element that may contain other elements. -public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSerializable { +public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXFileSystemSynchronizedExceptionSet, PlistSerializable { // MARK: - Attributes /// A list of relative paths to children subfolders for which exceptions are applied. diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedExceptionSet.swift new file mode 100644 index 000000000..72f233d69 --- /dev/null +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedExceptionSet.swift @@ -0,0 +1,3 @@ +import Foundation + +public class PBXFileSystemSynchronizedExceptionSet: PBXObject { } diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.swift new file mode 100644 index 000000000..8958dc9c1 --- /dev/null +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.swift @@ -0,0 +1,83 @@ +import Foundation + +public class PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: PBXFileSystemSynchronizedExceptionSet, PlistSerializable { + + // MARK: - Attributes + + /// A list of relative paths to children subfolders for which exceptions are applied. + public var membershipExceptions: [String]? + + /// Build phase that this exception set applies to. + public var buildPhase: PBXBuildPhase! { + get { + buildPhaseReference.getObject() as? PBXBuildPhase + } + set { + buildPhaseReference = newValue.reference + } + } + + /// Attributes by relative path. + /// Every item in the list is the relative path inside the root synchronized group. + /// For example `RemoveHeadersOnCopy` and `CodeSignOnCopy`. + public var attributesByRelativePath: [String: [String]]? + + var buildPhaseReference: PBXObjectReference + + // MARK: - Init + + public init( + buildPhase: PBXBuildPhase, + membershipExceptions: [String]?, + attributesByRelativePath: [String: [String]]? + ) { + buildPhaseReference = buildPhase.reference + self.membershipExceptions = membershipExceptions + self.attributesByRelativePath = attributesByRelativePath + super.init() + } + + // MARK: - Decodable + fileprivate enum CodingKeys: String, CodingKey { + case buildPhase + case membershipExceptions + case attributesByRelativePath + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let referenceRepository = decoder.context.objectReferenceRepository + let objects = decoder.context.objects + let buildPhaseReference: String = try container.decode(.buildPhase) + self.buildPhaseReference = referenceRepository.getOrCreate(reference: buildPhaseReference, objects: objects) + membershipExceptions = try container.decodeIfPresent(.membershipExceptions) + attributesByRelativePath = try container.decodeIfPresent(.attributesByRelativePath) + try super.init(from: decoder) + } + + // MARK: - Equatable + + override func isEqual(to object: Any?) -> Bool { + guard let rhs = object as? PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet else { + return false + } + return isEqual(to: rhs) + } + + // MARK: - PlistSerializable + + func plistKeyAndValue(proj _: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { + var dictionary: [CommentedString: PlistValue] = [:] + dictionary["isa"] = .string(CommentedString(type(of: self).isa)) + if let membershipExceptions { + dictionary["membershipExceptions"] = .array(membershipExceptions.map { .string(CommentedString($0)) }) + } + if let attributesByRelativePath { + dictionary["attributesByRelativePath"] = .dictionary(Dictionary(uniqueKeysWithValues: attributesByRelativePath.map { key, value in + (CommentedString(key), .array(value.map { .string(CommentedString($0)) })) + })) + } + dictionary["buildPhase"] = .string(CommentedString(buildPhase.reference.value, comment: buildPhase.name() ?? "CopyFiles")) + return (key: CommentedString(reference, comment: "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet"), value: .dictionary(dictionary)) + } +} diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift index 882d27db5..5cc29adeb 100644 --- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift @@ -12,7 +12,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement { /// It returns a list of exception objects that override the configuration for some children /// in the synchronized root group. - public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? { + public var exceptions: [PBXFileSystemSynchronizedExceptionSet]? { set { exceptionsReferences = newValue?.references() } @@ -47,7 +47,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement { tabWidth: UInt? = nil, wrapsLines: Bool? = nil, explicitFileTypes: [String: String] = [:], - exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], + exceptions: [PBXFileSystemSynchronizedExceptionSet] = [], explicitFolders: [String] = []) { self.explicitFileTypes = explicitFileTypes exceptionsReferences = exceptions.references() @@ -88,9 +88,9 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement { override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:] dictionary["isa"] = .string(CommentedString(type(of: self).isa)) - if let exceptionsReferences, !exceptionsReferences.isEmpty { - dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in - .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet")) + if let exceptions, !exceptions.isEmpty { + dictionary["exceptions"] = .array(exceptions.map { exception in + .string(CommentedString(exception.reference.value, comment: type(of: exception).isa)) }) } if let explicitFileTypes { diff --git a/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift b/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift index 3c0ad0044..b9452b64f 100644 --- a/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift +++ b/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift @@ -72,6 +72,8 @@ final class PBXObjectParser { return try decoder.decode(PBXFileSystemSynchronizedRootGroup.self, from: data) case PBXFileSystemSynchronizedBuildFileExceptionSet.isa: return try decoder.decode(PBXFileSystemSynchronizedBuildFileExceptionSet.self, from: data) + case PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.isa: + return try decoder.decode(PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.self, from: data) default: throw PBXObjectError.unknownElement(isa) } diff --git a/Sources/XcodeProj/Objects/Project/PBXObjects.swift b/Sources/XcodeProj/Objects/Project/PBXObjects.swift index 22cfe75cd..22276036b 100644 --- a/Sources/XcodeProj/Objects/Project/PBXObjects.swift +++ b/Sources/XcodeProj/Objects/Project/PBXObjects.swift @@ -144,6 +144,13 @@ class PBXObjects: Equatable { var fileSystemSynchronizedBuildFileExceptionSets: [PBXObjectReference: PBXFileSystemSynchronizedBuildFileExceptionSet] { lock.whileLocked { _fileSystemSynchronizedBuildFileExceptionSets } } + + private var _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: [PBXObjectReference: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet] = [:] + var fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: [PBXObjectReference: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet] { + lock.whileLocked { _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet } + } + + // XCSwiftPackageProductDependency @@ -185,7 +192,8 @@ class PBXObjects: Equatable { lhs.swiftPackageProductDependencies == rhs._swiftPackageProductDependencies && lhs.remoteSwiftPackageReferences == rhs.remoteSwiftPackageReferences && lhs.fileSystemSynchronizedRootGroups == rhs.fileSystemSynchronizedRootGroups && - lhs.fileSystemSynchronizedBuildFileExceptionSets == rhs.fileSystemSynchronizedBuildFileExceptionSets + lhs.fileSystemSynchronizedBuildFileExceptionSets == rhs.fileSystemSynchronizedBuildFileExceptionSets && + lhs.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet == rhs.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet } // MARK: - Helpers @@ -232,6 +240,7 @@ class PBXObjects: Equatable { case let object as XCSwiftPackageProductDependency: _swiftPackageProductDependencies[objectReference] = object case let object as PBXFileSystemSynchronizedRootGroup: _fileSystemSynchronizedRootGroups[objectReference] = object case let object as PBXFileSystemSynchronizedBuildFileExceptionSet: _fileSystemSynchronizedBuildFileExceptionSets[objectReference] = object + case let object as PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet[objectReference] = object default: fatalError("Unhandled PBXObject type for \(object), this is likely a bug / todo") } } @@ -296,6 +305,8 @@ class PBXObjects: Equatable { return _fileSystemSynchronizedRootGroups.remove(at: index).value } else if let index = fileSystemSynchronizedBuildFileExceptionSets.index(forKey: reference) { return _fileSystemSynchronizedBuildFileExceptionSets.remove(at: index).value + } else if let index = fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.index(forKey: reference) { + return _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.remove(at: index).value } return nil @@ -363,6 +374,8 @@ class PBXObjects: Equatable { return object } else if let object = fileSystemSynchronizedBuildFileExceptionSets[reference] { return object + } else if let object = fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet[reference] { + return object } else { return nil } @@ -456,5 +469,6 @@ extension PBXObjects { swiftPackageProductDependencies.values.forEach(closure) fileSystemSynchronizedRootGroups.values.forEach(closure) fileSystemSynchronizedBuildFileExceptionSets.values.forEach(closure) + fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.values.forEach(closure) } } diff --git a/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift b/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift index 7321fb1d0..bfbb4adec 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift @@ -119,6 +119,12 @@ final class PBXProjEncoder { outputSettings: outputSettings, stateHolder: &stateHolder, to: &output) + try write(section: "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet", + proj: proj, + objects: proj.objects.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet, + outputSettings: outputSettings, + stateHolder: &stateHolder, + to: &output) try write(section: "PBXFileSystemSynchronizedRootGroup", proj: proj, objects: proj.objects.fileSystemSynchronizedRootGroups, diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index 4c9a1d98c..b8087736f 100644 --- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift +++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift @@ -322,3 +322,12 @@ extension PBXFileSystemSynchronizedBuildFileExceptionSet { return super.isEqual(to: rhs) } } + +extension PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet { + /// :nodoc: + func isEqual(to rhs: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet) -> Bool { + if membershipExceptions != rhs.membershipExceptions { return false } + if buildPhaseReference != rhs.buildPhaseReference { return false } + return super.isEqual(to: rhs) + } +} From 6bb836a802521a710333727b566e4f634d172da4 Mon Sep 17 00:00:00 2001 From: Yusuf Olokoba Date: Tue, 29 Oct 2024 13:19:01 -0400 Subject: [PATCH 2/2] Mention our usage of `XcodeProj` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ff763a9e6..d5e4797f3 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ XcodeProj is a library written in Swift for parsing and working with Xcode proje | XcodeGen | [github.com/yonaskolb/XcodeGen](https://github.com/yonaskolb/XcodeGen) | | xspm | [gitlab.com/Pyroh/xspm](https://gitlab.com/Pyroh/xspm) | | Privacy Manifest| [github.com/stelabouras/privacy-manifest](https://github.com/stelabouras/privacy-manifest) | +| Function | [github.com/fxnai/fxnios](https://github.com/fxnai/fxnios) | If you are also leveraging XcodeProj in your project, feel free to open a PR to include it in the list above.