diff --git a/ReferencePackages/UpdatedPackage/Package.swift b/ReferencePackages/UpdatedPackage/Package.swift index 95768bb..2f90abe 100644 --- a/ReferencePackages/UpdatedPackage/Package.swift +++ b/ReferencePackages/UpdatedPackage/Package.swift @@ -17,6 +17,7 @@ let package = Package( // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "ReferencePackage") + name: "ReferencePackage" + ) ] ) diff --git a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift index c420728..6592a6e 100644 --- a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift +++ b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift @@ -328,6 +328,15 @@ private extension SwiftPackageFileAnalyzer { ) throws -> [Change] { guard oldTarget != newTarget else { return [] } + // MARK: Target Resources + + let oldResourcePaths = Set(oldTarget.resources?.map(\.path) ?? []) + let newResourcePaths = Set(newTarget.resources?.map(\.path) ?? []) + + let addedResourcePaths = newResourcePaths.subtracting(oldResourcePaths) + let consistentResourcePaths = oldResourcePaths.intersection(newResourcePaths) + let removedResourcePaths = oldResourcePaths.subtracting(newResourcePaths) + // MARK: Target Dependencies let oldTargetDependencies = Set(oldTarget.targetDependencies ?? []) @@ -335,7 +344,7 @@ private extension SwiftPackageFileAnalyzer { let addedTargetDependencies = newTargetDependencies.subtracting(oldTargetDependencies) let removedTargetDependencies = oldTargetDependencies.subtracting(newTargetDependencies) - + // MARK: Product Dependencies let oldProductDependencies = Set(oldTarget.productDependencies ?? []) @@ -344,7 +353,29 @@ private extension SwiftPackageFileAnalyzer { let addedProductDependencies = newProductDependencies.subtracting(oldProductDependencies) let removedProductDependencies = oldProductDependencies.subtracting(newProductDependencies) + // MARK: Compiling list of changes + var listOfChanges = [String]() + + listOfChanges += addedResourcePaths.compactMap { path in + guard let resource = newTarget.resources?.first(where: { $0.path == path }) else { return nil } + return "Added resource \(resource.description)" + } + + listOfChanges += consistentResourcePaths.compactMap { path in + guard + let newResource = newTarget.resources?.first(where: { $0.path == path }), + let oldResource = oldTarget.resources?.first(where: { $0.path == path }) + else { return nil } + + return "Changed resource from `\(oldResource.description)` to `\(newResource.description)`" + } + + listOfChanges += removedResourcePaths.compactMap { path in + guard let resource = oldTarget.resources?.first(where: { $0.path == path }) else { return nil } + return "Removed resource \(resource.description)" + } + listOfChanges += addedTargetDependencies.map { "Added dependency .target(name: \"\($0)\")" } listOfChanges += addedProductDependencies.map { "Added dependency .product(name: \"\($0)\", ...)" } diff --git a/Sources/Shared/Package/SwiftPackageFileHelperModule/SwiftPackageDescription.swift b/Sources/Shared/Package/SwiftPackageFileHelperModule/SwiftPackageDescription.swift index 5cb2992..194d562 100644 --- a/Sources/Shared/Package/SwiftPackageFileHelperModule/SwiftPackageDescription.swift +++ b/Sources/Shared/Package/SwiftPackageFileHelperModule/SwiftPackageDescription.swift @@ -74,7 +74,7 @@ package extension SwiftPackageDescription { struct Product: Codable, Equatable, Hashable { - // TODO: Add `rule` property + // TODO: Add `type` property package let name: String package let targets: [String] @@ -179,13 +179,14 @@ package extension SwiftPackageDescription { package let productDependencies: [String]? /// `.target(name: ...) dependency package let targetDependencies: [String]? - + /// The resources used by the Target + package let resources: [Resource]? + // Ignoring following properties for now as they are not handled in the `PackageAnalyzer` // and thus would produce changes that are not visible // // let productMemberships: [String]? // let sources: [String] - // let resources: [Resource]? init( name: String, @@ -193,7 +194,8 @@ package extension SwiftPackageDescription { path: String, moduleType: ModuleType, productDependencies: [String]? = nil, - targetDependencies: [String]? = nil + targetDependencies: [String]? = nil, + resources: [Resource]? = nil ) { self.name = name self.type = type @@ -201,6 +203,7 @@ package extension SwiftPackageDescription { self.moduleType = moduleType self.productDependencies = productDependencies self.targetDependencies = targetDependencies + self.resources = resources } enum CodingKeys: String, CodingKey { @@ -210,6 +213,7 @@ package extension SwiftPackageDescription { case targetDependencies = "target_dependencies" case type case path + case resources } } } @@ -257,9 +261,78 @@ extension SwiftPackageDescription.Target: CustomStringConvertible { package extension SwiftPackageDescription.Target { struct Resource: Codable, Equatable { + package let path: String + package let rule: Rule + } +} - // TODO: Add `rule` property +extension SwiftPackageDescription.Target.Resource: CustomStringConvertible { - package let path: String + package var description: String { + return switch rule { + case .copy: ".copy(\"\(path)\")" + case .embeddInCode: ".embeddInCode(\"\(path)\")" + case let .process(metadata): + if let localization = metadata["localization"] { + ".process(\"\(path)\", localization: \"\(localization)\")" + } else { + ".process(\"\(path)\")" + } + } + } +} + + +package extension SwiftPackageDescription.Target.Resource { + + enum Rule: Codable, Equatable { + case copy + case embeddInCode + case process([String: String]) + + package init(from decoder: any Decoder) throws { + + enum RuleError: Error { + case unsupportedRule + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + + if (try? container.decode([String: String].self, forKey: .copy)) != nil { + self = .copy + return + } + + if (try? container.decode([String: String].self, forKey: .embeddInCode)) != nil { + self = .embeddInCode + return + } + + if let metadata = try? container.decode([String: String].self, forKey: .process) { + self = .process(metadata) + return + } + + throw RuleError.unsupportedRule + } + + package func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .copy: + try container.encode([:] as [String: String], forKey: .copy) + case .embeddInCode: + try container.encode([:] as [String: String], forKey: .embeddInCode) + case let .process(metadata): + try container.encode(metadata, forKey: .process) + } + } + + enum CodingKeys: String, CodingKey { + case copy + case embeddInCode = "embed_in_code" + case process + } } } diff --git a/Sources/Shared/Public/PADCore/Change.swift b/Sources/Shared/Public/PADCore/Change.swift index ebe4e1a..36c7135 100644 --- a/Sources/Shared/Public/PADCore/Change.swift +++ b/Sources/Shared/Public/PADCore/Change.swift @@ -26,7 +26,7 @@ public struct Change: Equatable { ) { self.changeType = changeType self.parentPath = parentPath - self.listOfChanges = listOfChanges + self.listOfChanges = listOfChanges.sorted() } } diff --git a/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift b/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift index 0f4f519..ee3a4e1 100644 --- a/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift +++ b/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift @@ -84,7 +84,12 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { path: "some/new/path", moduleType: .swiftTarget, productDependencies: ["Some Product Dependency", "New Product Dependency"], - targetDependencies: ["Some Target Dependency", "New Target Dependency"] + targetDependencies: ["Some Target Dependency", "New Target Dependency"], + resources: [ + .init(path: "copy-path", rule: .copy), + .init(path: "process-path", rule: .process([:])), + .init(path: "process-localization-path", rule: .process(["localization":"en_US"])), + ] ) ], toolsVersion: "1.0" @@ -106,7 +111,13 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { path: "some/old/path", moduleType: .swiftTarget, productDependencies: ["Some Product Dependency", "Old Product Dependency"], - targetDependencies: ["Some Target Dependency", "Old Target Dependency"] + targetDependencies: ["Some Target Dependency", "Old Target Dependency"], + resources: [ + .init(path: "new-copy-path", rule: .copy), + .init(path: "process-path", rule: .process(["localization":"en_US"])), + .init(path: "process-localization-path", rule: .process([:])), + .init(path: "embedd-in-code-path", rule: .embeddInCode), + ] ) ], toolsVersion: "2.0" @@ -204,13 +215,18 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { ), parentPath: ".targets", listOfChanges: [ + "Added resource .copy(\"copy-path\")", + "Changed resource from `.process(\"process-path\", localization: \"en_US\")` to `.process(\"process-path\")`", + "Changed resource from `.process(\"process-localization-path\")` to `.process(\"process-localization-path\", localization: \"en_US\")`", + "Removed resource .copy(\"new-copy-path\")", + "Removed resource .embeddInCode(\"embedd-in-code-path\")", "Added dependency .target(name: \"New Target Dependency\")", "Added dependency .product(name: \"New Product Dependency\", ...)", "Changed path from \"some/old/path\" to \"some/new/path\"", "Changed type from `.binaryTarget` to `.target`", "Removed dependency .target(name: \"Old Target Dependency\")", "Removed dependency .product(name: \"Old Product Dependency\", ...)" - ] + ].sorted() ), .init( changeType: .removal( @@ -220,7 +236,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ) ] - + XCTAssertEqual(changes.changes, expectedChanges) waitForExpectations(timeout: 1)