Skip to content

Commit 8bedc5d

Browse files
committed
refactored SwiftPackageFileAnalyzer
1 parent c9c48ba commit 8bedc5d

File tree

4 files changed

+195
-151
lines changed

4 files changed

+195
-151
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//
2+
// SwiftPackageFileAnalyzer+Targets.swift
3+
// public-api-diff
4+
//
5+
// Created by Alexander Guretzki on 10/02/2025.
6+
//
7+
8+
import Foundation
9+
10+
import PADCore
11+
import PADLogging
12+
13+
import FileHandlingModule
14+
import ShellModule
15+
import SwiftPackageFileHelperModule
16+
17+
extension SwiftPackageFileAnalyzer {
18+
19+
internal func analyzeTargets(
20+
old: [SwiftPackageDescription.Target],
21+
new: [SwiftPackageDescription.Target],
22+
oldProjectBasePath: String,
23+
newProjectBasePath: String
24+
) throws -> [Change] {
25+
guard old != new else { return [] }
26+
27+
let oldTargetNames = Set(old.map(\.name))
28+
let newTargetNames = Set(new.map(\.name))
29+
30+
let added = newTargetNames.subtracting(oldTargetNames)
31+
let removed = oldTargetNames.subtracting(newTargetNames)
32+
let consistent = Set(oldTargetNames).intersection(Set(newTargetNames))
33+
34+
var changes = [Change]()
35+
36+
changes += added.compactMap { addition in
37+
guard let addedTarget = new.first(where: { $0.name == addition }) else { return nil }
38+
return .init(
39+
changeType: .addition(description: addedTarget.description),
40+
parentPath: Constants.packageFileName(child: "targets")
41+
)
42+
}
43+
44+
try consistent.forEach { productName in
45+
guard
46+
let oldTarget = old.first(where: { $0.name == productName }),
47+
let newTarget = new.first(where: { $0.name == productName })
48+
else { return }
49+
50+
changes += try analyzeTarget(
51+
oldTarget: oldTarget,
52+
newTarget: newTarget,
53+
oldProjectBasePath: oldProjectBasePath,
54+
newProjectBasePath: newProjectBasePath
55+
)
56+
}
57+
58+
changes += removed.compactMap { removal in
59+
guard let removedTarget = old.first(where: { $0.name == removal }) else { return nil }
60+
return .init(
61+
changeType: .removal(description: removedTarget.description),
62+
parentPath: Constants.packageFileName(child: "targets")
63+
)
64+
}
65+
66+
return changes
67+
}
68+
69+
private func analyzeTarget(
70+
oldTarget: SwiftPackageDescription.Target,
71+
newTarget: SwiftPackageDescription.Target,
72+
oldProjectBasePath: String,
73+
newProjectBasePath: String
74+
) throws -> [Change] {
75+
guard oldTarget != newTarget else { return [] }
76+
77+
var listOfChanges = analyzeDependencies(
78+
oldTarget: oldTarget,
79+
newTarget: newTarget
80+
)
81+
82+
listOfChanges += try analyzeTargetResources(
83+
oldResources: oldTarget.resources ?? [],
84+
newResources: newTarget.resources ?? [],
85+
oldProjectBasePath: oldProjectBasePath,
86+
newProjectBasePath: newProjectBasePath
87+
)
88+
89+
if oldTarget.path != newTarget.path {
90+
listOfChanges += ["Changed path from \"\(oldTarget.path)\" to \"\(newTarget.path)\""]
91+
}
92+
93+
if oldTarget.type != newTarget.type {
94+
listOfChanges += ["Changed type from `.\(oldTarget.type.description)` to `.\(newTarget.type.description)`"]
95+
}
96+
97+
guard oldTarget.description != newTarget.description || !listOfChanges.isEmpty else { return [] }
98+
99+
return [.init(
100+
changeType: .modification(
101+
oldDescription: oldTarget.description,
102+
newDescription: newTarget.description
103+
),
104+
parentPath: Constants.packageFileName(child: "targets"),
105+
listOfChanges: listOfChanges
106+
)]
107+
108+
}
109+
}
110+
111+
// MARK: - SwiftPackageDescription.Target.Resource
112+
113+
private extension SwiftPackageFileAnalyzer {
114+
115+
func analyzeDependencies(
116+
oldTarget: SwiftPackageDescription.Target,
117+
newTarget: SwiftPackageDescription.Target
118+
) -> [String] {
119+
120+
let oldTargetDependencies = Set(oldTarget.targetDependencies ?? [])
121+
let newTargetDependencies = Set(newTarget.targetDependencies ?? [])
122+
123+
let addedTargetDependencies = newTargetDependencies.subtracting(oldTargetDependencies)
124+
let removedTargetDependencies = oldTargetDependencies.subtracting(newTargetDependencies)
125+
126+
let oldProductDependencies = Set(oldTarget.productDependencies ?? [])
127+
let newProductDependencies = Set(newTarget.productDependencies ?? [])
128+
129+
let addedProductDependencies = newProductDependencies.subtracting(oldProductDependencies)
130+
let removedProductDependencies = oldProductDependencies.subtracting(newProductDependencies)
131+
132+
var listOfChanges = [String]()
133+
listOfChanges += addedTargetDependencies.map { "Added dependency .target(name: \"\($0)\")" }
134+
listOfChanges += addedProductDependencies.map { "Added dependency .product(name: \"\($0)\", ...)" }
135+
listOfChanges += removedTargetDependencies.map { "Removed dependency .target(name: \"\($0)\")" }
136+
listOfChanges += removedProductDependencies.map { "Removed dependency .product(name: \"\($0)\", ...)" }
137+
return listOfChanges
138+
}
139+
140+
func analyzeTargetResources(
141+
oldResources: [SwiftPackageDescription.Target.Resource],
142+
newResources: [SwiftPackageDescription.Target.Resource],
143+
oldProjectBasePath: String,
144+
newProjectBasePath: String
145+
) throws -> [String] {
146+
147+
logger?.log("Old project base path \(oldProjectBasePath)", from: String(describing: Self.self))
148+
logger?.log("New project base path \(newProjectBasePath)", from: String(describing: Self.self))
149+
150+
let oldResourcePaths = Set(oldResources.map(\.path).map { $0.trimmingPrefix(oldProjectBasePath) })
151+
let newResourcePaths = Set(newResources.map(\.path).map { $0.trimmingPrefix(newProjectBasePath) })
152+
153+
let addedResourcePaths = newResourcePaths.subtracting(oldResourcePaths)
154+
let consistentResourcePaths = oldResourcePaths.intersection(newResourcePaths)
155+
let removedResourcePaths = oldResourcePaths.subtracting(newResourcePaths)
156+
157+
var listOfChanges = [String]()
158+
159+
listOfChanges += addedResourcePaths.compactMap { path in
160+
guard let resource = newResources.first(where: { $0.path.trimmingPrefix(newProjectBasePath) == path }) else { return nil }
161+
return "Added resource \(resource.description)"
162+
}
163+
164+
listOfChanges += consistentResourcePaths.compactMap { path in
165+
guard
166+
let newResource = newResources.first(where: { $0.path.trimmingPrefix(newProjectBasePath) == path }),
167+
let oldResource = oldResources.first(where: { $0.path.trimmingPrefix(oldProjectBasePath) == path }),
168+
newResource.description != oldResource.description
169+
else { return nil }
170+
171+
return "Changed resource from `\(oldResource.description)` to `\(newResource.description)`"
172+
}
173+
174+
listOfChanges += removedResourcePaths.compactMap { path in
175+
guard let resource = oldResources.first(where: { $0.path.trimmingPrefix(oldProjectBasePath) == path }) else { return nil }
176+
return "Removed resource \(resource.description)"
177+
}
178+
179+
return listOfChanges
180+
}
181+
}
182+
183+
// MARK: - Convenience Extension
184+
185+
private extension String {
186+
func trimmingPrefix(_ prefix: String) -> String {
187+
var trimmed = self
188+
trimmed.trimPrefix(prefix)
189+
return trimmed
190+
}
191+
}

Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift

Lines changed: 2 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public struct SwiftPackageFileAnalyzer: SwiftPackageFileAnalyzing {
1818

1919
private let fileHandler: any FileHandling
2020
private let shell: any ShellHandling
21-
private let logger: (any Logging)?
21+
internal let logger: (any Logging)?
2222

23-
private enum Constants {
23+
internal enum Constants {
2424
static let packageFileName = "Package.swift"
2525
static func packageFileName(child: String) -> String {
2626
".\(child)"
@@ -280,145 +280,6 @@ private extension SwiftPackageFileAnalyzer {
280280
)]
281281
}
282282

283-
// MARK: - Targets
284-
285-
private func analyzeTargets(
286-
old: [SwiftPackageDescription.Target],
287-
new: [SwiftPackageDescription.Target],
288-
oldProjectBasePath: String,
289-
newProjectBasePath: String
290-
) throws -> [Change] {
291-
guard old != new else { return [] }
292-
293-
let oldTargetNames = Set(old.map(\.name))
294-
let newTargetNames = Set(new.map(\.name))
295-
296-
let added = newTargetNames.subtracting(oldTargetNames)
297-
let removed = oldTargetNames.subtracting(newTargetNames)
298-
let consistent = Set(oldTargetNames).intersection(Set(newTargetNames))
299-
300-
var changes = [Change]()
301-
302-
changes += added.compactMap { addition in
303-
guard let addedTarget = new.first(where: { $0.name == addition }) else { return nil }
304-
return .init(
305-
changeType: .addition(description: addedTarget.description),
306-
parentPath: Constants.packageFileName(child: "targets")
307-
)
308-
}
309-
310-
try consistent.forEach { productName in
311-
guard
312-
let oldTarget = old.first(where: { $0.name == productName }),
313-
let newTarget = new.first(where: { $0.name == productName })
314-
else { return }
315-
316-
changes += try analyzeTarget(
317-
oldTarget: oldTarget,
318-
newTarget: newTarget,
319-
oldProjectBasePath: oldProjectBasePath,
320-
newProjectBasePath: newProjectBasePath
321-
)
322-
}
323-
324-
changes += removed.compactMap { removal in
325-
guard let removedTarget = old.first(where: { $0.name == removal }) else { return nil }
326-
return .init(
327-
changeType: .removal(description: removedTarget.description),
328-
parentPath: Constants.packageFileName(child: "targets")
329-
)
330-
}
331-
332-
return changes
333-
}
334-
335-
private func analyzeTarget(
336-
oldTarget: SwiftPackageDescription.Target,
337-
newTarget: SwiftPackageDescription.Target,
338-
oldProjectBasePath: String,
339-
newProjectBasePath: String
340-
) throws -> [Change] {
341-
guard oldTarget != newTarget else { return [] }
342-
343-
// MARK: Target Resources
344-
345-
logger?.log("Old project base path \(oldProjectBasePath)", from: String(describing: Self.self))
346-
logger?.log("New project base path \(newProjectBasePath)", from: String(describing: Self.self))
347-
348-
let oldResourcePaths = Set((oldTarget.resources?.map(\.path) ?? []).map { $0.trimmingPrefix(oldProjectBasePath) })
349-
let newResourcePaths = Set((newTarget.resources?.map(\.path) ?? []).map { $0.trimmingPrefix(newProjectBasePath) })
350-
351-
let addedResourcePaths = newResourcePaths.subtracting(oldResourcePaths)
352-
let consistentResourcePaths = oldResourcePaths.intersection(newResourcePaths)
353-
let removedResourcePaths = oldResourcePaths.subtracting(newResourcePaths)
354-
355-
// MARK: Target Dependencies
356-
357-
let oldTargetDependencies = Set(oldTarget.targetDependencies ?? [])
358-
let newTargetDependencies = Set(newTarget.targetDependencies ?? [])
359-
360-
let addedTargetDependencies = newTargetDependencies.subtracting(oldTargetDependencies)
361-
let removedTargetDependencies = oldTargetDependencies.subtracting(newTargetDependencies)
362-
363-
// MARK: Product Dependencies
364-
365-
let oldProductDependencies = Set(oldTarget.productDependencies ?? [])
366-
let newProductDependencies = Set(newTarget.productDependencies ?? [])
367-
368-
let addedProductDependencies = newProductDependencies.subtracting(oldProductDependencies)
369-
let removedProductDependencies = oldProductDependencies.subtracting(newProductDependencies)
370-
371-
// MARK: Compiling list of changes
372-
373-
var listOfChanges = [String]()
374-
375-
listOfChanges += addedResourcePaths.compactMap { path in
376-
guard let resource = newTarget.resources?.first(where: { $0.path == path }) else { return nil }
377-
return "Added resource \(resource.description)"
378-
}
379-
380-
listOfChanges += consistentResourcePaths.compactMap { path in
381-
guard
382-
let newResource = newTarget.resources?.first(where: { $0.path == path }),
383-
let oldResource = oldTarget.resources?.first(where: { $0.path == path })
384-
else { return nil }
385-
386-
return "Changed resource from `\(oldResource.description)` to `\(newResource.description)`"
387-
}
388-
389-
listOfChanges += removedResourcePaths.compactMap { path in
390-
guard let resource = oldTarget.resources?.first(where: { $0.path == path }) else { return nil }
391-
return "Removed resource \(resource.description)"
392-
}
393-
394-
listOfChanges += addedTargetDependencies.map { "Added dependency .target(name: \"\($0)\")" }
395-
listOfChanges += addedProductDependencies.map { "Added dependency .product(name: \"\($0)\", ...)" }
396-
397-
if oldTarget.path != newTarget.path {
398-
listOfChanges += ["Changed path from \"\(oldTarget.path)\" to \"\(newTarget.path)\""]
399-
}
400-
401-
if oldTarget.type != newTarget.type {
402-
listOfChanges += ["Changed type from `.\(oldTarget.type.description)` to `.\(newTarget.type.description)`"]
403-
}
404-
405-
listOfChanges += removedTargetDependencies.map { "Removed dependency .target(name: \"\($0)\")" }
406-
listOfChanges += removedProductDependencies.map { "Removed dependency .product(name: \"\($0)\", ...)" }
407-
408-
409-
guard oldTarget.description != newTarget.description || !listOfChanges.isEmpty else { return [] }
410-
411-
return [.init(
412-
changeType: .modification(
413-
oldDescription: oldTarget.description,
414-
newDescription: newTarget.description
415-
),
416-
parentPath: Constants.packageFileName(child: "targets"),
417-
listOfChanges: listOfChanges
418-
)]
419-
420-
}
421-
422283
// MARK: - Dependencies
423284

424285
private func analyzeDependencies(
@@ -500,11 +361,3 @@ private extension SwiftPackageFileAnalyzer {
500361
)]
501362
}
502363
}
503-
504-
private extension String {
505-
func trimmingPrefix(_ prefix: String) -> String {
506-
var trimmed = self
507-
trimmed.trimPrefix(prefix)
508-
return trimmed
509-
}
510-
}

Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ struct XcodeTools {
8888
let result = shell.execute(command)
8989
let derivedDataPath = "\(projectDirectoryPath)/\(Constants.derivedDataPath)"
9090

91-
logger?.debug(result, from: String(describing: Self.self))
91+
//logger?.debug(result, from: String(describing: Self.self))
9292

9393
// It might be that the archive failed but the .swiftinterface files are still created
9494
// so we have to check outside if they exist.

Tests/UnitTests/XcodeToolsTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private extension XcodeToolsTests {
101101
("📦 Archiving SCHEME from PROJECT_DIRECTORY_PATH", "XcodeTools")
102102
]
103103
var expectedHandleDebugCalls: [(message: String, subsystem: String)] = [
104-
(archiveResult, "XcodeTools")
104+
//(archiveResult, "XcodeTools")
105105
]
106106
var expectedHandleFileExistsCalls = ["PROJECT_DIRECTORY_PATH/.build"]
107107

0 commit comments

Comments
 (0)