From e6c050298b3b1574c945bd1e893cc3a353eaf27d Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Sun, 7 Jan 2024 12:43:40 +0100 Subject: [PATCH 1/6] Bump version --- Sources/Run/Subcommands/BinarySize.swift | 2 +- Sources/Run/Subcommands/Dependencies.swift | 2 +- Sources/Run/Subcommands/FullAnalyzes.swift | 2 +- Sources/Run/Subcommands/Platforms.swift | 2 +- Sources/Run/SwiftPackageInfo.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Run/Subcommands/BinarySize.swift b/Sources/Run/Subcommands/BinarySize.swift index 6979765..ada852c 100644 --- a/Sources/Run/Subcommands/BinarySize.swift +++ b/Sources/Run/Subcommands/BinarySize.swift @@ -40,7 +40,7 @@ extension SwiftPackageInfo { \(estimatedSizeNote) """, - version: "1.0" + version: "1.3.4" ) @OptionGroup var allArguments: AllArguments diff --git a/Sources/Run/Subcommands/Dependencies.swift b/Sources/Run/Subcommands/Dependencies.swift index 2e45a31..89197aa 100644 --- a/Sources/Run/Subcommands/Dependencies.swift +++ b/Sources/Run/Subcommands/Dependencies.swift @@ -31,7 +31,7 @@ extension SwiftPackageInfo { Show direct and indirect dependencies of a product, listing all dependencies that are linked to its binary. """, - version: "1.0" + version: "1.3.4" ) @OptionGroup var allArguments: AllArguments diff --git a/Sources/Run/Subcommands/FullAnalyzes.swift b/Sources/Run/Subcommands/FullAnalyzes.swift index b476772..9f724fe 100644 --- a/Sources/Run/Subcommands/FullAnalyzes.swift +++ b/Sources/Run/Subcommands/FullAnalyzes.swift @@ -32,7 +32,7 @@ extension SwiftPackageInfo { Runs all available providers (each one available via a subcommand, e.g. BinarySize), and generates a full report of a given Swift Package product for a specific version. """, - version: "1.0" + version: "1.3.4" ) @OptionGroup var allArguments: AllArguments diff --git a/Sources/Run/Subcommands/Platforms.swift b/Sources/Run/Subcommands/Platforms.swift index 255ffbc..ab9e2fe 100644 --- a/Sources/Run/Subcommands/Platforms.swift +++ b/Sources/Run/Subcommands/Platforms.swift @@ -31,7 +31,7 @@ extension SwiftPackageInfo { Informs supported platforms by a given Package.swift and its products, e.g 'iOS with 9.0 minimum deployment target'. """, - version: "1.0" + version: "1.3.4" ) @OptionGroup var allArguments: AllArguments diff --git a/Sources/Run/SwiftPackageInfo.swift b/Sources/Run/SwiftPackageInfo.swift index 0227976..7725af1 100644 --- a/Sources/Run/SwiftPackageInfo.swift +++ b/Sources/Run/SwiftPackageInfo.swift @@ -34,7 +34,7 @@ public struct SwiftPackageInfo: ParsableCommand { that can be used in your favor when deciding whether to adopt or not a Swift Package as a dependency on your app. """, - version: "1.1.0", + version: "1.3.4", subcommands: [ BinarySize.self, Platforms.self, From 29bbd1daa59e78b1f3a170f7450d6b4197ce49e9 Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Sun, 14 Jan 2024 21:07:22 +0100 Subject: [PATCH 2/6] Refactor to use SPM lib --- .../xcschemes/swift-package-info.xcscheme | 17 +- Package.swift | 226 ++++--- .../BinarySizeProvider.swift | 4 +- .../DependenciesProvider.swift | 248 ++++---- .../PlatformsProvider/PlatformsProvider.swift | 152 ++--- .../SwiftPackageService.swift | 276 +++------ .../Extensions/AbsolutePath+Core.swift} | 33 +- Sources/Core/InfoProvider.swift | 2 +- Sources/Core/Models/PackageContent.swift | 319 ---------- Sources/Core/Models/PackageModel.swift | 135 ++++ .../main.swift => Core/PackageLoader.swift} | 33 +- .../Fixtures/Fixture+SwiftPackage.swift | 24 +- Sources/Run/Subcommands/BinarySize.swift | 94 +-- Sources/Run/Subcommands/Dependencies.swift | 62 +- Sources/Run/Subcommands/FullAnalyzes.swift | 76 +-- Sources/Run/Subcommands/Platforms.swift | 62 +- Sources/Run/SwiftPackageInfo.swift | 17 +- .../Fixtures/Fixture+PackageContent.swift | 65 +- .../BinarySizeProviderTests.swift | 193 +++--- .../DependenciesProviderTests.swift | 574 +++++------------- .../PlatformsProviderTests.swift | 137 +++-- .../TagsResponseTests.swift | 66 -- .../Models/PackageContentTests.swift | 449 -------------- Tests/CoreTests/Resources/package_full.json | 181 ------ .../Resources/package_full_swift_5_5.json | 187 ------ .../Resources/package_full_swift_5_6.json | 204 ------- .../Resources/package_full_swift_5_9.json | 206 ------- .../package_identity_dependency.json | 186 ------ ...package_with_custom_target_dependency.json | 94 --- .../package_with_multiple_dependencies.json | 46 -- 30 files changed, 1111 insertions(+), 3257 deletions(-) rename Sources/{App/Services/SwiftPackageService/TagsResponse.swift => Core/Extensions/AbsolutePath+Core.swift} (66%) delete mode 100644 Sources/Core/Models/PackageContent.swift create mode 100644 Sources/Core/Models/PackageModel.swift rename Sources/{Run/main.swift => Core/PackageLoader.swift} (55%) delete mode 100644 Tests/AppTests/Services/SwiftPackageService/TagsResponseTests.swift delete mode 100644 Tests/CoreTests/Models/PackageContentTests.swift delete mode 100644 Tests/CoreTests/Resources/package_full.json delete mode 100644 Tests/CoreTests/Resources/package_full_swift_5_5.json delete mode 100644 Tests/CoreTests/Resources/package_full_swift_5_6.json delete mode 100644 Tests/CoreTests/Resources/package_full_swift_5_9.json delete mode 100644 Tests/CoreTests/Resources/package_identity_dependency.json delete mode 100644 Tests/CoreTests/Resources/package_with_custom_target_dependency.json delete mode 100644 Tests/CoreTests/Resources/package_with_multiple_dependencies.json diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme index 26d3b8a..f1296af 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme @@ -173,7 +173,8 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" - useCustomWorkingDirectory = "NO" + useCustomWorkingDirectory = "YES" + customWorkingDirectory = "/Users/marino.felipe/Documents/projects/personal/swift-package-info" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" @@ -190,15 +191,19 @@ + + + isEnabled = "NO"> + + Result { let sizeMeasurer = defaultSizeMeasurer(verbose) var binarySize: SizeOnDisk = .zero - let isProductDynamicLibrary = packageContent.products + let isProductDynamicLibrary = package.products .first{ $0.name == swiftPackage.product }? .isDynamicLibrary ?? false diff --git a/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift b/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift index 3e2973f..066bced 100644 --- a/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift +++ b/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift @@ -22,158 +22,158 @@ import Core import Foundation enum DependenciesProviderError: LocalizedError, Equatable { - case failedToMatchProduct + case failedToMatchProduct - var errorDescription: String? { - switch self { - case .failedToMatchProduct: - return "Failed to match product when evaluating dependencies" - } + var errorDescription: String? { + switch self { + case .failedToMatchProduct: + return "Failed to match product when evaluating dependencies" } + } } public struct DependenciesProvider { - public static func fetchInformation( - for swiftPackage: SwiftPackage, - packageContent: PackageContent, - verbose: Bool - ) -> Result { - guard let product = packageContent.products.first(where: { $0.name == swiftPackage.product }) else { - return .failure( - .init( - localizedError: DependenciesProviderError.failedToMatchProduct - ) - ) - } - - let productTargetNames = product.targets - let externalDependencies = getExternalDependencies( - forTargetNames: productTargetNames, - packageContent: packageContent - ) - - return .success( - .init( - providerName: "Dependencies", - providerKind: .dependencies, - information: DependenciesInformation(externalDependencies: externalDependencies) - ) + public static func fetchInformation( + for swiftPackage: SwiftPackage, + package: PackageWrapper, + verbose: Bool + ) -> Result { + guard let product = package.products.first(where: { $0.name == swiftPackage.product }) else { + return .failure( + .init( + localizedError: DependenciesProviderError.failedToMatchProduct ) + ) } - private static func getExternalDependencies( - forTargetNames targetNames: [String], - packageContent: PackageContent - ) -> [PackageContent.Dependency] { - let targets = packageContent.targets.filter { targetNames.contains($0.name) } - let targetDependencies = targets - .map(\.dependencies) - .reduce( - [], - + - ) + let productTargets = product.targets + let externalDependencies = getExternalDependencies( + forTargets: productTargets, + package: package + ) + + return .success( + .init( + providerName: "Dependencies", + providerKind: .dependencies, + information: DependenciesInformation(dependencies: externalDependencies) + ) + ) + } + + private static func getExternalDependencies( + forTargets targets: [PackageWrapper.Target], + package: PackageWrapper + ) -> [DependenciesInformation.Dependency] { + let externalDependencies = targets + .map(\.productDependencies) + .reduce( + [], + + + ) + + let transitiveExternalDependencies = targets + .map(\.allTransitiveProductDependencies) + .reduce([], +) + + let allExternalDependencies = externalDependencies + transitiveExternalDependencies + + let dependencies = allExternalDependencies.map(DependenciesInformation.Dependency.init) + + return dependencies.sorted(by: { $0.product < $1.product }) + } +} - let externalDependenciesNames = targetDependencies.compactMap(\.product) - let potentialExternalDependenciesNames = targetDependencies.compactMap(\.byName) +private extension PackageWrapper.Target { + var productDependencies: [PackageWrapper.Product] { + dependencies.compactMap(\.product) + } - let allTargetsNames = packageContent.targets.map(\.name) - var otherTargetsDependenciesNames = targetDependencies.compactMap(\.target) - otherTargetsDependenciesNames += potentialExternalDependenciesNames - .filter { allTargetsNames.contains($0) } + var targetDependencies: [PackageWrapper.Target] { + dependencies.compactMap(\.target) + } - var externalDependenciesFromOtherTargets: [PackageContent.Dependency] = [] - if otherTargetsDependenciesNames.isEmpty == false { - externalDependenciesFromOtherTargets = getExternalDependencies( - forTargetNames: otherTargetsDependenciesNames, - packageContent: packageContent - ) - } + var allTransitiveProductDependencies: [PackageWrapper.Product] { + mapTransitiveProducts(targets: targetDependencies) + } - let externalDependencies = packageContent.dependencies.filter { - externalDependenciesNames.contains($0.name) - || potentialExternalDependenciesNames.contains($0.name) - } + private func mapTransitiveProducts(targets: [PackageWrapper.Target]) -> [PackageWrapper.Product] { + let productDependencies = targets.map(\.dependencies) + .reduce([], +) + .compactMap(\.product) - let allDependencies = externalDependencies + externalDependenciesFromOtherTargets + let targetDependencies = targets.map(\.targetDependencies) + .reduce([], +) - return Array(Set(allDependencies)) - .sorted(by: { $0.name < $1.name }) + if targetDependencies.isEmpty == false { + return mapTransitiveProducts(targets: targetDependencies) + } else { + return productDependencies } + } } struct DependenciesInformation: Equatable, CustomConsoleMessagesConvertible { - struct Dependency: Equatable, Encodable { - let name: String - let version: String? - let branch: String? - let revision: String? - } + struct Dependency: Equatable, Encodable { + let product: String + let package: String + } - let externalDependencies: [PackageContent.Dependency] - let dependencies: [Dependency] + let dependencies: [Dependency] - var messages: [ConsoleMessage] { buildConsoleMessages() } + var messages: [ConsoleMessage] { buildConsoleMessages() } - init(externalDependencies: [PackageContent.Dependency]) { - self.externalDependencies = externalDependencies - self.dependencies = externalDependencies.map(Dependency.init(from:)) - } - - private func buildConsoleMessages() -> [ConsoleMessage] { - if externalDependencies.isEmpty { - return [ - .init( - text: "No third-party dependencies :)", - hasLineBreakAfter: false - ) - ] - } else { - return externalDependencies.map { dependency -> [ConsoleMessage] in - var messages: [ConsoleMessage] = [ - .init( - text: "\(dependency.name)", - hasLineBreakAfter: false - ), - .init( - text: " v. \(dependency.requirement?.range.first?.lowerBound ?? "")", - hasLineBreakAfter: false - ) - ] - - let isLast = dependency == externalDependencies.last - if isLast == false { - messages.append( - .init( - text: " | ", - hasLineBreakAfter: false - ) - ) - } - - return messages - } - .reduce( - [], - + + private func buildConsoleMessages() -> [ConsoleMessage] { + if dependencies.isEmpty { + return [ + .init( + text: "No third-party dependencies :)", + hasLineBreakAfter: false + ) + ] + } else { + return dependencies.map { dependency -> [ConsoleMessage] in + var messages: [ConsoleMessage] = [ + .init( + text: "\(dependency.product); \(dependency.package)", + hasLineBreakAfter: false + ), + ] + + let isLast = dependency == dependencies.last + if isLast == false { + messages.append( + .init( + text: " | ", + hasLineBreakAfter: false ) + ) } + + return messages + } + .reduce( + [], + + + ) } + } } extension DependenciesInformation: Encodable { - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(dependencies) - } + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(dependencies) + } } extension DependenciesInformation.Dependency { - init(from dependency: PackageContent.Dependency) { - self.init( - name: dependency.name, - version: dependency.requirement?.range.first?.lowerBound.description, - branch: dependency.requirement?.branch.first, - revision: dependency.requirement?.revision.first - ) - } + init( + from dependency: PackageWrapper.Product + ) { + self.init( + product: dependency.name, + package: dependency.package ?? "" + ) + } } diff --git a/Sources/App/Providers/PlatformsProvider/PlatformsProvider.swift b/Sources/App/Providers/PlatformsProvider/PlatformsProvider.swift index 2d66e17..093d517 100644 --- a/Sources/App/Providers/PlatformsProvider/PlatformsProvider.swift +++ b/Sources/App/Providers/PlatformsProvider/PlatformsProvider.swift @@ -21,97 +21,97 @@ import Core public struct PlatformsProvider { - public static func fetchInformation( - for swiftPackage: SwiftPackage, - packageContent: PackageContent, - verbose: Bool - ) -> Result { - .success( - .init( - providerName: "Platforms", - providerKind: .platforms, - information: PlatformsInformation( - platforms: packageContent.platforms - ) - ) + public static func fetchInformation( + for swiftPackage: SwiftPackage, + package: PackageWrapper, + verbose: Bool + ) -> Result { + .success( + .init( + providerName: "Platforms", + providerKind: .platforms, + information: PlatformsInformation( + platforms: package.platforms ) - } + ) + ) + } } struct PlatformsInformation: Equatable, Encodable, CustomConsoleMessagesConvertible { - let platforms: [PackageContent.Platform] - let iOS: String? - let macOS: String? - let tvOS: String? - let watchOS: String? + let platforms: [PackageWrapper.Platform] + let iOS: String? + let macOS: String? + let tvOS: String? + let watchOS: String? - var messages: [ConsoleMessage] { buildConsoleMessages() } + var messages: [ConsoleMessage] { buildConsoleMessages() } - init(platforms: [PackageContent.Platform]) { - self.platforms = platforms - self.iOS = platforms.iOSVersion - self.macOS = platforms.macOSVersion - self.tvOS = platforms.tvOSVersion - self.watchOS = platforms.watchOSVersion - } + init(platforms: [PackageWrapper.Platform]) { + self.platforms = platforms + self.iOS = platforms.iOSVersion + self.macOS = platforms.macOSVersion + self.tvOS = platforms.tvOSVersion + self.watchOS = platforms.watchOSVersion + } - private enum CodingKeys: String, CodingKey { - case iOS, macOS, tvOS, watchOS - } + private enum CodingKeys: String, CodingKey { + case iOS, macOS, tvOS, watchOS + } - private func buildConsoleMessages() -> [ConsoleMessage] { - if platforms.isEmpty { - return [ - .init( - text: "System default", - hasLineBreakAfter: false - ) - ] - } else { - return platforms - .map { platform -> [ConsoleMessage] in - var messages = [ - ConsoleMessage( - text: "\(platform.platformName) from v. \(platform.version)", - isBold: true, - hasLineBreakAfter: false - ) - ] + private func buildConsoleMessages() -> [ConsoleMessage] { + if platforms.isEmpty { + return [ + .init( + text: "System default", + hasLineBreakAfter: false + ) + ] + } else { + return platforms + .map { platform -> [ConsoleMessage] in + var messages = [ + ConsoleMessage( + text: "\(platform.platformName) from v. \(platform.version)", + isBold: true, + hasLineBreakAfter: false + ) + ] - let isLastPlatform = platform == platforms.last - if isLastPlatform == false { - messages.append( - .init( - text: " | ", - hasLineBreakAfter: false - ) - ) - } + let isLastPlatform = platform == platforms.last + if isLastPlatform == false { + messages.append( + .init( + text: " | ", + hasLineBreakAfter: false + ) + ) + } - return messages - } - .reduce( - [], - + - ) + return messages } + .reduce( + [], + + + ) } + } } -extension Array where Element == PackageContent.Platform { - var iOSVersion: String? { - first(where: \.platformName == "ios")?.version - } +extension Array where Element == PackageWrapper.Platform { + var iOSVersion: String? { + first(where: \.platformName == "ios")?.version + } - var macOSVersion: String? { - first(where: \.platformName == "macos")?.version - } + var macOSVersion: String? { + first(where: \.platformName == "macos")?.version + } - var tvOSVersion: String? { - first(where: \.platformName == "tvos")?.version - } + var tvOSVersion: String? { + first(where: \.platformName == "tvos")?.version + } - var watchOSVersion: String? { - first(where: \.platformName == "watchos")?.version - } + var watchOSVersion: String? { + first(where: \.platformName == "watchos")?.version + } } diff --git a/Sources/App/Services/SwiftPackageService/SwiftPackageService.swift b/Sources/App/Services/SwiftPackageService/SwiftPackageService.swift index 7100e9c..5b38ad6 100644 --- a/Sources/App/Services/SwiftPackageService/SwiftPackageService.swift +++ b/Sources/App/Services/SwiftPackageService/SwiftPackageService.swift @@ -24,21 +24,12 @@ import CombineHTTPClient import Foundation import HTTPClientCore -enum SwiftPackageServiceError: LocalizedError, Equatable { - case unableToDumpPackageContent(errorMessage: String) - case unableToFetchPackageContent(errorMessage: String) +import Basics +import PackageModel +import SourceControl +import TSCBasic - var errorDescription: String? { - switch self { - case let .unableToDumpPackageContent(errorMessage): - return "Failed to dump package content with error: \(errorMessage)" - case let .unableToFetchPackageContent(errorMessage): - return "Failed to fetch package content with error: \(errorMessage)" - } - } -} - -public struct SwiftPackageValidationResult: Equatable { +public struct SwiftPackageValidationResult { public enum SourceInformation: Equatable { case local case remote( @@ -51,68 +42,51 @@ public struct SwiftPackageValidationResult: Equatable { public let sourceInformation: SourceInformation public let isProductValid: Bool public let availableProducts: [String] - public let packageContent: PackageContent + public let package: Package } extension SwiftPackageValidationResult { init( - from packageContent: PackageContent, + from package: Package, product: String, sourceInformation: SourceInformation ) { self.init( sourceInformation: sourceInformation, - isProductValid: packageContent.products - .contains(where: \.name == product), - availableProducts: packageContent.products.map(\.name), - packageContent: packageContent + isProductValid: package.products.contains(where: \.name == product), + availableProducts: package.products.map(\.name), + package: package ) } } public final class SwiftPackageService { - private let httpClient: CombineHTTPClient - private let gitHubRequestBuilder: HTTPRequestBuilder - private let console: Console private let fileManager: FileManager - private let jsonDecoder: JSONDecoder + private let packageLoader: PackageLoader + private let repositoryProvider: RepositoryProvider init( - httpClient: CombineHTTPClient = .default, - gitHubRequestBuilder: HTTPRequestBuilder = .gitHub, - console: Console = .default, fileManager: FileManager = .default, - jsonDecoder: JSONDecoder = .default + packageLoader: PackageLoader = .live, + repositoryProvider: RepositoryProvider ) { - self.httpClient = httpClient - self.gitHubRequestBuilder = gitHubRequestBuilder - self.console = console self.fileManager = fileManager - self.jsonDecoder = jsonDecoder + self.packageLoader = packageLoader + self.repositoryProvider = repositoryProvider } public convenience init() { - self.init( - httpClient: .default, - gitHubRequestBuilder: .gitHub, - jsonDecoder: .default - ) - } - - deinit { - fetchToken?.cancel() + self.init(repositoryProvider: GitRepositoryProvider()) } - private var fetchToken: AnyCancellable? - public func validate( swiftPackage: SwiftPackage, verbose: Bool - ) throws -> SwiftPackageValidationResult { + ) async throws -> SwiftPackageValidationResult { if swiftPackage.isLocal { - return try runLocalValidation(for: swiftPackage, verbose: verbose) + return try await runLocalValidation(for: swiftPackage, verbose: verbose) } else { - return try runRemoteValidation(for: swiftPackage, verbose: verbose) + return try await runRemoteValidation(for: swiftPackage, verbose: verbose) } } @@ -121,181 +95,91 @@ public final class SwiftPackageService { private func runLocalValidation( for swiftPackage: SwiftPackage, verbose: Bool - ) throws -> SwiftPackageValidationResult { + ) async throws -> SwiftPackageValidationResult { .init( - from: try fetchLocalPackageContent( - atPath: swiftPackage.url.path, - verbose: verbose - ), + from: try await fetchLocalPackage(atPath: swiftPackage.url.path), product: swiftPackage.product, sourceInformation: .local ) } - private func fetchLocalPackageContent( - atPath path: String, - verbose: Bool - ) throws -> PackageContent { - if fileManager.fileExists(atPath: path) { - return try dumpPackageContent( - atPath: path, - verbose: verbose - ) - } - - throw SwiftPackageServiceError.unableToDumpPackageContent( - errorMessage: "Package.swift does not exist on local path: \(path)" - ) + private func fetchLocalPackage(atPath path: String) async throws -> Package { + let absolutePath = AbsolutePath.currentDir.appending(path) + return try await packageLoader.load(absolutePath) } // MARK: - Remote + // SPM Package ToolsVersion.current + private func runRemoteValidation( for swiftPackage: SwiftPackage, verbose: Bool - ) throws -> SwiftPackageValidationResult { - let repositoryRequest = try gitHubRequestBuilder - .path("/repos/\(swiftPackage.accountName)/\(swiftPackage.repositoryName)") - .build() - - let tagsRequest = try gitHubRequestBuilder - .path("/repos/\(swiftPackage.accountName)/\(swiftPackage.repositoryName)/git/refs/tags") - .build() - - let repositoryRequestPublisher: AnyPublisher< - HTTPResponse, - HTTPResponseError - > = httpClient.run(repositoryRequest, receiveOn: .global(qos: .userInteractive)) - let tagsRequestPublisher: AnyPublisher< - HTTPResponse, - HTTPResponseError - > = httpClient.run(tagsRequest, receiveOn: .global(qos: .userInteractive)) + ) async throws -> SwiftPackageValidationResult { + return try await withTemporaryDirectory(prefix: "spm-package-info-run-") { tempDirPath in + let repositoryManager = RepositoryManager( + fileSystem: localFileSystem, + path: tempDirPath, + provider: repositoryProvider, + initializationWarningHandler: { s in + print(s) + } + ) - let semaphore = DispatchSemaphore(value: 0) + let repositoryHandle = try await fetchRepository( + repositoryManager: repositoryManager, + swiftPackage: swiftPackage + ) - var isRepositoryValid = false - var isTagValid = false - var latestTag: String? + let cloneDirPath = tempDirPath.appending(swiftPackage.repositoryName) - fetchToken = Publishers.Zip(repositoryRequestPublisher, tagsRequestPublisher) - .sink { [weak console] completion in - semaphore.signal() - if case let .failure(error) = completion, verbose { - console?.lineBreakAndWrite(.init(text: error.localizedDescription, color: .red)) - } - } receiveValue: { repositoryResponse, tagsResponse in - isRepositoryValid = repositoryResponse.isSuccess + let workingCopy = try repositoryHandle.createWorkingCopy( + at: cloneDirPath, + editable: false + ) - if case let .success(response) = tagsResponse.value { - let tags = response.tags.normalized + let tags = try workingCopy.getTags() + try workingCopy.checkout(tag: tags.last ?? "") - isTagValid = tags - .map(\.name) - .contains(swiftPackage.version) - latestTag = tags.last?.name - } - semaphore.signal() - } + let isVersionPassedValid = tags.contains(swiftPackage.version) +// let resolvedTag = isVersionPassedValid ? swiftPackage.version : tags.last - semaphore.wait() + let package = try await packageLoader.load(cloneDirPath) return .init( - from: try fetchRemotePackageContent( - for: swiftPackage, - version: isTagValid - ? swiftPackage.version - : latestTag ?? "", - verbose: verbose - ), + from: package, product: swiftPackage.product, sourceInformation: .remote( - isRepositoryValid: isRepositoryValid, - isTagValid: isTagValid, - latestTag: latestTag + isRepositoryValid: true, + isTagValid: isVersionPassedValid, + latestTag: tags.last ) ) - } - - private func fetchRemotePackageContent( - for swiftPackage: SwiftPackage, - version: String, - verbose: Bool - ) throws -> PackageContent { - let repositoryTemporaryPath = "\(fileManager.temporaryDirectory.path)/\(swiftPackage.repositoryName)" - - if fileManager.fileExists(atPath: repositoryTemporaryPath) { - try fileManager.removeItem(atPath: repositoryTemporaryPath) - } - - let fetchOutput = try Shell.performShallowGitClone( - workingDirectory: fileManager.temporaryDirectory.path, - repositoryURLString: swiftPackage.url.absoluteString, - branchOrTag: version, - verbose: verbose, - timeout: 60 - ) - guard fetchOutput.succeeded else { - let errorMessage = String(data: fetchOutput.errorData, encoding: .utf8) ?? "" - throw SwiftPackageServiceError.unableToFetchPackageContent(errorMessage: errorMessage) - } - - return try dumpPackageContent( - atPath: repositoryTemporaryPath, - verbose: verbose - ) - } - - // MARK: - Common - - private func dumpPackageContent( - atPath path: String, - verbose: Bool - ) throws -> PackageContent { - let dumpOutput = try Shell.run( - "swift package dump-package", - workingDirectory: path, - verbose: verbose - ) - - guard dumpOutput.succeeded else { - let errorMessage = String(data: dumpOutput.errorData, encoding: .utf8) ?? "" - throw SwiftPackageServiceError.unableToDumpPackageContent(errorMessage: errorMessage) + } + } + + private func fetchRepository( + repositoryManager: RepositoryManager, + swiftPackage: SwiftPackage + ) async throws -> RepositoryManager.RepositoryHandle { + let observability = ObservabilitySystem { print("\($0): \($1)") } + + return try await withCheckedThrowingContinuation { continuation in + repositoryManager.lookup( + package: PackageIdentity(url: "\(swiftPackage.url)"), + repository: RepositorySpecifier(url: "\(swiftPackage.url)"), + skipUpdate: false, + observabilityScope: observability.topScope, + delegateQueue: .main, + callbackQueue: .main + ) { result in + switch result { + case let .success(handle): + continuation.resume(returning: handle) + case let .failure(error): + continuation.resume(throwing: error) + } } - - return try jsonDecoder.decode(PackageContent.self, from: dumpOutput.data) - } -} - -// MARK: - Extensions - -extension HTTPRequestBuilder { - static let gitHub: HTTPRequestBuilder = .init(scheme: .https, host: "api.github.com") -} - -extension CombineHTTPClient { - static let `default`: CombineHTTPClient = .init(session: URLSession(configuration: .default)) -} - -extension JSONDecoder { - static let `default`: JSONDecoder = .init() -} - -// MARK: - Extensions - Tags - -private extension Array where Element == TagsResponse.Tag { - var normalized: Self { - map(\.normalized) - } -} - -private extension TagsResponse.Tag { - var normalized: Self { - .init( - name: self.name - .replacingOccurrences( - of: "refs/tags/", - with: "" - ) - ) } + } } diff --git a/Sources/App/Services/SwiftPackageService/TagsResponse.swift b/Sources/Core/Extensions/AbsolutePath+Core.swift similarity index 66% rename from Sources/App/Services/SwiftPackageService/TagsResponse.swift rename to Sources/Core/Extensions/AbsolutePath+Core.swift index b8fffec..d2fe8d6 100644 --- a/Sources/App/Services/SwiftPackageService/TagsResponse.swift +++ b/Sources/Core/Extensions/AbsolutePath+Core.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Felipe Marino +// Copyright (c) 2024 Felipe Marino // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -18,21 +18,24 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -struct TagsResponse: Equatable { - struct Tag: Decodable, Equatable { - let name: String +import TSCBasic +import Foundation - enum CodingKeys: String, CodingKey { - case name = "ref" - } +public extension AbsolutePath { + /// The path to the program’s current directory. + static let currentDir: Self = { + do { + return try AbsolutePath(validating: currentDirPath) + } catch { + preconditionFailure( + "Unable to make AbsolutePath from currentDir, error: \(error.localizedDescription)" + ) } - - let tags: [Tag] + }() } -extension TagsResponse: Decodable { - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - tags = try container.decode([Tag].self) - } -} +#if DEBUG + var currentDirPath = FileManager.default.currentDirectoryPath +#else + let currentDirPath = FileManager.default.currentDirectoryPath +#endif diff --git a/Sources/Core/InfoProvider.swift b/Sources/Core/InfoProvider.swift index 5a42fee..390adf3 100644 --- a/Sources/Core/InfoProvider.swift +++ b/Sources/Core/InfoProvider.swift @@ -44,7 +44,7 @@ public enum ProviderKind: String, CodingKey { public typealias InfoProvider = ( _ swiftPackage: SwiftPackage, - _ packageContent: PackageContent, + _ packageContent: PackageWrapper, _ verbose: Bool ) -> Result diff --git a/Sources/Core/Models/PackageContent.swift b/Sources/Core/Models/PackageContent.swift deleted file mode 100644 index 188d6ee..0000000 --- a/Sources/Core/Models/PackageContent.swift +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) 2022 Felipe Marino -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import Foundation -import struct TSCUtility.Version - -public enum PackageContentError: LocalizedError { - case failedToDecodeTargetDependencyType - case failedToDecodeDependenciesFromSwift5Dot5Toolchain - case failedToDecodeDependenciesFromSwift5Dot6Toolchain - - public var errorDescription: String? { - switch self { - case .failedToDecodeTargetDependencyType: - return "Failed to decode target dependency type when evaluating Package.swift content" - case .failedToDecodeDependenciesFromSwift5Dot5Toolchain: - return "Failed to decode package dependencies from Package.swift generated using the Swift 5.5 toolchain" - case .failedToDecodeDependenciesFromSwift5Dot6Toolchain: - return "Failed to decode package dependencies from Package.swift generated using the Swift 5.6 toolchain" - } - } -} - -public struct PackageContent: Decodable, Equatable { - public struct Product: Decodable, Equatable { - public enum Kind: Equatable { - public enum LibraryKind: String, Decodable { - case dynamic - case `static` - case automatic - } - - case executable - case library(LibraryKind) - } - - public let name: String - public let targets: [String] - public let kind: Kind - - private enum CodingKeys: String, CodingKey { - case name - case targets - case kind = "type" - } - } - - public struct Dependency: Equatable, Hashable { - public struct Requirement: Equatable, Hashable { - public struct Range: Decodable, Equatable, Hashable { - public let lowerBound: Version - public let upperBound: Version - } - - public let range: [Range] - public let revision: [String] - public let branch: [String] - } - - #if compiler(<5.9) - public struct Location: Decodable, Equatable, Hashable { - public let remote: [String] - } - #else - public struct Location: Decodable, Hashable { - public struct RemoteURL: Decodable, Hashable { - let urlString: String - } - - public let remote: [RemoteURL] - } - #endif - - public let name: String - public let urlString: String - public let requirement: Requirement? - } - - public struct Platform: Decodable, Equatable { - public let platformName: String - public let version: String - } - - public struct Target: Decodable, Equatable { - public enum Dependency: Equatable { - public enum Content: Equatable { - public struct Platforms: Decodable, Equatable { - let platformNames: [String] - } - - case name(String) - case platforms(Platforms) - } - - case target([Content]) - case product([Content]) - case byName([Content]) - } - - public enum Kind: String, Decodable, Equatable { - case binary - case regular - case test - case system - } - - public let name: String - public let dependencies: [Dependency] - public let kind: Kind - - private enum CodingKeys: String, CodingKey { - case name - case dependencies - case kind = "type" - } - } - - public let name: String - public let platforms: [Platform] - public let products: [Product] - public let dependencies: [Dependency] - public let targets: [Target] - public let swiftLanguageVersions: [String]? -} - -extension PackageContent.Product.Kind: Decodable { - enum CodingKeys: String, CodingKey { - case executable - case library - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - guard let libraryKind = try container.decodeIfPresent([LibraryKind].self, forKey: .library)?.first else { - self = .executable - return - } - - self = .library(libraryKind) - } -} - -extension PackageContent.Target.Dependency: Decodable { - enum CodingKeys: String, CodingKey { - case target - case product - case byName - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - if let targets = try container.decodeIfPresent([Content?].self, forKey: .target)?.compactMap({ $0 }) { - self = .target(targets) - } else if let products = try container.decodeIfPresent([Content?].self, forKey: .product)?.compactMap({ $0 }) { - self = .product(products) - } else if let dependenciesNames = try container.decodeIfPresent([Content?].self, forKey: .byName)?.compactMap({ $0 }) { - self = .byName(dependenciesNames) - } else { - throw PackageContentError.failedToDecodeTargetDependencyType - } - } -} - -extension PackageContent.Target.Dependency.Content: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - guard let name = try? container.decode(String?.self) else { - let platforms = try container.decode(Platforms.self) - self = .platforms(platforms) - return - } - - self = .name(name) - } -} - -public extension PackageContent.Target.Dependency.Content { - var name: String? { - guard case let .name(name) = self else { return nil } - return name - } -} - -public extension PackageContent.Target.Dependency { - var target: String? { - guard case let .target(targets) = self else { return nil } - return targets.first(where: { $0.name != nil })?.name - } - - var product: String? { - guard case let .product(products) = self else { return nil } - return products[safeIndex: 1]?.name - } - - var byName: String? { - guard case let .byName(names) = self else { return nil } - return names.last?.name - } -} - -public extension PackageContent.Product { - var isDynamicLibrary: Bool { - guard case let .library(libraryKind) = self.kind else { return false } - return libraryKind == .dynamic - } -} - -extension PackageContent.Dependency.Requirement: Decodable { - enum CodingKeys: String, CodingKey { - case range - case revision - case branch - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let range = try container.decodeIfPresent([Range].self, forKey: .range) - self.range = range ?? [] - - let revision = try container.decodeIfPresent([String].self, forKey: .revision) - self.revision = revision ?? [] - - let branch = try container.decodeIfPresent([String].self, forKey: .branch) - self.branch = branch ?? [] - } -} - -extension PackageContent.Dependency: Decodable { - private enum CodingKeys: String, CodingKey { - case name - case identity - case urlString = "url" - case location - case requirement - // custom inner object present when generated from Swift 5.5 - case scm - // custom inner object and properties present when generated from Swift 5.6 - case sourceControl - case path - case fileSystem - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - if container.contains(.scm) { - let scmInnerDependencies = try container.decode( - [PackageContent.Dependency].self, - forKey: .scm - ) - guard let firstSCMInnerDependency = scmInnerDependencies.first else { - throw PackageContentError.failedToDecodeDependenciesFromSwift5Dot5Toolchain - } - - self = firstSCMInnerDependency - } else if container.contains(.sourceControl) { - let sourceControlInnerDependencies = try container.decode( - [PackageContent.Dependency].self, - forKey: .sourceControl - ) - guard let firstSourceControlInnerDependency = sourceControlInnerDependencies.first else { - throw PackageContentError.failedToDecodeDependenciesFromSwift5Dot6Toolchain - } - - self = firstSourceControlInnerDependency - } else if container.contains(.fileSystem) { - let fileSystemInnerDependencies = try container.decode( - [PackageContent.Dependency].self, - forKey: .fileSystem - ) - guard let firsFileSystemInnerDependency = fileSystemInnerDependencies.first else { - throw PackageContentError.failedToDecodeDependenciesFromSwift5Dot6Toolchain - } - - self = firsFileSystemInnerDependency - } else { - self.name = try container.decodeIfPresent(String.self, forKey: .name) - ?? container.decode(String.self, forKey: .identity) - - if - let location = try? container.decode(Location.self, forKey: .location), - let remote = location.remote.first - { - #if compiler(<5.9) - self.urlString = remote - #else - self.urlString = remote.urlString - #endif - } else { - self.urlString = try container.decodeIfPresent(String.self, forKey: .urlString) - ?? container.decodeIfPresent(String.self, forKey: .path) - ?? container.decode(String.self, forKey: .location) - } - - self.requirement = try container.decodeIfPresent(Requirement.self, forKey: .requirement) - } - } -} diff --git a/Sources/Core/Models/PackageModel.swift b/Sources/Core/Models/PackageModel.swift new file mode 100644 index 0000000..400d009 --- /dev/null +++ b/Sources/Core/Models/PackageModel.swift @@ -0,0 +1,135 @@ +// Copyright (c) 2024 Felipe Marino +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import PackageModel + +/// A wrapper over SwiftPM library Package type. +/// `Equatable` and `easily testable` +public struct PackageWrapper: Equatable { + public struct Product: Equatable { + public let name: String + public let package: String? + public let isDynamicLibrary: Bool? + public let targets: [Target] + } + + public struct Target: Equatable { + public enum Dependency: Equatable { + case target(Target) + case product(Product) + + public var target: Target? { + switch self { + case let .target(target): + return target + case .product: + return nil + } + } + + public var product: Product? { + switch self { + case .target: + return nil + case let .product(product): + return product + } + } + } + + public let name: String + public let dependencies: [Dependency] + } + + public struct Platform: Equatable { + public let platformName: String + public let version: String + } + + public let products: [Product] + public let platforms: [Platform] + public let targets: [Target] +} + +extension PackageWrapper { + public init(from package: Package) { + products = package.products.map(Product.init(from:)) + platforms = package.manifest.platforms.map(Platform.init(from:)) + targets = package.targets.map(Target.init(from:)) + } +} + +// MARK: - Mappers + +extension PackageWrapper.Target { + init(from target: PackageModel.Target) { + name = target.name + dependencies = target.dependencies.map(Dependency.init(from:)) + } +} + +extension PackageWrapper.Target.Dependency { + init(from dependency: PackageModel.Target.Dependency) { + switch dependency { + case let .target(target, _): + self = .target(PackageWrapper.Target(from: target)) + case let .product(product, _): + self = .product(PackageWrapper.Product(from: product)) + } + } +} + +extension PackageWrapper.Product { + init(from product: PackageModel.Product) { + name = product.name + package = nil + isDynamicLibrary = product.isDynamicLibrary + targets = product.targets.map(PackageWrapper.Target.init(from:)) + } +} + +extension PackageWrapper.Product { + init(from product: PackageModel.Target.ProductReference) { + name = product.name + package = product.package + isDynamicLibrary = nil + targets = [] + } +} + +extension PackageWrapper.Platform { + init(from platformDescription: PackageModel.PlatformDescription) { + platformName = platformDescription.platformName + version = platformDescription.version + } +} + +// MARK: - PackageModel Extensions + +private extension Product { + var isDynamicLibrary: Bool { + switch type { + case .library(.dynamic): + return true + default: + return false + } + } +} diff --git a/Sources/Run/main.swift b/Sources/Core/PackageLoader.swift similarity index 55% rename from Sources/Run/main.swift rename to Sources/Core/PackageLoader.swift index ca31e8b..6e021a6 100644 --- a/Sources/Run/main.swift +++ b/Sources/Core/PackageLoader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Felipe Marino +// Copyright (c) 2024 Felipe Marino // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,33 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -SwiftPackageInfo.main() +import Basics +import PackageModel +import TSCBasic +import Workspace + +/// Loads the content of a Package.swift, the dependency graph included +/// +/// The ``PackageLoader`` uses the SPM library to load the package representation +public struct PackageLoader { + /// Loads a Package.swift at a given `packagePath` + public var load: (AbsolutePath) async throws -> Package +} + +extension PackageLoader { + /// Makes a **Live** ``PackageLoader`` instance + public static let live: Self = { + .init( + load: { packagePath in + let observability = ObservabilitySystem { print("\($0): \($1)") } + + let workspace = try Workspace(forRootPackage: packagePath) + + return try await workspace.loadRootPackage( + at: packagePath, + observabilityScope: observability.topScope + ) + } + ) + }() +} diff --git a/Sources/CoreTestSupport/Doubles/Fixtures/Fixture+SwiftPackage.swift b/Sources/CoreTestSupport/Doubles/Fixtures/Fixture+SwiftPackage.swift index 964a305..b14519c 100644 --- a/Sources/CoreTestSupport/Doubles/Fixtures/Fixture+SwiftPackage.swift +++ b/Sources/CoreTestSupport/Doubles/Fixtures/Fixture+SwiftPackage.swift @@ -23,16 +23,16 @@ import Foundation import Core public extension Fixture { - static func makeSwiftPackage( - url: URL = URL(string: "https://www.apple.com")!, - isLocal: Bool = false, - product: String = "Some" - ) -> SwiftPackage { - .init( - url: url, - isLocal: isLocal, - version: "1.0.0", - product: product - ) - } + static func makeSwiftPackage( + url: URL = URL(string: "https://www.apple.com")!, + isLocal: Bool = false, + product: String = "Some" + ) -> SwiftPackage { + .init( + url: url, + isLocal: isLocal, + version: "1.0.0", + product: product + ) + } } diff --git a/Sources/Run/Subcommands/BinarySize.swift b/Sources/Run/Subcommands/BinarySize.swift index ada852c..60b107e 100644 --- a/Sources/Run/Subcommands/BinarySize.swift +++ b/Sources/Run/Subcommands/BinarySize.swift @@ -24,71 +24,73 @@ import App import Reports extension SwiftPackageInfo { - public struct BinarySize: ParsableCommand { - static let estimatedSizeNote = """ + public struct BinarySize: AsyncParsableCommand { + static let estimatedSizeNote = """ * Note: The estimated size may not reflect the exact amount since it doesn't account optimizations such as app thinning. Its methodology is inspired by [cocoapods-size](https://github.com/google/cocoapods-size), and thus works by comparing archives with no bitcode and ARM64 arch. Such a strategy has proven to be very consistent with the size added to iOS apps downloaded and installed via TestFlight. """ - public static var configuration = CommandConfiguration( - abstract: "Estimated binary size of a Swift Package product.", - discussion: """ + public static var configuration = CommandConfiguration( + abstract: "Estimated binary size of a Swift Package product.", + discussion: """ Measures the estimated binary size impact of a Swift Package product, such as "ArgumentParser" declared on `swift-argument-parser`. \(estimatedSizeNote) """, - version: "1.3.4" - ) + version: "1.3.4" + ) + + @OptionGroup var allArguments: AllArguments - @OptionGroup var allArguments: AllArguments + public init() {} - public init() {} + public func run() async throws { + try runArgumentsValidation(arguments: allArguments) + var swiftPackage = makeSwiftPackage(from: allArguments) + swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) - public func run() throws { - try runArgumentsValidation(arguments: allArguments) - var swiftPackage = makeSwiftPackage(from: allArguments) - swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) + let package = try await validate( + swiftPackage: &swiftPackage, + verbose: allArguments.verbose + ) - let packageContent = try validate( - swiftPackage: &swiftPackage, - verbose: allArguments.verbose - ) + let report = Report(swiftPackage: swiftPackage) - let report = Report(swiftPackage: swiftPackage) + let packageWrapper = PackageWrapper(from: package) - try BinarySizeProvider.fetchInformation( - for: swiftPackage, - packageContent: packageContent, - verbose: allArguments.verbose - ) - .onSuccess { - try report.generate( - for: $0, - format: allArguments.report - ) - } - .onFailure { Console.default.write($0.message) } - } + try BinarySizeProvider.fetchInformation( + for: swiftPackage, + package: packageWrapper, + verbose: allArguments.verbose + ) + .onSuccess { + try report.generate( + for: $0, + format: allArguments.report + ) + } + .onFailure { Console.default.write($0.message) } } + } } extension SwiftPackage: CustomConsoleMessagesConvertible { - public var messages: [ConsoleMessage] { - [ - .init( - text: "Identified Swift Package:", - color: .green, - isBold: true, - hasLineBreakAfter: false - ), - .init( - text: description, - color: .noColor, - isBold: false - ) - ] - } + public var messages: [ConsoleMessage] { + [ + .init( + text: "Identified Swift Package:", + color: .green, + isBold: true, + hasLineBreakAfter: false + ), + .init( + text: description, + color: .noColor, + isBold: false + ) + ] + } } diff --git a/Sources/Run/Subcommands/Dependencies.swift b/Sources/Run/Subcommands/Dependencies.swift index 89197aa..6192d0c 100644 --- a/Sources/Run/Subcommands/Dependencies.swift +++ b/Sources/Run/Subcommands/Dependencies.swift @@ -24,44 +24,46 @@ import App import Reports extension SwiftPackageInfo { - public struct Dependencies: ParsableCommand { - public static var configuration = CommandConfiguration( - abstract: "List dependencies of a Package product.", - discussion: """ + public struct Dependencies: AsyncParsableCommand { + public static var configuration = CommandConfiguration( + abstract: "List dependencies of a Package product.", + discussion: """ Show direct and indirect dependencies of a product, listing all dependencies that are linked to its binary. """, - version: "1.3.4" - ) + version: "1.3.4" + ) + + @OptionGroup var allArguments: AllArguments - @OptionGroup var allArguments: AllArguments + public init() {} - public init() {} + public func run() async throws { + try runArgumentsValidation(arguments: allArguments) + var swiftPackage = makeSwiftPackage(from: allArguments) + swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) - public func run() throws { - try runArgumentsValidation(arguments: allArguments) - var swiftPackage = makeSwiftPackage(from: allArguments) - swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) + let package = try await validate( + swiftPackage: &swiftPackage, + verbose: allArguments.verbose + ) - let packageContent = try validate( - swiftPackage: &swiftPackage, - verbose: allArguments.verbose - ) + let report = Report(swiftPackage: swiftPackage) - let report = Report(swiftPackage: swiftPackage) + let packageWrapper = PackageWrapper(from: package) - try DependenciesProvider.fetchInformation( - for: swiftPackage, - packageContent: packageContent, - verbose: allArguments.verbose - ) - .onSuccess { - try report.generate( - for: $0, - format: allArguments.report - ) - } - .onFailure { Console.default.write($0.message) } - } + try DependenciesProvider.fetchInformation( + for: swiftPackage, + package: packageWrapper, + verbose: allArguments.verbose + ) + .onSuccess { + try report.generate( + for: $0, + format: allArguments.report + ) + } + .onFailure { Console.default.write($0.message) } } + } } diff --git a/Sources/Run/Subcommands/FullAnalyzes.swift b/Sources/Run/Subcommands/FullAnalyzes.swift index 9f724fe..7034aaa 100644 --- a/Sources/Run/Subcommands/FullAnalyzes.swift +++ b/Sources/Run/Subcommands/FullAnalyzes.swift @@ -25,51 +25,53 @@ import Foundation import Reports extension SwiftPackageInfo { - public struct FullAnalyzes: ParsableCommand { - public static var configuration = CommandConfiguration( - abstract: "All available information about a Swift Package product.", - discussion: """ + public struct FullAnalyzes: AsyncParsableCommand { + public static var configuration = CommandConfiguration( + abstract: "All available information about a Swift Package product.", + discussion: """ Runs all available providers (each one available via a subcommand, e.g. BinarySize), and generates a full report of a given Swift Package product for a specific version. """, - version: "1.3.4" - ) + version: "1.3.4" + ) + + @OptionGroup var allArguments: AllArguments - @OptionGroup var allArguments: AllArguments + public init() {} - public init() {} + public func run() async throws { + try runArgumentsValidation(arguments: allArguments) + var swiftPackage = makeSwiftPackage(from: allArguments) + swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) - public func run() throws { - try runArgumentsValidation(arguments: allArguments) - var swiftPackage = makeSwiftPackage(from: allArguments) - swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) + let package = try await validate( + swiftPackage: &swiftPackage, + verbose: allArguments.verbose + ) - let packageContent = try validate( - swiftPackage: &swiftPackage, - verbose: allArguments.verbose - ) + let report = Report(swiftPackage: swiftPackage) - let report = Report(swiftPackage: swiftPackage) + let packageWrapper = PackageWrapper(from: package) - // Providers have a synchronous API and are run in sequence. Each of them, even when performing async tasks, have to fulfill a sync API. - // For current setup it works as wanted, since the only heavy provider is binary size, that executes xcodebuild commands that are logged into the console - // when verbose flag is passed in. All other providers have sync logic that consumes PackageContent (Package.swift) decoded and provide specific - // information over it. - // Adding providers with asynchronous tasks should be avoided, and in case of adding any appears, things can be re-evaluated to run providers concurrently. - var providedInfos: [ProvidedInfo] = [] - SwiftPackageInfo.subcommandsProviders.forEach { subcommandProvider in - subcommandProvider( - swiftPackage, - packageContent, - allArguments.verbose - ) - .onSuccess { providedInfos.append($0) } - .onFailure { Console.default.write($0.message) } - } - try report.generate( - for: providedInfos, - format: allArguments.report - ) - } + // Providers have a synchronous API and are run in sequence. Each of them, even when performing async tasks, have to fulfill a sync API. + // For current setup it works as wanted, since the only heavy provider is binary size, that executes xcodebuild commands that are logged into the console + // when verbose flag is passed in. All other providers have sync logic that consumes PackageContent (Package.swift) decoded and provide specific + // information over it. + // Adding providers with asynchronous tasks should be avoided, and in case of adding any appears, things can be re-evaluated to run providers concurrently. + var providedInfos: [ProvidedInfo] = [] + SwiftPackageInfo.subcommandsProviders.forEach { subcommandProvider in + subcommandProvider( + swiftPackage, + packageWrapper, + allArguments.verbose + ) + .onSuccess { providedInfos.append($0) } + .onFailure { Console.default.write($0.message) } + } + try report.generate( + for: providedInfos, + format: allArguments.report + ) } + } } diff --git a/Sources/Run/Subcommands/Platforms.swift b/Sources/Run/Subcommands/Platforms.swift index ab9e2fe..959ef4a 100644 --- a/Sources/Run/Subcommands/Platforms.swift +++ b/Sources/Run/Subcommands/Platforms.swift @@ -24,44 +24,46 @@ import App import Reports extension SwiftPackageInfo { - public struct Platforms: ParsableCommand { - public static var configuration = CommandConfiguration( - abstract: "Shows platforms supported b a Package product.", - discussion: """ + public struct Platforms: AsyncParsableCommand { + public static var configuration = CommandConfiguration( + abstract: "Shows platforms supported b a Package product.", + discussion: """ Informs supported platforms by a given Package.swift and its products, e.g 'iOS with 9.0 minimum deployment target'. """, - version: "1.3.4" - ) + version: "1.3.4" + ) + + @OptionGroup var allArguments: AllArguments - @OptionGroup var allArguments: AllArguments + public init() {} - public init() {} + public func run() async throws { + try runArgumentsValidation(arguments: allArguments) + var swiftPackage = makeSwiftPackage(from: allArguments) + swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) - public func run() throws { - try runArgumentsValidation(arguments: allArguments) - var swiftPackage = makeSwiftPackage(from: allArguments) - swiftPackage.messages.forEach(Console.default.lineBreakAndWrite) + let package = try await validate( + swiftPackage: &swiftPackage, + verbose: allArguments.verbose + ) - let packageContent = try validate( - swiftPackage: &swiftPackage, - verbose: allArguments.verbose - ) + let report = Report(swiftPackage: swiftPackage) - let report = Report(swiftPackage: swiftPackage) + let packageWrapper = PackageWrapper(from: package) - try PlatformsProvider.fetchInformation( - for: swiftPackage, - packageContent: packageContent, - verbose: allArguments.verbose - ) - .onSuccess { - try report.generate( - for: $0, - format: allArguments.report - ) - } - .onFailure { Console.default.write($0.message) } - } + try PlatformsProvider.fetchInformation( + for: swiftPackage, + package: packageWrapper, + verbose: allArguments.verbose + ) + .onSuccess { + try report.generate( + for: $0, + format: allArguments.report + ) + } + .onFailure { Console.default.write($0.message) } } + } } diff --git a/Sources/Run/SwiftPackageInfo.swift b/Sources/Run/SwiftPackageInfo.swift index 7725af1..edc3eb2 100644 --- a/Sources/Run/SwiftPackageInfo.swift +++ b/Sources/Run/SwiftPackageInfo.swift @@ -24,9 +24,12 @@ import Core import App import Reports +import PackageModel + // MARK: - Main parsable command -public struct SwiftPackageInfo: ParsableCommand { +@main +public struct SwiftPackageInfo: AsyncParsableCommand { public static var configuration = CommandConfiguration( abstract: "A tool for analyzing Swift Packages", discussion: """ @@ -45,9 +48,9 @@ public struct SwiftPackageInfo: ParsableCommand { ) static var subcommandsProviders: [InfoProvider] = [ - BinarySizeProvider.fetchInformation(for:packageContent:verbose:), - PlatformsProvider.fetchInformation(for:packageContent:verbose:), - DependenciesProvider.fetchInformation(for:packageContent:verbose:) + BinarySizeProvider.fetchInformation(for:package:verbose:), + PlatformsProvider.fetchInformation(for:package:verbose:), + DependenciesProvider.fetchInformation(for:package:verbose:) ] public init() {} @@ -158,9 +161,9 @@ extension ParsableCommand { func validate( swiftPackage: inout SwiftPackage, verbose: Bool - ) throws -> PackageContent { + ) async throws -> Package { let swiftPackageService = SwiftPackageService() - let packageResponse = try swiftPackageService.validate( + let packageResponse = try await swiftPackageService.validate( swiftPackage: swiftPackage, verbose: verbose ) @@ -200,6 +203,6 @@ extension ParsableCommand { swiftPackage.product = firstProduct } - return packageResponse.packageContent + return packageResponse.package } } diff --git a/Tests/AppTests/Fixtures/Fixture+PackageContent.swift b/Tests/AppTests/Fixtures/Fixture+PackageContent.swift index 4a46020..ee8f3c0 100644 --- a/Tests/AppTests/Fixtures/Fixture+PackageContent.swift +++ b/Tests/AppTests/Fixtures/Fixture+PackageContent.swift @@ -17,6 +17,25 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// Copyright (c) 2022 Felipe Marino +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. import Foundation import CoreTestSupport @@ -24,30 +43,26 @@ import CoreTestSupport @testable import Core public extension Fixture { - static func makePackageContent( - platforms: [PackageContent.Platform] = [], - products: [PackageContent.Product] = [], - dependencies: [PackageContent.Dependency] = [], - targets: [PackageContent.Target] = [], - swiftLanguageVersions: [String]? = [] - ) -> PackageContent { - .init( - name: "Package", - platforms: platforms, - products: products, - dependencies: dependencies, - targets: targets, - swiftLanguageVersions: swiftLanguageVersions - ) - } + static func makePackageWrapper( + products: [PackageWrapper.Product] = [], + platforms: [PackageWrapper.Platform] = [], + targets: [PackageWrapper.Target] = [], + swiftLanguageVersions: [String]? = [] + ) -> PackageWrapper { + PackageWrapper( + products: products, + platforms: platforms, + targets: targets + ) + } - static func makePackageContentPlatform( - platformName: String = "ios", - version: String = "13.5" - ) -> PackageContent.Platform { - .init( - platformName: platformName, - version: version - ) - } + static func makePackageContentPlatform( + platformName: String = "ios", + version: String = "13.5" + ) -> PackageWrapper.Platform { + .init( + platformName: platformName, + version: version + ) + } } diff --git a/Tests/AppTests/Providers/BinarySizeProvider/BinarySizeProviderTests.swift b/Tests/AppTests/Providers/BinarySizeProvider/BinarySizeProviderTests.swift index 6e12dab..c0e6d09 100644 --- a/Tests/AppTests/Providers/BinarySizeProvider/BinarySizeProviderTests.swift +++ b/Tests/AppTests/Providers/BinarySizeProvider/BinarySizeProviderTests.swift @@ -25,106 +25,117 @@ import CoreTestSupport @testable import Core final class BinarySizeProviderTests: XCTestCase { - func testFetchInformation() throws { - var defaultSizeMeasurerCallsCount = 0 - var lastVerbose: Bool? + func testFetchInformation() throws { + var defaultSizeMeasurerCallsCount = 0 + var lastVerbose: Bool? - var sizeMeasurerCallsCount = 0 - var lastSwiftPackage: SwiftPackage? - var lastIsDynamic: Bool? + var sizeMeasurerCallsCount = 0 + var lastSwiftPackage: SwiftPackage? + var lastIsDynamic: Bool? - defaultSizeMeasurer = { verbose in - lastVerbose = verbose - defaultSizeMeasurerCallsCount += 1 + defaultSizeMeasurer = { verbose in + lastVerbose = verbose + defaultSizeMeasurerCallsCount += 1 - return { swiftPackage, isDynamic in - lastSwiftPackage = swiftPackage - lastIsDynamic = isDynamic - sizeMeasurerCallsCount += 1 + return { swiftPackage, isDynamic in + lastSwiftPackage = swiftPackage + lastIsDynamic = isDynamic + sizeMeasurerCallsCount += 1 - return .init( - amount: 908, - formatted: "908 kb" - ) - } - } - - let productName = "Product" - let swiftPackage = Fixture.makeSwiftPackage( - product: productName - ) - let result = BinarySizeProvider.fetchInformation( - for: swiftPackage, - packageContent: Fixture.makePackageContent( - products: [ - .init( - name: productName, - targets: ["Target"], - kind: .library(.dynamic) - ) - ] - ), - verbose: true + return .init( + amount: 908, + formatted: "908 kb" ) + } + } - let providedInfo = try result.get() - XCTAssertEqual( - providedInfo.providerName, - "Binary Size" - ) - XCTAssertEqual( - providedInfo.providerKind, - .binarySize - ) + let productName = "Product" + let swiftPackage = Fixture.makeSwiftPackage( + product: productName + ) + let result = BinarySizeProvider.fetchInformation( + for: swiftPackage, + package: Fixture.makePackageWrapper( + products: [ + .init( + name: productName, + package: nil, + isDynamicLibrary: true, + targets: [ + .init( + name: "Target", + dependencies: [] + ) + ] + ) + ] + ), + verbose: true + ) - XCTAssertEqual( - defaultSizeMeasurerCallsCount, - 1 - ) - XCTAssertEqual( - lastVerbose, - true - ) - XCTAssertEqual( - sizeMeasurerCallsCount, - 1 - ) - XCTAssertEqual( - lastSwiftPackage, - swiftPackage - ) - XCTAssertEqual( - lastIsDynamic, - true - ) + let providedInfo = try result.get() + XCTAssertEqual( + providedInfo.providerName, + "Binary Size" + ) + XCTAssertEqual( + providedInfo.providerKind, + .binarySize + ) - XCTAssertEqual( - providedInfo.messages, - [ - ConsoleMessage( - text: "Binary size increases by ", - color: .noColor, - isBold: false, - hasLineBreakAfter: false - ), - ConsoleMessage( - text: "908 kb", - color: .yellow, - isBold: true, - hasLineBreakAfter: false - ) - ] - ) + XCTAssertEqual( + defaultSizeMeasurerCallsCount, + 1 + ) + XCTAssertEqual( + lastVerbose, + true + ) + XCTAssertEqual( + sizeMeasurerCallsCount, + 1 + ) + XCTAssertEqual( + lastSwiftPackage, + swiftPackage + ) + XCTAssertEqual( + lastIsDynamic, + true + ) - let encodedProvidedInfo = try JSONEncoder().encode(providedInfo) - let encodedProvidedInfoString = String( - data: encodedProvidedInfo, - encoding: .utf8 + XCTAssertEqual( + providedInfo.messages, + [ + ConsoleMessage( + text: "Binary size increases by ", + color: .noColor, + isBold: false, + hasLineBreakAfter: false + ), + ConsoleMessage( + text: "908 kb", + color: .yellow, + isBold: true, + hasLineBreakAfter: false ) + ] + ) - XCTAssertEqual( - encodedProvidedInfoString, - #"{"amount":908,"formatted":"908 kb"}"# - ) - } + let encodedProvidedInfo = try JSONEncoder.sortedAndPrettyPrinted.encode(providedInfo) + let encodedProvidedInfoString = String( + data: encodedProvidedInfo, + encoding: .utf8 + ) + + XCTAssertEqual( + encodedProvidedInfoString, + #""" + { + "amount" : 908, + "formatted" : "908 kb" + } + """# + ) + } } diff --git a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift index 727be6a..a0c0abb 100644 --- a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift +++ b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift @@ -25,443 +25,141 @@ import CoreTestSupport @testable import Core final class DependenciesProviderTests: XCTestCase { - /// Test behavior when a product external dependency is declared by name, without matching any declared - /// dependency name. - /// - /// - note: Is expected to return that there are no dependencies available, since unfortunately there's no way - /// to define a Target dependency when it's name doesn't match with any of available Package dependencies names. - func testProvidedInfoWhenHasExternalDependencyDeclaredByNameThatDoesNotMatch() throws { - let result = DependenciesProvider.fetchInformation( - for: Fixture.makeSwiftPackage(), - packageContent: Fixture.makePackageContent( - products: [ - .init( - name: "Some", - targets: [ - "Target1" - ], - kind: .library(.automatic) - ) - ], - dependencies: [ - .init( - name: "dependency-1", - urlString: "https://www.some.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "dependency-2", - urlString: "https://www.some2.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ) - ], - targets: [ - .init( - name: "Target1", - dependencies: [ - .byName( - [ - .name("Dependency1"), - .name("Target2") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target2", - dependencies: [ - .byName( - [ - .name("Dependency1") - ] - ) - ], - kind: .regular - ) - ] - ), - verbose: true - ) - - let providedInfo = try result.get() - XCTAssertEqual( - providedInfo.providerName, - "Dependencies" - ) - - XCTAssertEqual( - providedInfo.messages, - [ - .init( - text: "No third-party dependencies :)", - hasLineBreakAfter: false - ) - ] - ) - - let encodedProvidedInfo = try JSONEncoder().encode(providedInfo) - let encodedProvidedInfoString = String( - data: encodedProvidedInfo, - encoding: .utf8 - ) - - XCTAssertEqual( - encodedProvidedInfoString, - "[]" - ) - } - - func testProvidedInfo() throws { - let result = DependenciesProvider.fetchInformation( - for: Fixture.makeSwiftPackage(), - packageContent: Fixture.makePackageContent( - products: [ - .init( - name: "Some", - targets: [ - "Target1" - ], - kind: .library(.automatic) - ) - ], - dependencies: [ - .init( - name: "dependency-1", - urlString: "https://www.some.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "dependency-2", - urlString: "https://www.some2.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "dependency-3", - urlString: "https://www.some3.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ) - ], - targets: [ - .init( - name: "Target1", - dependencies: [ - .byName( - [ - .name("dependency-1") - ] - ), - .byName( - [ - .name("Target2") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target2", - dependencies: [ - .byName( - [ - .name("dependency-2") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target3", - dependencies: [ - .byName( - [ - .name("dependency-1") - ] - ), - .byName( - [ - .name("dependency-3") - ] - ) - ], - kind: .regular - ) - ] - ), - verbose: true - ) - - let providedInfo = try result.get() - XCTAssertEqual( - providedInfo.providerName, - "Dependencies" - ) - - XCTAssertEqual( - providedInfo.messages, - [ - .init( - text: "dependency-1", - hasLineBreakAfter: false - ), - .init( - text: " v. 1.0.0", - hasLineBreakAfter: false - ), - .init( - text: " | ", - hasLineBreakAfter: false - ), - .init( - text: "dependency-2", - hasLineBreakAfter: false - ), - .init( - text: " v. 1.0.0", - hasLineBreakAfter: false - ) - ] - ) - - let encodedProvidedInfo = try JSONEncoder().encode(providedInfo) - let encodedProvidedInfoString = String( - data: encodedProvidedInfo, - encoding: .utf8 - ) - - XCTAssertEqual( - encodedProvidedInfoString, - #"[{"name":"dependency-1","version":"1.0.0"},{"name":"dependency-2","version":"1.0.0"}]"# - ) - } - - func testProvidedInfoWithManyNestedAndIndirectDependencies() throws { - let result = DependenciesProvider.fetchInformation( - for: Fixture.makeSwiftPackage(), - packageContent: Fixture.makePackageContent( - products: [ - .init( - name: "Some", - targets: [ - "Target1" - ], - kind: .library(.automatic) - ) - ], - dependencies: [ - .init( - name: "dependency-1", - urlString: "https://www.some.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "dependency-2", - urlString: "https://www.some2.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "dependency-3", - urlString: "https://www.some3.com", - requirement: .init( - range: [ - .init( - lowerBound: "1.0.0", - upperBound: "1.1.0" - ) - ], - revision: [], - branch: [] - ) - ) - ], - targets: [ - .init( - name: "Target1", - dependencies: [ - .byName( - [ - .name("dependency-1") - ] - ), - .target( - [ - .name("Target2") - ] - ), - .target( - [ - .name("Target3") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target2", - dependencies: [ - .byName( - [ - .name("dependency-1"), - .name("dependency-1") - ] - ), - .product( - [ - .name("dependency-2"), - .name("dependency-2"), - ] - ), - .target( - [ - .name("Target3") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target3", - dependencies: [ - .byName( - [ - .name("dependency-1"), - .name("dependency-1") - ] - ), - .product( - [ - .name("dependency-3"), - .name("dependency-3") - ] - ) - ], - kind: .regular - ) - ] - ), - verbose: true - ) - - let providedInfo = try result.get() - XCTAssertEqual( - providedInfo.providerName, - "Dependencies" - ) - XCTAssertEqual( - providedInfo.providerKind, - .dependencies - ) - - XCTAssertEqual( - providedInfo.messages, - [ - .init( - text: "dependency-1", - hasLineBreakAfter: false - ), - .init( - text: " v. 1.0.0", - hasLineBreakAfter: false - ), - .init( - text: " | ", - hasLineBreakAfter: false - ), - .init( - text: "dependency-2", - hasLineBreakAfter: false - ), - .init( - text: " v. 1.0.0", - hasLineBreakAfter: false - ), - .init( - text: " | ", - hasLineBreakAfter: false - ), - .init( - text: "dependency-3", - hasLineBreakAfter: false - ), - .init( - text: " v. 1.0.0", - hasLineBreakAfter: false - ) + func testWithTransitiveDependencies() throws { + let productName = "Some" + + let target3 = PackageWrapper.Target( + name: "Target3", + dependencies: [ + .product( + .init( + name: "dependency-2", + package: "dependency-2", + isDynamicLibrary: false, + targets: [] + ) + ), + .product( + .init( + name: "dependency-3", + package: "dependency-3", + isDynamicLibrary: false, + targets: [] + ) + ) + ] + ) + + let target2 = PackageWrapper.Target( + name: "Target2", + dependencies: [ + .product( + .init( + name: "dependency-2", + package: "dependency-2", + isDynamicLibrary: false, + targets: [] + ) + ), + .target(target3) + ] + ) + + let target1 = PackageWrapper.Target( + name: "Target1", + dependencies: [ + .product( + .init( + name: "dependency-1", + package: "dependency-1", + isDynamicLibrary: false, + targets: [] + ) + ), + .target(target2) + ] + ) + + let result = DependenciesProvider.fetchInformation( + for: Fixture.makeSwiftPackage( + product: productName + ), + package: Fixture.makePackageWrapper( + products: [ + .init( + name: productName, + package: nil, + isDynamicLibrary: nil, + targets: [ + target1 ] - ) - - let encodedProvidedInfo = try JSONEncoder().encode(providedInfo) - let encodedProvidedInfoString = String( - data: encodedProvidedInfo, - encoding: .utf8 - ) - - XCTAssertEqual( - encodedProvidedInfoString, - #"[{"name":"dependency-1","version":"1.0.0"},{"name":"dependency-2","version":"1.0.0"},{"name":"dependency-3","version":"1.0.0"}]"# - ) - } + ) + ], + targets: [ + target1, + target2, + target3 + ] + ), + verbose: true + ) + + let providedInfo = try result.get() + XCTAssertEqual( + providedInfo.providerName, + "Dependencies" + ) + + XCTAssertEqual( + providedInfo.messages, + [ + .init( + text: "dependency-1; dependency-1", + hasLineBreakAfter: false + ), + .init( + text: " | ", + hasLineBreakAfter: false + ), + .init( + text: "dependency-2; dependency-2", + hasLineBreakAfter: false + ), + .init( + text: " | ", + hasLineBreakAfter: false + ), + .init( + text: "dependency-3; dependency-3", + hasLineBreakAfter: false + ), + ] + ) + + let encodedProvidedInfo = try JSONEncoder.sortedAndPrettyPrinted.encode(providedInfo) + let encodedProvidedInfoString = String( + data: encodedProvidedInfo, + encoding: .utf8 + ) + + XCTAssertEqual( + encodedProvidedInfoString, + #""" + [ + { + "package" : "dependency-1", + "product" : "dependency-1" + }, + { + "package" : "dependency-2", + "product" : "dependency-2" + }, + { + "package" : "dependency-3", + "product" : "dependency-3" + } + ] + """# + ) + } } diff --git a/Tests/AppTests/Providers/PlatformsProvider/PlatformsProviderTests.swift b/Tests/AppTests/Providers/PlatformsProvider/PlatformsProviderTests.swift index 08adaef..0e625a8 100644 --- a/Tests/AppTests/Providers/PlatformsProvider/PlatformsProviderTests.swift +++ b/Tests/AppTests/Providers/PlatformsProvider/PlatformsProviderTests.swift @@ -25,73 +25,80 @@ import CoreTestSupport @testable import Core final class PlatformsProviderTests: XCTestCase { - func testFetchInformation() throws { - let result = PlatformsProvider.fetchInformation( - for: Fixture.makeSwiftPackage(), - packageContent: Fixture.makePackageContent( - platforms: [ - Fixture.makePackageContentPlatform(), - Fixture.makePackageContentPlatform( - platformName: "macos", - version: "10.15" - ), - Fixture.makePackageContentPlatform( - platformName: "watchos", - version: "7.3.2" - ), - Fixture.makePackageContentPlatform( - platformName: "tvos", - version: "14.0" - ), - ] - ), - verbose: true - ) + func testFetchInformation() throws { + let result = PlatformsProvider.fetchInformation( + for: Fixture.makeSwiftPackage(), + package: Fixture.makePackageWrapper( + platforms: [ + Fixture.makePackageContentPlatform(), + Fixture.makePackageContentPlatform( + platformName: "macos", + version: "10.15" + ), + Fixture.makePackageContentPlatform( + platformName: "watchos", + version: "7.3.2" + ), + Fixture.makePackageContentPlatform( + platformName: "tvos", + version: "14.0" + ), + ] + ), + verbose: true + ) - let providedInfo = try result.get() - XCTAssertEqual( - providedInfo.providerName, - "Platforms" - ) - XCTAssertEqual( - providedInfo.providerKind, - .platforms - ) + let providedInfo = try result.get() + XCTAssertEqual( + providedInfo.providerName, + "Platforms" + ) + XCTAssertEqual( + providedInfo.providerKind, + .platforms + ) - let expectedPlatformMessage: (String) -> ConsoleMessage = { contentText in - ConsoleMessage( - text: contentText, - color: .noColor, - isBold: true, - hasLineBreakAfter: false - ) - } - let expectedSeparatorMessage = ConsoleMessage( - text: " | ", - hasLineBreakAfter: false - ) - XCTAssertEqual( - providedInfo.messages, - [ - expectedPlatformMessage("ios from v. 13.5"), - expectedSeparatorMessage, - expectedPlatformMessage("macos from v. 10.15"), - expectedSeparatorMessage, - expectedPlatformMessage("watchos from v. 7.3.2"), - expectedSeparatorMessage, - expectedPlatformMessage("tvos from v. 14.0") - ] - ) + let expectedPlatformMessage: (String) -> ConsoleMessage = { contentText in + ConsoleMessage( + text: contentText, + color: .noColor, + isBold: true, + hasLineBreakAfter: false + ) + } + let expectedSeparatorMessage = ConsoleMessage( + text: " | ", + hasLineBreakAfter: false + ) + XCTAssertEqual( + providedInfo.messages, + [ + expectedPlatformMessage("ios from v. 13.5"), + expectedSeparatorMessage, + expectedPlatformMessage("macos from v. 10.15"), + expectedSeparatorMessage, + expectedPlatformMessage("watchos from v. 7.3.2"), + expectedSeparatorMessage, + expectedPlatformMessage("tvos from v. 14.0") + ] + ) - let encodedProvidedInfo = try JSONEncoder().encode(providedInfo) - let encodedProvidedInfoString = String( - data: encodedProvidedInfo, - encoding: .utf8 - ) + let encodedProvidedInfo = try JSONEncoder.sortedAndPrettyPrinted.encode(providedInfo) + let encodedProvidedInfoString = String( + data: encodedProvidedInfo, + encoding: .utf8 + ) - XCTAssertEqual( - encodedProvidedInfoString, - #"{"macOS":"10.15","tvOS":"14.0","iOS":"13.5","watchOS":"7.3.2"}"# - ) - } + XCTAssertEqual( + encodedProvidedInfoString, + #""" + { + "iOS" : "13.5", + "macOS" : "10.15", + "tvOS" : "14.0", + "watchOS" : "7.3.2" + } + """# + ) + } } diff --git a/Tests/AppTests/Services/SwiftPackageService/TagsResponseTests.swift b/Tests/AppTests/Services/SwiftPackageService/TagsResponseTests.swift deleted file mode 100644 index c989fbd..0000000 --- a/Tests/AppTests/Services/SwiftPackageService/TagsResponseTests.swift +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2022 Felipe Marino -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import XCTest -@testable import App - -final class TagsResponseTests: XCTestCase { - private let jsonDecoder: JSONDecoder = .init() - - func testDecodingFromJSON() throws { - let jsonString = """ - [ - { - "ref": "refs/tags/7.5.1", - "node_id": "MDM6UmVmODkwMzM1NTY6cmVmcy90YWdzLzcuNS4x", - "url": "https://api.github.com/repos/firebase/firebase-ios-sdk/git/refs/tags/7.5.1", - "object": { - "sha": "447cf74cc561d408bc66c0294145a624645038ac", - "type": "commit", - "url": "https://api.github.com/repos/firebase/firebase-ios-sdk/git/commits/447cf74cc561d408bc66c0294145a624645038ac" - } - }, - { - "ref": "refs/tags/7.6.0", - "node_id": "MDM6UmVmODkwMzM1NTY6cmVmcy90YWdzLzcuNi4w", - "url": "https://api.github.com/repos/firebase/firebase-ios-sdk/git/refs/tags/7.6.0", - "object": { - "sha": "0dd2ad1054177670dfa5bb1bbc6767e2a965095d", - "type": "commit", - "url": "https://api.github.com/repos/firebase/firebase-ios-sdk/git/commits/0dd2ad1054177670dfa5bb1bbc6767e2a965095d" - } - } - ] - """ - - let encodedJSON = try XCTUnwrap(jsonString.data(using: .utf8)) - let tagsResponse = try jsonDecoder.decode(TagsResponse.self, from: encodedJSON) - - XCTAssertEqual( - tagsResponse, - .init( - tags: [ - .init(name: "refs/tags/7.5.1"), - .init(name: "refs/tags/7.6.0") - ] - ) - ) - } -} diff --git a/Tests/CoreTests/Models/PackageContentTests.swift b/Tests/CoreTests/Models/PackageContentTests.swift deleted file mode 100644 index d73f609..0000000 --- a/Tests/CoreTests/Models/PackageContentTests.swift +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright (c) 2022 Felipe Marino -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import XCTest -import CoreTestSupport - -@testable import Core - -final class PackageContentTests: XCTestCase { - private let jsonDecoder: JSONDecoder = .init() - - func testDecodingFromJSON() throws { - let fixtureData = try dataFromJSON(named: "package_full", bundle: .module) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - XCTAssertEqual( - packageContent, - .defaultExpectedFullPackage() - ) - } - - func testIsDynamicLibrary() throws { - let packageContent = PackageContent( - name: "SomePackage", - platforms: [ - .init( - platformName: "ios", - version: "9.0" - ), - .init( - platformName: "macos", - version: "10.15" - ) - ], - products: [ - .init( - name: "Product1", - targets: [ - "Target1", - "Target2" - ], - kind: .library(.automatic) - ), - .init( - name: "Product2", - targets: [ - "Target1", - "Target3" - ], - kind: .library(.static) - ), - .init( - name: "Product3", - targets: [ - "Target2" - ], - kind: .executable - ), - .init( - name: "Product4", - targets: [ - "Target1" - ], - kind: .library(.dynamic) - ) - ], - dependencies: [], - targets: [], - swiftLanguageVersions: [] - ) - - packageContent.products.forEach { product in - XCTAssertEqual(product.isDynamicLibrary, product == packageContent.products.last) - } - } - - func testDifferentTypesOfDependencyRequirement() throws { - let fixtureData = try dataFromJSON(named: "package_with_multiple_dependencies", bundle: .module) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - let expectedPackageContent = PackageContent( - name: "SomePackage", - platforms: [], - products: [], - dependencies: [ - .init( - name: "swift-argument-parser", - urlString: "https://github.com/apple/swift-argument-parser", - requirement: .init( - range: [ - .init( - lowerBound: "0.3.0", - upperBound: "0.4.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "SomeDependency", - urlString: "https://github.com/someDev/some-dependency", - requirement: .init( - range: [], - revision: [ - "123456CrazyHash" - ], - branch: [] - ) - ), - .init( - name: "SomeOtherDependency", - urlString: "https://github.com/someOtherDev/some-other-dependency", - requirement: .init( - range: [], - revision: [], - branch: [ - "some-fork-123" - ] - ) - ) - ], - targets: [], - swiftLanguageVersions: [] - ) - - XCTAssertEqual( - packageContent, - expectedPackageContent - ) - } - - func testWhenTargetDependencyIsOfTypeTargetWithNestedPlatforms() throws { - let fixtureData = try dataFromJSON( - named: "package_with_custom_target_dependency", - bundle: .module - ) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - let expectedPackageContent = PackageContent( - name: "SomePackage", - platforms: [], - products: [], - dependencies: [], - targets: [ - .init( - name: "Target1", - dependencies: [ - .target( - [ - .name("Target2"), - .platforms( - .init( - platformNames: [ - "ios" - ] - ) - ) - ] - ) - ], - kind: .regular - ), - .init( - name: "Target2", - dependencies: [ - .product( - [ - .name("Product3"), - .platforms( - .init( - platformNames: [ - "ios" - ] - ) - ) - ] - ) - ], - kind: .regular - ), - .init( - name: "Target3", - dependencies: [ - .byName( - [ - .name("Name4"), - .platforms( - .init( - platformNames: [ - "ios" - ] - ) - ) - ] - ) - ], - kind: .regular - ) - ], - swiftLanguageVersions: [] - ) - - XCTAssertEqual( - packageContent, - expectedPackageContent - ) - } - - #if compiler(>=5.5) - func testWhenPackageContentIsGeneratedFromSwift5Dot5Toolchain() throws { - let fixtureData = try dataFromJSON( - named: "package_full_swift_5_5", - bundle: .module - ) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - XCTAssertEqual( - packageContent, - .defaultExpectedFullPackage() - ) - } - #endif - - - #if compiler(<5.7) - func testWhenPackageContentIsGeneratedFromSwift5Dot6Toolchain() throws { - let fixtureData = try dataFromJSON( - named: "package_full_swift_5_6", - bundle: .module - ) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - XCTAssertEqual( - packageContent, - .defaultExpectedFullPackage( - dependencies: [ - .init( - name: "swift-argument-parser", - urlString: "https://github.com/apple/swift-argument-parser", - requirement: .init( - range: [ - .init( - lowerBound: "0.3.0", - upperBound: "0.4.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "packages", - urlString: "path/Packages", - requirement: nil - ) - ] - ) - ) - } - #endif - - #if compiler(>=5.9) - func testWhenPackageContentIsGeneratedFromSwift5Dot9Toolchain() throws { - let fixtureData = try dataFromJSON( - named: "package_full_swift_5_9", - bundle: .module - ) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - XCTAssertEqual( - packageContent, - .defaultExpectedFullPackage( - dependencies: [ - .init( - name: "swift-argument-parser", - urlString: "https://github.com/apple/swift-argument-parser", - requirement: .init( - range: [ - .init( - lowerBound: "0.3.0", - upperBound: "0.4.0" - ) - ], - revision: [], - branch: [] - ) - ), - .init( - name: "packages", - urlString: "path/Packages", - requirement: nil - ) - ] - ) - ) - } - #endif - - func testWhenPackageDependencyHasIdentityOnly() throws { - let fixtureData = try dataFromJSON( - named: "package_identity_dependency", - bundle: .module - ) - let packageContent = try jsonDecoder.decode(PackageContent.self, from: fixtureData) - - XCTAssertEqual( - packageContent, - .defaultExpectedFullPackage(), - "Package should be decoded correctly when dependency has identity over name" - ) - } -} - -// MARK: - Fixtures - -private extension PackageContent { - static func defaultExpectedFullPackage( - dependencies: [PackageContent.Dependency] = [ - .init( - name: "swift-argument-parser", - urlString: "https://github.com/apple/swift-argument-parser", - requirement: .init( - range: [ - .init( - lowerBound: "0.3.0", - upperBound: "0.4.0" - ) - ], - revision: [], - branch: [] - ) - ) - ] - ) -> Self { - .init( - name: "SomePackage", - platforms: [ - .init( - platformName: "ios", - version: "9.0" - ), - .init( - platformName: "macos", - version: "10.15" - ) - ], - products: [ - .init( - name: "Product1", - targets: [ - "Target1", - "Target2" - ], - kind: .library(.automatic) - ), - .init( - name: "Product2", - targets: [ - "Target1", - "Target3" - ], - kind: .library(.static) - ), - .init( - name: "Product3", - targets: [ - "Target2" - ], - kind: .executable - ), - .init( - name: "Product4", - targets: [ - "Target1" - ], - kind: .library(.dynamic) - ) - ], - dependencies: dependencies, - targets: [ - .init( - name: "Target1", - dependencies: [ - .product( - [ - .name("swift-argument-parser") - ] - ) - ], - kind: .regular - ), - .init( - name: "Target2", - dependencies: [ - .byName( - [ - .name("Target1") - ] - ) - ], - kind: .binary - ), - .init( - name: "Target3", - dependencies: [ - .product( - [ - .name("ArgumentParser"), - .name("swift-argument-parser") - ] - ), - .target( - [ - .name("Target1") - ] - ) - ], - kind: .test - ), - .init( - name: "Target4", - dependencies: [], - kind: .system - ) - ], - swiftLanguageVersions: [ - "5" - ] - ) - } -} diff --git a/Tests/CoreTests/Resources/package_full.json b/Tests/CoreTests/Resources/package_full.json deleted file mode 100644 index d7a79a3..0000000 --- a/Tests/CoreTests/Resources/package_full.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies" : [ - { - "name" : "swift-argument-parser", - "requirement" : { - "range" : [ - { - "lowerBound" : "0.3.0", - "upperBound" : "0.4.0" - } - ] - }, - "url" : "https:\/\/github.com\/apple\/swift-argument-parser" - } - ], - "name" : "SomePackage", - "pkgConfig" : null, - "platforms" : [ - { - "options" : [ - - ], - "platformName" : "ios", - "version" : "9.0" - }, - { - "options" : [ - - ], - "platformName" : "macos", - "version" : "10.15" - } - ], - "products" : [ - { - "name" : "Product1", - "targets" : [ - "Target1", - "Target2" - ], - "type" : { - "library" : [ - "automatic" - ] - } - }, - { - "name" : "Product2", - "targets" : [ - "Target1", - "Target3" - ], - "type" : { - "library" : [ - "static" - ] - } - }, - { - "name" : "Product3", - "targets" : [ - "Target2" - ], - "type" : { - "executable" : null - } - }, - { - "name" : "Product4", - "targets" : [ - "Target1" - ], - "type" : { - "library" : [ - "dynamic" - ] - } - } - ], - "providers" : null, - "swiftLanguageVersions" : [ - "5" - ], - "targets" : [ - { - "dependencies" : [ - { - "product" : [ - "swift-argument-parser", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Sources/2", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "binary" - }, - { - "dependencies" : [ - { - "product" : [ - "ArgumentParser", - "swift-argument-parser", - null - ] - }, - { - "target" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Tests", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "test" - }, - { - "dependencies" : [ - - ], - "exclude" : [ - - ], - "name" : "Target4", - "path" : "Path", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "system" - } - ], - "toolsVersion" : { - "_version" : "5.0.0" - } -} diff --git a/Tests/CoreTests/Resources/package_full_swift_5_5.json b/Tests/CoreTests/Resources/package_full_swift_5_5.json deleted file mode 100644 index f12fe06..0000000 --- a/Tests/CoreTests/Resources/package_full_swift_5_5.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies": [ - { - "scm": [ - { - "identity": "swift-argument-parser", - "location": "https://github.com/apple/swift-argument-parser", - "name": "swift-argument-parser", - "productFilter": null, - "requirement": { - "range": [ - { - "lowerBound": "0.3.0", - "upperBound": "0.4.0" - } - ] - } - } - ] - } - ], - "name" : "SomePackage", - "pkgConfig" : null, - "platforms" : [ - { - "options" : [ - - ], - "platformName" : "ios", - "version" : "9.0" - }, - { - "options" : [ - - ], - "platformName" : "macos", - "version" : "10.15" - } - ], - "products" : [ - { - "name" : "Product1", - "targets" : [ - "Target1", - "Target2" - ], - "type" : { - "library" : [ - "automatic" - ] - } - }, - { - "name" : "Product2", - "targets" : [ - "Target1", - "Target3" - ], - "type" : { - "library" : [ - "static" - ] - } - }, - { - "name" : "Product3", - "targets" : [ - "Target2" - ], - "type" : { - "executable" : null - } - }, - { - "name" : "Product4", - "targets" : [ - "Target1" - ], - "type" : { - "library" : [ - "dynamic" - ] - } - } - ], - "providers" : null, - "swiftLanguageVersions" : [ - "5" - ], - "targets" : [ - { - "dependencies" : [ - { - "product" : [ - "swift-argument-parser", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Sources/2", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "binary" - }, - { - "dependencies" : [ - { - "product" : [ - "ArgumentParser", - "swift-argument-parser", - null - ] - }, - { - "target" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Tests", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "test" - }, - { - "dependencies" : [ - - ], - "exclude" : [ - - ], - "name" : "Target4", - "path" : "Path", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "system" - } - ], - "toolsVersion" : { - "_version" : "5.0.0" - } -} diff --git a/Tests/CoreTests/Resources/package_full_swift_5_6.json b/Tests/CoreTests/Resources/package_full_swift_5_6.json deleted file mode 100644 index b875b3c..0000000 --- a/Tests/CoreTests/Resources/package_full_swift_5_6.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies" : [ - { - "sourceControl" : [ - { - "identity": "swift-argument-parser", - "location" : { - "remote" : [ - "https://github.com/apple/swift-argument-parser" - ] - }, - "productFilter" : null, - "requirement" : { - "range" : [ - { - "lowerBound" : "0.3.0", - "upperBound" : "0.4.0" - } - ] - } - } - ], - }, - { - "fileSystem" : [ - { - "identity" : "packages", - "path" : "path/Packages", - "productFilter" : null - } - ] - } - ], - "name" : "SomePackage", - "packageKind" : { - "root" : [ - "path/swift-argument-parser" - ] - }, - "pkgConfig" : null, - "platforms" : [ - { - "options" : [ - - ], - "platformName" : "ios", - "version" : "9.0" - }, - { - "options" : [ - - ], - "platformName" : "macos", - "version" : "10.15" - } - ], - "products" : [ - { - "name" : "Product1", - "targets" : [ - "Target1", - "Target2" - ], - "type" : { - "library" : [ - "automatic" - ] - } - }, - { - "name" : "Product2", - "targets" : [ - "Target1", - "Target3" - ], - "type" : { - "library" : [ - "static" - ] - } - }, - { - "name" : "Product3", - "targets" : [ - "Target2" - ], - "type" : { - "executable" : null - } - }, - { - "name" : "Product4", - "targets" : [ - "Target1" - ], - "type" : { - "library" : [ - "dynamic" - ] - } - } - ], - "providers" : null, - "swiftLanguageVersions" : [ - "5" - ], - "targets" : [ - { - "dependencies" : [ - { - "product" : [ - "swift-argument-parser", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Sources/2", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "binary" - }, - { - "dependencies" : [ - { - "product" : [ - "ArgumentParser", - "swift-argument-parser", - null - ] - }, - { - "target" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Tests", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "test" - }, - { - "dependencies" : [ - - ], - "exclude" : [ - - ], - "name" : "Target4", - "path" : "Path", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "system" - } - ], - "toolsVersion" : { - "_version" : "5.3.0" - } -} diff --git a/Tests/CoreTests/Resources/package_full_swift_5_9.json b/Tests/CoreTests/Resources/package_full_swift_5_9.json deleted file mode 100644 index cd7bc96..0000000 --- a/Tests/CoreTests/Resources/package_full_swift_5_9.json +++ /dev/null @@ -1,206 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies" : [ - { - "sourceControl" : [ - { - "identity": "swift-argument-parser", - "location" : { - "remote" : [ - { - "urlString": "https://github.com/apple/swift-argument-parser" - } - ] - }, - "productFilter" : null, - "requirement" : { - "range" : [ - { - "lowerBound" : "0.3.0", - "upperBound" : "0.4.0" - } - ] - } - } - ], - }, - { - "fileSystem" : [ - { - "identity" : "packages", - "path" : "path/Packages", - "productFilter" : null - } - ] - } - ], - "name" : "SomePackage", - "packageKind" : { - "root" : [ - "path/swift-argument-parser" - ] - }, - "pkgConfig" : null, - "platforms" : [ - { - "options" : [ - - ], - "platformName" : "ios", - "version" : "9.0" - }, - { - "options" : [ - - ], - "platformName" : "macos", - "version" : "10.15" - } - ], - "products" : [ - { - "name" : "Product1", - "targets" : [ - "Target1", - "Target2" - ], - "type" : { - "library" : [ - "automatic" - ] - } - }, - { - "name" : "Product2", - "targets" : [ - "Target1", - "Target3" - ], - "type" : { - "library" : [ - "static" - ] - } - }, - { - "name" : "Product3", - "targets" : [ - "Target2" - ], - "type" : { - "executable" : null - } - }, - { - "name" : "Product4", - "targets" : [ - "Target1" - ], - "type" : { - "library" : [ - "dynamic" - ] - } - } - ], - "providers" : null, - "swiftLanguageVersions" : [ - "5" - ], - "targets" : [ - { - "dependencies" : [ - { - "product" : [ - "swift-argument-parser", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Sources/2", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "binary" - }, - { - "dependencies" : [ - { - "product" : [ - "ArgumentParser", - "swift-argument-parser", - null - ] - }, - { - "target" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Tests", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "test" - }, - { - "dependencies" : [ - - ], - "exclude" : [ - - ], - "name" : "Target4", - "path" : "Path", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "system" - } - ], - "toolsVersion" : { - "_version" : "5.3.0" - } -} diff --git a/Tests/CoreTests/Resources/package_identity_dependency.json b/Tests/CoreTests/Resources/package_identity_dependency.json deleted file mode 100644 index 0101344..0000000 --- a/Tests/CoreTests/Resources/package_identity_dependency.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies": [ - { - "scm": [ - { - "identity": "swift-argument-parser", - "location": "https://github.com/apple/swift-argument-parser", - "productFilter": null, - "requirement": { - "range": [ - { - "lowerBound": "0.3.0", - "upperBound": "0.4.0" - } - ] - } - } - ] - } - ], - "name" : "SomePackage", - "pkgConfig" : null, - "platforms" : [ - { - "options" : [ - - ], - "platformName" : "ios", - "version" : "9.0" - }, - { - "options" : [ - - ], - "platformName" : "macos", - "version" : "10.15" - } - ], - "products" : [ - { - "name" : "Product1", - "targets" : [ - "Target1", - "Target2" - ], - "type" : { - "library" : [ - "automatic" - ] - } - }, - { - "name" : "Product2", - "targets" : [ - "Target1", - "Target3" - ], - "type" : { - "library" : [ - "static" - ] - } - }, - { - "name" : "Product3", - "targets" : [ - "Target2" - ], - "type" : { - "executable" : null - } - }, - { - "name" : "Product4", - "targets" : [ - "Target1" - ], - "type" : { - "library" : [ - "dynamic" - ] - } - } - ], - "providers" : null, - "swiftLanguageVersions" : [ - "5" - ], - "targets" : [ - { - "dependencies" : [ - { - "product" : [ - "swift-argument-parser", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Sources/2", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "binary" - }, - { - "dependencies" : [ - { - "product" : [ - "ArgumentParser", - "swift-argument-parser", - null - ] - }, - { - "target" : [ - "Target1", - null - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Tests", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "test" - }, - { - "dependencies" : [ - - ], - "exclude" : [ - - ], - "name" : "Target4", - "path" : "Path", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "system" - } - ], - "toolsVersion" : { - "_version" : "5.0.0" - } -} diff --git a/Tests/CoreTests/Resources/package_with_custom_target_dependency.json b/Tests/CoreTests/Resources/package_with_custom_target_dependency.json deleted file mode 100644 index 3661a82..0000000 --- a/Tests/CoreTests/Resources/package_with_custom_target_dependency.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies" : [], - "name" : "SomePackage", - "pkgConfig" : null, - "platforms" : [], - "products" : [], - "providers" : null, - "swiftLanguageVersions" : [], - "targets" : [ - { - "dependencies" : [ - { - "target" : [ - "Target2", - { - "platformNames": [ - "ios" - ] - } - ] - } - ], - "exclude" : [ - - ], - "name" : "Target1", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "product" : [ - "Product3", - { - "platformNames": [ - "ios" - ] - } - ] - } - ], - "exclude" : [ - - ], - "name" : "Target2", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - }, - { - "dependencies" : [ - { - "byName" : [ - "Name4", - { - "platformNames": [ - "ios" - ] - } - ] - } - ], - "exclude" : [ - - ], - "name" : "Target3", - "path" : "Path1", - "resources" : [ - - ], - "settings" : [ - - ], - "type" : "regular" - } - ], - "toolsVersion" : { - "_version" : "5.0.0" - } -} diff --git a/Tests/CoreTests/Resources/package_with_multiple_dependencies.json b/Tests/CoreTests/Resources/package_with_multiple_dependencies.json deleted file mode 100644 index 8719c02..0000000 --- a/Tests/CoreTests/Resources/package_with_multiple_dependencies.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "cLanguageStandard" : null, - "cxxLanguageStandard" : null, - "dependencies" : [ - { - "name" : "swift-argument-parser", - "requirement" : { - "range" : [ - { - "lowerBound" : "0.3.0", - "upperBound" : "0.4.0" - } - ] - }, - "url" : "https:\/\/github.com\/apple\/swift-argument-parser" - }, - { - "name" : "SomeDependency", - "requirement" : { - "revision" : [ - "123456CrazyHash" - ] - }, - "url" : "https:\/\/github.com\/someDev\/some-dependency" - }, - { - "name" : "SomeOtherDependency", - "requirement" : { - "branch" : [ - "some-fork-123" - ] - }, - "url" : "https:\/\/github.com\/someOtherDev\/some-other-dependency" - } - ], - "name" : "SomePackage", - "pkgConfig" : null, - "platforms" : [], - "products" : [], - "providers" : null, - "swiftLanguageVersions" : [], - "targets" : [], - "toolsVersion" : { - "_version" : "5.0.0" - } -} From 5311a68ad5eb332eba034577192b587e7c36ea7a Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Sun, 14 Jan 2024 21:40:23 +0100 Subject: [PATCH 3/6] Improve dependencies console message --- .../DependenciesProvider.swift | 27 +++- .../DependenciesProviderTests.swift | 134 ++++++++++++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift b/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift index 066bced..ee5dd08 100644 --- a/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift +++ b/Sources/App/Providers/DependenciesProvider/DependenciesProvider.swift @@ -120,6 +120,12 @@ struct DependenciesInformation: Equatable, CustomConsoleMessagesConvertible { } let dependencies: [Dependency] + private let consoleDependencies: [Dependency] + + init(dependencies: [Dependency]) { + self.dependencies = dependencies + self.consoleDependencies = Array(dependencies.prefix(3)) + } var messages: [ConsoleMessage] { buildConsoleMessages() } @@ -132,15 +138,15 @@ struct DependenciesInformation: Equatable, CustomConsoleMessagesConvertible { ) ] } else { - return dependencies.map { dependency -> [ConsoleMessage] in + return consoleDependencies.enumerated().map { index, dependency -> [ConsoleMessage] in var messages: [ConsoleMessage] = [ .init( - text: "\(dependency.product); \(dependency.package)", + text: "\(dependency.product)", hasLineBreakAfter: false ), ] - let isLast = dependency == dependencies.last + let isLast = index == consoleDependencies.count - 1 if isLast == false { messages.append( .init( @@ -150,6 +156,21 @@ struct DependenciesInformation: Equatable, CustomConsoleMessagesConvertible { ) } + if isLast && dependencies.count > 3 { + messages.append( + .init( + text: " | ", + hasLineBreakAfter: false + ) + ) + messages.append( + .init( + text: "Use `--report jsonDump` to see all..", + hasLineBreakAfter: false + ) + ) + } + return messages } .reduce( diff --git a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift index a0c0abb..ad8dde9 100644 --- a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift +++ b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift @@ -162,4 +162,138 @@ final class DependenciesProviderTests: XCTestCase { """# ) } + + func testWithMoreThan3ExternalDependencies() throws { + let productName = "Some" + + let target = PackageWrapper.Target( + name: "Target1", + dependencies: [ + .product( + .init( + name: "dependency-1", + package: "dependency-1", + isDynamicLibrary: false, + targets: [] + ) + ), + .product( + .init( + name: "dependency-2", + package: "dependency-2", + isDynamicLibrary: false, + targets: [] + ) + ), + .product( + .init( + name: "dependency-3", + package: "dependency-3", + isDynamicLibrary: false, + targets: [] + ) + ), + .product( + .init( + name: "dependency-4", + package: "dependency-4", + isDynamicLibrary: false, + targets: [] + ) + ), + ] + ) + + let result = DependenciesProvider.fetchInformation( + for: Fixture.makeSwiftPackage( + product: productName + ), + package: Fixture.makePackageWrapper( + products: [ + .init( + name: productName, + package: nil, + isDynamicLibrary: nil, + targets: [ + target + ] + ) + ], + targets: [ + target + ] + ), + verbose: true + ) + + let providedInfo = try result.get() + XCTAssertEqual( + providedInfo.providerName, + "Dependencies" + ) + + XCTAssertEqual( + providedInfo.messages, + [ + .init( + text: "dependency-1", + hasLineBreakAfter: false + ), + .init( + text: " | ", + hasLineBreakAfter: false + ), + .init( + text: "dependency-2", + hasLineBreakAfter: false + ), + .init( + text: " | ", + hasLineBreakAfter: false + ), + .init( + text: "dependency-3", + hasLineBreakAfter: false + ), + .init( + text: " | ", + hasLineBreakAfter: false + ), + .init( + text: "Use `--report jsonDump` to see all..", + hasLineBreakAfter: false + ), + ] + ) + + let encodedProvidedInfo = try JSONEncoder.sortedAndPrettyPrinted.encode(providedInfo) + let encodedProvidedInfoString = String( + data: encodedProvidedInfo, + encoding: .utf8 + ) + + XCTAssertEqual( + encodedProvidedInfoString, + #""" + [ + { + "package" : "dependency-1", + "product" : "dependency-1" + }, + { + "package" : "dependency-2", + "product" : "dependency-2" + }, + { + "package" : "dependency-3", + "product" : "dependency-3" + }, + { + "package" : "dependency-4", + "product" : "dependency-4" + } + ] + """# + ) + } } From 2432f97fa0d73b4c682b3727aaa508f867549794 Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Sun, 14 Jan 2024 21:42:26 +0100 Subject: [PATCH 4/6] Remove custom dir --- .../xcode/xcshareddata/xcschemes/swift-package-info.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme index f1296af..e6485c5 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-package-info.xcscheme @@ -173,7 +173,7 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" - useCustomWorkingDirectory = "YES" + useCustomWorkingDirectory = "NO" customWorkingDirectory = "/Users/marino.felipe/Documents/projects/personal/swift-package-info" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" From d0a43e55730111825c72b4d212ae22aa3360f01e Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Tue, 16 Jan 2024 08:38:09 +0100 Subject: [PATCH 5/6] Update Package swift tools version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 1978135..11fb61c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 93212354c6a3529c86a9f60b1bd1674a1ba54fa4 Mon Sep 17 00:00:00 2001 From: Felipe Marino Date: Tue, 16 Jan 2024 08:58:01 +0100 Subject: [PATCH 6/6] Fix test --- .../DependenciesProvider/DependenciesProviderTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift index ad8dde9..ecd602f 100644 --- a/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift +++ b/Tests/AppTests/Providers/DependenciesProvider/DependenciesProviderTests.swift @@ -114,7 +114,7 @@ final class DependenciesProviderTests: XCTestCase { providedInfo.messages, [ .init( - text: "dependency-1; dependency-1", + text: "dependency-1", hasLineBreakAfter: false ), .init( @@ -122,7 +122,7 @@ final class DependenciesProviderTests: XCTestCase { hasLineBreakAfter: false ), .init( - text: "dependency-2; dependency-2", + text: "dependency-2", hasLineBreakAfter: false ), .init( @@ -130,7 +130,7 @@ final class DependenciesProviderTests: XCTestCase { hasLineBreakAfter: false ), .init( - text: "dependency-3; dependency-3", + text: "dependency-3", hasLineBreakAfter: false ), ]