diff --git a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer+Targets.swift b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer+Targets.swift new file mode 100644 index 0000000..f6d4447 --- /dev/null +++ b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer+Targets.swift @@ -0,0 +1,191 @@ +// +// SwiftPackageFileAnalyzer+Targets.swift +// public-api-diff +// +// Created by Alexander Guretzki on 10/02/2025. +// + +import Foundation + +import PADCore +import PADLogging + +import FileHandlingModule +import ShellModule +import SwiftPackageFileHelperModule + +extension SwiftPackageFileAnalyzer { + + internal func analyzeTargets( + old: [SwiftPackageDescription.Target], + new: [SwiftPackageDescription.Target], + oldProjectBasePath: String, + newProjectBasePath: String + ) throws -> [Change] { + guard old != new else { return [] } + + let oldTargetNames = Set(old.map(\.name)) + let newTargetNames = Set(new.map(\.name)) + + let added = newTargetNames.subtracting(oldTargetNames) + let removed = oldTargetNames.subtracting(newTargetNames) + let consistent = Set(oldTargetNames).intersection(Set(newTargetNames)) + + var changes = [Change]() + + changes += added.compactMap { addition in + guard let addedTarget = new.first(where: { $0.name == addition }) else { return nil } + return .init( + changeType: .addition(description: addedTarget.description), + parentPath: Constants.packageFileName(child: "targets") + ) + } + + try consistent.forEach { productName in + guard + let oldTarget = old.first(where: { $0.name == productName }), + let newTarget = new.first(where: { $0.name == productName }) + else { return } + + changes += try analyzeTarget( + oldTarget: oldTarget, + newTarget: newTarget, + oldProjectBasePath: oldProjectBasePath, + newProjectBasePath: newProjectBasePath + ) + } + + changes += removed.compactMap { removal in + guard let removedTarget = old.first(where: { $0.name == removal }) else { return nil } + return .init( + changeType: .removal(description: removedTarget.description), + parentPath: Constants.packageFileName(child: "targets") + ) + } + + return changes + } + + private func analyzeTarget( + oldTarget: SwiftPackageDescription.Target, + newTarget: SwiftPackageDescription.Target, + oldProjectBasePath: String, + newProjectBasePath: String + ) throws -> [Change] { + guard oldTarget != newTarget else { return [] } + + var listOfChanges = analyzeDependencies( + oldTarget: oldTarget, + newTarget: newTarget + ) + + listOfChanges += try analyzeTargetResources( + oldResources: oldTarget.resources ?? [], + newResources: newTarget.resources ?? [], + oldProjectBasePath: oldProjectBasePath, + newProjectBasePath: newProjectBasePath + ) + + if oldTarget.path != newTarget.path { + listOfChanges += ["Changed path from \"\(oldTarget.path)\" to \"\(newTarget.path)\""] + } + + if oldTarget.type != newTarget.type { + listOfChanges += ["Changed type from `.\(oldTarget.type.description)` to `.\(newTarget.type.description)`"] + } + + guard oldTarget.description != newTarget.description || !listOfChanges.isEmpty else { return [] } + + return [.init( + changeType: .modification( + oldDescription: oldTarget.description, + newDescription: newTarget.description + ), + parentPath: Constants.packageFileName(child: "targets"), + listOfChanges: listOfChanges + )] + + } +} + +// MARK: - SwiftPackageDescription.Target.Resource + +private extension SwiftPackageFileAnalyzer { + + func analyzeDependencies( + oldTarget: SwiftPackageDescription.Target, + newTarget: SwiftPackageDescription.Target + ) -> [String] { + + let oldTargetDependencies = Set(oldTarget.targetDependencies ?? []) + let newTargetDependencies = Set(newTarget.targetDependencies ?? []) + + let addedTargetDependencies = newTargetDependencies.subtracting(oldTargetDependencies) + let removedTargetDependencies = oldTargetDependencies.subtracting(newTargetDependencies) + + let oldProductDependencies = Set(oldTarget.productDependencies ?? []) + let newProductDependencies = Set(newTarget.productDependencies ?? []) + + let addedProductDependencies = newProductDependencies.subtracting(oldProductDependencies) + let removedProductDependencies = oldProductDependencies.subtracting(newProductDependencies) + + var listOfChanges = [String]() + listOfChanges += addedTargetDependencies.map { "Added dependency .target(name: \"\($0)\")" } + listOfChanges += addedProductDependencies.map { "Added dependency .product(name: \"\($0)\", ...)" } + listOfChanges += removedTargetDependencies.map { "Removed dependency .target(name: \"\($0)\")" } + listOfChanges += removedProductDependencies.map { "Removed dependency .product(name: \"\($0)\", ...)" } + return listOfChanges + } + + func analyzeTargetResources( + oldResources: [SwiftPackageDescription.Target.Resource], + newResources: [SwiftPackageDescription.Target.Resource], + oldProjectBasePath: String, + newProjectBasePath: String + ) throws -> [String] { + + logger?.log("Old project base path \(oldProjectBasePath)", from: String(describing: Self.self)) + logger?.log("New project base path \(newProjectBasePath)", from: String(describing: Self.self)) + + let oldResourcePaths = Set(oldResources.map(\.path).map { $0.trimmingPrefix(oldProjectBasePath) }) + let newResourcePaths = Set(newResources.map(\.path).map { $0.trimmingPrefix(newProjectBasePath) }) + + let addedResourcePaths = newResourcePaths.subtracting(oldResourcePaths) + let consistentResourcePaths = oldResourcePaths.intersection(newResourcePaths) + let removedResourcePaths = oldResourcePaths.subtracting(newResourcePaths) + + var listOfChanges = [String]() + + listOfChanges += addedResourcePaths.compactMap { path in + guard let resource = newResources.first(where: { $0.path.trimmingPrefix(newProjectBasePath) == path }) else { return nil } + return "Added resource \(resource.description)" + } + + listOfChanges += consistentResourcePaths.compactMap { path in + guard + let newResource = newResources.first(where: { $0.path.trimmingPrefix(newProjectBasePath) == path }), + let oldResource = oldResources.first(where: { $0.path.trimmingPrefix(oldProjectBasePath) == path }), + newResource.description != oldResource.description + else { return nil } + + return "Changed resource from `\(oldResource.description)` to `\(newResource.description)`" + } + + listOfChanges += removedResourcePaths.compactMap { path in + guard let resource = oldResources.first(where: { $0.path.trimmingPrefix(oldProjectBasePath) == path }) else { return nil } + return "Removed resource \(resource.description)" + } + + return listOfChanges + } +} + +// MARK: - Convenience Extension + +private extension String { + func trimmingPrefix(_ prefix: String) -> String { + var trimmed = self + trimmed.trimPrefix(prefix) + return trimmed + } +} diff --git a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift index e236ec1..07f4e8b 100644 --- a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift +++ b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift @@ -18,9 +18,9 @@ public struct SwiftPackageFileAnalyzer: SwiftPackageFileAnalyzing { private let fileHandler: any FileHandling private let shell: any ShellHandling - private let logger: (any Logging)? + internal let logger: (any Logging)? - private enum Constants { + internal enum Constants { static let packageFileName = "Package.swift" static func packageFileName(child: String) -> String { ".\(child)" @@ -280,142 +280,6 @@ private extension SwiftPackageFileAnalyzer { )] } - // MARK: - Targets - - private func analyzeTargets( - old: [SwiftPackageDescription.Target], - new: [SwiftPackageDescription.Target], - oldProjectBasePath: String, - newProjectBasePath: String - ) throws -> [Change] { - guard old != new else { return [] } - - let oldTargetNames = Set(old.map(\.name)) - let newTargetNames = Set(new.map(\.name)) - - let added = newTargetNames.subtracting(oldTargetNames) - let removed = oldTargetNames.subtracting(newTargetNames) - let consistent = Set(oldTargetNames).intersection(Set(newTargetNames)) - - var changes = [Change]() - - changes += added.compactMap { addition in - guard let addedTarget = new.first(where: { $0.name == addition }) else { return nil } - return .init( - changeType: .addition(description: addedTarget.description), - parentPath: Constants.packageFileName(child: "targets") - ) - } - - try consistent.forEach { productName in - guard - let oldTarget = old.first(where: { $0.name == productName }), - let newTarget = new.first(where: { $0.name == productName }) - else { return } - - changes += try analyzeTarget( - oldTarget: oldTarget, - newTarget: newTarget, - oldProjectBasePath: oldProjectBasePath, - newProjectBasePath: newProjectBasePath - ) - } - - changes += removed.compactMap { removal in - guard let removedTarget = old.first(where: { $0.name == removal }) else { return nil } - return .init( - changeType: .removal(description: removedTarget.description), - parentPath: Constants.packageFileName(child: "targets") - ) - } - - return changes - } - - private func analyzeTarget( - oldTarget: SwiftPackageDescription.Target, - newTarget: SwiftPackageDescription.Target, - oldProjectBasePath: String, - newProjectBasePath: String - ) throws -> [Change] { - guard oldTarget != newTarget else { return [] } - - // MARK: Target Resources - - let oldResourcePaths = Set((oldTarget.resources?.map(\.path) ?? []).map { $0.trimmingPrefix(oldProjectBasePath) }) - let newResourcePaths = Set((newTarget.resources?.map(\.path) ?? []).map { $0.trimmingPrefix(newProjectBasePath) }) - - 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 ?? []) - let newProductDependencies = Set(newTarget.productDependencies ?? []) - - 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)\", ...)" } - - if oldTarget.path != newTarget.path { - listOfChanges += ["Changed path from \"\(oldTarget.path)\" to \"\(newTarget.path)\""] - } - - if oldTarget.type != newTarget.type { - listOfChanges += ["Changed type from `.\(oldTarget.type.description)` to `.\(newTarget.type.description)`"] - } - - listOfChanges += removedTargetDependencies.map { "Removed dependency .target(name: \"\($0)\")" } - listOfChanges += removedProductDependencies.map { "Removed dependency .product(name: \"\($0)\", ...)" } - - - guard oldTarget.description != newTarget.description || !listOfChanges.isEmpty else { return [] } - - return [.init( - changeType: .modification( - oldDescription: oldTarget.description, - newDescription: newTarget.description - ), - parentPath: Constants.packageFileName(child: "targets"), - listOfChanges: listOfChanges - )] - - } - // MARK: - Dependencies private func analyzeDependencies( @@ -497,11 +361,3 @@ private extension SwiftPackageFileAnalyzer { )] } } - -private extension String { - func trimmingPrefix(_ prefix: String) -> String { - var trimmed = self - trimmed.trimPrefix(prefix) - return trimmed - } -} diff --git a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift index 20325a2..36b8636 100644 --- a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift +++ b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift @@ -88,7 +88,7 @@ struct XcodeTools { let result = shell.execute(command) let derivedDataPath = "\(projectDirectoryPath)/\(Constants.derivedDataPath)" - logger?.debug(result, from: String(describing: Self.self)) + //logger?.debug(result, from: String(describing: Self.self)) // It might be that the archive failed but the .swiftinterface files are still created // so we have to check outside if they exist. diff --git a/Tests/UnitTests/XcodeToolsTests.swift b/Tests/UnitTests/XcodeToolsTests.swift index dd50017..2ea5659 100644 --- a/Tests/UnitTests/XcodeToolsTests.swift +++ b/Tests/UnitTests/XcodeToolsTests.swift @@ -101,7 +101,7 @@ private extension XcodeToolsTests { ("📦 Archiving SCHEME from PROJECT_DIRECTORY_PATH", "XcodeTools") ] var expectedHandleDebugCalls: [(message: String, subsystem: String)] = [ - (archiveResult, "XcodeTools") + //(archiveResult, "XcodeTools") ] var expectedHandleFileExistsCalls = ["PROJECT_DIRECTORY_PATH/.build"]