Skip to content

Commit

Permalink
Merge pull request #95 from Adyen/target-rule-diff
Browse files Browse the repository at this point in the history
Adding Swift Package Target Resource diffing
  • Loading branch information
goergisn authored Jan 24, 2025
2 parents 5902cd4 + 735f026 commit 1b3586d
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 13 deletions.
3 changes: 2 additions & 1 deletion ReferencePackages/UpdatedPackage/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,23 @@ 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 ?? [])
let newTargetDependencies = Set(newTarget.targetDependencies ?? [])

let addedTargetDependencies = newTargetDependencies.subtracting(oldTargetDependencies)
let removedTargetDependencies = oldTargetDependencies.subtracting(newTargetDependencies)

// MARK: Product Dependencies

let oldProductDependencies = Set(oldTarget.productDependencies ?? [])
Expand All @@ -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)\", ...)" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -179,28 +179,31 @@ 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,
type: TargetType,
path: String,
moduleType: ModuleType,
productDependencies: [String]? = nil,
targetDependencies: [String]? = nil
targetDependencies: [String]? = nil,
resources: [Resource]? = nil
) {
self.name = name
self.type = type
self.path = path
self.moduleType = moduleType
self.productDependencies = productDependencies
self.targetDependencies = targetDependencies
self.resources = resources
}

enum CodingKeys: String, CodingKey {
Expand All @@ -210,6 +213,7 @@ package extension SwiftPackageDescription {
case targetDependencies = "target_dependencies"
case type
case path
case resources
}
}
}
Expand Down Expand Up @@ -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
}
}
}
2 changes: 1 addition & 1 deletion Sources/Shared/Public/PADCore/Change.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct Change: Equatable {
) {
self.changeType = changeType
self.parentPath = parentPath
self.listOfChanges = listOfChanges
self.listOfChanges = listOfChanges.sorted()
}
}

Expand Down
24 changes: 20 additions & 4 deletions Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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(
Expand All @@ -220,7 +236,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase {
listOfChanges: []
)
]

XCTAssertEqual(changes.changes, expectedChanges)

waitForExpectations(timeout: 1)
Expand Down

0 comments on commit 1b3586d

Please sign in to comment.