diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index e372e5573c2..b976a7807d1 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -13,7 +13,7 @@ import Basics import PackageLoading import PackageModel -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedTarget import struct SPMBuildCore.BuildParameters import struct SPMBuildCore.BuildToolPluginInvocationResult import struct SPMBuildCore.PrebuildCommandResult @@ -26,9 +26,7 @@ public final class ClangTargetBuildDescription { public let target: ResolvedTarget /// The underlying clang target. - public var clangTarget: ClangTarget { - target.underlyingTarget as! ClangTarget - } + public let clangTarget: ClangTarget /// The tools version of the package that declared the target. This can /// can be used to conditionalize semantically significant changes in how @@ -45,7 +43,7 @@ public final class ClangTargetBuildDescription { /// The list of all resource files in the target, including the derived ones. public var resources: [Resource] { - self.target.underlyingTarget.resources + self.pluginDerivedResources + self.target.underlying.resources + self.pluginDerivedResources } /// Path to the bundle generated for this module (if any). @@ -54,7 +52,7 @@ public final class ClangTargetBuildDescription { return .none } - if let bundleName = target.underlyingTarget.potentialBundleName { + if let bundleName = target.underlying.potentialBundleName { return self.buildParameters.bundlePath(named: bundleName) } else { return .none @@ -119,10 +117,11 @@ public final class ClangTargetBuildDescription { fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { - guard target.underlyingTarget is ClangTarget else { + guard let clangTarget = target.underlying as? ClangTarget else { throw InternalError("underlying target type mismatch \(target)") } + self.clangTarget = clangTarget self.fileSystem = fileSystem self.target = target self.toolsVersion = toolsVersion diff --git a/Sources/Build/BuildDescription/PluginDescription.swift b/Sources/Build/BuildDescription/PluginDescription.swift index 82abf9a3b3e..03a8d62dc0b 100644 --- a/Sources/Build/BuildDescription/PluginDescription.swift +++ b/Sources/Build/BuildDescription/PluginDescription.swift @@ -50,7 +50,7 @@ public final class PluginDescription: Codable { testDiscoveryTarget: Bool = false, fileSystem: FileSystem ) throws { - guard target.underlyingTarget is PluginTarget else { + guard target.underlying is PluginTarget else { throw InternalError("underlying target type mismatch \(target)") } diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 5f1c65a667b..98e3b60cbc2 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -236,7 +236,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // Support for linking tests against executables is conditional on the tools // version of the package that defines the executable product. let executableTarget = try product.executableTarget - if let target = executableTarget.underlyingTarget as? SwiftTarget, + if let target = executableTarget.underlying as? SwiftTarget, self.toolsVersion >= .v5_5, self.buildParameters.driverParameters.canRenameEntrypointFunctionName, target.supportsTestableExecutablesFeature diff --git a/Sources/Build/BuildDescription/SharedTargetBuildDescription.swift b/Sources/Build/BuildDescription/SharedTargetBuildDescription.swift index f159669db86..a4c0e467676 100644 --- a/Sources/Build/BuildDescription/SharedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SharedTargetBuildDescription.swift @@ -53,7 +53,7 @@ struct SharedTargetBuildDescription { additionalFileRules: additionalFileRules, defaultLocalization: target.defaultLocalization, targetName: target.name, - targetPath: target.underlyingTarget.path, + targetPath: target.underlying.path, observabilityScope: observabilityScope ) let pluginDerivedResources = derivedResources diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index 2fee925a81a..6f120aabf02 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -33,6 +33,8 @@ public final class SwiftTargetBuildDescription { /// The target described by this target. public let target: ResolvedTarget + private let swiftTarget: SwiftTarget + /// The tools version of the package that declared the target. This can /// can be used to conditionalize semantically significant changes in how /// a target is built. @@ -57,7 +59,7 @@ public final class SwiftTargetBuildDescription { /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { - if let bundleName = target.underlyingTarget.potentialBundleName, needsResourceBundle { + if let bundleName = target.underlying.potentialBundleName, needsResourceBundle { return self.buildParameters.bundlePath(named: bundleName) } else { return .none @@ -83,7 +85,7 @@ public final class SwiftTargetBuildDescription { /// The list of all resource files in the target, including the derived ones. public var resources: [Resource] { - self.target.underlyingTarget.resources + self.pluginDerivedResources + self.target.underlying.resources + self.pluginDerivedResources } /// The objects in this target, containing either machine code or bitcode @@ -139,7 +141,7 @@ public final class SwiftTargetBuildDescription { /// The swift version for this target. var swiftVersion: SwiftLanguageVersion { - (self.target.underlyingTarget as! SwiftTarget).swiftVersion + self.swiftTarget.swiftVersion } /// Describes the purpose of a test target, including any special roles such as containing a list of discovered @@ -257,10 +259,11 @@ public final class SwiftTargetBuildDescription { fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { - guard target.underlyingTarget is SwiftTarget else { + guard let swiftTarget = target.underlying as? SwiftTarget else { throw InternalError("underlying target type mismatch \(target)") } + self.swiftTarget = swiftTarget self.package = package self.target = target self.toolsVersion = toolsVersion @@ -514,7 +517,7 @@ public final class SwiftTargetBuildDescription { // when we link the executable, we will ask the linker to rename the entry point // symbol to just `_main` again (or if the linker doesn't support it, we'll // generate a source containing a redirect). - if (self.target.underlyingTarget as? SwiftTarget)?.supportsTestableExecutablesFeature == true + if (self.target.underlying as? SwiftTarget)?.supportsTestableExecutablesFeature == true && !self.isTestTarget && self.toolsVersion >= .v5_5 { // We only do this if the linker supports it, as indicated by whether we diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index e32bdd5bade..e1331ca2543 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -11,10 +11,10 @@ //===----------------------------------------------------------------------===// import Basics -import struct SPMBuildCore.BuildParameters -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedTarget import struct PackageModel.Resource import struct SPMBuildCore.BuildToolPluginInvocationResult +import struct SPMBuildCore.BuildParameters public enum BuildDescriptionError: Swift.Error { case requestedFileNotPartOfTarget(targetName: String, requestedFilePath: AbsolutePath) diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift index f83a9a53e42..5ac42266c5b 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift @@ -13,7 +13,7 @@ import struct LLBuildManifest.Node import struct Basics.AbsolutePath import struct Basics.InternalError -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedTarget import PackageModel extension LLBuildManifestBuilder { diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index 7c8407c8ebd..3a9ca3bd3bb 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -17,7 +17,7 @@ import struct Basics.TSCAbsolutePath import struct LLBuildManifest.Node import struct LLBuildManifest.LLBuildManifest import struct SPMBuildCore.BuildParameters -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedTarget import protocol TSCBasic.FileSystem import enum TSCBasic.ProcessEnv import func TSCBasic.topologicalSort @@ -212,8 +212,8 @@ extension LLBuildManifestBuilder { // product into its constituent targets. continue } - guard target.underlyingTarget.type != .systemModule, - target.underlyingTarget.type != .binary + guard target.underlying.type != .systemModule, + target.underlying.type != .binary else { // Much like non-Swift targets, system modules will consist of a modulemap // somewhere in the filesystem, with the path to that module being either @@ -416,11 +416,11 @@ extension LLBuildManifestBuilder { func addStaticTargetInputs(_ target: ResolvedTarget) throws { // Ignore C Modules. - if target.underlyingTarget is SystemLibraryTarget { return } + if target.underlying is SystemLibraryTarget { return } // Ignore Binary Modules. - if target.underlyingTarget is BinaryTarget { return } + if target.underlying is BinaryTarget { return } // Ignore Plugin Targets. - if target.underlyingTarget is PluginTarget { return } + if target.underlying is PluginTarget { return } // Depend on the binary for executable targets. if target.type == .executable { diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 2e7d973cf3f..6370ab2cc0a 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -511,7 +511,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS for package in graph.rootPackages where package.manifest.toolsVersion >= .v5_3 { for target in package.targets { // Get the set of unhandled files in targets. - var unhandledFiles = Set(target.underlyingTarget.others) + var unhandledFiles = Set(target.underlying.others) if unhandledFiles.isEmpty { continue } // Subtract out any that were inputs to any commands generated by plugins. diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift index e46758f19df..40dcff1b776 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -21,7 +21,7 @@ extension BuildPlan { let dependencies = try clangTarget.target.recursiveDependencies(satisfying: clangTarget.buildEnvironment) for case .target(let dependency, _) in dependencies { - switch dependency.underlyingTarget { + switch dependency.underlying { case is SwiftTarget: if case let .swift(dependencyTargetDescription)? = targetMap[dependency] { if let moduleMap = dependencyTargetDescription.moduleMap { diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index bb792e9c340..bba44144e84 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -13,8 +13,8 @@ import struct Basics.AbsolutePath import struct Basics.Triple import struct Basics.InternalError -import class PackageGraph.ResolvedProduct -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedProduct +import struct PackageGraph.ResolvedTarget import class PackageModel.BinaryTarget import class PackageModel.ClangTarget import class PackageModel.Target @@ -32,7 +32,7 @@ extension BuildPlan { // Add flags for system targets. for systemModule in dependencies.systemModules { - guard case let target as SystemLibraryTarget = systemModule.underlyingTarget else { + guard case let target as SystemLibraryTarget = systemModule.underlying else { throw InternalError("This should not be possible.") } // Add pkgConfig libs arguments. @@ -53,7 +53,7 @@ extension BuildPlan { // Link C++ if needed. // Note: This will come from build settings in future. for target in dependencies.staticTargets { - if case let target as ClangTarget = target.underlyingTarget, target.isCXX { + if case let target as ClangTarget = target.underlying, target.isCXX { let triple = buildProduct.buildParameters.triple if triple.isDarwin() { buildProduct.additionalFlags += ["-lc++"] @@ -67,7 +67,7 @@ extension BuildPlan { } for target in dependencies.staticTargets { - switch target.underlyingTarget { + switch target.underlying { case is SwiftTarget: // Swift targets are guaranteed to have a corresponding Swift description. guard case .swift(let description) = targetMap[target] else { @@ -131,7 +131,7 @@ extension BuildPlan { // For test targets, we need to consider the first level of transitive dependencies since the first level is always test targets. let topLevelDependencies: [PackageModel.Target] if product.type == .test { - topLevelDependencies = product.targets.flatMap { $0.underlyingTarget.dependencies }.compactMap { + topLevelDependencies = product.targets.flatMap { $0.underlying.dependencies }.compactMap { switch $0 { case .product: return nil @@ -149,7 +149,7 @@ extension BuildPlan { switch dependency { // Include all the dependencies of a target. case .target(let target, _): - let isTopLevel = topLevelDependencies.contains(target.underlyingTarget) || product.targets.contains(target) + let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(target) let topLevelIsMacro = isTopLevel && product.type == .macro let topLevelIsPlugin = isTopLevel && product.type == .plugin let topLevelIsTest = isTopLevel && product.type == .test @@ -200,9 +200,9 @@ extension BuildPlan { case .executable, .snippet, .macro: if product.targets.contains(target) { staticTargets.append(target) - } else if product.type == .test && (target.underlyingTarget as? SwiftTarget)?.supportsTestableExecutablesFeature == true { + } 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.underlyingTarget) || product.targets.contains(target) + let isTopLevel = topLevelDependencies.contains(target.underlying) || product.targets.contains(target) if let toolsVersion = graph.package(for: product)?.manifest.toolsVersion, toolsVersion >= .v5_5, isTopLevel { staticTargets.append(target) } @@ -220,7 +220,7 @@ extension BuildPlan { systemModules.append(target) // Add binary to binary paths set. case .binary: - guard let binaryTarget = target.underlyingTarget as? BinaryTarget else { + guard let binaryTarget = target.underlying as? BinaryTarget else { throw InternalError("invalid binary target '\(target.name)'") } switch binaryTarget.kind { diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 614b5aa48eb..acdca547553 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -21,7 +21,7 @@ extension BuildPlan { // depends on. let environment = swiftTarget.buildParameters.buildEnvironment for case .target(let dependency, _) in try swiftTarget.target.recursiveDependencies(satisfying: environment) { - switch dependency.underlyingTarget { + switch dependency.underlying { case let underlyingTarget as ClangTarget where underlyingTarget.type == .library: guard case let .clang(target)? = targetMap[dependency] else { throw InternalError("unexpected clang target \(underlyingTarget)") diff --git a/Sources/Build/BuildPlan/BuildPlan+Test.swift b/Sources/Build/BuildPlan/BuildPlan+Test.swift index cf33e7d8136..93fd9da29c7 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Test.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Test.swift @@ -16,8 +16,8 @@ import struct Basics.AbsolutePath import struct LLBuildManifest.TestDiscoveryTool import struct LLBuildManifest.TestEntryPointTool import struct PackageGraph.PackageGraph -import class PackageGraph.ResolvedProduct -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedProduct +import struct PackageGraph.ResolvedTarget import struct PackageModel.Sources import class PackageModel.SwiftTarget import class PackageModel.Target @@ -59,7 +59,7 @@ extension BuildPlan { // tests into a separate target/module named "PackageDiscoveredTests". Then, that entry point file may import that module and // obtain that list to pass it to the `XCTMain(...)` function and avoid needing to maintain a list of tests itself. if testProduct.testEntryPointTarget != nil && explicitlyEnabledDiscovery && !isEntryPointPathSpecifiedExplicitly { - let testEntryPointName = testProduct.underlyingProduct.testEntryPointPath?.basename ?? SwiftTarget.defaultTestEntryPointName + let testEntryPointName = testProduct.underlying.testEntryPointPath?.basename ?? SwiftTarget.defaultTestEntryPointName observabilityScope.emit(warning: "'--enable-test-discovery' was specified so the '\(testEntryPointName)' entry point file for '\(testProduct.name)' will be ignored and an entry point will be generated automatically. To use test discovery with a custom entry point file, pass '--experimental-test-entry-point-path '.") } else if testProduct.testEntryPointTarget == nil, let testEntryPointPath = explicitlySpecifiedPath, !fileSystem.exists(testEntryPointPath) { observabilityScope.emit(error: "'--experimental-test-entry-point-path' was specified but the file '\(testEntryPointPath)' could not be found.") @@ -80,12 +80,13 @@ extension BuildPlan { let discoveryTarget = SwiftTarget( name: discoveryTargetName, - dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) }, + dependencies: testProduct.underlying.targets.map { .target($0, conditions: []) }, packageAccess: true, // test target is allowed access to package decls by default testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir) ) let discoveryResolvedTarget = ResolvedTarget( - target: discoveryTarget, + packageIdentity: testProduct.packageIdentity, + underlying: discoveryTarget, dependencies: testProduct.targets.map { .target($0, conditions: []) }, defaultLocalization: testProduct.defaultLocalization, supportedPlatforms: testProduct.supportedPlatforms, @@ -119,12 +120,13 @@ extension BuildPlan { let entryPointTarget = SwiftTarget( name: testProduct.name, type: .library, - dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) } + swiftTargetDependencies, + dependencies: testProduct.underlying.targets.map { .target($0, conditions: []) } + swiftTargetDependencies, packageAccess: true, // test target is allowed access to package decls testEntryPointSources: entryPointSources ) let entryPointResolvedTarget = ResolvedTarget( - target: entryPointTarget, + packageIdentity: testProduct.packageIdentity, + underlying: entryPointTarget, dependencies: testProduct.targets.map { .target($0, conditions: []) } + resolvedTargetDependencies, defaultLocalization: testProduct.defaultLocalization, supportedPlatforms: testProduct.supportedPlatforms, @@ -153,7 +155,7 @@ extension BuildPlan { resolvedTargetDependencies = [.target(discoveryTargets!.resolved, conditions: [])] case .swiftTesting: discoveryTargets = nil - swiftTargetDependencies = testProduct.targets.map { .target($0.underlyingTarget, conditions: []) } + swiftTargetDependencies = testProduct.targets.map { .target($0.underlying, conditions: []) } resolvedTargetDependencies = testProduct.targets.map { .target($0, conditions: []) } } @@ -162,13 +164,14 @@ extension BuildPlan { if isEntryPointPathSpecifiedExplicitly { // Allow using the explicitly-specified test entry point target, but still perform test discovery and thus declare a dependency on the discovery targets. let entryPointTarget = SwiftTarget( - name: entryPointResolvedTarget.underlyingTarget.name, - dependencies: entryPointResolvedTarget.underlyingTarget.dependencies + swiftTargetDependencies, + name: entryPointResolvedTarget.underlying.name, + dependencies: entryPointResolvedTarget.underlying.dependencies + swiftTargetDependencies, packageAccess: entryPointResolvedTarget.packageAccess, - testEntryPointSources: entryPointResolvedTarget.underlyingTarget.sources + testEntryPointSources: entryPointResolvedTarget.underlying.sources ) let entryPointResolvedTarget = ResolvedTarget( - target: entryPointTarget, + packageIdentity: testProduct.packageIdentity, + underlying: entryPointTarget, dependencies: entryPointResolvedTarget.dependencies + resolvedTargetDependencies, defaultLocalization: testProduct.defaultLocalization, supportedPlatforms: testProduct.supportedPlatforms, diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index cd6ff1cb2f5..3689501dd01 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -157,7 +157,7 @@ extension BuildParameters { /// Returns the scoped view of build settings for a given target. func createScope(for target: ResolvedTarget) -> BuildSettings.Scope { - return BuildSettings.Scope(target.underlyingTarget.buildSettings, environment: buildEnvironment) + return BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment) } } @@ -358,13 +358,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // This can affect what flags to pass and other semantics. let toolsVersion = graph.package(for: target)?.manifest.toolsVersion ?? .v5_5 - switch target.underlyingTarget { + switch target.underlying { case is SwiftTarget: guard let package = graph.package(for: target) else { throw InternalError("package not found for \(target)") } - let requiredMacroProducts = try target.recursiveTargetDependencies().filter { $0.underlyingTarget.type == .macro }.compactMap { macroProductsByTarget[$0] } + let requiredMacroProducts = try target.recursiveTargetDependencies().filter { $0.underlying.type == .macro }.compactMap { macroProductsByTarget[$0] } var generateTestObservation = false if target.type == .test && shouldGenerateTestObservation { @@ -410,7 +410,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case is SystemLibraryTarget, is BinaryTarget: break default: - throw InternalError("unhandled \(target.underlyingTarget)") + throw InternalError("unhandled \(target.underlying)") } } @@ -536,7 +536,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Add search paths from the system library targets. for target in graph.reachableTargets { - if let systemLib = target.underlyingTarget as? SystemLibraryTarget { + if let systemLib = target.underlying as? SystemLibraryTarget { arguments.append(contentsOf: try self.pkgConfig(for: systemLib).cFlags) // Add the path to the module map. arguments += ["-I", systemLib.moduleMapPath.parentDirectory.pathString] @@ -572,7 +572,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Add search paths from the system library targets. for target in graph.reachableTargets { - if let systemLib = target.underlyingTarget as? SystemLibraryTarget { + if let systemLib = target.underlying as? SystemLibraryTarget { arguments += try self.pkgConfig(for: systemLib).cFlags } } @@ -694,7 +694,7 @@ extension Basics.Triple { extension ResolvedPackage { var isRemote: Bool { - switch self.underlyingPackage.manifest.packageKind { + switch self.underlying.manifest.packageKind { case .registry, .remoteSourceControl, .localSourceControl: return true case .root, .fileSystem: @@ -709,7 +709,7 @@ extension ResolvedProduct { } private var isBinaryOnly: Bool { - return self.targets.filter({ !($0.underlyingTarget is BinaryTarget) }).isEmpty + return self.targets.filter({ !($0.underlying is BinaryTarget) }).isEmpty } private var isPlugin: Bool { diff --git a/Sources/Commands/PackageTools/APIDiff.swift b/Sources/Commands/PackageTools/APIDiff.swift index eb711ce92ed..3eeb2b813ca 100644 --- a/Sources/Commands/PackageTools/APIDiff.swift +++ b/Sources/Commands/PackageTools/APIDiff.swift @@ -177,7 +177,7 @@ struct APIDiff: SwiftCommand { observabilityScope.emit(error: "'\(productName)' is not a library product") continue } - modulesToDiff.formUnion(product.targets.filter { $0.underlyingTarget is SwiftTarget }.map(\.c99name)) + modulesToDiff.formUnion(product.targets.filter { $0.underlying is SwiftTarget }.map(\.c99name)) } for targetName in targets { guard let target = packageGraph @@ -191,7 +191,7 @@ struct APIDiff: SwiftCommand { observabilityScope.emit(error: "'\(targetName)' is not a library target") continue } - guard target.underlyingTarget is SwiftTarget else { + guard target.underlying is SwiftTarget else { observabilityScope.emit(error: "'\(targetName)' is not a Swift language target") continue } diff --git a/Sources/Commands/PackageTools/CompletionTool.swift b/Sources/Commands/PackageTools/CompletionTool.swift index b7423bd29f6..56f9ddbe517 100644 --- a/Sources/Commands/PackageTools/CompletionTool.swift +++ b/Sources/Commands/PackageTools/CompletionTool.swift @@ -69,14 +69,14 @@ extension SwiftPackageTool { ShowDependencies.dumpDependenciesOf(rootPackage: graph.rootPackages[0], mode: .flatlist, on: TSCBasic.stdoutStream) case .listExecutables: let graph = try swiftTool.loadPackageGraph() - let package = graph.rootPackages[0].underlyingPackage + let package = graph.rootPackages[0].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].underlyingPackage + let package = graph.rootPackages[0].underlying let executables = package.targets.filter { $0.type == .snippet } for executable in executables { print(executable.name) diff --git a/Sources/Commands/PackageTools/InstalledPackages.swift b/Sources/Commands/PackageTools/InstalledPackages.swift index 62e93e40c6f..c473701479a 100644 --- a/Sources/Commands/PackageTools/InstalledPackages.swift +++ b/Sources/Commands/PackageTools/InstalledPackages.swift @@ -62,7 +62,7 @@ extension SwiftPackageTool { case 0: throw StringError("No Executable Products in Package.swift.") case 1: - productToInstall = possibleCandidates[0].underlyingProduct + productToInstall = possibleCandidates[0].underlying default: guard let product, let first = possibleCandidates.first(where: { $0.name == product }) else { throw StringError( @@ -73,7 +73,7 @@ extension SwiftPackageTool { ) } - productToInstall = first.underlyingProduct + productToInstall = first.underlying } if let existingPkg = alreadyExisting.first(where: { $0.name == productToInstall.name }) { diff --git a/Sources/Commands/PackageTools/PluginCommand.swift b/Sources/Commands/PackageTools/PluginCommand.swift index b4184d08220..f02d6c1b696 100644 --- a/Sources/Commands/PackageTools/PluginCommand.swift +++ b/Sources/Commands/PackageTools/PluginCommand.swift @@ -371,7 +371,7 @@ struct PluginCommand: SwiftCommand { let directDependencyPluginTargets = directDependencyPackages.flatMap { $0.products.filter { $0.type == .plugin } }.flatMap { $0.targets } // As well as any plugin targets in root packages. let rootPackageTargets = graph.rootPackages.filter { $0.matching(identity: packageIdentity) }.flatMap { $0.targets } - return (directDependencyPluginTargets + rootPackageTargets).compactMap { $0.underlyingTarget as? PluginTarget }.filter { + return (directDependencyPluginTargets + rootPackageTargets).compactMap { $0.underlying as? PluginTarget }.filter { switch $0.capability { case .buildTool: return false case .command: return true diff --git a/Sources/Commands/Utilities/APIDigester.swift b/Sources/Commands/Utilities/APIDigester.swift index 069c348b3ee..447612e70f0 100644 --- a/Sources/Commands/Utilities/APIDigester.swift +++ b/Sources/Commands/Utilities/APIDigester.swift @@ -317,7 +317,7 @@ extension PackageGraph { .flatMap(\.products) .filter { $0.type.isLibrary } .flatMap(\.targets) - .filter { $0.underlyingTarget is SwiftTarget } + .filter { $0.underlying is SwiftTarget } .map { $0.c99name } } } diff --git a/Sources/PackageGraph/ModuleAliasTracker.swift b/Sources/PackageGraph/ModuleAliasTracker.swift index b57aac58917..9929bd7ecd8 100644 --- a/Sources/PackageGraph/ModuleAliasTracker.swift +++ b/Sources/PackageGraph/ModuleAliasTracker.swift @@ -15,9 +15,9 @@ import Basics // This is a helper class that tracks module aliases in a package dependency graph // and handles overriding upstream aliases where aliases themselves conflict. -class ModuleAliasTracker { - var aliasMap = [String: [ModuleAliasModel]]() - var idToAliasMap = [PackageIdentity: [String: [ModuleAliasModel]]]() +struct ModuleAliasTracker { + fileprivate var aliasMap = [String: [ModuleAliasModel]]() + fileprivate var idToAliasMap = [PackageIdentity: [String: [ModuleAliasModel]]]() var idToProductToAllTargets = [PackageIdentity: [String: [Target]]]() var productToDirectTargets = [String: [Target]]() var productToAllTargets = [String: [Target]]() @@ -27,7 +27,7 @@ class ModuleAliasTracker { var appliedAliases = Set() init() {} - func addTargetAliases(targets: [Target], package: PackageIdentity) throws { + mutating func addTargetAliases(targets: [Target], package: PackageIdentity) throws { let targetDependencies = targets.map{$0.dependencies}.flatMap{$0} for dep in targetDependencies { if case let .product(productRef, _) = dep, @@ -47,11 +47,13 @@ class ModuleAliasTracker { } } - func addAliases(_ aliases: [String: String], - productID: String, - productName: String, - originPackage: PackageIdentity, - consumingPackage: PackageIdentity) throws { + mutating func addAliases( + _ aliases: [String: String], + productID: String, + productName: String, + originPackage: PackageIdentity, + consumingPackage: PackageIdentity + ) throws { if let aliasDict = idToAliasMap[originPackage] { let existingAliases = aliasDict.values.flatMap{$0}.filter { aliases.keys.contains($0.name) } for existingAlias in existingAliases { @@ -70,8 +72,7 @@ class ModuleAliasTracker { } } - func addPackageIDChain(parent: PackageIdentity, - child: PackageIdentity) { + mutating func addPackageIDChain(parent: PackageIdentity, child: PackageIdentity) { if parentToChildIDs[parent]?.contains(child) ?? false { // Already added } else { @@ -82,8 +83,7 @@ class ModuleAliasTracker { } // This func should be called once per product - func trackTargetsPerProduct(product: Product, - package: PackageIdentity) { + mutating func trackTargetsPerProduct(product: Product, package: PackageIdentity) { let targetDeps = product.targets.map{$0.dependencies}.flatMap{$0} var allTargetDeps = product.targets.map{$0.recursiveDependentTargets.map{$0.dependencies}}.flatMap{$0}.flatMap{$0} allTargetDeps.append(contentsOf: targetDeps) @@ -114,7 +114,7 @@ class ModuleAliasTracker { } } - func propagateAliases(observabilityScope: ObservabilityScope) { + mutating func propagateAliases(observabilityScope: ObservabilityScope) { // First get the root package ID var pkgID = childToParentID.first?.key var rootPkg = pkgID @@ -144,9 +144,11 @@ class ModuleAliasTracker { // Propagate defined aliases upstream. If they are chained, the final // alias value will be applied - func propagate(productID: String, - observabilityScope: ObservabilityScope, - aliasBuffer: inout [String: ModuleAliasModel]) { + mutating private func propagate( + productID: String, + observabilityScope: ObservabilityScope, + aliasBuffer: inout [String: ModuleAliasModel] + ) { let productAliases = aliasMap[productID] ?? [] for aliasModel in productAliases { // Alias buffer is used to carry down aliases defined upstream @@ -197,8 +199,7 @@ class ModuleAliasTracker { } // Merge all the upstream aliases and override them if necessary - func merge(productID: String, - observabilityScope: ObservabilityScope) { + mutating func merge(productID: String, observabilityScope: ObservabilityScope) { guard let children = parentToChildProducts[productID] else { return } @@ -259,7 +260,7 @@ class ModuleAliasTracker { // chain but not in a product consumed by other packages. Such targets still // need to have aliases applied to them so they can be built with correct // dependent binary names - func fillInRest(package: PackageIdentity) { + mutating func fillInRest(package: PackageIdentity) { if let productToTargets = idToProductToAllTargets[package] { for (_, productTargets) in productToTargets { let unAliased = productTargets.contains{$0.moduleAliases == nil} @@ -290,13 +291,15 @@ class ModuleAliasTracker { } } - private func chainModuleAliases(targets: [Target], - checkedTargets: [Target], - targetAliases: [String: [String]], - childTargets: [Target], - childAliases: [String: [String]], - childPrechainAliases: [String: [String]], - observabilityScope: ObservabilityScope) { + private mutating func chainModuleAliases( + targets: [Target], + checkedTargets: [Target], + targetAliases: [String: [String]], + childTargets: [Target], + childAliases: [String: [String]], + childPrechainAliases: [String: [String]], + observabilityScope: ObservabilityScope + ) { guard !targets.isEmpty else { return } var aliasDict = [String: String]() var prechainAliasDict = [String: [String]]() @@ -419,7 +422,7 @@ class ModuleAliasTracker { } // Used to keep track of module alias info for each package -class ModuleAliasModel { +private class ModuleAliasModel { let name: String var alias: String let originPackage: PackageIdentity diff --git a/Sources/PackageGraph/PackageGraph+Loading.swift b/Sources/PackageGraph/PackageGraph+Loading.swift index d3f7cf4859b..1e82171692f 100644 --- a/Sources/PackageGraph/PackageGraph+Loading.swift +++ b/Sources/PackageGraph/PackageGraph+Loading.swift @@ -19,7 +19,6 @@ import func TSCBasic.topologicalSort import func TSCBasic.bestMatch extension PackageGraph { - /// Load the package graph for the given package path. public static func load( root: PackageGraphRoot, @@ -211,7 +210,10 @@ private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], obse } // Make sure that any diagnostics we emit below are associated with the package. - let packageDiagnosticsScope = observabilityScope.makeChildScope(description: "Package Dependency Validation", metadata: package.underlyingPackage.diagnosticsMetadata) + let packageDiagnosticsScope = observabilityScope.makeChildScope( + description: "Package Dependency Validation", + metadata: package.underlying.diagnosticsMetadata + ) // Otherwise emit a warning if none of the dependency package's products are used. let dependencyIsUsed = dependency.products.contains(where: productDependencies.contains) @@ -226,7 +228,7 @@ fileprivate extension ResolvedProduct { /// Returns true if and only if the product represents a command plugin target. var isCommandPlugin: Bool { guard type == .plugin else { return false } - guard let target = underlyingProduct.targets.compactMap({ $0 as? PluginTarget }).first else { return false } + guard let target = underlying.targets.compactMap({ $0 as? PluginTarget }).first else { return false } guard case .command = target.capability else { return false } return true } @@ -371,6 +373,7 @@ private func createResolvedPackages( // Create target builders for each target in the package. let targetBuilders = package.targets.map { ResolvedTargetBuilder( + packageIdentity: package.identity, target: $0, observabilityScope: packageObservabilityScope, platformVersionProvider: platformVersionProvider @@ -384,6 +387,7 @@ private func createResolvedPackages( targetBuilder.dependencies += try targetBuilder.target.dependencies.compactMap { dependency in switch dependency { case .target(let target, let conditions): + try targetBuilder.target.validateDependency(target: target) guard let targetBuilder = targetMap[target] else { throw InternalError("unknown target \(target.name)") } @@ -781,7 +785,7 @@ private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder], } guard hasAliases else { return false } - let aliasTracker = ModuleAliasTracker() + var aliasTracker = ModuleAliasTracker() for packageBuilder in packageBuilders { try aliasTracker.addTargetAliases(targets: packageBuilder.package.targets, package: packageBuilder.package.identity) @@ -858,6 +862,7 @@ private final class ResolvedProductBuilder: ResolvedBuilder { override func constructImpl() throws -> ResolvedProduct { return ResolvedProduct( + packageIdentity: packageBuilder.package.identity, product: product, targets: try targets.map{ try $0.construct() } ) @@ -866,7 +871,6 @@ private final class ResolvedProductBuilder: ResolvedBuilder { /// Builder for resolved target. private final class ResolvedTargetBuilder: ResolvedBuilder { - /// Enumeration to represent target dependencies. enum Dependency { @@ -877,12 +881,12 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { case product(_ product: ResolvedProductBuilder, conditions: [PackageCondition]) } + /// The reference to its package. + let packageIdentity: PackageIdentity + /// The target reference. let target: Target - /// DiagnosticsEmitter with which to emit diagnostics - let diagnosticsEmitter: DiagnosticsEmitter - /// The target dependencies of this target. var dependencies: [Dependency] = [] @@ -892,36 +896,31 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { /// The platforms supported by this package. var supportedPlatforms: [SupportedPlatform] = [] + let observabilityScope: ObservabilityScope let platformVersionProvider: PlatformVersionProvider init( + packageIdentity: PackageIdentity, target: Target, observabilityScope: ObservabilityScope, platformVersionProvider: PlatformVersionProvider ) { + self.packageIdentity = packageIdentity self.target = target - self.diagnosticsEmitter = observabilityScope.makeDiagnosticsEmitter() { - var metadata = ObservabilityMetadata() - metadata.targetName = target.name - return metadata - } + self.observabilityScope = observabilityScope self.platformVersionProvider = platformVersionProvider } - func diagnoseInvalidUseOfUnsafeFlags(_ product: ResolvedProduct) throws { - // Diagnose if any target in this product uses an unsafe flag. - for target in try product.recursiveTargetDependencies() { - if target.underlyingTarget.usesUnsafeFlags { - self.diagnosticsEmitter.emit(.productUsesUnsafeFlags(product: product.name, target: target.name)) - } + override func constructImpl() throws -> ResolvedTarget { + let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter() { + var metadata = ObservabilityMetadata() + metadata.targetName = target.name + return metadata } - } - override func constructImpl() throws -> ResolvedTarget { let dependencies = try self.dependencies.map { dependency -> ResolvedTarget.Dependency in switch dependency { case .target(let targetBuilder, let conditions): - try self.target.validateDependency(target: targetBuilder.target) return .target(try targetBuilder.construct(), conditions: conditions) case .product(let productBuilder, let conditions): try self.target.validateDependency( @@ -930,14 +929,15 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { ) let product = try productBuilder.construct() if !productBuilder.packageBuilder.isAllowedToVendUnsafeProducts { - try self.diagnoseInvalidUseOfUnsafeFlags(product) + try product.diagnoseInvalidUseOfUnsafeFlags(diagnosticsEmitter) } return .product(product, conditions: conditions) } } return ResolvedTarget( - target: self.target, + packageIdentity: self.packageIdentity, + underlying: self.target, dependencies: dependencies, defaultLocalization: self.defaultLocalization, supportedPlatforms: self.supportedPlatforms, @@ -947,21 +947,30 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { } extension Target { - - func validateDependency(target: Target) throws { - if self.type == .plugin && target.type == .library { - throw PackageGraphError.unsupportedPluginDependency(targetName: self.name, dependencyName: target.name, dependencyType: target.type.rawValue, dependencyPackage: nil) + func validateDependency(target: Target) throws { + if self.type == .plugin && target.type == .library { + throw PackageGraphError.unsupportedPluginDependency( + targetName: self.name, + dependencyName: target.name, + dependencyType: target.type.rawValue, + dependencyPackage: nil + ) + } } - } - func validateDependency(product: Product, productPackage: PackageIdentity) throws { - if self.type == .plugin && product.type.isLibrary { - throw PackageGraphError.unsupportedPluginDependency(targetName: self.name, dependencyName: product.name, dependencyType: product.type.description, dependencyPackage: productPackage.description) + + func validateDependency(product: Product, productPackage: PackageIdentity) throws { + if self.type == .plugin && product.type.isLibrary { + throw PackageGraphError.unsupportedPluginDependency( + targetName: self.name, + dependencyName: product.name, + dependencyType: product.type.description, + dependencyPackage: productPackage.description + ) + } } - } } /// Builder for resolved package. private final class ResolvedPackageBuilder: ResolvedBuilder { - /// The package reference. let package: Package @@ -1013,7 +1022,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { override func constructImpl() throws -> ResolvedPackage { return ResolvedPackage( - package: self.package, + underlying: self.package, defaultLocalization: self.defaultLocalization, supportedPlatforms: self.supportedPlatforms, dependencies: try self.dependencies.map{ try $0.construct() }, diff --git a/Sources/PackageGraph/PackageGraph.swift b/Sources/PackageGraph/PackageGraph.swift index 1263d09b039..63c0503ccbe 100644 --- a/Sources/PackageGraph/PackageGraph.swift +++ b/Sources/PackageGraph/PackageGraph.swift @@ -10,11 +10,10 @@ // //===----------------------------------------------------------------------===// +import OrderedCollections import PackageLoading import PackageModel -import func TSCBasic.topologicalSort - enum PackageGraphError: Swift.Error { /// Indicates a non-root package with no targets. case noModules(Package) @@ -276,3 +275,69 @@ extension PackageGraphError: CustomStringConvertible { } } } + +enum GraphError: Error { + /// A cycle was detected in the input. + case unexpectedCycle +} + +/// Perform a topological sort of an graph. +/// +/// This function is optimized for use cases where cycles are unexpected, and +/// does not attempt to retain information on the exact nodes in the cycle. +/// +/// - Parameters: +/// - nodes: The list of input nodes to sort. +/// - successors: A closure for fetching the successors of a particular node. +/// +/// - Returns: A list of the transitive closure of nodes reachable from the +/// inputs, ordered such that every node in the list follows all of its +/// predecessors. +/// +/// - Throws: GraphError.unexpectedCycle +/// +/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges +/// reachable from the input nodes via the relation. +func topologicalSort( + _ nodes: [T], successors: (T) throws -> [T] +) throws -> [T] { + // Implements a topological sort via recursion and reverse postorder DFS. + func visit(_ node: T, + _ stack: inout OrderedSet, _ visited: inout Set, _ result: inout [T], + _ successors: (T) throws -> [T]) throws { + // Mark this node as visited -- we are done if it already was. + if !visited.insert(node.id).inserted { + return + } + + // Otherwise, visit each adjacent node. + for succ in try successors(node) { + guard stack.append(succ.id).inserted else { + // If the successor is already in this current stack, we have found a cycle. + // + // FIXME: We could easily include information on the cycle we found here. + throw GraphError.unexpectedCycle + } + try visit(succ, &stack, &visited, &result, successors) + let popped = stack.removeLast() + assert(popped == succ.id) + } + + // Add to the result. + result.append(node) + } + + // FIXME: This should use a stack not recursion. + var visited = Set() + var result = [T]() + var stack = OrderedSet() + for node in nodes { + precondition(stack.isEmpty) + stack.append(node.id) + try visit(node, &stack, &visited, &result, successors) + let popped = stack.removeLast() + assert(popped == node.id) + } + + return result.reversed() +} diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 88ecbf49f65..3a0e8058857 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -102,6 +102,7 @@ public struct PackageGraphRoot { // at which time the current special casing can be deprecated. var adjustedDependencies = input.dependencies if let explicitProduct { + // FIXME: `dependenciesRequired` modifies manifests and prevents conversion of `Manifest` to a value type for dependency in manifests.values.lazy.map({ $0.dependenciesRequired(for: .everything) }).joined() { adjustedDependencies.append(dependency.filtered(by: .specific([explicitProduct]))) } diff --git a/Sources/PackageGraph/Resolution/ResolvedPackage.swift b/Sources/PackageGraph/Resolution/ResolvedPackage.swift index 481f5c7cda5..9ecd70e5bbb 100644 --- a/Sources/PackageGraph/Resolution/ResolvedPackage.swift +++ b/Sources/PackageGraph/Resolution/ResolvedPackage.swift @@ -14,25 +14,25 @@ import Basics import PackageModel /// A fully resolved package. Contains resolved targets, products and dependencies of the package. -public final class ResolvedPackage { - /// The underlying package reference. - public let underlyingPackage: Package - +public struct ResolvedPackage: Hashable { // The identity of the package. public var identity: PackageIdentity { - return self.underlyingPackage.identity + return self.underlying.identity } /// The manifest describing the package. public var manifest: Manifest { - return self.underlyingPackage.manifest + return self.underlying.manifest } /// The local path of the package. public var path: AbsolutePath { - return self.underlyingPackage.path + return self.underlying.path } + /// The underlying package reference. + public let underlying: Package + /// The targets contained in the package. public let targets: [ResolvedTarget] @@ -54,7 +54,7 @@ public final class ResolvedPackage { private let platformVersionProvider: PlatformVersionProvider public init( - package: Package, + underlying: Package, defaultLocalization: String?, supportedPlatforms: [SupportedPlatform], dependencies: [ResolvedPackage], @@ -63,15 +63,15 @@ public final class ResolvedPackage { registryMetadata: RegistryReleaseMetadata?, platformVersionProvider: PlatformVersionProvider ) { - self.underlyingPackage = package - self.defaultLocalization = defaultLocalization - self.supportedPlatforms = supportedPlatforms - self.dependencies = dependencies + self.underlying = underlying self.targets = targets self.products = products + self.dependencies = dependencies + self.defaultLocalization = defaultLocalization + self.supportedPlatforms = supportedPlatforms self.registryMetadata = registryMetadata self.platformVersionProvider = platformVersionProvider - } + } public func getSupportedPlatform(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform { self.platformVersionProvider.getDerived( @@ -82,18 +82,12 @@ public final class ResolvedPackage { } } -extension ResolvedPackage: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - - public static func == (lhs: ResolvedPackage, rhs: ResolvedPackage) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } -} - extension ResolvedPackage: CustomStringConvertible { public var description: String { return "" } } + +extension ResolvedPackage: Identifiable { + public var id: PackageIdentity { self.underlying.identity } +} diff --git a/Sources/PackageGraph/Resolution/ResolvedProduct.swift b/Sources/PackageGraph/Resolution/ResolvedProduct.swift index 489fe468db4..8800c473031 100644 --- a/Sources/PackageGraph/Resolution/ResolvedProduct.swift +++ b/Sources/PackageGraph/Resolution/ResolvedProduct.swift @@ -13,23 +13,25 @@ import Basics import PackageModel -public final class ResolvedProduct { - /// The underlying product. - public let underlyingProduct: Product - +public struct ResolvedProduct: Hashable { /// The name of this product. public var name: String { - return underlyingProduct.name + self.underlying.name } - /// The top level targets contained in this product. - public let targets: [ResolvedTarget] - /// The type of this product. public var type: ProductType { - return underlyingProduct.type + self.underlying.type } + public let packageIdentity: PackageIdentity + + /// The underlying product. + public let underlying: Product + + /// The top level targets contained in this product. + public let targets: [ResolvedTarget] + /// Executable target for test entry point file. public let testEntryPointTarget: ResolvedTarget? @@ -49,19 +51,22 @@ public final class ResolvedProduct { /// Note: This property is only valid for executable products. public var executableTarget: ResolvedTarget { get throws { - guard type == .executable || type == .snippet || type == .macro else { + guard self.type == .executable || self.type == .snippet || self.type == .macro else { throw InternalError("`executableTarget` should only be called for executable targets") } - guard let underlyingExecutableTarget = targets.map({ $0.underlyingTarget }).executables.first, let executableTarget = targets.first(where: { $0.underlyingTarget == underlyingExecutableTarget }) else { + guard let underlyingExecutableTarget = targets.map(\.underlying).executables.first, + let executableTarget = targets.first(where: { $0.underlying == underlyingExecutableTarget }) + else { throw InternalError("could not determine executable target") } return executableTarget } } - public init(product: Product, targets: [ResolvedTarget]) { - assert(product.targets.count == targets.count && product.targets.map({ $0.name }) == targets.map({ $0.name })) - self.underlyingProduct = product + public init(packageIdentity: PackageIdentity, product: Product, targets: [ResolvedTarget]) { + assert(product.targets.count == targets.count && product.targets.map(\.name) == targets.map(\.name)) + self.packageIdentity = packageIdentity + self.underlying = product self.targets = targets // defaultLocalization is currently shared across the entire package @@ -76,12 +81,15 @@ public final class ResolvedProduct { self.testEntryPointTarget = product.testEntryPointPath.map { testEntryPointPath in // Create an executable resolved target with the entry point file, adding product's targets as dependencies. let dependencies: [Target.Dependency] = product.targets.map { .target($0, conditions: []) } - let swiftTarget = SwiftTarget(name: product.name, - dependencies: dependencies, - packageAccess: true, // entry point target so treated as a part of the package - testEntryPointPath: testEntryPointPath) + let swiftTarget = SwiftTarget( + name: product.name, + dependencies: dependencies, + packageAccess: true, // entry point target so treated as a part of the package + testEntryPointPath: testEntryPointPath + ) return ResolvedTarget( - target: swiftTarget, + packageIdentity: packageIdentity, + underlying: swiftTarget, dependencies: targets.map { .target($0, conditions: []) }, defaultLocalization: defaultLocalization ?? .none, // safe since this is a derived product supportedPlatforms: platforms, @@ -94,21 +102,22 @@ public final class ResolvedProduct { /// True if this product contains Swift targets. public var containsSwiftTargets: Bool { - // C targets can't import Swift targets in SwiftPM (at least not right - // now), so we can just look at the top-level targets. - // - // If that ever changes, we'll need to do something more complex here, - // recursively checking dependencies for SwiftTargets, and considering - // dynamic library targets to be Swift targets (since the dylib could - // contain Swift code we don't know about as part of this build). - return targets.contains { $0.underlyingTarget is SwiftTarget } + // C targets can't import Swift targets in SwiftPM (at least not right + // now), so we can just look at the top-level targets. + // + // If that ever changes, we'll need to do something more complex here, + // recursively checking dependencies for SwiftTargets, and considering + // dynamic library targets to be Swift targets (since the dylib could + // contain Swift code we don't know about as part of this build). + self.targets.contains { $0.underlying is SwiftTarget } } /// Returns the recursive target dependencies. public func recursiveTargetDependencies() throws -> [ResolvedTarget] { let recursiveDependencies = try targets.lazy.flatMap { try $0.recursiveTargetDependencies() } - return Array(Set(targets).union(recursiveDependencies)) + return Array(Set(self.targets).union(recursiveDependencies)) } + private static func computePlatforms(targets: [ResolvedTarget]) -> ([SupportedPlatform], PlatformVersionProvider) { let declaredPlatforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in merge(into: &partial, platforms: item.supportedPlatforms) @@ -118,7 +127,7 @@ public final class ResolvedProduct { declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name }), PlatformVersionProvider(implementation: .mergingFromTargets(targets)) ) - } + } public func getSupportedPlatform(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform { self.platformVersionProvider.getDerived( @@ -127,27 +136,27 @@ public final class ResolvedProduct { usingXCTest: usingXCTest ) } -} -extension ResolvedProduct: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - - public static func == (lhs: ResolvedProduct, rhs: ResolvedProduct) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + func diagnoseInvalidUseOfUnsafeFlags(_ diagnosticsEmitter: DiagnosticsEmitter) throws { + // Diagnose if any target in this product uses an unsafe flag. + for target in try self.recursiveTargetDependencies() { + if target.underlying.usesUnsafeFlags { + diagnosticsEmitter.emit(.productUsesUnsafeFlags(product: self.name, target: target.name)) + } + } } } extension ResolvedProduct: CustomStringConvertible { public var description: String { - return "" + "" } } extension ResolvedProduct { public var isLinkingXCTest: Bool { - // To retain existing behavior, we have to check both the product type, as well as the types of all of its targets. - return self.type == .test || self.targets.contains(where: { $0.type == .test }) + // To retain existing behavior, we have to check both the product type, as well as the types of all of its + // targets. + self.type == .test || self.targets.contains(where: { $0.type == .test }) } } diff --git a/Sources/PackageGraph/Resolution/ResolvedTarget.swift b/Sources/PackageGraph/Resolution/ResolvedTarget.swift index 7b73ec323f8..63e53badfd0 100644 --- a/Sources/PackageGraph/Resolution/ResolvedTarget.swift +++ b/Sources/PackageGraph/Resolution/ResolvedTarget.swift @@ -14,8 +14,8 @@ import PackageModel import func TSCBasic.topologicalSort -/// Represents a fully resolved target. All the dependencies for the target are resolved. -public final class ResolvedTarget { +/// Represents a fully resolved target. All the dependencies for this target are also stored as resolved. +public struct ResolvedTarget: Hashable { /// 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. @@ -70,17 +70,11 @@ public final class ResolvedTarget { } } - /// The underlying target represented in this resolved target. - public let underlyingTarget: Target - /// The name of this target. public var name: String { - return underlyingTarget.name + return underlying.name } - /// The dependencies of this target. - public let dependencies: [Dependency] - /// Returns dependencies which satisfy the input build environment, based on their conditions. /// - Parameters: /// - environment: The build environment to use to filter dependencies on. @@ -90,12 +84,12 @@ public final class ResolvedTarget { /// Returns the recursive dependencies, across the whole package-graph. public func recursiveDependencies() throws -> [Dependency] { - return try topologicalSort(self.dependencies) { $0.dependencies } + return try TSCBasic.topologicalSort(self.dependencies) { $0.dependencies } } /// Returns the recursive target dependencies, across the whole package-graph. public func recursiveTargetDependencies() throws -> [ResolvedTarget] { - return try topologicalSort(self.dependencies) { $0.dependencies }.compactMap { $0.target } + return try TSCBasic.topologicalSort(self.dependencies) { $0.dependencies }.compactMap { $0.target } } /// Returns the recursive dependencies, across the whole package-graph, which satisfy the input build environment, @@ -103,38 +97,46 @@ public final class ResolvedTarget { /// - Parameters: /// - environment: The build environment to use to filter dependencies on. public func recursiveDependencies(satisfying environment: BuildEnvironment) throws -> [Dependency] { - return try topologicalSort(dependencies(satisfying: environment)) { dependency in + return try TSCBasic.topologicalSort(dependencies(satisfying: environment)) { dependency in return dependency.dependencies.filter { $0.satisfies(environment) } } } /// The language-level target name. public var c99name: String { - return underlyingTarget.c99name + return underlying.c99name } /// Module aliases for dependencies of this target. The key is an /// original target name and the value is a new unique name mapped /// to the name of its .swiftmodule binary. public var moduleAliases: [String: String]? { - return underlyingTarget.moduleAliases + return underlying.moduleAliases } /// Allows access to package symbols from other targets in the package public var packageAccess: Bool { - return underlyingTarget.packageAccess + return underlying.packageAccess } /// The "type" of target. public var type: Target.Kind { - return underlyingTarget.type + return underlying.type } /// The sources for the target. public var sources: Sources { - return underlyingTarget.sources + return underlying.sources } + let packageIdentity: PackageIdentity + + /// The underlying target represented in this resolved target. + public let underlying: Target + + /// The dependencies of this target. + public let dependencies: [Dependency] + /// The default localization for resources. public let defaultLocalization: String? @@ -148,13 +150,15 @@ public final class ResolvedTarget { /// Create a resolved target instance. public init( - target: Target, - dependencies: [Dependency], - defaultLocalization: String?, + packageIdentity: PackageIdentity, + underlying: Target, + dependencies: [ResolvedTarget.Dependency], + defaultLocalization: String? = nil, supportedPlatforms: [SupportedPlatform], platformVersionProvider: PlatformVersionProvider ) { - self.underlyingTarget = target + self.packageIdentity = packageIdentity + self.underlying = underlying self.dependencies = dependencies self.defaultLocalization = defaultLocalization self.supportedPlatforms = supportedPlatforms @@ -171,19 +175,45 @@ public final class ResolvedTarget { } } -extension ResolvedTarget: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) +extension ResolvedTarget: CustomStringConvertible { + public var description: String { + return "" } +} - public static func == (lhs: ResolvedTarget, rhs: ResolvedTarget) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) +extension ResolvedTarget.Dependency: CustomStringConvertible { + public var description: String { + var str = "" +extension ResolvedTarget.Dependency: Identifiable { + public struct ID: Hashable { + enum Kind: Hashable { + case target + case product + } + + let kind: Kind + let packageIdentity: PackageIdentity + let name: String + } + + public var id: ID { + switch self { + case .target(let target, _): + return .init(kind: .target, packageIdentity: target.packageIdentity, name: target.name) + case .product(let product, _): + return .init(kind: .product, packageIdentity: product.packageIdentity, name: product.name) + } } } @@ -210,17 +240,3 @@ extension ResolvedTarget.Dependency: Hashable { } } } - -extension ResolvedTarget.Dependency: CustomStringConvertible { - public var description: String { - var str = " [PackageCondition] { - var conditions = [PackageCondition]() + var conditions: [PackageCondition] = [] if let config = condition?.config.flatMap({ BuildConfiguration(rawValue: $0) }) { - let condition = ConfigurationCondition(configuration: config) - conditions.append(.configuration(condition)) + conditions.append(.init(configuration: config)) } if let platforms = condition?.platformNames.map({ @@ -1159,11 +1158,9 @@ public final class PackageBuilder { } else { return PackageModel.Platform.custom(name: $0, oldestSupportedVersion: .unknown) } - }), - !platforms.isEmpty + }), !platforms.isEmpty { - let condition = PlatformsCondition(platforms: platforms) - conditions.append(.platforms(condition)) + conditions.append(.init(platforms: platforms)) } return conditions diff --git a/Sources/PackageModel/BuildConfiguration.swift b/Sources/PackageModel/BuildConfiguration.swift index f4df1d7dde7..4bf08bef3a3 100644 --- a/Sources/PackageModel/BuildConfiguration.swift +++ b/Sources/PackageModel/BuildConfiguration.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// The configuration of the build environment. -public enum BuildConfiguration: String, CaseIterable, Codable { +public enum BuildConfiguration: String, CaseIterable, Codable, Sendable { case debug case release diff --git a/Sources/PackageModel/Manifest/PackageConditionDescription.swift b/Sources/PackageModel/Manifest/PackageConditionDescription.swift index a8fef073cba..f875bafd16e 100644 --- a/Sources/PackageModel/Manifest/PackageConditionDescription.swift +++ b/Sources/PackageModel/Manifest/PackageConditionDescription.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// Represents a manifest condition. -public struct PackageConditionDescription: Codable, Equatable, Sendable { +public struct PackageConditionDescription: Codable, Hashable, Sendable { public let platformNames: [String] public let config: String? @@ -77,7 +77,7 @@ struct PackageConditionWrapper: Codable, Equatable, Hashable { /// One of possible conditions used in package manifests to restrict targets from being built for certain platforms or /// build configurations. -public enum PackageCondition: Hashable { +public enum PackageCondition: Hashable, Sendable { case platforms(PlatformsCondition) case configuration(ConfigurationCondition) @@ -116,7 +116,7 @@ public enum PackageCondition: Hashable { } /// Platforms condition implies that an assignment is valid on these platforms. -public struct PlatformsCondition: PackageConditionProtocol, Equatable, Hashable { +public struct PlatformsCondition: PackageConditionProtocol, Hashable, Sendable { public let platforms: [Platform] public init(platforms: [Platform]) { @@ -131,7 +131,7 @@ public struct PlatformsCondition: PackageConditionProtocol, Equatable, Hashable /// A configuration condition implies that an assignment is valid on /// a particular build configuration. -public struct ConfigurationCondition: PackageConditionProtocol, Equatable, Hashable { +public struct ConfigurationCondition: PackageConditionProtocol, Hashable, Sendable { public let configuration: BuildConfiguration public init(configuration: BuildConfiguration) { diff --git a/Sources/PackageModel/Manifest/PlatformDescription.swift b/Sources/PackageModel/Manifest/PlatformDescription.swift index 5d091f0cd93..dbd5875eff0 100644 --- a/Sources/PackageModel/Manifest/PlatformDescription.swift +++ b/Sources/PackageModel/Manifest/PlatformDescription.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public struct PlatformDescription: Codable, Equatable, Sendable { +public struct PlatformDescription: Codable, Hashable, Sendable { public let platformName: String public let version: String public let options: [String] diff --git a/Sources/PackageModel/Manifest/ProductDescription.swift b/Sources/PackageModel/Manifest/ProductDescription.swift index 465caf3194a..dc0f05349f2 100644 --- a/Sources/PackageModel/Manifest/ProductDescription.swift +++ b/Sources/PackageModel/Manifest/ProductDescription.swift @@ -13,7 +13,7 @@ import Basics /// The product description -public struct ProductDescription: Equatable, Codable, Sendable { +public struct ProductDescription: Hashable, Codable, Sendable { /// The name of the product. public let name: String diff --git a/Sources/PackageModel/Manifest/SystemPackageProviderDescription.swift b/Sources/PackageModel/Manifest/SystemPackageProviderDescription.swift index f9c35822b0d..cef227bee18 100644 --- a/Sources/PackageModel/Manifest/SystemPackageProviderDescription.swift +++ b/Sources/PackageModel/Manifest/SystemPackageProviderDescription.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// Represents system package providers. -public enum SystemPackageProviderDescription: Equatable, Codable, Sendable { +public enum SystemPackageProviderDescription: Hashable, Codable, Sendable { case brew([String]) case apt([String]) case yum([String]) diff --git a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift index adeb965da9a..a0eb9be2575 100644 --- a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift +++ b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift @@ -14,20 +14,20 @@ public enum TargetBuildSettingDescription { /// The tool for which a build setting is declared. - public enum Tool: String, Codable, Equatable, CaseIterable, Sendable { + public enum Tool: String, Codable, Hashable, CaseIterable, Sendable { case c case cxx case swift case linker } - public enum InteroperabilityMode: String, Codable, Equatable, Sendable { + public enum InteroperabilityMode: String, Codable, Hashable, Sendable { case C case Cxx } /// The kind of the build setting, with associate configuration - public enum Kind: Codable, Equatable, Sendable { + public enum Kind: Codable, Hashable, Sendable { case headerSearchPath(String) case define(String) case linkedLibrary(String) @@ -52,8 +52,7 @@ public enum TargetBuildSettingDescription { } /// An individual build setting. - public struct Setting: Codable, Equatable, Sendable { - + public struct Setting: Codable, Hashable, Sendable { /// The tool associated with this setting. public let tool: Tool diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift index f21dafac8d3..d131c99f37e 100644 --- a/Sources/PackageModel/Manifest/TargetDescription.swift +++ b/Sources/PackageModel/Manifest/TargetDescription.swift @@ -11,10 +11,9 @@ //===----------------------------------------------------------------------===// /// The description of an individual target. -public struct TargetDescription: Equatable, Encodable, Sendable { - +public struct TargetDescription: Hashable, Encodable, Sendable { /// The target type. - public enum TargetType: String, Equatable, Encodable, Sendable { + public enum TargetType: String, Hashable, Encodable, Sendable { case regular case executable case test @@ -25,7 +24,7 @@ public struct TargetDescription: Equatable, Encodable, Sendable { } /// Represents a target's dependency on another entity. - public enum Dependency: Equatable, Sendable { + public enum Dependency: Hashable, Sendable { case target(name: String, condition: PackageConditionDescription?) case product(name: String, package: String?, moduleAliases: [String: String]? = nil, condition: PackageConditionDescription?) case byName(name: String, condition: PackageConditionDescription?) @@ -39,8 +38,8 @@ public struct TargetDescription: Equatable, Encodable, Sendable { } } - public struct Resource: Encodable, Equatable, Sendable { - public enum Rule: Encodable, Equatable, Sendable { + public struct Resource: Encodable, Hashable, Sendable { + public enum Rule: Encodable, Hashable, Sendable { case process(localization: Localization?) case copy case embedInCode @@ -111,18 +110,18 @@ public struct TargetDescription: Equatable, Encodable, Sendable { public let pluginCapability: PluginCapability? /// Represents the declared capability of a package plugin. - public enum PluginCapability: Equatable, Sendable { + public enum PluginCapability: Hashable, Sendable { case buildTool case command(intent: PluginCommandIntent, permissions: [PluginPermission]) } - public enum PluginCommandIntent: Equatable, Codable, Sendable { + public enum PluginCommandIntent: Hashable, Codable, Sendable { case documentationGeneration case sourceCodeFormatting case custom(verb: String, description: String) } - public enum PluginNetworkPermissionScope: Equatable, Codable, Sendable { + public enum PluginNetworkPermissionScope: Hashable, Codable, Sendable { case none case local(ports: [Int]) case all(ports: [Int]) @@ -141,7 +140,7 @@ public struct TargetDescription: Equatable, Encodable, Sendable { } } - public enum PluginPermission: Equatable, Codable, Sendable { + public enum PluginPermission: Hashable, Codable, Sendable { case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) case writeToPackageDirectory(reason: String) } @@ -156,7 +155,7 @@ public struct TargetDescription: Equatable, Encodable, Sendable { public let pluginUsages: [PluginUsage]? /// Represents a target's usage of a plugin target or product. - public enum PluginUsage: Equatable, Sendable { + public enum PluginUsage: Hashable, Sendable { case plugin(name: String, package: String?) } diff --git a/Sources/PackageModel/PackageModel.swift b/Sources/PackageModel/PackageModel.swift index 811849d92e5..3be8a42ca1e 100644 --- a/Sources/PackageModel/PackageModel.swift +++ b/Sources/PackageModel/PackageModel.swift @@ -95,6 +95,16 @@ public final class Package: Encodable { } } +extension Package: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + + public static func == (lhs: Package, rhs: Package) -> Bool { + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } +} + extension Package { public var diagnosticsMetadata: ObservabilityMetadata { return .packageMetadata(identity: self.identity, kind: self.manifest.packageKind) diff --git a/Sources/PackageModel/PackageReference.swift b/Sources/PackageModel/PackageReference.swift index 7b62bfe511d..ff6b448101c 100644 --- a/Sources/PackageModel/PackageReference.swift +++ b/Sources/PackageModel/PackageReference.swift @@ -18,7 +18,7 @@ import Foundation /// This represents a reference to a package containing its identity and location. public struct PackageReference { /// The kind of package reference. - public enum Kind: Equatable, CustomStringConvertible, Sendable { + public enum Kind: Hashable, CustomStringConvertible, Sendable { /// A root package. case root(AbsolutePath) diff --git a/Sources/PackageModel/Platform.swift b/Sources/PackageModel/Platform.swift index 8cce1a3dc8b..509db8842d5 100644 --- a/Sources/PackageModel/Platform.swift +++ b/Sources/PackageModel/Platform.swift @@ -13,7 +13,7 @@ import struct TSCUtility.Version /// Represents a platform. -public struct Platform: Equatable, Hashable, Codable { +public struct Platform: Equatable, Hashable, Codable, Sendable { /// The name of the platform. public let name: String @@ -53,7 +53,7 @@ public struct Platform: Equatable, Hashable, Codable { } /// Represents a platform supported by a target. -public struct SupportedPlatform: Equatable, Codable { +public struct SupportedPlatform: Hashable, Codable, Sendable { /// The platform. public let platform: Platform @@ -71,8 +71,8 @@ public struct SupportedPlatform: Equatable, Codable { } /// Represents a platform version. -public struct PlatformVersion: Equatable, Hashable, Codable { - +public struct PlatformVersion: Equatable, Hashable, Codable, Sendable { + // FIXME: this should be optional /// The unknown platform version. public static let unknown: PlatformVersion = .init("0.0.0") diff --git a/Sources/PackageModel/PlatformRegistry.swift b/Sources/PackageModel/PlatformRegistry.swift index c56864b292b..aec3e04fcc6 100644 --- a/Sources/PackageModel/PlatformRegistry.swift +++ b/Sources/PackageModel/PlatformRegistry.swift @@ -11,8 +11,7 @@ //===----------------------------------------------------------------------===// /// A registry for available platforms. -public final class PlatformRegistry { - +public struct PlatformRegistry { /// The current registry is hardcoded and static so we can just use /// a singleton for now. public static let `default`: PlatformRegistry = .init() @@ -31,6 +30,19 @@ public final class PlatformRegistry { /// The static list of known platforms. private static var _knownPlatforms: [Platform] { - return [.macOS, .macCatalyst, .iOS, .tvOS, .watchOS, .visionOS, .linux, .windows, .android, .wasi, .driverKit, .openbsd] + [ + .android, + .driverKit, + .iOS, + .linux, + .macOS, + .macCatalyst, + .openbsd, + .tvOS, + .visionOS, + .wasi, + .watchOS, + .windows, + ] } } diff --git a/Sources/PackageModel/RegistryReleaseMetadata.swift b/Sources/PackageModel/RegistryReleaseMetadata.swift index f0e1affc2e9..206f8f178f1 100644 --- a/Sources/PackageModel/RegistryReleaseMetadata.swift +++ b/Sources/PackageModel/RegistryReleaseMetadata.swift @@ -15,7 +15,7 @@ import struct Foundation.URL import struct TSCUtility.Version -public struct RegistryReleaseMetadata { +public struct RegistryReleaseMetadata: Hashable { public let source: Source public let metadata: Metadata public let signature: RegistrySignature? @@ -31,7 +31,7 @@ public struct RegistryReleaseMetadata { } /// Metadata of the given release, provided by the registry. - public struct Metadata { + public struct Metadata: Hashable { public let author: Author? public let description: String? public let licenseURL: URL? @@ -52,7 +52,7 @@ public struct RegistryReleaseMetadata { self.scmRepositoryURLs = scmRepositoryURLs } - public struct Author { + public struct Author: Hashable { public let name: String public let emailAddress: String? public let description: String? @@ -74,7 +74,7 @@ public struct RegistryReleaseMetadata { } } - public struct Organization { + public struct Organization: Hashable { public let name: String public let emailAddress: String? public let description: String? @@ -90,7 +90,7 @@ public struct RegistryReleaseMetadata { } /// Information from the signing certificate. - public struct RegistrySignature: Codable { + public struct RegistrySignature: Hashable, Codable { public let signedBy: SigningEntity? public let format: String public let value: [UInt8] @@ -106,13 +106,13 @@ public struct RegistryReleaseMetadata { } } - public enum SigningEntity: Codable, Equatable, Sendable { + public enum SigningEntity: Codable, Hashable, Sendable { case recognized(type: String, commonName: String?, organization: String?, identity: String?) case unrecognized(commonName: String?, organization: String?) } /// Information about the source of the release. - public enum Source: Equatable { + public enum Source: Hashable { case registry(URL) } } diff --git a/Sources/PackageModel/SwiftLanguageVersion.swift b/Sources/PackageModel/SwiftLanguageVersion.swift index 03408c8cba1..2aabc03c138 100644 --- a/Sources/PackageModel/SwiftLanguageVersion.swift +++ b/Sources/PackageModel/SwiftLanguageVersion.swift @@ -17,7 +17,7 @@ import struct TSCBasic.RegEx import struct TSCUtility.Version /// Represents a Swift language version. -public struct SwiftLanguageVersion: Sendable { +public struct SwiftLanguageVersion: Hashable, Sendable { /// Swift language version 3. public static let v3 = SwiftLanguageVersion(uncheckedString: "3") diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift index faf1bb5cda0..541e37794de 100644 --- a/Sources/PackageModel/Target/Target.swift +++ b/Sources/PackageModel/Target/Target.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Basics -import Dispatch import protocol TSCUtility.PolymorphicCodableProtocol @@ -42,9 +41,9 @@ public class Target: PolymorphicCodableProtocol { case package case excluded } + /// A reference to a product from a target dependency. public struct ProductReference: Codable { - /// The name of the product dependency. public let name: String diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index e93f24d5fe3..9dc4797dd30 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -61,26 +61,25 @@ internal struct PluginContextSerializer { // Construct the FileList var targetFiles: [WireInput.Target.TargetInfo.File] = [] - targetFiles.append(contentsOf: try target.underlyingTarget.sources.paths.map { + targetFiles.append(contentsOf: try target.underlying.sources.paths.map { .init(basePathId: try serialize(path: $0.parentDirectory), name: $0.basename, type: .source) }) - targetFiles.append(contentsOf: try target.underlyingTarget.resources.map { + targetFiles.append(contentsOf: try target.underlying.resources.map { .init(basePathId: try serialize(path: $0.path.parentDirectory), name: $0.path.basename, type: .resource) }) - targetFiles.append(contentsOf: try target.underlyingTarget.ignored.map { + targetFiles.append(contentsOf: try target.underlying.ignored.map { .init(basePathId: try serialize(path: $0.parentDirectory), name: $0.basename, type: .unknown) }) - targetFiles.append(contentsOf: try target.underlyingTarget.others.map { + targetFiles.append(contentsOf: try target.underlying.others.map { .init(basePathId: try serialize(path: $0.parentDirectory), name: $0.basename, type: .unknown) }) // Create a scope for evaluating build settings. - let scope = BuildSettings.Scope(target.underlyingTarget.buildSettings, environment: buildEnvironment) + let scope = BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment) // Look at the target and decide what to serialize. At this point we may decide to not serialize it at all. let targetInfo: WireInput.Target.TargetInfo - switch target.underlyingTarget { - + switch target.underlying { case let target as SwiftTarget: targetInfo = .swiftSourceModuleInfo( moduleName: target.c99name, diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 66eef0d25f9..98254a7c91c 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -389,15 +389,15 @@ extension PackageGraph { for dependency in target.dependencies(satisfying: buildEnvironment) { switch dependency { case .target(let target, _): - if let pluginTarget = target.underlyingTarget as? PluginTarget { + if let pluginTarget = target.underlying as? PluginTarget { assert(pluginTarget.capability == .buildTool) pluginTargets.append(pluginTarget) } else { - dependencyTargets.append(target.underlyingTarget) + dependencyTargets.append(target.underlying) } case .product(let product, _): - pluginTargets.append(contentsOf: product.targets.compactMap{ $0.underlyingTarget as? PluginTarget }) + pluginTargets.append(contentsOf: product.targets.compactMap{ $0.underlying as? PluginTarget }) } } @@ -578,7 +578,10 @@ public extension PluginTarget { builtToolName = target.name executableOrBinaryTarget = target case .product(let productRef, _): - guard let product = packageGraph.allProducts.first(where: { $0.name == productRef.name }), let executableTarget = product.targets.map({ $0.underlyingTarget }).executables.spm_only else { + guard + let product = packageGraph.allProducts.first(where: { $0.name == productRef.name }), + let executableTarget = product.targets.map({ $0.underlying }).executables.spm_only + else { throw StringError("no product named \(productRef.name)") } builtToolName = productRef.name diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index 6bf9a08ac6d..f1aa33b86d1 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -13,7 +13,7 @@ import Foundation // FIXME: should be an internal import -import class PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedTarget /*private*/ import class PackageLoading.ManifestLoader /*private*/ import struct PackageModel.ToolsVersion diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index ac27f2c3220..0aaa89c39cb 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -1149,9 +1149,9 @@ extension Workspace { self.loadManifest( packageIdentity: identity, - packageKind: previousPackage.underlyingPackage.manifest.packageKind, + packageKind: previousPackage.underlying.manifest.packageKind, packagePath: previousPackage.path, - packageLocation: previousPackage.underlyingPackage.manifest.packageLocation, + packageLocation: previousPackage.underlying.manifest.packageLocation, observabilityScope: observabilityScope ) { result in let result = result.tryMap { manifest -> Package in diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 58beb74531d..3621752b8d6 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -247,7 +247,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { self.fileSystem = fileSystem self.observabilityScope = observabilityScope.makeChildScope( description: "Package PIF Builder", - metadata: package.underlyingPackage.diagnosticsMetadata + metadata: package.underlying.diagnosticsMetadata ) executableTargetProductMap = try Dictionary(throwingUniqueKeysWithValues: @@ -429,7 +429,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } // Tests can have a custom deployment target based on the minimum supported by XCTest. - if mainTarget.underlyingTarget.type == .test { + if mainTarget.underlying.type == .test { settings[.MACOSX_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .macOS, usingXCTest: true) settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .iOS, usingXCTest: true) settings[.TVOS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .tvOS, usingXCTest: true) @@ -456,12 +456,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.GENERATE_INFOPLIST_FILE] = "YES" } - if let clangTarget = mainTarget.underlyingTarget as? ClangTarget { + if let clangTarget = mainTarget.underlying as? ClangTarget { // Let the target itself find its own headers. settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard settings[.CLANG_CXX_LANGUAGE_STANDARD] = clangTarget.cxxLanguageStandard - } else if let swiftTarget = mainTarget.underlyingTarget as? SwiftTarget { + } else if let swiftTarget = mainTarget.underlying as? SwiftTarget { settings[.SWIFT_VERSION] = swiftTarget.swiftVersion.description } @@ -477,7 +477,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { var impartedSettings = PIF.BuildSettings() try addManifestBuildSettings( - from: mainTarget.underlyingTarget, + from: mainTarget.underlying, debugSettings: &debugSettings, releaseSettings: &releaseSettings, impartedSettings: &impartedSettings @@ -534,7 +534,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } var settings = PIF.BuildSettings() - let usesUnsafeFlags = dependencies.contains { $0.target?.underlyingTarget.usesUnsafeFlags == true } + let usesUnsafeFlags = dependencies.contains { $0.target?.underlying.usesUnsafeFlags == true } settings[.USES_SWIFTPM_UNSAFE_FLAGS] = usesUnsafeFlags ? "YES" : "NO" // If there are no system modules in the dependency graph, mark the target as extension-safe. @@ -612,7 +612,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { let moduleMapFileContents: String? let shouldImpartModuleMap: Bool - if let clangTarget = target.underlyingTarget as? ClangTarget { + if let clangTarget = target.underlying as? ClangTarget { // Let the target itself find its own headers. settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard @@ -637,7 +637,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { moduleMapFileContents = nil shouldImpartModuleMap = false } - } else if let swiftTarget = target.underlyingTarget as? SwiftTarget { + } else if let swiftTarget = target.underlying as? SwiftTarget { settings[.SWIFT_VERSION] = swiftTarget.swiftVersion.description // Generate ObjC compatibility header for Swift library targets. settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR] = "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" @@ -666,7 +666,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } impartedSettings[.OTHER_LDRFLAGS] = [] - if target.underlyingTarget.isCxx { + if target.underlying.isCxx { impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]].append("-lc++") } @@ -690,7 +690,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { var releaseSettings = settings try addManifestBuildSettings( - from: target.underlyingTarget, + from: target.underlying, debugSettings: &debugSettings, releaseSettings: &releaseSettings, impartedSettings: &impartedSettings @@ -703,7 +703,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } private func addSystemTarget(for target: ResolvedTarget) throws { - guard let systemTarget = target.underlyingTarget as? SystemLibraryTarget else { + guard let systemTarget = target.underlying as? SystemLibraryTarget else { throw InternalError("unexpected target type") } @@ -785,7 +785,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { linkProduct: Bool ) { // Only add the binary target as a library when we want to link against the product. - if let binaryTarget = target.underlyingTarget as? BinaryTarget { + if let binaryTarget = target.underlying as? BinaryTarget { let ref = binaryGroup.addFileReference(path: binaryTarget.artifactPath.pathString) pifTarget.addLibrary(ref, platformFilters: conditions.toPlatformFilters()) } else { @@ -814,7 +814,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } private func addResourceBundle(for target: ResolvedTarget, in pifTarget: PIFTargetBuilder) -> String? { - guard !target.underlyingTarget.resources.isEmpty else { + guard !target.underlying.resources.isEmpty else { return nil } @@ -845,7 +845,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { resourcesTarget.addBuildConfiguration(name: "Release", settings: settings) let coreDataFileTypes = [XCBuildFileType.xcdatamodeld, .xcdatamodel].flatMap { $0.fileTypes } - for resource in target.underlyingTarget.resources { + for resource in target.underlying.resources { // FIXME: Handle rules here. let resourceFile = groupTree.addFileReference( path: resource.path.pathString, @@ -1378,7 +1378,7 @@ extension ResolvedProduct { var pifTargetGUID: PIF.GUID { "PACKAGE-PRODUCT:\(name)" } var mainTarget: ResolvedTarget { - targets.first { $0.type == underlyingProduct.type.targetType }! + targets.first { $0.type == underlying.type.targetType }! } /// Returns the recursive dependencies, limited to the target's package, which satisfy the input build environment, diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index 8e2e0361f7c..4848829bc27 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -380,7 +380,7 @@ class PluginTests: XCTestCase { let package = try XCTUnwrap(packageGraph.rootPackages.first) // Find the regular target in our test package. - let libraryTarget = try XCTUnwrap(package.targets.map(\.underlyingTarget).first{ $0.name == "MyLibrary" } as? SwiftTarget) + let libraryTarget = try XCTUnwrap(package.targets.map(\.underlying).first{ $0.name == "MyLibrary" } as? SwiftTarget) XCTAssertEqual(libraryTarget.type, .library) // Set up a delegate to handle callbacks from the command plugin. @@ -433,7 +433,7 @@ class PluginTests: XCTestCase { diagnosticsChecker: (DiagnosticsTestResult) throws -> Void ) async { // Find the named plugin. - let plugins = package.targets.compactMap{ $0.underlyingTarget as? PluginTarget } + let plugins = package.targets.compactMap{ $0.underlying as? PluginTarget } guard let plugin = plugins.first(where: { $0.name == pluginName }) else { return XCTFail("There is no plugin target named ‘\(pluginName)’") } @@ -442,7 +442,7 @@ class PluginTests: XCTestCase { // Find the named input targets to the plugin. var targets: [ResolvedTarget] = [] for targetName in targetNames { - guard let target = package.targets.first(where: { $0.underlyingTarget.name == targetName }) else { + guard let target = package.targets.first(where: { $0.underlying.name == targetName }) else { return XCTFail("There is no target named ‘\(targetName)’") } XCTAssertTrue(target.type != .plugin, "Target \(target) is a plugin") @@ -657,7 +657,7 @@ class PluginTests: XCTestCase { // Find the regular target in our test package. let libraryTarget = try XCTUnwrap( package.targets - .map(\.underlyingTarget) + .map(\.underlying) .first{ $0.name == "MyLibrary" } as? SwiftTarget ) XCTAssertEqual(libraryTarget.type, .library) @@ -710,7 +710,7 @@ class PluginTests: XCTestCase { } // Find the relevant plugin. - let plugins = package.targets.compactMap{ $0.underlyingTarget as? PluginTarget } + let plugins = package.targets.compactMap { $0.underlying as? PluginTarget } guard let plugin = plugins.first(where: { $0.name == "NeverendingPlugin" }) else { return XCTFail("There is no plugin target named ‘NeverendingPlugin’") } diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index 9c7c6856094..1b1c20ff25b 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -22,8 +22,7 @@ import class TSCBasic.InMemoryFileSystem import class TSCTestSupport.XCTestCasePerf -class PackageGraphPerfTests: XCTestCasePerf { - +final class PackageGraphPerfTests: XCTestCasePerf { func testLoading100Packages() throws { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") @@ -49,8 +48,16 @@ class PackageGraphPerfTests: XCTestCasePerf { } else { let depName = "Foo\(pkg + 1)" let depUrl = "/\(depName)" - dependencies = [.localSourceControl(deprecatedName: depName, path: try .init(validating: depUrl), requirement: .upToNextMajor(from: "1.0.0"))] - targets = [try TargetDescription(name: name, dependencies: [.byName(name: depName, condition: nil)], path: ".")] + dependencies = [.localSourceControl( + deprecatedName: depName, + path: try .init(validating: depUrl), + requirement: .upToNextMajor(from: "1.0.0") + )] + targets = [try TargetDescription( + name: name, + dependencies: [.byName(name: depName, condition: nil)], + path: "." + )] } // Create manifest. let isRoot = pkg == 1 @@ -79,7 +86,11 @@ class PackageGraphPerfTests: XCTestCasePerf { measure { let observability = ObservabilitySystem.makeForTesting() let g = try! PackageGraph.load( - root: PackageGraphRoot(input: PackageGraphRootInput(packages: [rootManifest.path]), manifests: [rootManifest.path: rootManifest], observabilityScope: observability.topScope), + root: PackageGraphRoot( + input: PackageGraphRootInput(packages: [rootManifest.path]), + manifests: [rootManifest.path: rootManifest], + observabilityScope: observability.topScope + ), identityResolver: identityResolver, externalManifests: externalManifests, binaryArtifacts: [:], @@ -95,7 +106,10 @@ class PackageGraphPerfTests: XCTestCasePerf { let lastPackageNumber = 20 let packageNumberSequence = (1...lastPackageNumber) - let fs = InMemoryFileSystem(emptyFiles: packageNumberSequence.map({ "/Package\($0)/Sources/Target\($0)/s.swift" }) + ["/PackageA/Sources/TargetA/s.swift"]) + let fs = InMemoryFileSystem( + emptyFiles: packageNumberSequence.map({ "/Package\($0)/Sources/Target\($0)/s.swift" }) + + ["/PackageA/Sources/TargetA/s.swift"] + ) let packageSequence: [Manifest] = try packageNumberSequence.map { (sequenceNumber: Int) -> Manifest in let dependencySequence = sequenceNumber < lastPackageNumber ? Array((sequenceNumber + 1)...lastPackageNumber) : [] @@ -105,10 +119,19 @@ class PackageGraphPerfTests: XCTestCasePerf { toolsVersion: .v5_7, dependencies: try dependencySequence.map({ .fileSystem(path: try .init(validating: "/Package\($0)")) }), products: [ - try .init(name: "Package\(sequenceNumber)", type: .library(.dynamic), targets: ["Target\(sequenceNumber)"]) + try .init( + name: "Package\(sequenceNumber)", + type: .library(.dynamic), + targets: ["Target\(sequenceNumber)"] + ) ], targets: [ - try .init(name: "Target\(sequenceNumber)", dependencies: dependencySequence.map { .product(name: "Target\($0)", package: "Package\($0)") }) + try .init( + name: "Target\(sequenceNumber)", + dependencies: dependencySequence.map { + .product(name: "Target\($0)", package: "Package\($0)") + } + ) ] ) } @@ -127,7 +150,11 @@ class PackageGraphPerfTests: XCTestCasePerf { measure { do { for _ in 0.. Void) { - do { - try body() - } catch { - XCTFail("\(error)", file: file, line: line) - } -} - class TargetDependencyTests: XCTestCase { - func test1() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2) - - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) - } + let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2) + + XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) } func test2() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2, t1) - let t4 = ResolvedTarget(name: "t4", deps: t2, t3, t1) - - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) - } + let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2, t1) + let t4 = ResolvedTarget(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]) } func test3() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2, t1) - let t4 = ResolvedTarget(name: "t4", deps: t1, t2, t3) - - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) - } + let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2, t1) + let t4 = ResolvedTarget(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]) } func test4() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2) - let t4 = ResolvedTarget(name: "t4", deps: t3) - - XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) - XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) - XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) - } + let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2) + let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3) + + XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1]) + XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1]) + XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1]) } func test5() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2) - let t4 = ResolvedTarget(name: "t4", deps: t3) - let t5 = ResolvedTarget(name: "t5", deps: t2) - let t6 = ResolvedTarget(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 t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2) + let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3) + let t5 = ResolvedTarget(packageIdentity: "pkg", name: "t5", deps: t2) + let t6 = ResolvedTarget(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]) } func test6() throws { - testTargets { - let t1 = ResolvedTarget(name: "t1") - let t2 = ResolvedTarget(name: "t2", deps: t1) - let t3 = ResolvedTarget(name: "t3", deps: t2) - let t4 = ResolvedTarget(name: "t4", deps: t3) - let t5 = ResolvedTarget(name: "t5", deps: t2) - let t6 = ResolvedTarget(name: "t6", deps: t4, t5) // 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 t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2) + let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3) + let t5 = ResolvedTarget(packageIdentity: "pkg", name: "t5", deps: t2) + let t6 = ResolvedTarget( + packageIdentity: "pkg", + name: "t6", + deps: t4, + t5 + ) // 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]) + } + + func testConditions() throws { + let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1") + let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t2NoConditions = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1) + let t2WithConditions = ResolvedTarget( + packageIdentity: "pkg", + name: "t2", + deps: t1, + conditions: [.init(platforms: [.linux])] + ) + + // FIXME: we should test for actual `t2` and `t2NoConditions` equality, but `SwiftTarget` is a reference type, + // which currently breaks this test, and it shouldn't + XCTAssertEqual(t2.dependencies, t2NoConditions.dependencies) + XCTAssertEqual(t2.dependencies, t2WithConditions.dependencies) } } diff --git a/Tests/PackageGraphTests/TopologicalSortTests.swift b/Tests/PackageGraphTests/TopologicalSortTests.swift new file mode 100644 index 00000000000..5877110fd18 --- /dev/null +++ b/Tests/PackageGraphTests/TopologicalSortTests.swift @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2016-2023 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 +// +//===----------------------------------------------------------------------===// + + +@testable import PackageGraph +import XCTest + +private func XCTAssertThrows( + _ expectedError: T, + file: StaticString = #file, + line: UInt = #line, + _ body: () throws -> Void +) where T: Equatable { + do { + try body() + XCTFail("body completed successfully", file: file, line: line) + } catch let error as T { + XCTAssertEqual(error, expectedError, file: file, line: line) + } catch { + XCTFail("unexpected error thrown: \(error)", file: file, line: line) + } +} + +extension Int: Identifiable { + public var id: Self { self } +} + +private func topologicalSort(_ nodes: [Int], _ successors: [Int: [Int]]) throws -> [Int] { + return try topologicalSort(nodes, successors: { successors[$0] ?? [] }) +} +private func topologicalSort(_ node: Int, _ successors: [Int: [Int]]) throws -> [Int] { + return try topologicalSort([node], successors) +} + +final class TopologicalSortTests: XCTestCase { + func testTopologicalSort() throws { + // A trivial graph. + XCTAssertEqual([1, 2], try topologicalSort(1, [1: [2]])) + XCTAssertEqual([1, 2], try topologicalSort([2, 1], [1: [2]])) + + // A diamond. + let diamond: [Int: [Int]] = [ + 1: [3, 2], + 2: [4], + 3: [4] + ] + XCTAssertEqual([1, 2, 3, 4], try topologicalSort(1, diamond)) + XCTAssertEqual([2, 3, 4], try topologicalSort([3, 2], diamond)) + XCTAssertEqual([1, 2, 3, 4], try topologicalSort([4, 3, 2, 1], diamond)) + + // Test cycle detection. + XCTAssertThrows(GraphError.unexpectedCycle) { _ = try topologicalSort(1, [1: [1]]) } + XCTAssertThrows(GraphError.unexpectedCycle) { _ = try topologicalSort(1, [1: [2], 2: [1]]) } + } +} diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index bfffa9e60f5..ed9a5a52035 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -297,7 +297,7 @@ class PluginInvocationTests: XCTestCase { XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") // Find the build tool plugin. - let buildToolPlugin = try XCTUnwrap(packageGraph.packages.first?.targets.map(\.underlyingTarget).first{ $0.name == "MyPlugin" } as? PluginTarget) + let buildToolPlugin = try XCTUnwrap(packageGraph.packages.first?.targets.map(\.underlying).first{ $0.name == "MyPlugin" } as? PluginTarget) XCTAssertEqual(buildToolPlugin.name, "MyPlugin") XCTAssertEqual(buildToolPlugin.capability, .buildTool) @@ -857,7 +857,7 @@ class PluginInvocationTests: XCTestCase { XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") // Find the build tool plugin. - let buildToolPlugin = try XCTUnwrap(packageGraph.packages.first?.targets.map(\.underlyingTarget).filter{ $0.name == "X" }.first as? PluginTarget) + let buildToolPlugin = try XCTUnwrap(packageGraph.packages.first?.targets.map(\.underlying).filter{ $0.name == "X" }.first as? PluginTarget) XCTAssertEqual(buildToolPlugin.name, "X") XCTAssertEqual(buildToolPlugin.capability, .buildTool) @@ -1179,7 +1179,7 @@ class PluginInvocationTests: XCTestCase { // Find the build tool plugin. let buildToolPlugin = try XCTUnwrap(packageGraph.packages.first?.targets - .map(\.underlyingTarget) + .map(\.underlying) .filter { $0.name == "Foo" } .first as? PluginTarget) XCTAssertEqual(buildToolPlugin.name, "Foo")