diff --git a/Sources/XcodeProj/Objects/SwiftPackage/XCSwiftPackageProductDependency.swift b/Sources/XcodeProj/Objects/SwiftPackage/XCSwiftPackageProductDependency.swift index f7ae0d4c6..238e31f9a 100644 --- a/Sources/XcodeProj/Objects/SwiftPackage/XCSwiftPackageProductDependency.swift +++ b/Sources/XcodeProj/Objects/SwiftPackage/XCSwiftPackageProductDependency.swift @@ -18,12 +18,17 @@ public class XCSwiftPackageProductDependency: PBXContainerItem, PlistSerializabl } } + /// Is it a Plugin. + var isPlugin: Bool + // MARK: - Init public init(productName: String, - package: XCRemoteSwiftPackageReference? = nil) { + package: XCRemoteSwiftPackageReference? = nil, + isPlugin: Bool = false) { self.productName = productName packageReference = package?.reference + self.isPlugin = isPlugin super.init() } @@ -31,7 +36,16 @@ public class XCSwiftPackageProductDependency: PBXContainerItem, PlistSerializabl let objects = decoder.context.objects let repository = decoder.context.objectReferenceRepository let container = try decoder.container(keyedBy: CodingKeys.self) - productName = try container.decode(String.self, forKey: .productName) + let rawProductName = try container.decode(String.self, forKey: .productName) + let pluginPrefix = "plugin:" + if rawProductName.hasPrefix(pluginPrefix) { + productName = String(rawProductName.dropFirst(pluginPrefix.count)) + isPlugin = true + } else { + productName = rawProductName + isPlugin = false + } + if let packageString: String = try container.decodeIfPresent(.package) { packageReference = repository.getOrCreate(reference: packageString, objects: objects) } else { @@ -46,7 +60,11 @@ public class XCSwiftPackageProductDependency: PBXContainerItem, PlistSerializabl if let package = package { dictionary["package"] = .string(.init(package.reference.value, comment: "XCRemoteSwiftPackageReference \"\(package.name ?? "")\"")) } - dictionary["productName"] = .string(.init(productName)) + if isPlugin { + dictionary["productName"] = .string(.init("plugin:" + productName)) + } else { + dictionary["productName"] = .string(.init(productName)) + } return (key: CommentedString(reference, comment: productName), value: .dictionary(dictionary)) diff --git a/Sources/XcodeProj/Utils/ReferenceGenerator.swift b/Sources/XcodeProj/Utils/ReferenceGenerator.swift index 2336d82b6..f3e8946ef 100644 --- a/Sources/XcodeProj/Utils/ReferenceGenerator.swift +++ b/Sources/XcodeProj/Utils/ReferenceGenerator.swift @@ -99,6 +99,15 @@ final class ReferenceGenerator: ReferenceGenerating { fixReference(for: $0, identifiers: identifiers) } + // Build Tool Plug-ins + target.dependencies.forEach { + guard let product = $0.product, product.isPlugin else { return } + + var identifiers = identifiers + identifiers.append(product.productName) + fixReference(for: product, identifiers: identifiers) + } + fixReference(for: target, identifiers: identifiers) } } diff --git a/Tests/XcodeProjTests/Objects/SwiftPackage/XCSwiftPackageProductDependencyTests.swift b/Tests/XcodeProjTests/Objects/SwiftPackage/XCSwiftPackageProductDependencyTests.swift index 475712a2b..a473311a1 100644 --- a/Tests/XcodeProjTests/Objects/SwiftPackage/XCSwiftPackageProductDependencyTests.swift +++ b/Tests/XcodeProjTests/Objects/SwiftPackage/XCSwiftPackageProductDependencyTests.swift @@ -19,6 +19,25 @@ final class XCSwiftPackageProductDependencyTests: XCTestCase { // Then XCTAssertEqual(got.productName, "xcodeproj") XCTAssertEqual(got.packageReference?.value, "packageReference") + XCTAssertEqual(got.isPlugin, false) + } + + func test_initAsPlugin() throws { + // Given + let decoder = XcodeprojPropertyListDecoder() + let encoder = PropertyListEncoder() + let plist = ["reference": "reference", + "productName": "plugin:xcodeproj", + "package": "packageReference"] + let data = try encoder.encode(plist) + + // When + let got = try decoder.decode(XCSwiftPackageProductDependency.self, from: data) + + // Then + XCTAssertEqual(got.productName, "xcodeproj") + XCTAssertEqual(got.packageReference?.value, "packageReference") + XCTAssertEqual(got.isPlugin, true) } func test_plistValues() throws { @@ -39,6 +58,25 @@ final class XCSwiftPackageProductDependencyTests: XCTestCase { ])) } + func test_plistValuesAsPlugin() throws { + // Given + let proj = PBXProj() + let package = XCRemoteSwiftPackageReference(repositoryURL: "repository") + let subject = XCSwiftPackageProductDependency(productName: "product", + package: package, + isPlugin: true) + + // When + let got = try subject.plistKeyAndValue(proj: proj, reference: "reference") + + // Then + XCTAssertEqual(got.value, .dictionary([ + "isa": "XCSwiftPackageProductDependency", + "productName": "plugin:product", + "package": .string(.init(package.reference.value, comment: "XCRemoteSwiftPackageReference \"\(package.name ?? "")\"")), + ])) + } + func test_equal() { // Given let package = XCRemoteSwiftPackageReference(repositoryURL: "repository") @@ -50,4 +88,20 @@ final class XCSwiftPackageProductDependencyTests: XCTestCase { // Then XCTAssertEqual(first, second) } + + func test_isPlugin() { + // Given + let plugin = XCSwiftPackageProductDependency(productName: "product", isPlugin: true) + + // Then + XCTAssertTrue(plugin.isPlugin) + } + + func test_isNotPlugin() { + // Given + let plugin = XCSwiftPackageProductDependency(productName: "product") + + // Then + XCTAssertFalse(plugin.isPlugin) + } } diff --git a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift index 988d2f106..08a3f9760 100644 --- a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift +++ b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift @@ -11,9 +11,13 @@ class ReferenceGeneratorTests: XCTestCase { let containerItemProxy = project.makeContainerItemProxy(fileReference: remoteProjectFileReference) let productReferenceProxy = project.makeReferenceProxy(containerItemProxy: containerItemProxy) let productsGroup = project.makeProductsGroup(children: [productReferenceProxy]) + let pluginDependency = project.makePluginDependency() + let (target, _) = project.makeTarget(productReferenceProxy: productReferenceProxy) + target.dependencies.append(pluginDependency) pbxProject.projectReferences.append(["ProductGroup": productsGroup.reference, "ProjectRef": remoteProjectFileReference.reference]) + pbxProject.targets.append(target) let referenceGenerator = ReferenceGenerator(outputSettings: PBXOutputSettings()) try referenceGenerator.generateReferences(proj: project) @@ -22,6 +26,7 @@ class ReferenceGeneratorTests: XCTestCase { XCTAssert(!containerItemProxy.reference.temporary) XCTAssert(!productReferenceProxy.reference.temporary) XCTAssert(!remoteProjectFileReference.reference.temporary) + XCTAssert(!pluginDependency.productReference!.temporary) } func test_projectReferencingRemoteXcodeprojBundle_generatesDeterministicIdentifiers() throws { @@ -32,7 +37,9 @@ class ReferenceGeneratorTests: XCTestCase { let containerItemProxy = project.makeContainerItemProxy(fileReference: remoteProjectFileReference) let productReferenceProxy = project.makeReferenceProxy(containerItemProxy: containerItemProxy) let productsGroup = project.makeProductsGroup(children: [productReferenceProxy]) + let pluginDependency = project.makePluginDependency() let (target, buildFile) = project.makeTarget(productReferenceProxy: productReferenceProxy) + target.dependencies.append(pluginDependency) pbxProject.projectReferences.append(["ProductGroup": productsGroup.reference, "ProjectRef": remoteProjectFileReference.reference]) @@ -41,7 +48,7 @@ class ReferenceGeneratorTests: XCTestCase { let referenceGenerator = ReferenceGenerator(outputSettings: PBXOutputSettings()) try referenceGenerator.generateReferences(proj: project) - return [remoteProjectFileReference, containerItemProxy, productReferenceProxy, productsGroup, buildFile] + return [remoteProjectFileReference, containerItemProxy, productReferenceProxy, productsGroup, buildFile, pluginDependency.productReference!.getObject()!] .map { $0.reference.value } } @@ -132,6 +139,15 @@ private extension PBXProj { return productsGroup } + func makePluginDependency() -> PBXTargetDependency { + let packageReference = XCRemoteSwiftPackageReference(repositoryURL: "repository") + let packageDependency = XCSwiftPackageProductDependency(productName: "product", package: packageReference, isPlugin: true) + let targetDependency = PBXTargetDependency(product: packageDependency) + add(object: targetDependency.productReference!.getObject()!) + + return targetDependency + } + func makeTarget(productReferenceProxy: PBXReferenceProxy) -> (target: PBXTarget, buildFile: PBXBuildFile) { let buildFile = PBXBuildFile(file: productReferenceProxy) add(object: buildFile)