diff --git a/CMakeLists.txt b/CMakeLists.txt index ddbb5447b3d..029f3ffa337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,5 +51,8 @@ find_package(dispatch QUIET) find_package(Foundation QUIET) find_package(SQLite3 REQUIRED) +# Enable `package` modifier for the whole package. +add_compile_options("$<$:-package-name;SwiftPM>") + add_subdirectory(Sources) add_subdirectory(cmake/modules) diff --git a/Sources/Basics/CMakeLists.txt b/Sources/Basics/CMakeLists.txt index dd61eda01eb..c27ffb59ffd 100644 --- a/Sources/Basics/CMakeLists.txt +++ b/Sources/Basics/CMakeLists.txt @@ -12,8 +12,11 @@ add_library(Basics Archiver/ZipArchiver.swift Archiver/UniversalArchiver.swift AuthorizationProvider.swift - ByteString+Extensions.swift Cancellator.swift + Collections/ByteString+Extensions.swift + Collections/Dictionary+Extensions.swift + Collections/IdentifiableSet.swift + Collections/String+Extensions.swift Concurrency/ConcurrencyHelpers.swift Concurrency/NSLock+Extensions.swift Concurrency/SendableBox.swift @@ -21,7 +24,6 @@ add_library(Basics Concurrency/ThreadSafeBox.swift Concurrency/ThreadSafeKeyValueStore.swift Concurrency/TokenBucket.swift - Dictionary+Extensions.swift DispatchTimeInterval+Extensions.swift EnvironmentVariables.swift Errors.swift @@ -53,7 +55,6 @@ add_library(Basics Sandbox.swift SendableTimeInterval.swift Serialization/SerializedJSON.swift - String+Extensions.swift SwiftVersion.swift SQLiteBackedCache.swift Triple+Basics.swift diff --git a/Sources/Basics/ByteString+Extensions.swift b/Sources/Basics/Collections/ByteString+Extensions.swift similarity index 100% rename from Sources/Basics/ByteString+Extensions.swift rename to Sources/Basics/Collections/ByteString+Extensions.swift diff --git a/Sources/Basics/Dictionary+Extensions.swift b/Sources/Basics/Collections/Dictionary+Extensions.swift similarity index 100% rename from Sources/Basics/Dictionary+Extensions.swift rename to Sources/Basics/Collections/Dictionary+Extensions.swift diff --git a/Sources/Basics/Collections/IdentifiableSet.swift b/Sources/Basics/Collections/IdentifiableSet.swift new file mode 100644 index 00000000000..3a0e671d169 --- /dev/null +++ b/Sources/Basics/Collections/IdentifiableSet.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Replacement for `Set` elements that can't be `Hashable`, but can be `Identifiable`. +public struct IdentifiableSet: Collection { + public init() { + self.storage = [:] + } + + public init(_ sequence: some Sequence) { + self.storage = .init(pickLastWhenDuplicateFound: sequence) + } + + fileprivate typealias Storage = [Element.ID: Element] + + public struct Index: Comparable { + public static func < (lhs: IdentifiableSet.Index, rhs: IdentifiableSet.Index) -> Bool { + lhs.storageIndex < rhs.storageIndex + } + + fileprivate let storageIndex: Storage.Index + } + + private var storage: Storage + + public var startIndex: Index { + Index(storageIndex: storage.startIndex) + } + + public var endIndex: Index { + Index(storageIndex: storage.endIndex) + } + + public subscript(position: Index) -> Element { + self.storage[position.storageIndex].value + } + + public subscript(id: Element.ID) -> Element? { + self.storage[id] + } + + public func index(after i: Index) -> Index { + Index(storageIndex: self.storage.index(after: i.storageIndex)) + } + + public func union(_ otherSequence: some Sequence) -> Self { + var result = self + for element in otherSequence { + result.storage[element.id] = element + } + return result + } + + public mutating func formUnion(_ otherSequence: some Sequence) { + for element in otherSequence { + self.storage[element.id] = element + } + } + + public func intersection(_ otherSequence: some Sequence) -> Self { + var keysToRemove = Set(self.storage.keys).subtracting(otherSequence.map(\.id)) + var result = Self() + for key in keysToRemove { + result.storage.removeValue(forKey: key) + } + return result + } + + public func subtracting(_ otherSequence: some Sequence) -> Self { + var result = self + for element in otherSequence { + result.storage.removeValue(forKey: element.id) + } + return result + } + + public func contains(id: Element.ID) -> Bool { + self.storage.keys.contains(id) + } +} + +extension Dictionary where Value: Identifiable, Key == Value.ID { + fileprivate init(pickLastWhenDuplicateFound sequence: some Sequence) { + self.init(sequence.map { ($0.id, $0) }, uniquingKeysWith: { $1 }) + } +} + +extension IdentifiableSet: Equatable { + public static func ==(_ lhs: Self, _ rhs: Self) -> Bool { + lhs.storage.keys == rhs.storage.keys + } +} + +extension IdentifiableSet: Hashable { + public func hash(into hasher: inout Hasher) { + for key in self.storage.keys { + hasher.combine(key) + } + } +} diff --git a/Sources/Basics/String+Extensions.swift b/Sources/Basics/Collections/String+Extensions.swift similarity index 100% rename from Sources/Basics/String+Extensions.swift rename to Sources/Basics/Collections/String+Extensions.swift diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 98e3b60cbc2..bb96cd383a2 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -311,7 +311,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // setting is the package-level right now. We might need to figure out a better // answer for libraries if/when we support specifying deployment target at the // target-level. - args += try self.buildParameters.targetTripleArgs(for: self.product.targets[0]) + args += try self.buildParameters.targetTripleArgs(for: self.product.targets[self.product.targets.startIndex]) // Add arguments from declared build settings. args += self.buildSettingsFlags diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift index 1ffc408995a..16f79dc0f35 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift @@ -33,7 +33,7 @@ extension LLBuildManifestBuilder { } func addStaticTargetInputs(_ target: ResolvedTarget) { - if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { + if case .swift(let desc)? = self.plan.targetMap[target.id], target.type == .library { inputs.append(file: desc.moduleOutputPath) } } @@ -46,7 +46,7 @@ extension LLBuildManifestBuilder { case .product(let product, _): switch product.type { case .executable, .snippet, .library(.dynamic), .macro: - guard let planProduct = plan.productMap[product] else { + guard let planProduct = plan.productMap[product.id] else { throw InternalError("unknown product \(product)") } // Establish a dependency on binary of the product. diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift index c6a8a72699a..3061b25ee7b 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift @@ -26,7 +26,7 @@ extension LLBuildManifestBuilder { testInputs = [testBundleInfoPlistPath] self.manifest.addWriteInfoPlistCommand( - principalClass: "\(buildProduct.product.targets[0].c99name).SwiftPMXCTestObserver", + principalClass: "\(buildProduct.product.targets[buildProduct.product.targets.startIndex].c99name).SwiftPMXCTestObserver", outputPath: testBundleInfoPlistPath ) } else { @@ -107,7 +107,7 @@ extension LLBuildManifestBuilder { outputs: [output] ) - if self.plan.graph.reachableProducts.contains(buildProduct.product) { + if self.plan.graph.reachableProducts.contains(id: buildProduct.product.id) { if buildProduct.product.type != .test { self.addNode(output, toTarget: .main) } diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index 372da42df0f..a51bf24d954 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -191,8 +191,9 @@ extension LLBuildManifestBuilder { public func addTargetsToExplicitBuildManifest() throws { // Sort the product targets in topological order in order to collect and "bubble up" // their respective dependency graphs to the depending targets. - let nodes: [ResolvedTarget.Dependency] = self.plan.targetMap.keys.map { - ResolvedTarget.Dependency.target($0, conditions: []) + let nodes: [ResolvedTarget.Dependency] = try self.plan.targetMap.keys.compactMap { + guard let target = self.plan.graph.allTargets[$0] else { throw InternalError("unknown target \($0)") } + return ResolvedTarget.Dependency.target(target, conditions: []) } let allPackageDependencies = try topologicalSort(nodes, successors: { $0.dependencies }) // Instantiate the inter-module dependency oracle which will cache commonly-scanned @@ -225,7 +226,7 @@ extension LLBuildManifestBuilder { // be able to detect such targets' modules. continue } - guard let description = plan.targetMap[target] else { + guard let description = plan.targetMap[target.id] else { throw InternalError("Expected description for target \(target)") } switch description { @@ -327,7 +328,7 @@ extension LLBuildManifestBuilder { throw InternalError("unknown dependency product for \(dependency)") } for dependencyProductTarget in dependencyProduct.targets { - guard let dependencyTargetDescription = self.plan.targetMap[dependencyProductTarget] else { + guard let dependencyTargetDescription = self.plan.targetMap[dependencyProductTarget.id] else { throw InternalError("unknown dependency target for \(dependencyProductTarget)") } try self.addTargetDependencyInfo( @@ -339,7 +340,7 @@ extension LLBuildManifestBuilder { // Product dependencies are broken down into the targets that make them up. guard let dependencyTarget = dependency.target, - let dependencyTargetDescription = self.plan.targetMap[dependencyTarget] + let dependencyTargetDescription = self.plan.targetMap[dependencyTarget.id] else { throw InternalError("unknown dependency target for \(dependency)") } @@ -426,10 +427,10 @@ extension LLBuildManifestBuilder { if target.type == .executable { // FIXME: Optimize. let product = try plan.graph.allProducts.first { - try $0.type == .executable && $0.executableTarget == target + try $0.type == .executable && $0.executableTarget.id == target.id } if let product { - guard let planProduct = plan.productMap[product] else { + guard let planProduct = plan.productMap[product.id] else { throw InternalError("unknown product \(product)") } try inputs.append(file: planProduct.binaryPath) @@ -437,7 +438,7 @@ extension LLBuildManifestBuilder { return } - switch self.plan.targetMap[target] { + switch self.plan.targetMap[target.id] { case .swift(let target)?: inputs.append(file: target.moduleOutputPath) case .clang(let target)?: @@ -457,7 +458,7 @@ extension LLBuildManifestBuilder { case .product(let product, _): switch product.type { case .executable, .snippet, .library(.dynamic), .macro: - guard let planProduct = plan.productMap[product] else { + guard let planProduct = plan.productMap[product.id] else { throw InternalError("unknown product \(product)") } // Establish a dependency on binary of the product. diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift index b681e8324ee..4d9b6d62787 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -253,7 +253,7 @@ extension LLBuildManifestBuilder { private func addTestDiscoveryGenerationCommand() throws { for testDiscoveryTarget in self.plan.targets.compactMap(\.testDiscoveryTargetBuildDescription) { let testTargets = testDiscoveryTarget.target.dependencies - .compactMap(\.target).compactMap { self.plan.targetMap[$0] } + .compactMap(\.target).compactMap { self.plan.targetMap[$0.id] } let objectFiles = try testTargets.flatMap { try $0.objects }.sorted().map(Node.file) let outputs = testDiscoveryTarget.target.sources.paths @@ -280,7 +280,7 @@ extension LLBuildManifestBuilder { // Get the Swift target build descriptions of all discovery targets this synthesized entry point target // depends on. let discoveredTargetDependencyBuildDescriptions = testEntryPointTarget.target.dependencies - .compactMap(\.target) + .compactMap(\.target?.id) .compactMap { self.plan.targetMap[$0] } .compactMap(\.testDiscoveryTargetBuildDescription) diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index ed736adbcd6..4e53d621ae3 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -451,8 +451,8 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Load the package graph. let graph = try getPackageGraph() - let buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] - let prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] + let buildToolPluginInvocationResults: [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] + let prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] // Invoke any build tool plugins in the graph to generate prebuild commands and build commands. if let pluginConfiguration, !self.productsBuildParameters.shouldSkipBuilding { let buildOperationForPluginDependencies = BuildOperation( @@ -488,7 +488,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Surface any diagnostics from build tool plugins. var succeeded = true - for (target, results) in buildToolPluginInvocationResults { + for (_, (target, results)) in buildToolPluginInvocationResults { // There is one result for each plugin that gets applied to a target. for result in results { let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter { @@ -513,7 +513,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Run any prebuild commands provided by build tool plugins. Any failure stops the build. prebuildCommandResults = try graph.reachableTargets.reduce(into: [:], { partial, target in - partial[target] = try buildToolPluginInvocationResults[target].map { try self.runPrebuildCommands(for: $0) } + partial[target.id] = try buildToolPluginInvocationResults[target.id].map { + try self.runPrebuildCommands(for: $0.results) + } }) } else { buildToolPluginInvocationResults = [:] @@ -528,8 +530,8 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS if unhandledFiles.isEmpty { continue } // Subtract out any that were inputs to any commands generated by plugins. - if let result = buildToolPluginInvocationResults[target] { - let handledFiles = result.flatMap{ $0.buildCommands.flatMap{ $0.inputFiles } } + if let result = buildToolPluginInvocationResults[target.id]?.results { + let handledFiles = result.flatMap { $0.buildCommands.flatMap { $0.inputFiles } } unhandledFiles.subtract(handledFiles) } if unhandledFiles.isEmpty { continue } @@ -556,7 +558,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS toolsBuildParameters: self.toolsBuildParameters, graph: graph, additionalFileRules: additionalFileRules, - buildToolPluginInvocationResults: buildToolPluginInvocationResults, + buildToolPluginInvocationResults: buildToolPluginInvocationResults.mapValues(\.results), prebuildCommandResults: prebuildCommandResults, disableSandbox: self.pluginConfiguration?.disableSandbox ?? false, fileSystem: self.fileSystem, diff --git a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift index aebc43273da..1a41e0fe067 100644 --- a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift +++ b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift @@ -382,8 +382,8 @@ public struct BuildDescription: Codable { } var targetCommandLines: [TargetName: [CommandLineFlag]] = [:] var generatedSourceTargets: [TargetName] = [] - for (target, description) in plan.targetMap { - guard case .swift(let desc) = description else { + for (targetID, description) in plan.targetMap { + guard case .swift(let desc) = description, let target = plan.graph.allTargets[targetID] else { continue } let buildParameters = plan.buildParameters(for: target) diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift index 40dcff1b776..e56a5e16585 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -23,7 +23,7 @@ extension BuildPlan { for case .target(let dependency, _) in dependencies { switch dependency.underlying { case is SwiftTarget: - if case let .swift(dependencyTargetDescription)? = targetMap[dependency] { + if case let .swift(dependencyTargetDescription)? = targetMap[dependency.id] { if let moduleMap = dependencyTargetDescription.moduleMap { clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] } @@ -34,7 +34,7 @@ extension BuildPlan { clangTarget.additionalFlags += ["-I", target.includeDir.pathString] // Add the modulemap of the dependency if it has one. - if case let .clang(dependencyTargetDescription)? = targetMap[dependency] { + if case let .clang(dependencyTargetDescription)? = targetMap[dependency.id] { if let moduleMap = dependencyTargetDescription.moduleMap { clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] } diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index bba44144e84..5d11f4d1b44 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -70,7 +70,7 @@ extension BuildPlan { switch target.underlying { case is SwiftTarget: // Swift targets are guaranteed to have a corresponding Swift description. - guard case .swift(let description) = targetMap[target] else { + guard case .swift(let description) = targetMap[target.id] else { throw InternalError("unknown target \(target)") } @@ -91,14 +91,14 @@ extension BuildPlan { } buildProduct.staticTargets = dependencies.staticTargets - buildProduct.dylibs = try dependencies.dylibs.map{ - guard let product = productMap[$0] else { + buildProduct.dylibs = try dependencies.dylibs.map { + guard let product = productMap[$0.id] else { throw InternalError("unknown product \($0)") } return product } buildProduct.objects += try dependencies.staticTargets.flatMap { targetName -> [AbsolutePath] in - guard let target = targetMap[targetName] else { + guard let target = targetMap[targetName.id] else { throw InternalError("unknown target \(targetName)") } return try target.objects @@ -149,7 +149,7 @@ extension BuildPlan { switch dependency { // Include all the dependencies of a target. case .target(let target, _): - let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(target) + let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(id: target.id) let topLevelIsMacro = isTopLevel && product.type == .macro let topLevelIsPlugin = isTopLevel && product.type == .plugin let topLevelIsTest = isTopLevel && product.type == .test @@ -198,18 +198,18 @@ extension BuildPlan { // any test products... this is to allow testing of executables. Note that they are also still // built as separate products that the test can invoke as subprocesses. case .executable, .snippet, .macro: - if product.targets.contains(target) { + if product.targets.contains(id: target.id) { staticTargets.append(target) } else if product.type == .test && (target.underlying as? SwiftTarget)?.supportsTestableExecutablesFeature == true { // Only "top-level" targets should really be considered here, not transitive ones. - let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(target) + let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(id: target.id) if let toolsVersion = graph.package(for: product)?.manifest.toolsVersion, toolsVersion >= .v5_5, isTopLevel { staticTargets.append(target) } } // Test targets should be included only if they are directly in the product's target list. case .test: - if product.targets.contains(target) { + if product.targets.contains(id: target.id) { staticTargets.append(target) } // Library targets should always be included. @@ -249,7 +249,7 @@ extension BuildPlan { // Add derived test targets, if necessary if buildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets { - if product.type == .test, let derivedTestTargets = derivedTestTargetsMap[product] { + if product.type == .test, let derivedTestTargets = derivedTestTargetsMap[product.id] { staticTargets.append(contentsOf: derivedTestTargets) } } diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index acdca547553..36b1cacde0c 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -23,7 +23,7 @@ extension BuildPlan { for case .target(let dependency, _) in try swiftTarget.target.recursiveDependencies(satisfying: environment) { switch dependency.underlying { case let underlyingTarget as ClangTarget where underlyingTarget.type == .library: - guard case let .clang(target)? = targetMap[dependency] else { + guard case let .clang(target)? = targetMap[dependency.id] else { throw InternalError("unexpected clang target \(underlyingTarget)") } // Add the path to modulemap of the dependency. Currently we require that all Clang targets have a diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index be3b4400353..e37387b3cee 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -199,10 +199,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { public let graph: PackageGraph /// The target build description map. - public let targetMap: [ResolvedTarget: TargetBuildDescription] + public let targetMap: [ResolvedTarget.ID: TargetBuildDescription] /// The product build description map. - public let productMap: [ResolvedProduct: ProductBuildDescription] + public let productMap: [ResolvedProduct.ID: ProductBuildDescription] /// The plugin descriptions. Plugins are represented in the package graph /// as targets, but they are not directly included in the build graph. @@ -219,13 +219,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } /// The results of invoking any build tool plugins used by targets in this build. - public let buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] + public let buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] /// The results of running any prebuild commands for the targets in this build. This includes any derived /// source files as well as directories to which any changes should cause us to reevaluate the build plan. - public let prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] + public let prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] - private(set) var derivedTestTargetsMap: [ResolvedProduct: [ResolvedTarget]] = [:] + package private(set) var derivedTestTargetsMap: [ResolvedProduct.ID: [ResolvedTarget]] = [:] /// Cache for pkgConfig flags. private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]() @@ -250,8 +250,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { buildParameters: BuildParameters, graph: PackageGraph, additionalFileRules: [FileRuleDescription] = [], - buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:], + buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] = [:], + prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] = [:], fileSystem: any FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -273,8 +273,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { toolsBuildParameters: BuildParameters, graph: PackageGraph, additionalFileRules: [FileRuleDescription] = [], - buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:], + buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] = [:], + prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] = [:], disableSandbox: Bool = false, fileSystem: any FileSystem, observabilityScope: ObservabilityScope @@ -288,7 +288,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { self.fileSystem = fileSystem self.observabilityScope = observabilityScope.makeChildScope(description: "Build Plan") - var productMap: [ResolvedProduct: ProductBuildDescription] = [:] + var productMap: [ResolvedProduct.ID: (product: ResolvedProduct, buildDescription: ProductBuildDescription)] = [:] // Create product description for each product we have in the package graph that is eligible. for product in graph.allProducts where product.shouldCreateProductDescription { let buildParameters: BuildParameters @@ -305,19 +305,19 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Determine the appropriate tools version to use for the product. // This can affect what flags to pass and other semantics. let toolsVersion = package.manifest.toolsVersion - productMap[product] = try ProductBuildDescription( + productMap[product.id] = try (product, ProductBuildDescription( package: package, product: product, toolsVersion: toolsVersion, buildParameters: buildParameters, fileSystem: fileSystem, observabilityScope: observabilityScope - ) + )) } - let macroProductsByTarget = productMap.keys.filter { $0.type == .macro } - .reduce(into: [ResolvedTarget: ResolvedProduct]()) { - if let target = $1.targets.first { - $0[target] = $1 + let macroProductsByTarget = productMap.values.filter { $0.product.type == .macro } + .reduce(into: [ResolvedTarget.ID: ResolvedProduct]()) { + if let target = $1.product.targets.first { + $0[target.id] = $1.product } } @@ -325,7 +325,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Plugin targets are noted, since they need to be compiled, but they do // not get directly incorporated into the build description that will be // given to LLBuild. - var targetMap = [ResolvedTarget: TargetBuildDescription]() + var targetMap = [ResolvedTarget.ID: TargetBuildDescription]() var pluginDescriptions = [PluginDescription]() var shouldGenerateTestObservation = true for target in graph.allTargets.sorted(by: { $0.name < $1.name }) { @@ -367,7 +367,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan { throw InternalError("package not found for \(target)") } - let requiredMacroProducts = try target.recursiveTargetDependencies().filter { $0.underlying.type == .macro }.compactMap { macroProductsByTarget[$0] } + let requiredMacroProducts = try target.recursiveTargetDependencies() + .filter { $0.underlying.type == .macro } + .compactMap { macroProductsByTarget[$0.id] } var generateTestObservation = false if target.type == .test && shouldGenerateTestObservation { @@ -375,15 +377,15 @@ public class BuildPlan: SPMBuildCore.BuildPlan { shouldGenerateTestObservation = false // Only generate the code once. } - targetMap[target] = try .swift( + targetMap[target.id] = try .swift( SwiftTargetBuildDescription( package: package, target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, buildParameters: buildParameters, - buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [], - prebuildCommandResults: prebuildCommandResults[target] ?? [], + buildToolPluginInvocationResults: buildToolPluginInvocationResults[target.id] ?? [], + prebuildCommandResults: prebuildCommandResults[target.id] ?? [], requiredMacroProducts: requiredMacroProducts, shouldGenerateTestObservation: generateTestObservation, disableSandbox: self.disableSandbox, @@ -392,14 +394,14 @@ public class BuildPlan: SPMBuildCore.BuildPlan { ) ) case is ClangTarget: - targetMap[target] = try .clang( + targetMap[target.id] = try .clang( ClangTargetBuildDescription( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, buildParameters: buildParameters, - buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [], - prebuildCommandResults: prebuildCommandResults[target] ?? [], + buildToolPluginInvocationResults: buildToolPluginInvocationResults[target.id] ?? [], + prebuildCommandResults: prebuildCommandResults[target.id] ?? [], fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -410,7 +412,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } try pluginDescriptions.append(PluginDescription( target: target, - products: package.products.filter { $0.targets.contains(target) }, + products: package.products.filter { $0.targets.contains(id: target.id) }, package: package, toolsVersion: toolsVersion, fileSystem: fileSystem @@ -444,18 +446,18 @@ public class BuildPlan: SPMBuildCore.BuildPlan { for item in derivedTestTargets { var derivedTestTargets = [item.entryPointTargetBuildDescription.target] - targetMap[item.entryPointTargetBuildDescription.target] = .swift(item.entryPointTargetBuildDescription) + targetMap[item.entryPointTargetBuildDescription.target.id] = .swift(item.entryPointTargetBuildDescription) if let discoveryTargetBuildDescription = item.discoveryTargetBuildDescription { - targetMap[discoveryTargetBuildDescription.target] = .swift(discoveryTargetBuildDescription) + targetMap[discoveryTargetBuildDescription.target.id] = .swift(discoveryTargetBuildDescription) derivedTestTargets.append(discoveryTargetBuildDescription.target) } - self.derivedTestTargetsMap[item.product] = derivedTestTargets + self.derivedTestTargetsMap[item.product.id] = derivedTestTargets } } - self.productMap = productMap + self.productMap = productMap.mapValues(\.buildDescription) self.targetMap = targetMap self.pluginDescriptions = pluginDescriptions @@ -568,7 +570,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { var arguments = ["repl", "-I" + buildPath, "-L" + buildPath] // Link the special REPL product that contains all of the library targets. - let replProductName = self.graph.rootPackages[0].identity.description + Product.replProductSuffix + let replProductName = self.graph.rootPackages[self.graph.rootPackages.startIndex].identity.description + + Product.replProductSuffix arguments.append("-l" + replProductName) // The graph should have the REPL product. diff --git a/Sources/Commands/PackageTools/ArchiveSource.swift b/Sources/Commands/PackageTools/ArchiveSource.swift index b45e4cf586c..c9c997f1395 100644 --- a/Sources/Commands/PackageTools/ArchiveSource.swift +++ b/Sources/Commands/PackageTools/ArchiveSource.swift @@ -39,7 +39,7 @@ extension SwiftPackageTool { archivePath = output } else { let graph = try swiftTool.loadPackageGraph() - let packageName = graph.rootPackages[0].manifest.displayName // TODO: use identity instead? + let packageName = graph.rootPackages[graph.rootPackages.startIndex].manifest.displayName // TODO: use identity instead? archivePath = packageDirectory.appending("\(packageName).zip") } diff --git a/Sources/Commands/PackageTools/CompletionTool.swift b/Sources/Commands/PackageTools/CompletionTool.swift index 56f9ddbe517..d6548e7f146 100644 --- a/Sources/Commands/PackageTools/CompletionTool.swift +++ b/Sources/Commands/PackageTools/CompletionTool.swift @@ -66,17 +66,21 @@ extension SwiftPackageTool { let graph = try swiftTool.loadPackageGraph() // command's result output goes on stdout // ie "swift package list-dependencies" should output to stdout - ShowDependencies.dumpDependenciesOf(rootPackage: graph.rootPackages[0], mode: .flatlist, on: TSCBasic.stdoutStream) + ShowDependencies.dumpDependenciesOf( + rootPackage: graph.rootPackages[graph.rootPackages.startIndex], + mode: .flatlist, + on: TSCBasic.stdoutStream + ) case .listExecutables: let graph = try swiftTool.loadPackageGraph() - let package = graph.rootPackages[0].underlying + let package = graph.rootPackages[graph.rootPackages.startIndex].underlying let executables = package.targets.filter { $0.type == .executable } for executable in executables { print(executable.name) } case .listSnippets: let graph = try swiftTool.loadPackageGraph() - let package = graph.rootPackages[0].underlying + let package = graph.rootPackages[graph.rootPackages.startIndex].underlying let executables = package.targets.filter { $0.type == .snippet } for executable in executables { print(executable.name) diff --git a/Sources/Commands/PackageTools/Learn.swift b/Sources/Commands/PackageTools/Learn.swift index ecdab48a608..8e4af0d9410 100644 --- a/Sources/Commands/PackageTools/Learn.swift +++ b/Sources/Commands/PackageTools/Learn.swift @@ -92,7 +92,7 @@ extension SwiftPackageTool { func run(_ swiftTool: SwiftTool) throws { let graph = try swiftTool.loadPackageGraph() - let package = graph.rootPackages[0] + let package = graph.rootPackages[graph.rootPackages.startIndex] print(package.products.map { $0.description }) let snippetGroups = try loadSnippetsAndSnippetGroups(fileSystem: swiftTool.fileSystem, from: package) diff --git a/Sources/Commands/PackageTools/PluginCommand.swift b/Sources/Commands/PackageTools/PluginCommand.swift index f02d6c1b696..839b9f17b52 100644 --- a/Sources/Commands/PackageTools/PluginCommand.swift +++ b/Sources/Commands/PackageTools/PluginCommand.swift @@ -200,7 +200,7 @@ struct PluginCommand: SwiftCommand { // At this point we know we found exactly one command plugin, so we run it. In SwiftPM CLI, we have only one root package. try PluginCommand.run( plugin: matchingPlugins[0], - package: packageGraph.rootPackages[0], + package: packageGraph.rootPackages[packageGraph.rootPackages.startIndex], packageGraph: packageGraph, options: pluginOptions, arguments: unparsedArguments, diff --git a/Sources/Commands/PackageTools/ShowDependencies.swift b/Sources/Commands/PackageTools/ShowDependencies.swift index 7d0610cad8c..9818bed91b0 100644 --- a/Sources/Commands/PackageTools/ShowDependencies.swift +++ b/Sources/Commands/PackageTools/ShowDependencies.swift @@ -40,7 +40,11 @@ extension SwiftPackageTool { // command's result output goes on stdout // ie "swift package show-dependencies" should output to stdout let stream: OutputByteStream = try outputPath.map { try LocalFileOutputByteStream($0) } ?? TSCBasic.stdoutStream - Self.dumpDependenciesOf(rootPackage: graph.rootPackages[0], mode: format, on: stream) + Self.dumpDependenciesOf( + rootPackage: graph.rootPackages[graph.rootPackages.startIndex], + mode: format, + on: stream + ) } static func dumpDependenciesOf(rootPackage: ResolvedPackage, mode: ShowDependenciesMode, on stream: OutputByteStream) { diff --git a/Sources/PackageGraph/PackageGraph+Loading.swift b/Sources/PackageGraph/PackageGraph+Loading.swift index 46f1bae7fb8..bb8bfe05059 100644 --- a/Sources/PackageGraph/PackageGraph+Loading.swift +++ b/Sources/PackageGraph/PackageGraph+Loading.swift @@ -178,7 +178,7 @@ extension PackageGraph { private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], observabilityScope: ObservabilityScope) { for package in rootPackages { // List all dependency products dependent on by the package targets. - let productDependencies: Set = Set(package.targets.flatMap({ target in + let productDependencies = IdentifiableSet(package.targets.flatMap({ target in return target.dependencies.compactMap({ targetDependency in switch targetDependency { case .product(let product, _): @@ -215,7 +215,7 @@ private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], obse ) // Otherwise emit a warning if none of the dependency package's products are used. - let dependencyIsUsed = dependency.products.contains(where: productDependencies.contains) + let dependencyIsUsed = dependency.products.contains(where: { productDependencies.contains(id: $0.id) }) if !dependencyIsUsed && !observabilityScope.errorsReportedInAnyScope { packageDiagnosticsScope.emit(.unusedDependency(dependency.identity.description)) } @@ -863,7 +863,7 @@ private final class ResolvedProductBuilder: ResolvedBuilder { return ResolvedProduct( packageIdentity: packageBuilder.package.identity, product: product, - targets: try targets.map{ try $0.construct() } + targets: IdentifiableSet(try targets.map { try $0.construct() }) ) } } diff --git a/Sources/PackageGraph/PackageGraph.swift b/Sources/PackageGraph/PackageGraph.swift index 63c0503ccbe..15d33a97495 100644 --- a/Sources/PackageGraph/PackageGraph.swift +++ b/Sources/PackageGraph/PackageGraph.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import struct Basics.IdentifiableSet import OrderedCollections import PackageLoading import PackageModel @@ -51,23 +52,24 @@ enum PackageGraphError: Swift.Error { /// A collection of packages. public struct PackageGraph { /// The root packages. - public let rootPackages: [ResolvedPackage] + public let rootPackages: IdentifiableSet /// The complete list of contained packages, in topological order starting /// with the root packages. public let packages: [ResolvedPackage] /// The list of all targets reachable from root targets. - public let reachableTargets: Set + public let reachableTargets: IdentifiableSet /// The list of all products reachable from root targets. - public let reachableProducts: Set + public let reachableProducts: IdentifiableSet /// Returns all the targets in the graph, regardless if they are reachable from the root targets or not. - public let allTargets: Set + public let allTargets: IdentifiableSet /// Returns all the products in the graph, regardless if they are reachable from the root targets or not. - public let allProducts: Set + + public let allProducts: IdentifiableSet /// Package dependencies required for a fully resolved graph. /// @@ -78,7 +80,7 @@ public struct PackageGraph { /// Returns true if a given target is present in root packages and is not excluded for the given build environment. public func isInRootPackages(_ target: ResolvedTarget, satisfying buildEnvironment: BuildEnvironment) -> Bool { // FIXME: This can be easily cached. - return rootPackages.flatMap({ (package: ResolvedPackage) -> Set in + return rootPackages.reduce(into: IdentifiableSet()) { (accumulator: inout IdentifiableSet, package: ResolvedPackage) in let allDependencies = package.targets.flatMap { $0.dependencies } let unsatisfiedDependencies = allDependencies.filter { !$0.satisfies(buildEnvironment) } let unsatisfiedDependencyTargets = unsatisfiedDependencies.compactMap { (dep: ResolvedTarget.Dependency) -> ResolvedTarget? in @@ -90,26 +92,26 @@ public struct PackageGraph { } } - return Set(package.targets).subtracting(unsatisfiedDependencyTargets) - }).contains(target) + accumulator.formUnion(IdentifiableSet(package.targets).subtracting(unsatisfiedDependencyTargets)) + }.contains(id: target.id) } public func isRootPackage(_ package: ResolvedPackage) -> Bool { // FIXME: This can be easily cached. - return rootPackages.contains(package) + return self.rootPackages.contains(id: package.id) } - private let targetsToPackages: [ResolvedTarget: ResolvedPackage] + private let targetsToPackages: [ResolvedTarget.ID: ResolvedPackage] /// Returns the package that contains the target, or nil if the target isn't in the graph. public func package(for target: ResolvedTarget) -> ResolvedPackage? { - return self.targetsToPackages[target] + return self.targetsToPackages[target.id] } - private let productsToPackages: [ResolvedProduct: ResolvedPackage] + private let productsToPackages: [ResolvedProduct.ID: ResolvedPackage] /// Returns the package that contains the product, or nil if the product isn't in the graph. public func package(for product: ResolvedProduct) -> ResolvedPackage? { - return self.productsToPackages[product] + return self.productsToPackages[product.id] } /// All root and root dependency packages provided as input to the graph. @@ -125,7 +127,7 @@ public struct PackageGraph { dependencies requiredDependencies: [PackageReference], binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]] ) throws { - self.rootPackages = rootPackages + let rootPackages = IdentifiableSet(rootPackages) self.requiredDependencies = requiredDependencies self.inputPackages = rootPackages + rootDependencies self.binaryArtifacts = binaryArtifacts @@ -135,11 +137,11 @@ public struct PackageGraph { // we include all targets, including tests in non-root packages, since // this is intended for lookup and not traversal. self.targetsToPackages = packages.reduce(into: [:], { partial, package in - package.targets.forEach{ partial[$0] = package } + package.targets.forEach{ partial[$0.id] = package } }) - allTargets = Set(packages.flatMap({ package -> [ResolvedTarget] in - if rootPackages.contains(package) { + let allTargets = IdentifiableSet(packages.flatMap({ package -> [ResolvedTarget] in + if rootPackages.contains(id: package.id) { return package.targets } else { // Don't include tests targets from non-root packages so swift-test doesn't @@ -152,11 +154,11 @@ public struct PackageGraph { // we include all products, including tests in non-root packages, since // this is intended for lookup and not traversal. self.productsToPackages = packages.reduce(into: [:], { partial, package in - package.products.forEach{ partial[$0] = package } + package.products.forEach { partial[$0.id] = package } }) - allProducts = Set(packages.flatMap({ package -> [ResolvedProduct] in - if rootPackages.contains(package) { + let allProducts = IdentifiableSet(packages.flatMap({ package -> [ResolvedProduct] in + if rootPackages.contains(id: package.id) { return package.products } else { // Don't include tests products from non-root packages so swift-test doesn't @@ -170,20 +172,23 @@ public struct PackageGraph { let inputProducts = inputPackages.flatMap { $0.products } let recursiveDependencies = try inputTargets.lazy.flatMap { try $0.recursiveDependencies() } - self.reachableTargets = Set(inputTargets).union(recursiveDependencies.compactMap { $0.target }) - self.reachableProducts = Set(inputProducts).union(recursiveDependencies.compactMap { $0.product }) + self.reachableTargets = IdentifiableSet(inputTargets).union(recursiveDependencies.compactMap { $0.target }) + self.reachableProducts = IdentifiableSet(inputProducts).union(recursiveDependencies.compactMap { $0.product }) + self.rootPackages = rootPackages + self.allTargets = allTargets + self.allProducts = allProducts } /// Computes a map from each executable target in any of the root packages to the corresponding test targets. - public func computeTestTargetsForExecutableTargets() throws -> [ResolvedTarget: [ResolvedTarget]] { - var result = [ResolvedTarget: [ResolvedTarget]]() + package func computeTestTargetsForExecutableTargets() throws -> [ResolvedTarget.ID: [ResolvedTarget]] { + var result = [ResolvedTarget.ID: [ResolvedTarget]]() - let rootTargets = rootPackages.map({ $0.targets }).flatMap({ $0 }) + let rootTargets = IdentifiableSet(rootPackages.flatMap { $0.targets }) // Create map of test target to set of its direct dependencies. - let testTargetDepMap: [ResolvedTarget: Set] = try { + let testTargetDepMap: [ResolvedTarget.ID: IdentifiableSet] = try { let testTargetDeps = rootTargets.filter({ $0.type == .test }).map({ - ($0, Set($0.dependencies.compactMap{ $0.target }.filter{ $0.type != .plugin })) + ($0.id, IdentifiableSet($0.dependencies.compactMap { $0.target }.filter { $0.type != .plugin })) }) return try Dictionary(throwingUniqueKeysWithValues: testTargetDeps) }() @@ -200,7 +205,7 @@ public struct PackageGraph { !deps.intersection(dependencies + [target]).isEmpty }).map({ $0.key }) - result[target] = testTargets + result[target.id] = testTargets.compactMap { rootTargets[$0] } } return result diff --git a/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift b/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift index d35da0e0238..32a7e37a64f 100644 --- a/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift +++ b/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import struct Basics.IdentifiableSet import struct PackageModel.MinimumDeploymentTarget import struct PackageModel.Platform import struct PackageModel.PlatformVersion @@ -31,7 +32,7 @@ func merge(into partial: inout [SupportedPlatform], platforms: [SupportedPlatfor public struct PlatformVersionProvider: Hashable { public enum Implementation: Hashable { - case mergingFromTargets([ResolvedTarget]) + case mergingFromTargets(IdentifiableSet) case customXCTestMinimumDeploymentTargets([PackageModel.Platform: PlatformVersion]) case minimumDeploymentTargetDefault } diff --git a/Sources/PackageGraph/Resolution/ResolvedPackage.swift b/Sources/PackageGraph/Resolution/ResolvedPackage.swift index 9ecd70e5bbb..bf8d81eac78 100644 --- a/Sources/PackageGraph/Resolution/ResolvedPackage.swift +++ b/Sources/PackageGraph/Resolution/ResolvedPackage.swift @@ -14,7 +14,7 @@ import Basics import PackageModel /// A fully resolved package. Contains resolved targets, products and dependencies of the package. -public struct ResolvedPackage: Hashable { +public struct ResolvedPackage { // The identity of the package. public var identity: PackageIdentity { return self.underlying.identity @@ -91,3 +91,6 @@ extension ResolvedPackage: CustomStringConvertible { extension ResolvedPackage: Identifiable { public var id: PackageIdentity { self.underlying.identity } } + +@available(*, unavailable, message: "Use `Identifiable` conformance or `IdentifiableSet` instead") +extension ResolvedPackage: Hashable {} diff --git a/Sources/PackageGraph/Resolution/ResolvedProduct.swift b/Sources/PackageGraph/Resolution/ResolvedProduct.swift index 8800c473031..1208b5b0d7f 100644 --- a/Sources/PackageGraph/Resolution/ResolvedProduct.swift +++ b/Sources/PackageGraph/Resolution/ResolvedProduct.swift @@ -13,7 +13,7 @@ import Basics import PackageModel -public struct ResolvedProduct: Hashable { +public struct ResolvedProduct { /// The name of this product. public var name: String { self.underlying.name @@ -30,7 +30,7 @@ public struct ResolvedProduct: Hashable { public let underlying: Product /// The top level targets contained in this product. - public let targets: [ResolvedTarget] + public let targets: IdentifiableSet /// Executable target for test entry point file. public let testEntryPointTarget: ResolvedTarget? @@ -63,8 +63,8 @@ public struct ResolvedProduct: Hashable { } } - public init(packageIdentity: PackageIdentity, product: Product, targets: [ResolvedTarget]) { - assert(product.targets.count == targets.count && product.targets.map(\.name) == targets.map(\.name)) + public init(packageIdentity: PackageIdentity, product: Product, targets: IdentifiableSet) { + assert(product.targets.count == targets.count && product.targets.map(\.name).sorted() == targets.map(\.name).sorted()) self.packageIdentity = packageIdentity self.underlying = product self.targets = targets @@ -115,10 +115,12 @@ public struct ResolvedProduct: Hashable { /// Returns the recursive target dependencies. public func recursiveTargetDependencies() throws -> [ResolvedTarget] { let recursiveDependencies = try targets.lazy.flatMap { try $0.recursiveTargetDependencies() } - return Array(Set(self.targets).union(recursiveDependencies)) + return Array(IdentifiableSet(self.targets).union(recursiveDependencies)) } - private static func computePlatforms(targets: [ResolvedTarget]) -> ([SupportedPlatform], PlatformVersionProvider) { + private static func computePlatforms( + targets: IdentifiableSet + ) -> ([SupportedPlatform], PlatformVersionProvider) { let declaredPlatforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in merge(into: &partial, platforms: item.supportedPlatforms) } @@ -160,3 +162,19 @@ extension ResolvedProduct { self.type == .test || self.targets.contains(where: { $0.type == .test }) } } + +extension ResolvedProduct: Identifiable { + /// Resolved target identity that uniquely identifies it in a resolution graph. + public struct ID: Hashable { + public let targetName: String + let packageIdentity: PackageIdentity + public let buildTriple: BuildTriple + } + + public var id: ID { + ID(targetName: self.name, packageIdentity: self.packageIdentity, buildTriple: self.buildTriple) + } +} + +@available(*, unavailable, message: "Use `Identifiable` conformance or `IdentifiableSet` instead") +extension ResolvedProduct: Hashable {} diff --git a/Sources/PackageGraph/Resolution/ResolvedTarget.swift b/Sources/PackageGraph/Resolution/ResolvedTarget.swift index ec0f83c584f..4e9fecaf804 100644 --- a/Sources/PackageGraph/Resolution/ResolvedTarget.swift +++ b/Sources/PackageGraph/Resolution/ResolvedTarget.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -13,7 +13,7 @@ import PackageModel /// Represents a fully resolved target. All the dependencies for this target are also stored as resolved. -public struct ResolvedTarget: Hashable { +public struct ResolvedTarget { /// Represents dependency of a resolved target. public enum Dependency { /// Direct dependency of the target. This target is in the same package and should be statically linked. @@ -219,9 +219,9 @@ extension ResolvedTarget.Dependency: Equatable { public static func == (lhs: ResolvedTarget.Dependency, rhs: ResolvedTarget.Dependency) -> Bool { switch (lhs, rhs) { case (.target(let lhsTarget, _), .target(let rhsTarget, _)): - return lhsTarget == rhsTarget + return lhsTarget.id == rhsTarget.id case (.product(let lhsProduct, _), .product(let rhsProduct, _)): - return lhsProduct == rhsProduct + return lhsProduct.id == rhsProduct.id case (.product, .target), (.target, .product): return false } @@ -232,9 +232,25 @@ extension ResolvedTarget.Dependency: Hashable { public func hash(into hasher: inout Hasher) { switch self { case .target(let target, _): - hasher.combine(target) + hasher.combine(target.id) case .product(let product, _): - hasher.combine(product) + hasher.combine(product.id) } } } + +extension ResolvedTarget: Identifiable { + /// Resolved target identity that uniquely identifies it in a resolution graph. + public struct ID: Hashable { + public let targetName: String + let packageIdentity: PackageIdentity + public let buildTriple: BuildTriple + } + + public var id: ID { + ID(targetName: self.name, packageIdentity: self.packageIdentity, buildTriple: self.buildTriple) + } +} + +@available(*, unavailable, message: "Use `Identifiable` conformance or `IdentifiableSet` instead") +extension ResolvedTarget: Hashable {} diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index dc4fab2cc8d..83852545d3e 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -28,12 +28,12 @@ internal struct PluginContextSerializer { var paths: [WireInput.URL] = [] var pathsToIds: [AbsolutePath: WireInput.URL.Id] = [:] var targets: [WireInput.Target] = [] - var targetsToIds: [ResolvedTarget: WireInput.Target.Id] = [:] + var targetsToWireIDs: [ResolvedTarget.ID: WireInput.Target.Id] = [:] var products: [WireInput.Product] = [] - var productsToIds: [ResolvedProduct: WireInput.Product.Id] = [:] + var productsToWireIDs: [ResolvedProduct.ID: WireInput.Product.Id] = [:] var packages: [WireInput.Package] = [] - var packagesToIds: [ResolvedPackage: WireInput.Package.Id] = [:] - + var packagesToWireIDs: [ResolvedPackage.ID: WireInput.Package.Id] = [:] + /// Adds a path to the serialized structure, if it isn't already there. /// Either way, this function returns the path's wire ID. mutating func serialize(path: AbsolutePath) throws -> WireInput.URL.Id { @@ -57,8 +57,8 @@ internal struct PluginContextSerializer { // tion returns the target's wire ID. If not, it returns nil. mutating func serialize(target: ResolvedTarget) throws -> WireInput.Target.Id? { // If we've already seen the target, just return the wire ID we already assigned to it. - if let id = targetsToIds[target] { return id } - + if let id = targetsToWireIDs[target.id] { return id } + // Construct the FileList var targetFiles: [WireInput.Target.TargetInfo.File] = [] targetFiles.append(contentsOf: try target.underlying.sources.paths.map { @@ -174,7 +174,7 @@ internal struct PluginContextSerializer { directoryId: try serialize(path: target.sources.root), dependencies: dependencies, info: targetInfo)) - targetsToIds[target] = id + targetsToWireIDs[target.id] = id return id } @@ -183,8 +183,8 @@ internal struct PluginContextSerializer { // tion returns the product's wire ID. If not, it returns nil. mutating func serialize(product: ResolvedProduct) throws -> WireInput.Product.Id? { // If we've already seen the product, just return the wire ID we already assigned to it. - if let id = productsToIds[product] { return id } - + if let id = productsToWireIDs[product.id] { return id } + // Look at the product and decide what to serialize. At this point we may decide to not serialize it at all. let productInfo: WireInput.Product.ProductInfo switch product.type { @@ -217,7 +217,7 @@ internal struct PluginContextSerializer { name: product.name, targetIds: try product.targets.compactMap{ try serialize(target: $0) }, info: productInfo)) - productsToIds[product] = id + productsToWireIDs[product.id] = id return id } @@ -225,8 +225,8 @@ internal struct PluginContextSerializer { // Either way, this function returns the package's wire ID. mutating func serialize(package: ResolvedPackage) throws -> WireInput.Package.Id { // If we've already seen the package, just return the wire ID we already assigned to it. - if let id = packagesToIds[package] { return id } - + if let id = packagesToWireIDs[package.id] { return id } + // Determine how we should represent the origin of the package to the plugin. func origin(for package: ResolvedPackage) throws -> WireInput.Package.Origin { switch package.manifest.packageKind { @@ -262,7 +262,7 @@ internal struct PluginContextSerializer { dependencies: dependencies, productIds: try package.products.compactMap{ try serialize(product: $0) }, targetIds: try package.targets.compactMap{ try serialize(target: $0) })) - packagesToIds[package] = id + packagesToWireIDs[package.id] = id return id } } diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index abe8f321433..c6a7391c174 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -395,8 +395,8 @@ extension PackageGraph { observabilityScope: ObservabilityScope, fileSystem: FileSystem, builtToolHandler: (_ name: String, _ path: RelativePath) throws -> AbsolutePath? = { _, _ in return nil } - ) throws -> [ResolvedTarget: [BuildToolPluginInvocationResult]] { - var pluginResultsByTarget: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:] + ) throws -> [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] { + var pluginResultsByTarget: [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] = [:] for target in self.allTargets.sorted(by: { $0.name < $1.name }) { // Infer plugins from the declared dependencies, and collect them as well as any regular dependencies. Although usage of build tool plugins is declared separately from dependencies in the manifest, in the internal model we currently consider both to be dependencies. var pluginTargets: [PluginTarget] = [] @@ -587,7 +587,7 @@ extension PackageGraph { } // Associate the list of results with the target. The list will have one entry for each plugin used by the target. - pluginResultsByTarget[target] = buildToolPluginResults + pluginResultsByTarget[target.id] = (target, buildToolPluginResults) } return pluginResultsByTarget } diff --git a/Sources/SPMTestSupport/MockBuildTestHelper.swift b/Sources/SPMTestSupport/MockBuildTestHelper.swift index c92fe8b1753..19e414dc7b1 100644 --- a/Sources/SPMTestSupport/MockBuildTestHelper.swift +++ b/Sources/SPMTestSupport/MockBuildTestHelper.swift @@ -144,7 +144,17 @@ public struct BuildPlanResult { .compactMap { $0 as? Build.ProductBuildDescription } .map { ($0.product.name, $0) } ) - self.targetMap = try Dictionary(throwingUniqueKeysWithValues: plan.targetMap.map { ($0.0.name, $0.1) }) + self.targetMap = try Dictionary( + throwingUniqueKeysWithValues: plan.targetMap.compactMap { + guard + let target = plan.graph.allTargets[$0] ?? + IdentifiableSet(plan.derivedTestTargetsMap.values.flatMap { $0 })[$0] + else { + throw BuildError.error("Target \($0) not found.") + } + return (target.name, $1) + } + ) } public func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) { diff --git a/Sources/SPMTestSupport/PackageGraphTester.swift b/Sources/SPMTestSupport/PackageGraphTester.swift index 0d480a6ea96..fc4d3c231b5 100644 --- a/Sources/SPMTestSupport/PackageGraphTester.swift +++ b/Sources/SPMTestSupport/PackageGraphTester.swift @@ -12,6 +12,8 @@ import XCTest +import struct Basics.IdentifiableSet + import PackageModel import PackageGraph @@ -128,21 +130,21 @@ public final class PackageGraphResult { return graph.packages.first(where: { $0.identity == package }) } - private func reachableBuildTargets(in environment: BuildEnvironment) throws -> Set { + private func reachableBuildTargets(in environment: BuildEnvironment) throws -> IdentifiableSet { let inputTargets = graph.inputPackages.lazy.flatMap { $0.targets } let recursiveBuildTargetDependencies = try inputTargets .flatMap { try $0.recursiveDependencies(satisfying: environment) } .compactMap { $0.target } - return Set(inputTargets).union(recursiveBuildTargetDependencies) + return IdentifiableSet(inputTargets).union(recursiveBuildTargetDependencies) } - private func reachableBuildProducts(in environment: BuildEnvironment) throws -> Set { + private func reachableBuildProducts(in environment: BuildEnvironment) throws -> IdentifiableSet { let recursiveBuildProductDependencies = try graph.inputPackages .lazy .flatMap { $0.targets } .flatMap { try $0.recursiveDependencies(satisfying: environment) } .compactMap { $0.product } - return Set(graph.inputPackages.flatMap { $0.products }).union(recursiveBuildProductDependencies) + return IdentifiableSet(graph.inputPackages.flatMap { $0.products }).union(recursiveBuildProductDependencies) } } diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 5d7e7a396e8..824df666a0d 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -64,7 +64,7 @@ public struct BuildDescription { // FIXME: should not use `ResolvedTarget` in the public interface public func getBuildTarget(for target: ResolvedTarget) -> BuildTarget? { - if let description = buildPlan.targetMap[target] { + if let description = buildPlan.targetMap[target.id] { switch description { case .clang(let description): return description diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 3621752b8d6..6907fb9b3bb 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -106,7 +106,7 @@ public final class PIFBuilder { /// Constructs a `PIF.TopLevelObject` representing the package graph. public func construct() throws -> PIF.TopLevelObject { try memoize(to: &pif) { - let rootPackage = graph.rootPackages[0] + let rootPackage = self.graph.rootPackages[graph.rootPackages.startIndex] let sortedPackages = graph.packages.sorted { $0.manifest.displayName < $1.manifest.displayName } // TODO: use identity instead? var projects: [PIFProjectBuilder] = try sortedPackages.map { package in @@ -232,7 +232,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { private let fileSystem: FileSystem private let observabilityScope: ObservabilityScope private var binaryGroup: PIFGroupBuilder! - private let executableTargetProductMap: [ResolvedTarget: ResolvedProduct] + private let executableTargetProductMap: [ResolvedTarget.ID: ResolvedProduct] var isRootPackage: Bool { package.manifest.packageKind.isRoot } @@ -250,8 +250,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { metadata: package.underlying.diagnosticsMetadata ) - executableTargetProductMap = try Dictionary(throwingUniqueKeysWithValues: - package.products.filter { $0.type == .executable }.map { ($0.mainTarget, $0) } + self.executableTargetProductMap = try Dictionary( + throwingUniqueKeysWithValues: package.products + .filter { $0.type == .executable } + .map { ($0.mainTarget.id, $0) } ) super.init() @@ -791,7 +793,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } else { // If this is an executable target, the dependency should be to the PIF target created from the its // product, as we don't have PIF targets corresponding to executable targets. - let targetGUID = executableTargetProductMap[target]?.pifTargetGUID ?? target.pifTargetGUID + let targetGUID = executableTargetProductMap[target.id]?.pifTargetGUID ?? target.pifTargetGUID let linkProduct = linkProduct && target.type != .systemModule && target.type != .executable pifTarget.addDependency( toTargetWithGUID: targetGUID, diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index ce047c2c459..4e891750fc9 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -41,8 +41,8 @@ extension Build.BuildPlan { buildParameters: BuildParameters, graph: PackageGraph, additionalFileRules: [FileRuleDescription] = [], - buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:], + buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] = [:], + prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] = [:], fileSystem: any FileSystem, observabilityScope: ObservabilityScope ) throws { diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index 5bbff2004a7..6ea628d70f7 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -638,7 +638,11 @@ final class PackageToolTests: CommandsTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let output = BufferedOutputByteStream() - SwiftPackageTool.ShowDependencies.dumpDependenciesOf(rootPackage: graph.rootPackages[0], mode: .dot, on: output) + SwiftPackageTool.ShowDependencies.dumpDependenciesOf( + rootPackage: graph.rootPackages[graph.rootPackages.startIndex], + mode: .dot, + on: output + ) let dotFormat = output.bytes.description var alreadyPutOut: Set = [] diff --git a/Tests/PackageGraphTests/PackageGraphTests.swift b/Tests/PackageGraphTests/PackageGraphTests.swift index 33debc92f08..27af5fee6c4 100644 --- a/Tests/PackageGraphTests/PackageGraphTests.swift +++ b/Tests/PackageGraphTests/PackageGraphTests.swift @@ -84,11 +84,11 @@ class PackageGraphTests: XCTestCase { let fooPackage = try XCTUnwrap(g.packages.first{ $0.identity == .plain("Foo") }) let fooTarget = try XCTUnwrap(g.allTargets.first{ $0.name == "Foo" }) let fooDepTarget = try XCTUnwrap(g.allTargets.first{ $0.name == "FooDep" }) - XCTAssert(g.package(for: fooTarget) == fooPackage) - XCTAssert(g.package(for: fooDepTarget) == fooPackage) + XCTAssertEqual(g.package(for: fooTarget)?.id, fooPackage.id) + XCTAssertEqual(g.package(for: fooDepTarget)?.id, fooPackage.id) let barPackage = try XCTUnwrap(g.packages.first{ $0.identity == .plain("Bar") }) let barTarget = try XCTUnwrap(g.allTargets.first{ $0.name == "Bar" }) - XCTAssert(g.package(for: barTarget) == barPackage) + XCTAssertEqual(g.package(for: barTarget)?.id, barPackage.id) } func testProductDependencies() throws { @@ -2258,7 +2258,15 @@ class PackageGraphTests: XCTestCase { try ProductDescription(name: "foo", type: .library(.automatic), targets: ["foo"]), try ProductDescription(name: "cbar", type: .library(.automatic), targets: ["cbar"]), try ProductDescription(name: "bar", type: .library(.automatic), targets: ["bar"]), - try ProductDescription(name: "multi-target", type: .library(.automatic), targets: ["bar", "cbar", "bar", "test"]), + try ProductDescription( + name: "multi-target", + type: .library(.automatic), + targets: [ + "bar", + "cbar", + "test" + ] + ), ], targets: [ try TargetDescription(name: "foo", type: .system), diff --git a/Tests/PackageGraphTests/ResolvedTargetTests.swift b/Tests/PackageGraphTests/ResolvedTargetTests.swift index c24c76d4626..5c10466603e 100644 --- a/Tests/PackageGraphTests/ResolvedTargetTests.swift +++ b/Tests/PackageGraphTests/ResolvedTargetTests.swift @@ -16,14 +16,23 @@ import PackageGraph @testable import PackageModel import SPMTestSupport +private func XCTAssertEqualTargetIDs( + _ lhs: [ResolvedTarget], + _ rhs: [ResolvedTarget], + file: StaticString = #filePath, + line: UInt = #line +) { + XCTAssertEqual(lhs.map(\.id), rhs.map(\.id), file: file, line: line) +} + final class ResolvedTargetDependencyTests: XCTestCase { func test1() throws { let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1") let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1) let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func test2() throws { @@ -32,9 +41,9 @@ final class ResolvedTargetDependencyTests: XCTestCase { let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2, t1) let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t2, t3, t1) - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + XCTAssertEqualTargetIDs(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func test3() throws { @@ -43,9 +52,9 @@ final class ResolvedTargetDependencyTests: XCTestCase { let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2, t1) let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t1, t2, t3) - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + XCTAssertEqualTargetIDs(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func test4() throws { @@ -54,9 +63,9 @@ final class ResolvedTargetDependencyTests: XCTestCase { let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2) let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t3) - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + XCTAssertEqualTargetIDs(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func test5() throws { @@ -68,17 +77,17 @@ final class ResolvedTargetDependencyTests: XCTestCase { let t6 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t6", deps: t5, t4) // precise order is not important, but it is important that the following are true - let t6rd = try t6.recursiveTargetDependencies() - XCTAssertEqual(t6rd.firstIndex(of: t3)!, t6rd.index(after: t6rd.firstIndex(of: t4)!)) - XCTAssert(t6rd.firstIndex(of: t5)! < t6rd.firstIndex(of: t2)!) - XCTAssert(t6rd.firstIndex(of: t5)! < t6rd.firstIndex(of: t1)!) - XCTAssert(t6rd.firstIndex(of: t2)! < t6rd.firstIndex(of: t1)!) - XCTAssert(t6rd.firstIndex(of: t3)! < t6rd.firstIndex(of: t2)!) - - XCTAssertEqual(try t5.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + let t6rd = try t6.recursiveTargetDependencies().map(\.id) + XCTAssertEqual(t6rd.firstIndex(of: t3.id)!, t6rd.index(after: t6rd.firstIndex(of: t4.id)!)) + XCTAssert(t6rd.firstIndex(of: t5.id)! < t6rd.firstIndex(of: t2.id)!) + XCTAssert(t6rd.firstIndex(of: t5.id)! < t6rd.firstIndex(of: t1.id)!) + XCTAssert(t6rd.firstIndex(of: t2.id)! < t6rd.firstIndex(of: t1.id)!) + XCTAssert(t6rd.firstIndex(of: t3.id)! < t6rd.firstIndex(of: t2.id)!) + + XCTAssertEqualTargetIDs(try t5.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func test6() throws { @@ -95,17 +104,17 @@ final class ResolvedTargetDependencyTests: XCTestCase { ) // same as above, but these two swapped // precise order is not important, but it is important that the following are true - let t6rd = try t6.recursiveTargetDependencies() - XCTAssertEqual(t6rd.firstIndex(of: t3)!, t6rd.index(after: t6rd.firstIndex(of: t4)!)) - XCTAssert(t6rd.firstIndex(of: t5)! < t6rd.firstIndex(of: t2)!) - XCTAssert(t6rd.firstIndex(of: t5)! < t6rd.firstIndex(of: t1)!) - XCTAssert(t6rd.firstIndex(of: t2)! < t6rd.firstIndex(of: t1)!) - XCTAssert(t6rd.firstIndex(of: t3)! < t6rd.firstIndex(of: t2)!) - - XCTAssertEqual(try t5.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) + let t6rd = try t6.recursiveTargetDependencies().map(\.id) + XCTAssertEqual(t6rd.firstIndex(of: t3.id)!, t6rd.index(after: t6rd.firstIndex(of: t4.id)!)) + XCTAssert(t6rd.firstIndex(of: t5.id)! < t6rd.firstIndex(of: t2.id)!) + XCTAssert(t6rd.firstIndex(of: t5.id)! < t6rd.firstIndex(of: t1.id)!) + XCTAssert(t6rd.firstIndex(of: t2.id)! < t6rd.firstIndex(of: t1.id)!) + XCTAssert(t6rd.firstIndex(of: t3.id)! < t6rd.firstIndex(of: t2.id)!) + + XCTAssertEqualTargetIDs(try t5.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqualTargetIDs(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqualTargetIDs(try t2.recursiveTargetDependencies(), [t1]) } func testConditions() throws { diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index 4ec71f7a755..f2ab3cb231d 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -206,7 +206,7 @@ class PluginInvocationTests: XCTestCase { // Check the canned output to make sure nothing was lost in transport. XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(results.count, 1) - let (evalTarget, evalResults) = try XCTUnwrap(results.first) + let (evalTargetID, (evalTarget, evalResults)) = try XCTUnwrap(results.first) XCTAssertEqual(evalTarget.name, "Foo") XCTAssertEqual(evalResults.count, 1) @@ -887,7 +887,7 @@ class PluginInvocationTests: XCTestCase { fileSystem: localFileSystem ) - let diags = result.map{$0.value}.flatMap{$0}.map{$0.diagnostics}.flatMap{$0} + let diags = result.flatMap(\.value.results).flatMap(\.diagnostics) testDiagnostics(diags) { result in let msg = "a prebuild command cannot use executables built from source, including executable target 'Y'" result.check(diagnostic: .contains(msg), severity: .error) @@ -1067,7 +1067,10 @@ class PluginInvocationTests: XCTestCase { } } - func checkParseArtifactsPlatformCompatibility(artifactSupportedTriples: [Triple], hostTriple: Triple) async throws -> [ResolvedTarget: [BuildToolPluginInvocationResult]] { + func checkParseArtifactsPlatformCompatibility( + artifactSupportedTriples: [Triple], + hostTriple: Triple + ) async throws -> [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -1219,7 +1222,7 @@ class PluginInvocationTests: XCTestCase { pluginScriptRunner: pluginScriptRunner, observabilityScope: observability.topScope, fileSystem: localFileSystem - ) + ).mapValues(\.results) } }