diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXCopyFilesBuildPhase.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXCopyFilesBuildPhase.swift index 0fa51e1cf..58f8817bf 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXCopyFilesBuildPhase.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXCopyFilesBuildPhase.swift @@ -16,6 +16,67 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { case other } + public enum DstSubfolder: Equatable, Decodable { + case absolutePath + case productsDirectory + case wrapper + case executables + case resources + case javaResources + case frameworks + case sharedFrameworks + case sharedSupport + case plugins + case other + case product + case none + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "AbsolutePath": self = .absolutePath + case "ProductsDirectory": self = .productsDirectory + case "Wrapper": self = .wrapper + case "Executables": self = .executables + case "Resources": self = .resources + case "JavaResources": self = .javaResources + case "Frameworks": self = .frameworks + case "SharedFrameworks": self = .sharedFrameworks + case "SharedSupport": self = .sharedSupport + case "PlugIns": self = .plugins + case "Other": self = .other + case "Product": self = .product + case "None": self = .none + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .absolutePath: "AbsolutePath" + case .productsDirectory: "ProductsDirectory" + case .wrapper: "Wrapper" + case .executables: "Executables" + case .resources: "Resources" + case .javaResources: "JavaResources" + case .frameworks: "Frameworks" + case .sharedFrameworks: "SharedFrameworks" + case .sharedSupport: "SharedSupport" + case .plugins: "PlugIns" + case .other: "Other" + case .product: "Product" + case .none: "None" + case let .unknown(rawValue): rawValue + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(String.self) + self = .init(rawValue: rawValue) + } + } + // MARK: - Attributes /// Element destination path @@ -24,6 +85,8 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { /// Element destination subfolder spec public var dstSubfolderSpec: SubFolder? + public var dstSubfolder: DstSubfolder? + /// Copy files build phase name public var name: String? @@ -38,17 +101,20 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { /// - Parameters: /// - dstPath: Destination path. /// - dstSubfolderSpec: Destination subfolder spec. + /// - dstSubfolder: Destination subfolder. /// - buildActionMask: Build action mask. /// - files: Build files to copy. /// - runOnlyForDeploymentPostprocessing: Run only for deployment post processing. public init(dstPath: String? = nil, dstSubfolderSpec: SubFolder? = nil, + dstSubfolder: DstSubfolder? = nil, name: String? = nil, buildActionMask: UInt = defaultBuildActionMask, files: [PBXBuildFile] = [], runOnlyForDeploymentPostprocessing: Bool = false) { self.dstPath = dstPath self.dstSubfolderSpec = dstSubfolderSpec + self.dstSubfolder = dstSubfolder self.name = name super.init(files: files, buildActionMask: buildActionMask, @@ -61,6 +127,7 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { fileprivate enum CodingKeys: String, CodingKey { case dstPath case dstSubfolderSpec + case dstSubfolder case name } @@ -68,6 +135,7 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { let container = try decoder.container(keyedBy: CodingKeys.self) dstPath = try container.decodeIfPresent(.dstPath) dstSubfolderSpec = try container.decodeIntIfPresent(.dstSubfolderSpec).flatMap(SubFolder.init) + dstSubfolder = try container.decodeIfPresent(.dstSubfolder) name = try container.decodeIfPresent(.name) try super.init(from: decoder) } @@ -93,6 +161,9 @@ extension PBXCopyFilesBuildPhase: PlistSerializable { if let dstSubfolderSpec { dictionary["dstSubfolderSpec"] = .string(CommentedString("\(dstSubfolderSpec.rawValue)")) } + if let dstSubfolder { + dictionary["dstSubfolder"] = .string(CommentedString("\(dstSubfolder.rawValue)")) + } return (key: CommentedString(reference, comment: name ?? "CopyFiles"), value: .dictionary(dictionary)) } } diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index cd69699a9..f7c86b149 100644 --- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift +++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift @@ -75,6 +75,7 @@ extension PBXCopyFilesBuildPhase { func isEqual(to rhs: PBXCopyFilesBuildPhase) -> Bool { if dstPath != rhs.dstPath { return false } if dstSubfolderSpec != rhs.dstSubfolderSpec { return false } + if dstSubfolder != rhs.dstSubfolder { return false } if name != rhs.name { return false } return super.isEqual(to: rhs) } diff --git a/Tests/XcodeProjTests/Objects/BuildPhase/PBXCopyFilesBuildPhaseTests.swift b/Tests/XcodeProjTests/Objects/BuildPhase/PBXCopyFilesBuildPhaseTests.swift index 9d1d6c506..e809f2952 100644 --- a/Tests/XcodeProjTests/Objects/BuildPhase/PBXCopyFilesBuildPhaseTests.swift +++ b/Tests/XcodeProjTests/Objects/BuildPhase/PBXCopyFilesBuildPhaseTests.swift @@ -76,6 +76,29 @@ final class PBXCopyFilesBuildPhaseTests: XCTestCase { } catch {} } + func test_init_decodesDstSubfolder() { + var dictionary = testDictionary() + dictionary["dstSubfolder"] = "Frameworks" + let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) + let decoder = XcodeprojJSONDecoder() + do { + let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) + XCTAssertEqual(phase.dstSubfolder, .frameworks) + } catch {} + } + + func test_init_decodesUnknownDstSubfolder() { + var dictionary = testDictionary() + dictionary["dstSubfolder"] = "InvalidSubfolder" + let data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) + let decoder = XcodeprojJSONDecoder() + do { + let phase = try decoder.decode(PBXCopyFilesBuildPhase.self, from: data) + XCTAssertEqual(phase.dstSubfolder, .unknown("InvalidSubfolder")) + XCTAssertEqual(phase.dstSubfolder?.rawValue, "InvalidSubfolder") + } catch {} + } + func test_init_fails_whenFilesIsMissing() { var dictionary = testDictionary() dictionary.removeValue(forKey: "files") @@ -102,6 +125,26 @@ final class PBXCopyFilesBuildPhaseTests: XCTestCase { XCTAssertEqual(PBXCopyFilesBuildPhase.isa, "PBXCopyFilesBuildPhase") } + func test_equal_whenDstSubfolderIsDifferent_returnsFalse() { + let lhs = PBXCopyFilesBuildPhase(dstPath: "dstPath", + dstSubfolderSpec: .frameworks, + dstSubfolder: .frameworks, + name: "Copy") + let rhs = PBXCopyFilesBuildPhase(dstPath: "dstPath", + dstSubfolderSpec: .frameworks, + dstSubfolder: .resources, + name: "Copy") + XCTAssertNotEqual(lhs, rhs) + } + + func test_write_preservesUnknownDstSubfolderRawValue() throws { + let subject = PBXCopyFilesBuildPhase(dstSubfolder: .unknown("InvalidSubfolder")) + let proj = PBXProj.fixture() + let (_, plistValue) = try subject.plistKeyAndValue(proj: proj, reference: "ref") + + XCTAssertEqual(plistValue.dictionary?["dstSubfolder"]?.string, "InvalidSubfolder") + } + func testDictionary() -> [String: Any] { [ "dstPath": "dstPath",