From 5469b84f26518f3534234b518d07e9f54aa6bb1b Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 19 Jun 2025 14:30:56 -0400 Subject: [PATCH 01/23] Fix for trait-guarded deps being included in resolution --- Sources/PackageGraph/GraphLoadingNode.swift | 5 +- .../PackageGraph/ModulesGraph+Loading.swift | 30 +++--- Sources/PackageGraph/PackageGraphRoot.swift | 1 + .../Manifest/Manifest+Traits.swift | 101 ++++++++++++++++-- Sources/PackageModel/Manifest/Manifest.swift | 20 +++- Sources/Workspace/Workspace+Manifests.swift | 59 ++++++---- Tests/FunctionalTests/TraitTests.swift | 24 ++--- 7 files changed, 179 insertions(+), 61 deletions(-) diff --git a/Sources/PackageGraph/GraphLoadingNode.swift b/Sources/PackageGraph/GraphLoadingNode.swift index e0c4bb7a173..83bb5f63b12 100644 --- a/Sources/PackageGraph/GraphLoadingNode.swift +++ b/Sources/PackageGraph/GraphLoadingNode.swift @@ -30,14 +30,15 @@ public struct GraphLoadingNode: Equatable, Hashable { public let productFilter: ProductFilter /// The enabled traits for this package. - package var enabledTraits: Set + package var enabledTraits: Set? public init( identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter, - enabledTraits: Set + enabledTraits: Set? ) throws { +// print("\(identity) enabled TRAITSSSSSS : \(enabledTraits)") self.identity = identity self.manifest = manifest self.productFilter = productFilter diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 3e7b4632129..12935129fc7 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -78,7 +78,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: $0.manifest, productFilter: dependency.productFilter, - enabledTraits: [] + enabledTraits: root.enabledTraits[dependency.identity] ) } } @@ -104,15 +104,16 @@ extension ModulesGraph { guard let conditionTraits = $0.condition?.traits else { return true } - return !conditionTraits.intersection(node.item.enabledTraits).isEmpty + return !conditionTraits.intersection(node.item.enabledTraits ?? []).isEmpty }.map(\.name) - let calculatedTraits = try calculateEnabledTraits( - parentPackage: node.item.identity, - identity: dependency.identity, - manifest: manifest, - explictlyEnabledTraits: explictlyEnabledTraits.flatMap { Set($0) } - ) +// let calculatedTraits = try calculateEnabledTraits( +// parentPackage: node.item.identity, +// identity: dependency.identity, +// manifest: manifest, +// explictlyEnabledTraits: explictlyEnabledTraits.flatMap { Set($0) } +// ) + let calculatedTraits = try manifest.enabledTraits(using: explictlyEnabledTraits.flatMap { Set($0) }, node.item.identity.description) return try KeyedPair( GraphLoadingNode( @@ -153,7 +154,10 @@ extension ModulesGraph { allNodes[$0.key] = $0.item } onDuplicate: { first, second in // We are unifying the enabled traits on duplicate - allNodes[first.key]?.enabledTraits.formUnion(second.item.enabledTraits) + if let enabledTraits = second.item.enabledTraits { + allNodes[first.key]?.enabledTraits?.formUnion(enabledTraits) +// print("did this create traits for \(first.key)? \(allNodes[first.key]?.enabledTraits)") + } } // Create the packages. @@ -184,7 +188,7 @@ extension ModulesGraph { createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, fileSystem: fileSystem, observabilityScope: nodeObservabilityScope, - enabledTraits: node.enabledTraits + enabledTraits: node.enabledTraits ?? ["default"] ) let package = try builder.construct() manifestToPackage[manifest] = package @@ -406,11 +410,12 @@ private func createResolvedPackages( } let isAllowedToVendUnsafeProducts = unsafeAllowedPackages.contains { $0.identity == package.identity } +// print("node traits for \(node.identity.description): \(node.enabledTraits)") let allowedToOverride = rootManifests.values.contains(node.manifest) return ResolvedPackageBuilder( package, productFilter: node.productFilter, - enabledTraits: node.enabledTraits, + enabledTraits: node.enabledTraits ?? ["default"], isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts, allowedToOverride: allowedToOverride, platformVersionProvider: platformVersionProvider @@ -443,6 +448,7 @@ private func createResolvedPackages( var dependenciesByNameForModuleDependencyResolution = [String: ResolvedPackageBuilder]() var dependencyNamesForModuleDependencyResolutionOnly = [PackageIdentity: String]() +// print("manifest resolved package \(package.manifest.displayName) with traits \(packageBuilder.enabledTraits)") try package.manifest.dependenciesRequired( for: packageBuilder.productFilter, packageBuilder.enabledTraits @@ -1449,7 +1455,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { var products: [ResolvedProductBuilder] = [] /// The enabled traits of this package. - var enabledTraits: Set = [] + var enabledTraits: Set = ["default"] /// The dependencies of this package. var dependencies: [ResolvedPackageBuilder] = [] diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 8091ba65656..9cbee58e3d1 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -137,6 +137,7 @@ public struct PackageGraphRoot { // Check that the dependency is used in at least one of the manifests. // If not, then we can omit this dependency if pruning unused dependencies // is enabled. + // TODO bp: assure that trait-guarded deps are pruned regardless return manifests.values.reduce(false) { result, manifest in guard manifest.pruneDependencies else { return true } let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index 5f9260cc858..5cccace2126 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -336,6 +336,10 @@ extension Manifest { let traitGuardedDeps = try target.dependencies.filter { dep in let traits = dep.condition?.traits ?? [] + // If traits is empty, then we must manually validate the explicitly enabled traits. + if traits.isEmpty { + try validateEnabledTraits(enabledTraits) + } // For each trait that is a condition on this target dependency, assure that // each one is enabled in the manifest. return try traits.allSatisfy({ try isTraitEnabled(.init(stringLiteral: $0), enabledTraits) }) @@ -433,14 +437,95 @@ extension Manifest { } /// Determines whether a given package dependency is used by this manifest given a set of enabled traits. public func isPackageDependencyUsed(_ dependency: PackageDependency, enabledTraits: Set?) throws -> Bool { - let usedDependencies = try self.usedDependencies(withTraits: enabledTraits) - let foundKnownPackage = usedDependencies.knownPackage.contains(where: { - $0.caseInsensitiveCompare(dependency.identity.description) == .orderedSame - }) - - // if there is a target dependency referenced by name and the package it originates from is unknown, default to - // tentatively marking the package dependency as used. to be resolved later on. - return foundKnownPackage || (!foundKnownPackage && !usedDependencies.unknownPackage.isEmpty) + // TODO bp: assure that we are still pruning trait-guarded dependencies here + + // have package dependency -- seek out all target dependencies that reference this package dep + // for each target dep, see whether they're all trait-guarded - if each reference to the package dep is trait-guarded, then we can safely prune it + if self.pruneDependencies { + let usedDependencies = try self.usedDependencies(withTraits: enabledTraits) + let foundKnownPackage = usedDependencies.knownPackage.contains(where: { + $0.caseInsensitiveCompare(dependency.identity.description) == .orderedSame + }) + + // if there is a target dependency referenced by name and the package it originates from is unknown, default to + // tentatively marking the package dependency as used. to be resolved later on. + return foundKnownPackage || (!foundKnownPackage && !usedDependencies.unknownPackage.isEmpty) + } else { + // alternate path to compute trait-guarded package dependencies if the prune deps feature is not enabled +// let traitGuardedTargetDependencies = traitGuardedTargetDependencies() +// let traitGuardedPackageDependencies = traitGuardedTargetDependencies.flatMap({ $0.value }).reduce(into: [String: Set]()) { guardedPackages, targetDep in +// guard let package = targetDep.package, let traits = targetDep.condition?.traits else { +// return +// } +// +// guardedPackages[package, default: []].formUnion(traits) +// } +// print("manifest \(displayName) and traits \(enabledTraits)") + try validateEnabledTraits(enabledTraits) + + let targetDependenciesForPackageDependency = self.targets.flatMap({ $0.dependencies }) + .filter({ + $0.package?.caseInsensitiveCompare(dependency.identity.description) == .orderedSame + }) + + // if target deps is empty, default to returning true here. + let isTraitGuarded = targetDependenciesForPackageDependency.isEmpty ? false : targetDependenciesForPackageDependency.compactMap({ $0.condition?.traits }).allSatisfy({ + let condition = $0.subtracting(enabledTraits ?? []) +// if let obsScope { +// obsScope.emit(warning: "condition: \(condition) where enabled traits are: \(enabledTraits ?? []) and guarding traits are: \($0)") +// } +// return !$0.subtracting(enabledTraits ?? []).isEmpty + return !condition.isEmpty + }) + + let isUsedWithoutTraitGuarding = !targetDependenciesForPackageDependency.filter({ $0.condition?.traits == nil }).isEmpty + +// if let obsScope { +// obsScope.emit(warning: "\(dependency.identity) isUsedWithoutTraitGuarding: \(isUsedWithoutTraitGuarding)") +// obsScope.emit(warning: "\(dependency.identity) isTraitGuarded: \(isTraitGuarded)") +// } + // Special case until we allow for pruning dependencies +// if !isUsedWithoutTraitGuarding && !isTraitGuarded { +// return true +// } else if isUsedWithoutTraitGuarding && isTraitGuarded { +// return true +// } else if !isUsedWithoutTraitGuarding && isTraitGuarded { +// return false +// } else if isUsedWithoutTraitGuarding && !isTraitGuarded { +// return true +// } + + // Until the pruning unused dependencies features is fully implemented, this is how we will compute whether + // to omit a trait-guarded package dependency. +// if !isUsedWithoutTraitGuarding && isTraitGuarded { +// return false +// } + + return isUsedWithoutTraitGuarding || !isTraitGuarded +// return isUsedWithoutTraitGuarding || !isTraitGuarded +// .reduce(into: [String: [TargetDescription.Dependency]]()) { packageTargetDeps, targetDep in +// if let packageName = targetDep.package { +// packageTargetDeps[packageName, default: []].append(targetDep) +// } +// } + + + + // Check that this package dependency is potentially guarded by traits. + // Note that this package dependency can also be referenced by target dependencies without being + // guarded by traits... +// if let guardingTraits = traitGuardedPackageDependencies[dependency.identity.description] { +// let isTraitGuarded = guardingTraits.isSubset(of: enabledTraits ?? []) + // Depending on whether this dependency is used elsewhere without any trait guarding, this + // will determine whether we can prune this from the dependency graph. +// let isTraitGuarded = !guardingTraits.subtracting(enabledTraits ?? []).isEmpty +// } + +// return true +// if let enabledTraits = enabledTraits, let guardingTraits = traitGuardedPackageDependencies[dependency.identity.description] { +// return !guardingTraits.subtracting(enabledTraits).isEmpty +// } + } } } diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 798a4f6543b..7b24cb62f21 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -287,21 +287,31 @@ public final class Manifest: Sendable { guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else { var dependencies = self.dependencies - if pruneDependencies { +// if pruneDependencies { +// if let obsScope { +// obsScope.emit(warning: "\(self.displayName) DEPENDENCIES: \(dependencies)") +// } +// print("dependencies required for \(self.displayName) and traits: \(enabledTraits)") dependencies = try dependencies.filter({ - return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) + let isUsed = try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) +// return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) +// if let obsScope { +// obsScope.emit(warning: "is used? \(isUsed)") +// } + return isUsed }) - } +// } return dependencies } // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false if var dependencies = self._requiredDependencies[.nothing] { - if self.pruneDependencies { +// if self.pruneDependencies { +// print("dependencies required for \(self.displayName) and traits: \(enabledTraits)") dependencies = try dependencies.filter({ return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) }) - } +// } return dependencies } else { var requiredDependencies: Set = [] diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 6a23983e8a5..40966c431d5 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -216,9 +216,11 @@ extension Workspace { return true } return !conditionTraits.intersection(allRootEnabledTraits).isEmpty - }.map(\.name) ?? [] + }.map(\.name) - traitMap[dependency.identity] = Set(explicitlyEnabledTraits) + if let explicitlyEnabledTraits { + traitMap[dependency.identity] = Set(explicitlyEnabledTraits) + } } var unusedIdentities: OrderedCollections.OrderedSet = [] @@ -226,26 +228,26 @@ extension Workspace { let inputNodes: [GraphLoadingNode] = try root.packages.map { identity, package in inputIdentities.append(package.reference) - let traits: Set? = rootEnabledTraitsMap[package.reference.identity] ?? [] + let traits: Set? = rootEnabledTraitsMap[package.reference.identity] let node = try GraphLoadingNode( identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: traits ?? [] + enabledTraits: traits //?? [] ) return node } + root.dependencies.compactMap { dependency in let package = dependency.packageRef inputIdentities.append(package) return try manifestsMap[dependency.identity].map { manifest in - let traits: Set? = rootDependenciesEnabledTraitsMap[dependency.identity] ?? [] + let traits: Set? = rootDependenciesEnabledTraitsMap[dependency.identity] return try GraphLoadingNode( identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: traits ?? [] + enabledTraits: traits //?? [] ) } } @@ -265,11 +267,12 @@ extension Workspace { var requiredIdentities: OrderedCollections.OrderedSet = [] _ = try transitiveClosure(inputNodes) { node in - - try node.manifest.dependenciesRequired(for: node.productFilter, node.enabledTraits) +// print("checking \(node.manifest.displayName) with traits \(node.enabledTraits)") + return try node.manifest.dependenciesRequired(for: node.productFilter, node.enabledTraits) .compactMap { dependency in let package = dependency.packageRef +// print("checking if dep \(dependency.identity) is used in \(node.manifest.displayName) with traits \(node.enabledTraits)") // Check if traits are guarding the dependency from being enabled. // Also check whether we've enabled pruning unused dependencies. let isDepUsed = try node.manifest.isPackageDependencyUsed( @@ -280,7 +283,7 @@ extension Workspace { observabilityScope.emit(debug: """ '\(package.identity)' from '\(package.locationString)' was omitted \ from required dependencies because it is being guarded by the following traits:' \ - \(node.enabledTraits.joined(separator: ", ")) + \(node.enabledTraits?.joined(separator: ", ")) """) } else { unusedDepsPerPackage[node.identity, default: []] = unusedDepsPerPackage[ @@ -333,13 +336,13 @@ extension Workspace { guard let conditionTraits = $0.condition?.traits else { return true } - return !conditionTraits.intersection(node.enabledTraits).isEmpty + return !conditionTraits.intersection(node.enabledTraits ?? []).isEmpty }.map(\.name) return try manifestsMap[dependency.identity].map { manifest in // Calculate all transitively enabled traits for this manifest. - var allEnabledTraits: Set = [] + var allEnabledTraits: Set? if let explicitlyEnabledTraits { allEnabledTraits = Set(explicitlyEnabledTraits) @@ -425,9 +428,11 @@ extension Workspace { } return !conditionTraits .intersection(workspace.configuration.traitConfiguration.enabledTraits ?? []).isEmpty - }.map(\.name) ?? [] + }.map(\.name) - traitMap[dependency.identity] = Set(explicitlyEnabledTraits) + if let explicitlyEnabledTraits { + traitMap[dependency.identity] = Set(explicitlyEnabledTraits) + } } for (externalManifest, managedDependency, productFilter, _) in dependencies { @@ -591,7 +596,7 @@ extension Workspace { let rootManifests = try root.manifests.mapValues { manifest in let deps = try manifest.dependencies.filter { dep in - guard configuration.pruneDependencies else { return true } +// guard configuration.pruneDependencies else { return true } let enabledTraits = root.enabledTraits[manifest.packageIdentity] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed @@ -628,7 +633,7 @@ extension Workspace { // optimization: preload first level dependencies manifest (in parallel) let firstLevelDependencies = try topLevelManifests.values.map { manifest in try manifest.dependencies.filter { dep in - guard configuration.pruneDependencies else { return true } +// guard configuration.pruneDependencies else { return true } let enabledTraits: Set? = root.enabledTraits[manifest.packageIdentity] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed @@ -647,6 +652,7 @@ extension Workspace { GraphLoadingNode, PackageIdentity >] = { node in +// observabilityScope.emit(warning: "entering with \(node.item.manifest.displayName) and enabled traits: \(node.item.enabledTraits)") // optimization: preload manifest we know about in parallel let dependenciesRequired = try node.item.manifest.dependenciesRequired( for: node.item.productFilter, @@ -662,8 +668,10 @@ extension Workspace { observabilityScope: observabilityScope ) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } +// observabilityScope.emit(warning: "deps: \(dependenciesGuarded.count + dependenciesRequired.count)") return try (dependenciesRequired + dependenciesGuarded).compactMap { dependency in - try loadedManifests[dependency.identity].flatMap { manifest in + return try loadedManifests[dependency.identity].flatMap { manifest in + // we also compare the location as this function may attempt to load // dependencies that have the same identity but from a different location // which is an error case we diagnose an report about in the GraphLoading part which @@ -672,9 +680,11 @@ extension Workspace { guard let conditionTraits = $0.condition?.traits else { return true } - return !conditionTraits.intersection(node.item.enabledTraits).isEmpty + return !conditionTraits.intersection(node.item.enabledTraits ?? []).isEmpty }.map(\.name) + let usingTraits = explicitlyEnabledTraits.flatMap { Set($0) } + let calculatedTraits = try manifest.enabledTraits( using: explicitlyEnabledTraits.flatMap { Set($0) }, node.item.identity.description @@ -686,7 +696,7 @@ extension Workspace { identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: calculatedTraits ?? [] + enabledTraits: calculatedTraits ), key: dependency.identity ) : @@ -699,14 +709,15 @@ extension Workspace { do { let manifestGraphRoots = try topLevelManifests.map { identity, manifest in - let isRoot = manifest.packageKind.isRoot - let enabledTraits = isRoot ? root.enabledTraits[identity] : [] + // TODO bp: not sure if this is correct +// let isRoot = manifest.packageKind.isRoot +// let enabledTraits = isRoot ? root.enabledTraits[identity] : [] return try KeyedPair( GraphLoadingNode( identity: identity, manifest: manifest, productFilter: .everything, - enabledTraits: enabledTraits ?? [] + enabledTraits: root.enabledTraits[identity] ), key: identity ) @@ -718,7 +729,11 @@ extension Workspace { ) { allNodes[$0.key] = $0.item } onDuplicate: { old, new in - allNodes[old.key]?.enabledTraits.formUnion(new.item.enabledTraits) +// observabilityScope.emit(warning: "\(old.key) forming union between old \(allNodes[old.key]?.enabledTraits) and new \(new.item.enabledTraits)") + if let enabledTraits = new.item.enabledTraits { + allNodes[old.key]?.enabledTraits?.formUnion(enabledTraits) +// print("did add traits to \(old.key)? \(allNodes[old.key]?.enabledTraits)") + } } } diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index 160ddd6c81b..a7af4641efa 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -43,7 +43,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--experimental-prune-unused-dependencies"], +// extraArgs: ["--experimental-prune-unused-dependencies"], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -81,7 +81,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--traits", "default,Package9,Package10", "--experimental-prune-unused-dependencies"], + extraArgs: ["--traits", "default,Package9,Package10"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -122,7 +122,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--traits", "default,Package9", "--experimental-prune-unused-dependencies"], + extraArgs: ["--traits", "default,Package9"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -164,7 +164,7 @@ struct TraitTests { extraArgs: [ "--traits", "default,Package5,Package7,BuildCondition3", - "--experimental-prune-unused-dependencies", + //"--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) @@ -205,7 +205,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--disable-default-traits", "--experimental-prune-unused-dependencies"], + extraArgs: ["--disable-default-traits"/*"--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -238,7 +238,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--traits", "Package5,Package7", "--experimental-prune-unused-dependencies"], + extraArgs: ["--traits", "Package5,Package7"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -274,7 +274,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--enable-all-traits", "--experimental-prune-unused-dependencies"], + extraArgs: ["--enable-all-traits"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -321,7 +321,7 @@ struct TraitTests { extraArgs: [ "--enable-all-traits", "--disable-default-traits", - "--experimental-prune-unused-dependencies", +// "--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) @@ -384,7 +384,7 @@ struct TraitTests { try await fixture(name: "Traits") { fixturePath in let (stdout, _) = try await executeSwiftTest( fixturePath.appending("Example"), - extraArgs: ["--experimental-prune-unused-dependencies"], +// extraArgs: ["--experimental-prune-unused-dependencies"], buildSystem: buildSystem, ) let expectedOut = """ @@ -421,7 +421,7 @@ struct TraitTests { extraArgs: [ "--enable-all-traits", "--disable-default-traits", - "--experimental-prune-unused-dependencies", + // "--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) @@ -461,7 +461,7 @@ struct TraitTests { try await fixture(name: "Traits") { fixturePath in let (stdout, _) = try await executeSwiftPackage( fixturePath.appending("Package10"), - extraArgs: ["dump-symbol-graph", "--experimental-prune-unused-dependencies"], + extraArgs: ["dump-symbol-graph"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) let optionalPath = stdout @@ -489,7 +489,7 @@ struct TraitTests { try await fixture(name: "Traits") { fixturePath in let (stdout, _) = try await executeSwiftPackage( fixturePath.appending("Package10"), - extraArgs: ["plugin", "extract", "--experimental-prune-unused-dependencies"], + extraArgs: ["plugin", "extract"/*, "--experimental-prune-unused-dependencies"*/], buildSystem: buildSystem, ) let path = String(stdout.split(whereSeparator: \.isNewline).first!) From f67f3dfe467cb30abf03c9f9fa7eab00c44a9354 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 19 Jun 2025 14:39:26 -0400 Subject: [PATCH 02/23] Comment cleanup --- Sources/PackageGraph/GraphLoadingNode.swift | 1 - .../PackageGraph/ModulesGraph+Loading.swift | 10 +-- Sources/PackageGraph/PackageGraphRoot.swift | 2 +- .../Manifest/Manifest+Traits.swift | 61 ------------------- Sources/PackageModel/Manifest/Manifest.swift | 13 ---- Sources/Workspace/Workspace+Manifests.swift | 3 +- 6 files changed, 3 insertions(+), 87 deletions(-) diff --git a/Sources/PackageGraph/GraphLoadingNode.swift b/Sources/PackageGraph/GraphLoadingNode.swift index 83bb5f63b12..6b78b1d26ab 100644 --- a/Sources/PackageGraph/GraphLoadingNode.swift +++ b/Sources/PackageGraph/GraphLoadingNode.swift @@ -38,7 +38,6 @@ public struct GraphLoadingNode: Equatable, Hashable { productFilter: ProductFilter, enabledTraits: Set? ) throws { -// print("\(identity) enabled TRAITSSSSSS : \(enabledTraits)") self.identity = identity self.manifest = manifest self.productFilter = productFilter diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 12935129fc7..3e728645057 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -107,12 +107,6 @@ extension ModulesGraph { return !conditionTraits.intersection(node.item.enabledTraits ?? []).isEmpty }.map(\.name) -// let calculatedTraits = try calculateEnabledTraits( -// parentPackage: node.item.identity, -// identity: dependency.identity, -// manifest: manifest, -// explictlyEnabledTraits: explictlyEnabledTraits.flatMap { Set($0) } -// ) let calculatedTraits = try manifest.enabledTraits(using: explictlyEnabledTraits.flatMap { Set($0) }, node.item.identity.description) return try KeyedPair( @@ -154,9 +148,9 @@ extension ModulesGraph { allNodes[$0.key] = $0.item } onDuplicate: { first, second in // We are unifying the enabled traits on duplicate + // TODO bp: this may not be necessary anymore since we pre-compute all enabled and transitively enabled traits...? if let enabledTraits = second.item.enabledTraits { allNodes[first.key]?.enabledTraits?.formUnion(enabledTraits) -// print("did this create traits for \(first.key)? \(allNodes[first.key]?.enabledTraits)") } } @@ -410,7 +404,6 @@ private func createResolvedPackages( } let isAllowedToVendUnsafeProducts = unsafeAllowedPackages.contains { $0.identity == package.identity } -// print("node traits for \(node.identity.description): \(node.enabledTraits)") let allowedToOverride = rootManifests.values.contains(node.manifest) return ResolvedPackageBuilder( package, @@ -448,7 +441,6 @@ private func createResolvedPackages( var dependenciesByNameForModuleDependencyResolution = [String: ResolvedPackageBuilder]() var dependencyNamesForModuleDependencyResolutionOnly = [PackageIdentity: String]() -// print("manifest resolved package \(package.manifest.displayName) with traits \(packageBuilder.enabledTraits)") try package.manifest.dependenciesRequired( for: packageBuilder.productFilter, packageBuilder.enabledTraits diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 9cbee58e3d1..f8b65fc0094 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -139,7 +139,7 @@ public struct PackageGraphRoot { // is enabled. // TODO bp: assure that trait-guarded deps are pruned regardless return manifests.values.reduce(false) { result, manifest in - guard manifest.pruneDependencies else { return true } +// guard manifest.pruneDependencies else { return true } let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { return result || isUsed diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index 5cccace2126..8531b858663 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -437,10 +437,6 @@ extension Manifest { } /// Determines whether a given package dependency is used by this manifest given a set of enabled traits. public func isPackageDependencyUsed(_ dependency: PackageDependency, enabledTraits: Set?) throws -> Bool { - // TODO bp: assure that we are still pruning trait-guarded dependencies here - - // have package dependency -- seek out all target dependencies that reference this package dep - // for each target dep, see whether they're all trait-guarded - if each reference to the package dep is trait-guarded, then we can safely prune it if self.pruneDependencies { let usedDependencies = try self.usedDependencies(withTraits: enabledTraits) let foundKnownPackage = usedDependencies.knownPackage.contains(where: { @@ -452,15 +448,6 @@ extension Manifest { return foundKnownPackage || (!foundKnownPackage && !usedDependencies.unknownPackage.isEmpty) } else { // alternate path to compute trait-guarded package dependencies if the prune deps feature is not enabled -// let traitGuardedTargetDependencies = traitGuardedTargetDependencies() -// let traitGuardedPackageDependencies = traitGuardedTargetDependencies.flatMap({ $0.value }).reduce(into: [String: Set]()) { guardedPackages, targetDep in -// guard let package = targetDep.package, let traits = targetDep.condition?.traits else { -// return -// } -// -// guardedPackages[package, default: []].formUnion(traits) -// } -// print("manifest \(displayName) and traits \(enabledTraits)") try validateEnabledTraits(enabledTraits) let targetDependenciesForPackageDependency = self.targets.flatMap({ $0.dependencies }) @@ -471,60 +458,12 @@ extension Manifest { // if target deps is empty, default to returning true here. let isTraitGuarded = targetDependenciesForPackageDependency.isEmpty ? false : targetDependenciesForPackageDependency.compactMap({ $0.condition?.traits }).allSatisfy({ let condition = $0.subtracting(enabledTraits ?? []) -// if let obsScope { -// obsScope.emit(warning: "condition: \(condition) where enabled traits are: \(enabledTraits ?? []) and guarding traits are: \($0)") -// } -// return !$0.subtracting(enabledTraits ?? []).isEmpty return !condition.isEmpty }) let isUsedWithoutTraitGuarding = !targetDependenciesForPackageDependency.filter({ $0.condition?.traits == nil }).isEmpty -// if let obsScope { -// obsScope.emit(warning: "\(dependency.identity) isUsedWithoutTraitGuarding: \(isUsedWithoutTraitGuarding)") -// obsScope.emit(warning: "\(dependency.identity) isTraitGuarded: \(isTraitGuarded)") -// } - // Special case until we allow for pruning dependencies -// if !isUsedWithoutTraitGuarding && !isTraitGuarded { -// return true -// } else if isUsedWithoutTraitGuarding && isTraitGuarded { -// return true -// } else if !isUsedWithoutTraitGuarding && isTraitGuarded { -// return false -// } else if isUsedWithoutTraitGuarding && !isTraitGuarded { -// return true -// } - - // Until the pruning unused dependencies features is fully implemented, this is how we will compute whether - // to omit a trait-guarded package dependency. -// if !isUsedWithoutTraitGuarding && isTraitGuarded { -// return false -// } - return isUsedWithoutTraitGuarding || !isTraitGuarded -// return isUsedWithoutTraitGuarding || !isTraitGuarded -// .reduce(into: [String: [TargetDescription.Dependency]]()) { packageTargetDeps, targetDep in -// if let packageName = targetDep.package { -// packageTargetDeps[packageName, default: []].append(targetDep) -// } -// } - - - - // Check that this package dependency is potentially guarded by traits. - // Note that this package dependency can also be referenced by target dependencies without being - // guarded by traits... -// if let guardingTraits = traitGuardedPackageDependencies[dependency.identity.description] { -// let isTraitGuarded = guardingTraits.isSubset(of: enabledTraits ?? []) - // Depending on whether this dependency is used elsewhere without any trait guarding, this - // will determine whether we can prune this from the dependency graph. -// let isTraitGuarded = !guardingTraits.subtracting(enabledTraits ?? []).isEmpty -// } - -// return true -// if let enabledTraits = enabledTraits, let guardingTraits = traitGuardedPackageDependencies[dependency.identity.description] { -// return !guardingTraits.subtracting(enabledTraits).isEmpty -// } } } } diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 7b24cb62f21..9301b285c23 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -287,31 +287,18 @@ public final class Manifest: Sendable { guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else { var dependencies = self.dependencies -// if pruneDependencies { -// if let obsScope { -// obsScope.emit(warning: "\(self.displayName) DEPENDENCIES: \(dependencies)") -// } -// print("dependencies required for \(self.displayName) and traits: \(enabledTraits)") dependencies = try dependencies.filter({ let isUsed = try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) -// return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) -// if let obsScope { -// obsScope.emit(warning: "is used? \(isUsed)") -// } return isUsed }) -// } return dependencies } // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false if var dependencies = self._requiredDependencies[.nothing] { -// if self.pruneDependencies { -// print("dependencies required for \(self.displayName) and traits: \(enabledTraits)") dependencies = try dependencies.filter({ return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) }) -// } return dependencies } else { var requiredDependencies: Set = [] diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 40966c431d5..66e67789c92 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -729,10 +729,9 @@ extension Workspace { ) { allNodes[$0.key] = $0.item } onDuplicate: { old, new in -// observabilityScope.emit(warning: "\(old.key) forming union between old \(allNodes[old.key]?.enabledTraits) and new \(new.item.enabledTraits)") if let enabledTraits = new.item.enabledTraits { + // TODO bp: this may not be necessary anymore since we pre-compute all enabled and transitively enabled traits...? allNodes[old.key]?.enabledTraits?.formUnion(enabledTraits) -// print("did add traits to \(old.key)? \(allNodes[old.key]?.enabledTraits)") } } } From 0d46a4c954923c6464c57f1012aa498ce1e4ce99 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 19 Jun 2025 14:57:40 -0400 Subject: [PATCH 03/23] De-duplicate calculation of transitively enabled traits --- Sources/PackageGraph/PackageGraphRoot.swift | 1 - Sources/Workspace/Workspace+Manifests.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index f8b65fc0094..c4dc52ed8db 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -139,7 +139,6 @@ public struct PackageGraphRoot { // is enabled. // TODO bp: assure that trait-guarded deps are pruned regardless return manifests.values.reduce(false) { result, manifest in -// guard manifest.pruneDependencies else { return true } let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { return result || isUsed diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 66e67789c92..5cc82c0b55c 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -731,7 +731,7 @@ extension Workspace { } onDuplicate: { old, new in if let enabledTraits = new.item.enabledTraits { // TODO bp: this may not be necessary anymore since we pre-compute all enabled and transitively enabled traits...? - allNodes[old.key]?.enabledTraits?.formUnion(enabledTraits) +// allNodes[old.key]?.enabledTraits?.formUnion(enabledTraits) } } } From e6bc7e6220a36190054dff3070c5a00ec54081a1 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 25 Jun 2025 14:57:54 -0400 Subject: [PATCH 04/23] Precompute traits and persist to module graph load --- .../PackageDependency.swift | 2 +- .../PackageGraph/ModulesGraph+Loading.swift | 24 +--- Sources/PackageGraph/PackageGraphRoot.swift | 1 - .../PackageDependencyDescription.swift | 7 +- .../Workspace/Workspace+Dependencies.swift | 30 +++-- Sources/Workspace/Workspace+Manifests.swift | 104 +++++++++++------- .../_InternalTestSupport/MockWorkspace.swift | 9 +- 7 files changed, 106 insertions(+), 71 deletions(-) diff --git a/Sources/PackageDescription/PackageDependency.swift b/Sources/PackageDescription/PackageDependency.swift index dd654c5b37a..a71e44dffbc 100644 --- a/Sources/PackageDescription/PackageDependency.swift +++ b/Sources/PackageDescription/PackageDependency.swift @@ -56,7 +56,7 @@ extension Package { public let kind: Kind /// The dependencies traits configuration. - @available(_PackageDescription, introduced: 999.0) + @available(_PackageDescription, introduced: 6.1) public let traits: Set /// The name of the dependency. diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 3e728645057..c184bbc629f 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -56,6 +56,7 @@ extension ModulesGraph { }) let rootManifestNodes = try root.packages.map { identity, package in + // TODO bp // If we have enabled traits passed then we start with those. If there are no enabled // traits passed then the default traits will be used. let enabledTraits = root.enabledTraits[identity] @@ -63,12 +64,7 @@ extension ModulesGraph { identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: calculateEnabledTraits( - parentPackage: nil, - identity: identity, - manifest: package.manifest, - explictlyEnabledTraits: enabledTraits - ) + enabledTraits: enabledTraits ) } let rootDependencyNodes = try root.dependencies.lazy.filter { requiredDependencies.contains($0.packageRef) } @@ -100,21 +96,16 @@ extension ModulesGraph { // We are going to check the conditionally enabled traits here and enable them if // required. This checks the current node and then enables the conditional // dependencies of the dependency node. - let explictlyEnabledTraits = dependency.traits?.filter { - guard let conditionTraits = $0.condition?.traits else { - return true - } - return !conditionTraits.intersection(node.item.enabledTraits ?? []).isEmpty - }.map(\.name) - let calculatedTraits = try manifest.enabledTraits(using: explictlyEnabledTraits.flatMap { Set($0) }, node.item.identity.description) + // TODO bp: shouldn't need to do any traits computation here, + // if we've successfully computed them in the PackageGraphRoot. return try KeyedPair( GraphLoadingNode( identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: calculatedTraits + enabledTraits: root.enabledTraits[manifest.packageIdentity]//calculatedTraits ), key: dependency.identity ) @@ -148,10 +139,7 @@ extension ModulesGraph { allNodes[$0.key] = $0.item } onDuplicate: { first, second in // We are unifying the enabled traits on duplicate - // TODO bp: this may not be necessary anymore since we pre-compute all enabled and transitively enabled traits...? - if let enabledTraits = second.item.enabledTraits { - allNodes[first.key]?.enabledTraits?.formUnion(enabledTraits) - } + // TODO bp: to remove this, precompute traits elsewhere } // Create the packages. diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index c4dc52ed8db..18cc1d1bf8e 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -137,7 +137,6 @@ public struct PackageGraphRoot { // Check that the dependency is used in at least one of the manifests. // If not, then we can omit this dependency if pruning unused dependencies // is enabled. - // TODO bp: assure that trait-guarded deps are pruned regardless return manifests.values.reduce(false) { result, manifest in let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { diff --git a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift index 8cc316b3e81..5ed36dc0637 100644 --- a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift +++ b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift @@ -24,11 +24,16 @@ public enum PackageDependency: Equatable, Hashable, Sendable { /// A condition that limits the application of a dependencies trait. package struct Condition: Hashable, Sendable, Codable { /// The set of traits of this package that enable the dependency's trait. - package let traits: Set? + private let traits: Set? public init(traits: Set?) { self.traits = traits } + + public func isSatisfied(by enabledTraits: Set?) -> Bool { + guard let traits else { return true } + return !traits.intersection(enabledTraits ?? []).isEmpty + } } /// The name of the enabled trait. diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 0dcacd84e2a..dd824c11927 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -81,7 +81,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - let graphRoot = try PackageGraphRoot( + var graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, dependencyMapper: self.dependencyMapper, @@ -91,6 +91,8 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) + // TODO bp: find less hacky way to do this + graphRoot = currentManifests.root // Abort if we're unable to load the `Package.resolved` store or have any diagnostics. guard let resolvedPackagesStore = observabilityScope.trap({ try self.resolvedPackagesStore.load() }) else { return nil } @@ -164,6 +166,8 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) + // TODO bp + graphRoot = updatedDependencyManifests.root // If we have missing packages, something is fundamentally wrong with the resolution of the graph let stillMissingPackages = try updatedDependencyManifests.missingPackages guard stillMissingPackages.isEmpty else { @@ -348,7 +352,7 @@ extension Workspace { packages: root.packages, observabilityScope: observabilityScope ) - let graphRoot = try PackageGraphRoot( + var graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, @@ -360,11 +364,15 @@ extension Workspace { guard let resolvedPackagesStore = observabilityScope.trap({ try self.resolvedPackagesStore.load() }), !observabilityScope.errorsReported else { - return try await ( - self.loadDependencyManifests( - root: graphRoot, - observabilityScope: observabilityScope - ), + let dependencyManifests = try await self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ) + + // TODO bp + graphRoot = dependencyManifests.root + + return (dependencyManifests, .notRequired ) } @@ -462,6 +470,8 @@ extension Workspace { automaticallyAddManagedDependencies: true, observabilityScope: observabilityScope ) + // TODO bp + graphRoot = currentManifests.root try await self.updateBinaryArtifacts( manifests: currentManifests, @@ -513,7 +523,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - let graphRoot = try PackageGraphRoot( + var graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, @@ -526,6 +536,8 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) + // TODO bp + graphRoot = currentManifests.root guard !observabilityScope.errorsReported else { return currentManifests } @@ -625,6 +637,8 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) + // TODO bp + graphRoot = updatedDependencyManifests.root // If we still have missing packages, something is fundamentally wrong with the resolution of the graph let stillMissingPackages = try updatedDependencyManifests.missingPackages diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 5cc82c0b55c..5b45cfde299 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -51,7 +51,7 @@ extension Workspace { /// A struct representing all the current manifests (root + external) in a package graph. public struct DependencyManifests { /// The package graph root. - let root: PackageGraphRoot + var root: PackageGraphRoot /// The dependency manifests in the transitive closure of root manifest. let dependencies: [( @@ -212,10 +212,12 @@ extension Workspace { let rootDependenciesEnabledTraitsMap = root.dependencies .reduce(into: [PackageIdentity: Set]()) { traitMap, dependency in let explicitlyEnabledTraits = dependency.traits?.filter { - guard let conditionTraits = $0.condition?.traits else { - return true - } - return !conditionTraits.intersection(allRootEnabledTraits).isEmpty +// guard let conditionTraits = $0.condition?.traits else { +// return true +// } +// return !conditionTraits.intersection(allRootEnabledTraits).isEmpty + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: Set(allRootEnabledTraits)) }.map(\.name) if let explicitlyEnabledTraits { @@ -234,7 +236,7 @@ extension Workspace { identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: traits //?? [] + enabledTraits: traits ) return node } + root.dependencies.compactMap { dependency in @@ -247,7 +249,7 @@ extension Workspace { identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: traits //?? [] + enabledTraits: traits ) } } @@ -267,12 +269,10 @@ extension Workspace { var requiredIdentities: OrderedCollections.OrderedSet = [] _ = try transitiveClosure(inputNodes) { node in -// print("checking \(node.manifest.displayName) with traits \(node.enabledTraits)") return try node.manifest.dependenciesRequired(for: node.productFilter, node.enabledTraits) .compactMap { dependency in let package = dependency.packageRef -// print("checking if dep \(dependency.identity) is used in \(node.manifest.displayName) with traits \(node.enabledTraits)") // Check if traits are guarding the dependency from being enabled. // Also check whether we've enabled pruning unused dependencies. let isDepUsed = try node.manifest.isPackageDependencyUsed( @@ -333,10 +333,12 @@ extension Workspace { // should calculate enabled traits here. let explicitlyEnabledTraits = dependency.traits?.filter { - guard let conditionTraits = $0.condition?.traits else { - return true - } - return !conditionTraits.intersection(node.enabledTraits ?? []).isEmpty +// guard let conditionTraits = $0.condition?.traits else { +// return true +// } +// return !conditionTraits.intersection(node.enabledTraits ?? []).isEmpty + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: node.enabledTraits) }.map(\.name) return try manifestsMap[dependency.identity].map { manifest in @@ -423,11 +425,13 @@ extension Workspace { let rootDependenciesEnabledTraitsMap = root.dependencies .reduce(into: [PackageIdentity: Set]()) { traitMap, dependency in let explicitlyEnabledTraits = dependency.traits?.filter { - guard let conditionTraits = $0.condition?.traits else { - return true - } - return !conditionTraits - .intersection(workspace.configuration.traitConfiguration.enabledTraits ?? []).isEmpty +// guard let conditionTraits = $0.condition?.traits else { +// return true +// } +// return !conditionTraits +// .intersection(workspace.configuration.traitConfiguration.enabledTraits ?? []).isEmpty + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: workspace.configuration.traitConfiguration.enabledTraits) }.map(\.name) if let explicitlyEnabledTraits { @@ -596,7 +600,6 @@ extension Workspace { let rootManifests = try root.manifests.mapValues { manifest in let deps = try manifest.dependencies.filter { dep in -// guard configuration.pruneDependencies else { return true } let enabledTraits = root.enabledTraits[manifest.packageIdentity] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed @@ -630,12 +633,23 @@ extension Workspace { lhs // prefer roots! }) + var enabledTraitsMap = root.enabledTraits + // optimization: preload first level dependencies manifest (in parallel) let firstLevelDependencies = try topLevelManifests.values.map { manifest in try manifest.dependencies.filter { dep in -// guard configuration.pruneDependencies else { return true } + // Calculate conditional traits for dependencies here; add to enabled traits map. let enabledTraits: Set? = root.enabledTraits[manifest.packageIdentity] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) + let explicitlyEnabledTraits = dep.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: enabledTraits) + } + + // Add enabled traits dictated by the parent package + if let explicitlyEnabledTraits, !explicitlyEnabledTraits.isEmpty { + enabledTraitsMap[dep.identity, default: []].formUnion(explicitlyEnabledTraits.map(\.name)) + } return isDepUsed }.map(\.packageRef) }.flatMap(\.self) @@ -645,6 +659,21 @@ extension Workspace { observabilityScope: observabilityScope ) + var manifestMap: [PackageIdentity: Manifest] = firstLevelManifests + + // Part of pre-computing unified traits is that we must load each dependency's manifest itself -- + // to avoid redundancy of this task, store a manifestMap that will later be referenced by the + // below graph + + for parentManifest in topLevelManifests.values { + try parentManifest.dependencies.forEach { dep in + if let enabledDependencyTraits = enabledTraitsMap[dep.identity], let depManifest = manifestMap[dep.identity] { + let calculatedTraits = try depManifest.enabledTraits(using: enabledDependencyTraits, parentManifest.packageIdentity.description) + enabledTraitsMap[dep.identity]?.formUnion(calculatedTraits ?? []) + } + } + } + // Continue to load the rest of the manifest for this graph // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. var loadedManifests = firstLevelManifests @@ -652,7 +681,6 @@ extension Workspace { GraphLoadingNode, PackageIdentity >] = { node in -// observabilityScope.emit(warning: "entering with \(node.item.manifest.displayName) and enabled traits: \(node.item.enabledTraits)") // optimization: preload manifest we know about in parallel let dependenciesRequired = try node.item.manifest.dependenciesRequired( for: node.item.productFilter, @@ -668,28 +696,25 @@ extension Workspace { observabilityScope: observabilityScope ) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } -// observabilityScope.emit(warning: "deps: \(dependenciesGuarded.count + dependenciesRequired.count)") return try (dependenciesRequired + dependenciesGuarded).compactMap { dependency in return try loadedManifests[dependency.identity].flatMap { manifest in - // we also compare the location as this function may attempt to load - // dependencies that have the same identity but from a different location - // which is an error case we diagnose an report about in the GraphLoading part which - // is prepared to handle the case where not all manifest are available let explicitlyEnabledTraits = dependency.traits?.filter { - guard let conditionTraits = $0.condition?.traits else { - return true - } - return !conditionTraits.intersection(node.item.enabledTraits ?? []).isEmpty + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: node.item.enabledTraits) }.map(\.name) - let usingTraits = explicitlyEnabledTraits.flatMap { Set($0) } - let calculatedTraits = try manifest.enabledTraits( using: explicitlyEnabledTraits.flatMap { Set($0) }, node.item.identity.description ) + enabledTraitsMap[manifest.packageIdentity, default: []].formUnion(calculatedTraits ?? []) + + // we also compare the location as this function may attempt to load + // dependencies that have the same identity but from a different location + // which is an error case we diagnose an report about in the GraphLoading part which + // is prepared to handle the case where not all manifest are available return manifest.canonicalPackageLocation == dependency.packageRef.canonicalLocation ? try KeyedPair( GraphLoadingNode( @@ -709,9 +734,6 @@ extension Workspace { do { let manifestGraphRoots = try topLevelManifests.map { identity, manifest in - // TODO bp: not sure if this is correct -// let isRoot = manifest.packageKind.isRoot -// let enabledTraits = isRoot ? root.enabledTraits[identity] : [] return try KeyedPair( GraphLoadingNode( identity: identity, @@ -729,10 +751,7 @@ extension Workspace { ) { allNodes[$0.key] = $0.item } onDuplicate: { old, new in - if let enabledTraits = new.item.enabledTraits { - // TODO bp: this may not be necessary anymore since we pre-compute all enabled and transitively enabled traits...? -// allNodes[old.key]?.enabledTraits?.formUnion(enabledTraits) - } + // TODO bp } } @@ -767,8 +786,13 @@ extension Workspace { dependencies.append((node.manifest, dependency, node.productFilter, fileSystem ?? self.fileSystem)) } + // TODO bp: pass in new root here, with the updated enabledTraitsMap + var newRoot = root + newRoot.enabledTraits = enabledTraitsMap + return DependencyManifests( - root: root, +// root: root, + root: newRoot, dependencies: dependencies, workspace: self, observabilityScope: observabilityScope diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index 0eed3d9293f..acf70042af0 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -685,7 +685,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - let root = try PackageGraphRoot( + var root = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope @@ -696,6 +696,9 @@ public final class MockWorkspace { observabilityScope: observability.topScope ) + // TODO bp + // root.enabledTraits = dependencyManifests.root + let result = try await workspace.precomputeResolution( root: root, dependencyManifests: dependencyManifests, @@ -952,7 +955,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - let graphRoot = try PackageGraphRoot( + var graphRoot = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope @@ -961,6 +964,8 @@ public final class MockWorkspace { root: graphRoot, observabilityScope: observability.topScope ) + // TODO bp +// graphRoot = manifests.root result(manifests, observability.diagnostics) } From 6a3d2bb360747522cfbc8e76c1df44acd655de8c Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 25 Jun 2025 15:05:33 -0400 Subject: [PATCH 05/23] Fix trait tests to account for removal of guarded deps --- Tests/WorkspaceTests/WorkspaceTests.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 550d3f05e8d..45444ce2a41 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -15822,7 +15822,7 @@ final class WorkspaceTests: XCTestCase { .product( name: "Boo", package: "Boo", - // Trait2 disabled; should generate unused dependency warning + // Trait2 disabled; should remove this dependency from graph condition: .init(traits: ["Trait2"]) ), ] @@ -15873,14 +15873,16 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Foo"], deps: deps) { graph, diagnostics in PackageGraphTester(graph) { result in result.check(roots: "Foo") - result.check(packages: "Baz", "Foo", "Boo") - result.check(modules: "Bar", "Baz", "Boo", "Foo") +// result.check(packages: "Baz", "Foo", "Boo") + result.check(packages: "Baz", "Foo") + result.check(modules: "Bar", "Baz", "Foo") result.checkTarget("Foo") { result in result.check(dependencies: "Baz") } result.checkTarget("Bar") { result in result.check(dependencies: "Baz") } } - testDiagnostics(diagnostics) { result in - result.check(diagnostic: .contains("dependency 'boo' is not used by any target"), severity: .warning) - } + XCTAssertNoDiagnostics(diagnostics) +// testDiagnostics(diagnostics) { result in +// result.check(diagnostic: .contains("dependency 'boo' is not used by any target"), severity: .warning) +// } } await workspace.checkManagedDependencies { result in result.check(dependency: "baz", at: .checkout(.version("1.0.0"))) @@ -16187,7 +16189,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraphFailure(roots: ["Foo"], deps: deps) { diagnostics in testDiagnostics(diagnostics) { result in - result.check(diagnostic: .equal("Trait 'TraitNotFound' enabled by parent package 'foo' is not declared by package 'Baz'. The available traits declared by this package are: TraitFound."), severity: .error) + result.check(diagnostic: .equal("Trait 'TraitNotFound' enabled by parent package 'Foo' is not declared by package 'Baz'. The available traits declared by this package are: TraitFound."), severity: .error) } } await workspace.checkManagedDependencies { result in From e5c9eb847fa009245a3468ce8691c910b870b859 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 26 Jun 2025 16:56:44 -0400 Subject: [PATCH 06/23] Precompute traits + modify tests - Create a 'precomputeTraits' method that cycles through the graph to unify all the traits - Modify the traits error message to include both the package identity and display name for better readability TODO - change instances of optional enabledTraits to non-optional, defaulting instead to ["default"] - assure that loaded manifests arent fetching guarded dependencies - this is contingent on how we are calculating the unified traits afterwards --- .../PackageGraph/ModulesGraph+Loading.swift | 13 +- Sources/PackageGraph/ModulesGraph.swift | 72 ++++++++- Sources/PackageGraph/PackageGraphRoot.swift | 20 ++- .../Resolution/ResolvedPackage.swift | 4 +- .../Manifest/Manifest+Traits.swift | 86 ++++++---- Sources/Workspace/Workspace+Manifests.swift | 152 +++++++++++++++--- .../ManifestExtensions.swift | 16 +- .../_InternalTestSupport/MockDependency.swift | 20 +-- .../_InternalTestSupport/MockPackage.swift | 2 +- .../_InternalTestSupport/MockWorkspace.swift | 2 +- ...ckageDependencyDescriptionExtensions.swift | 8 +- Tests/FunctionalTests/TraitTests.swift | 2 +- .../PackageGraphTests/ModulesGraphTests.swift | 16 +- Tests/PackageModelTests/ManifestTests.swift | 25 ++- Tests/WorkspaceTests/WorkspaceTests.swift | 7 +- 15 files changed, 333 insertions(+), 112 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index c184bbc629f..45ab0a0ee63 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -170,7 +170,7 @@ extension ModulesGraph { createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, fileSystem: fileSystem, observabilityScope: nodeObservabilityScope, - enabledTraits: node.enabledTraits ?? ["default"] + enabledTraits: node.enabledTraits ?? []//["default"] TODO bp ) let package = try builder.construct() manifestToPackage[manifest] = package @@ -290,9 +290,12 @@ private func checkAllDependenciesAreUsed( // that can be configured by enabling traits e.g. the depdency has a trait for its logging // behaviour. This allows the root package to configure traits of transitive dependencies // without emitting an unused dependency warning. - if !dependency.enabledTraits.isEmpty { + if dependency.manifest.supportsTraits { continue } +// if !dependency.enabledTraits.isEmpty { +// continue +// } // Make sure that any diagnostics we emit below are associated with the package. let packageDiagnosticsScope = observabilityScope.makeChildScope( @@ -396,7 +399,7 @@ private func createResolvedPackages( return ResolvedPackageBuilder( package, productFilter: node.productFilter, - enabledTraits: node.enabledTraits ?? ["default"], + enabledTraits: node.enabledTraits /*?? []*/, isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts, allowedToOverride: allowedToOverride, platformVersionProvider: platformVersionProvider @@ -1435,7 +1438,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { var products: [ResolvedProductBuilder] = [] /// The enabled traits of this package. - var enabledTraits: Set = ["default"] + var enabledTraits: Set? //= ["default"] TODO bp /// The dependencies of this package. var dependencies: [ResolvedPackageBuilder] = [] @@ -1459,7 +1462,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { init( _ package: Package, productFilter: ProductFilter, - enabledTraits: Set, + enabledTraits: Set?, isAllowedToVendUnsafeProducts: Bool, allowedToOverride: Bool, platformVersionProvider: PlatformVersionProvider diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 7c7e9800be5..bf2924fc747 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -445,6 +445,62 @@ func topologicalSortIdentifiable( return result.reversed() } +public func precomputeTraits( + root: PackageGraphRoot, + _ topLevelManifests: [Manifest], + _ manifestMap: [PackageIdentity: Manifest] +) throws -> [PackageIdentity: Set] { + var enabledTraits = root.enabledTraits + + var visited: Set = [] + + func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { + let parentTraits = enabledTraits[parent.packageIdentity] + let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) + let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) + + _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in + return try manifestMap[dependency.identity].flatMap({ manifest in + + let explicitlyEnabledTraits = dependency.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: parentTraits) + }.map(\.name) + + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } + + // Check for existing traits; TODO bp see if these are the same? + if let depTraits = enabledTraits[dependency.identity] { + enabledTraitsSet?.formUnion(depTraits) + } + + let calculatedTraits = try manifest.enabledTraits( + using: enabledTraitsSet, + .init(parent) + ) + + enabledTraits[dependency.identity] = calculatedTraits + + let result = visited.insert(dependency.identity) + if result.inserted { + try dependencies(of: manifest, dependency.productFilter) + } + + return manifest + }) + }) + } + + for manifest in topLevelManifests { + let result = visited.insert(manifest.packageIdentity) + if result.inserted { + try dependencies(of: manifest) + } + } + + return enabledTraits +} + @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) public func loadModulesGraph( identityResolver: IdentityResolver = DefaultIdentityResolver(), @@ -471,13 +527,27 @@ public func loadModulesGraph( let packages = Array(rootManifests.keys) let input = PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) - let graphRoot = try PackageGraphRoot( + var graphRoot = try PackageGraphRoot( input: input, manifests: rootManifests, explicitProduct: explicitProduct, observabilityScope: observabilityScope ) + let manifestMap = manifests.reduce(into: [PackageIdentity: Manifest]()) { manifestMap, manifest in + manifestMap[manifest.packageIdentity] = manifest + } + + let updatedTraitsMap = try precomputeTraits(root: graphRoot, manifests, manifestMap) + // TODO bp: Post-process the trait computation here; a little hacky since we actually do this during dependency resolution... +// for manifest in manifests { +// var enabledTraits = graphRoot.enabledTraits[manifest.packageIdentity] +// enabledTraits = try manifest.enabledTraits(using: enabledTraits, nil) +// +// graphRoot.enabledTraits[manifest.packageIdentity] = enabledTraits +// } + graphRoot.enabledTraits = updatedTraitsMap + return try ModulesGraph.load( root: graphRoot, identityResolver: identityResolver, diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 18cc1d1bf8e..74b8a05d1eb 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -116,10 +116,24 @@ public struct PackageGraphRoot { // Calculate the enabled traits for each dependency of this root: manifest.dependencies.forEach { dependency in - if let traits = dependency.traits { - let traitNames = traits.map(\.name) - traitsMap[dependency.identity, default: []].formUnion(Set(traitNames)) + // TODO bp: check for condition on the dependency traits here + let explicitlyEnabledTraits = dependency.traits?.filter({ + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: enabledTraits) + }).map(\.name) //?? [] + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } + + if let depTraits = traitsMap[dependency.identity] { + enabledTraitsSet?.formUnion(depTraits) } + + // to fix with precompute fix here + traitsMap[dependency.identity] = enabledTraitsSet +// +// if let traits = dependency.traits { +// let traitNames = traits.map(\.name) +// traitsMap[dependency.identity, default: []].formUnion(Set(traitNames)) +// } } } diff --git a/Sources/PackageGraph/Resolution/ResolvedPackage.swift b/Sources/PackageGraph/Resolution/ResolvedPackage.swift index 8534d9de2aa..5ace11d7207 100644 --- a/Sources/PackageGraph/Resolution/ResolvedPackage.swift +++ b/Sources/PackageGraph/Resolution/ResolvedPackage.swift @@ -40,7 +40,7 @@ public struct ResolvedPackage { public let products: [ResolvedProduct] /// The enabled traits of this package. - public let enabledTraits: Set + public let enabledTraits: Set? /// The dependencies of the package. public let dependencies: [PackageIdentity] @@ -62,7 +62,7 @@ public struct ResolvedPackage { defaultLocalization: String?, supportedPlatforms: [SupportedPlatform], dependencies: [PackageIdentity], - enabledTraits: Set, + enabledTraits: Set?, modules: IdentifiableSet, products: [ResolvedProduct], registryMetadata: RegistryReleaseMetadata?, diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index 8531b858663..ac7aa4f361b 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -17,6 +17,29 @@ import Foundation /// Validator methods that check the correctness of traits and their support as defined in the manifest. extension Manifest { + public struct PackageIdentifier: Hashable, CustomStringConvertible { + public var identity: String + public var name: String? + + public init(identity: String, name: String? = nil) { + self.identity = identity + self.name = name + } + + public init(_ parent: Manifest) { + self.identity = parent.packageIdentity.description + self.name = parent.displayName + } + + public var description: String { + var result = "'\(identity)'" + if let name { + result.append(" (\(name))") + } + return result + } + } + /// Determines whether traits are supported for this Manifest. public var supportsTraits: Bool { !self.traits.isEmpty @@ -27,7 +50,7 @@ extension Manifest { guard !trait.isDefault else { if !supportsTraits { throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait.name, availableTraits: traits.map({ $0.name }) ) @@ -40,11 +63,11 @@ extension Manifest { } /// Validates a trait by checking that it is defined in the manifest; if not, an error is thrown. - private func validateTrait(_ trait: String, parentPackage: String? = nil) throws { + private func validateTrait(_ trait: String, parentPackage: PackageIdentifier? = nil) throws { guard trait != "default" else { if !supportsTraits { throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait, availableTraits: traits.map({ $0.name }) ) @@ -56,10 +79,10 @@ extension Manifest { // Check if the passed trait is a valid trait. if self.traits.first(where: { $0.name == trait }) == nil { throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait, availableTraits: self.traits.map({ $0.name }), - parentPackage: parentPackage + parent: parentPackage ) } } @@ -69,13 +92,13 @@ extension Manifest { /// error indicating the issue will be thrown. private func validateEnabledTraits( _ explicitlyEnabledTraits: Set?, - _ parentPackage: String? = nil + _ parentPackage: PackageIdentifier? = nil ) throws { guard supportsTraits else { if let explicitlyEnabledTraits, !explicitlyEnabledTraits.contains("default") { throw TraitError.traitsNotSupported( - parentPackage: parentPackage, - package: self.displayName, + parent: parentPackage, + package: .init(self), explicitlyEnabledTraits: explicitlyEnabledTraits.map({ $0 }) ) } @@ -97,8 +120,8 @@ extension Manifest { // We throw an error when default traits are disabled for a package without any traits // This allows packages to initially move new API behind traits once. throw TraitError.traitsNotSupported( - parentPackage: parentPackage, - package: displayName, + parent: parentPackage, + package: .init(self), explicitlyEnabledTraits: enabledTraits.map({ $0 }) ) } @@ -109,14 +132,14 @@ extension Manifest { switch traitConfiguration { case .disableAllTraits: throw TraitError.traitsNotSupported( - parentPackage: nil, - package: displayName, + parent: nil, + package: .init(self), explicitlyEnabledTraits: [] ) case .enabledTraits(let traits): throw TraitError.traitsNotSupported( - parentPackage: nil, - package: displayName, + parent: nil, + package: .init(self), explicitlyEnabledTraits: traits.map({ $0 }) ) case .enableAllTraits, .default: @@ -188,7 +211,7 @@ extension Manifest { /// Calculates the set of all transitive traits that are enabled for this manifest using the passed set of /// explicitly enabled traits, and the parent package that defines the enabled traits for this package. /// This method is intended for use with non-root packages. - public func enabledTraits(using explicitlyEnabledTraits: Set?, _ parentPackage: String?) throws -> Set? { + public func enabledTraits(using explicitlyEnabledTraits: Set?, _ parentPackage: PackageIdentifier?) throws -> Set? { // If this manifest does not support traits, but the passed configuration either // disables default traits or enables non-default traits (i.e. traits that would // not exist for this manifest) then we must throw an error. @@ -204,7 +227,6 @@ extension Manifest { } return enabledTraits - } /// Determines if a trait is enabled with a given set of enabled traits. @@ -235,7 +257,7 @@ extension Manifest { // If manifest does not define default traits, then throw an invalid trait error. throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait.name, availableTraits: self.traits.map(\.name) ) @@ -268,7 +290,7 @@ extension Manifest { // If manifest does not define default traits, then throw an invalid trait error. throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait.name, availableTraits: self.traits.map(\.name) ) @@ -283,7 +305,7 @@ extension Manifest { /// Calculates and returns a set of all enabled traits, beginning with a set of explicitly enabled traits (which can either be the default traits of a manifest, or a configuration of enabled traits determined from a user-generated trait configuration) and determines which traits are transitively enabled. private func calculateAllEnabledTraits( explictlyEnabledTraits: Set?, - _ parentPackage: String? = nil + _ parentPackage: PackageIdentifier? = nil ) throws -> Set { try validateEnabledTraits(explictlyEnabledTraits, parentPackage) // This the point where we flatten the enabled traits and resolve the recursive traits @@ -306,9 +328,9 @@ extension Manifest { .flatMap { trait in guard let traitDescription = traitsMap[trait] else { throw TraitError.invalidTrait( - package: self.displayName, + package: .init(self), trait: trait, - parentPackage: parentPackage + parent: parentPackage ) } return traitDescription.enabledTraits @@ -416,7 +438,7 @@ extension Manifest { target: String, _ dependency: TargetDescription.Dependency, enabledTraits: Set?, - enableAllTraits: Bool = false + enableAllTraits: Bool = false // TODO bp: remove this parameter ) throws -> Bool { guard self.supportsTraits, !enableAllTraits else { return true } guard let target = self.targetMap[target] else { return false } @@ -473,17 +495,17 @@ extension Manifest { public enum TraitError: Swift.Error { /// Indicates that an invalid trait was enabled. case invalidTrait( - package: String, + package: Manifest.PackageIdentifier, trait: String, availableTraits: [String] = [], - parentPackage: String? = nil + parent: Manifest.PackageIdentifier? = nil ) /// Indicates that the manifest does not support traits, yet a method was called with a configuration of enabled /// traits. case traitsNotSupported( - parentPackage: String?, - package: String, + parent: Manifest.PackageIdentifier? = nil, + package: Manifest.PackageIdentifier, explicitlyEnabledTraits: [String] ) } @@ -495,9 +517,9 @@ extension TraitError: CustomStringConvertible { availableTraits = availableTraits.sorted() var errorMsg = "Trait '\(trait)'" if let parentPackage { - errorMsg += " enabled by parent package '\(parentPackage)'" + errorMsg += " enabled by parent package \(parentPackage)" } - errorMsg += " is not declared by package '\(package)'." + errorMsg += " is not declared by package \(package)." if availableTraits.isEmpty { errorMsg += " There are no available traits declared by this package." } else { @@ -510,21 +532,21 @@ extension TraitError: CustomStringConvertible { if explicitlyEnabledTraits.isEmpty { if let parentPackage { return """ - Disabled default traits by package '\(parentPackage)' on package '\(package)' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + Disabled default traits by package \(parentPackage) on package \(package) that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. """ } else { return """ - Disabled default traits on package '\(package)' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + Disabled default traits on package \(package) that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. """ } } else { if let parentPackage { return """ - Package '\(parentPackage)' enables traits [\(explicitlyEnabledTraits.joined(separator: ", "))] on package '\(package)' that declares no traits. + Package \(parentPackage) enables traits [\(explicitlyEnabledTraits.joined(separator: ", "))] on package \(package) that declares no traits. """ } else { return """ - Traits [\(explicitlyEnabledTraits.joined(separator: ", "))] have been enabled on package '\(package)' that declares no traits. + Traits [\(explicitlyEnabledTraits.joined(separator: ", "))] have been enabled on package \(package) that declares no traits. """ } } diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 5b45cfde299..1f93d07c1b8 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -280,11 +280,18 @@ extension Workspace { enabledTraits: node.enabledTraits ) if !isDepUsed && workspace.configuration.pruneDependencies { - observabilityScope.emit(debug: """ + if let enabledTraits = node.enabledTraits { + observabilityScope.emit(debug: """ '\(package.identity)' from '\(package.locationString)' was omitted \ from required dependencies because it is being guarded by the following traits:' \ - \(node.enabledTraits?.joined(separator: ", ")) + \(enabledTraits.joined(separator: ", ")) """) + } else { + observabilityScope.emit(debug: """ + '\(package.identity)' from '\(package.locationString)' was omitted \ + from required dependencies because it is unused + """) + } } else { unusedDepsPerPackage[node.identity, default: []] = unusedDepsPerPackage[ node.identity, @@ -644,12 +651,20 @@ extension Workspace { let explicitlyEnabledTraits = dep.traits?.filter { guard let condition = $0.condition else { return true } return condition.isSatisfied(by: enabledTraits) - } + }.map(\.name) + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } // Add enabled traits dictated by the parent package - if let explicitlyEnabledTraits, !explicitlyEnabledTraits.isEmpty { - enabledTraitsMap[dep.identity, default: []].formUnion(explicitlyEnabledTraits.map(\.name)) +// if let explicitlyEnabledTraits, !explicitlyEnabledTraits.isEmpty { +// enabledTraitsMap[dep.identity] = +// enabledTraitsMap[dep.identity, default: []].formUnion(explicitlyEnabledTraits.map(\.name)) +// } + if let depTraits = enabledTraitsMap[dep.identity] { + enabledTraitsSet?.formUnion(depTraits) } + + enabledTraitsMap[dep.identity] = enabledTraitsSet + return isDepUsed }.map(\.packageRef) }.flatMap(\.self) @@ -665,14 +680,14 @@ extension Workspace { // to avoid redundancy of this task, store a manifestMap that will later be referenced by the // below graph - for parentManifest in topLevelManifests.values { - try parentManifest.dependencies.forEach { dep in - if let enabledDependencyTraits = enabledTraitsMap[dep.identity], let depManifest = manifestMap[dep.identity] { - let calculatedTraits = try depManifest.enabledTraits(using: enabledDependencyTraits, parentManifest.packageIdentity.description) - enabledTraitsMap[dep.identity]?.formUnion(calculatedTraits ?? []) - } - } - } +// for parentManifest in topLevelManifests.values { +// try parentManifest.dependencies.forEach { dep in +// if let enabledDependencyTraits = enabledTraitsMap[dep.identity], let depManifest = manifestMap[dep.identity] { +// let calculatedTraits = try depManifest.enabledTraits(using: enabledDependencyTraits, parentManifest.packageIdentity.description) +// enabledTraitsMap[dep.identity]?.formUnion(calculatedTraits ?? []) +// } +// } +// } // Continue to load the rest of the manifest for this graph // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. @@ -682,12 +697,13 @@ extension Workspace { PackageIdentity >] = { node in // optimization: preload manifest we know about in parallel + // avoid loading dependencies that are trait-guarded here. let dependenciesRequired = try node.item.manifest.dependenciesRequired( for: node.item.productFilter, node.item.enabledTraits ) - let dependenciesGuarded = node.item.manifest - .dependenciesTraitGuarded(withEnabledTraits: node.item.enabledTraits) +// let dependenciesGuarded = node.item.manifest +// .dependenciesTraitGuarded(withEnabledTraits: node.item.enabledTraits) let dependenciesToLoad = dependenciesRequired.map(\.packageRef) .filter { !loadedManifests.keys.contains($0.identity) } try await prepopulateManagedDependencies(dependenciesToLoad) @@ -696,7 +712,7 @@ extension Workspace { observabilityScope: observabilityScope ) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } - return try (dependenciesRequired + dependenciesGuarded).compactMap { dependency in + return try (dependenciesRequired /*+ dependenciesGuarded*/).compactMap { dependency in return try loadedManifests[dependency.identity].flatMap { manifest in let explicitlyEnabledTraits = dependency.traits?.filter { @@ -704,12 +720,20 @@ extension Workspace { return condition.isSatisfied(by: node.item.enabledTraits) }.map(\.name) + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } + + if let depTraits = enabledTraitsMap[dependency.identity] { + enabledTraitsSet?.formUnion(depTraits) + } + let calculatedTraits = try manifest.enabledTraits( - using: explicitlyEnabledTraits.flatMap { Set($0) }, - node.item.identity.description + using: enabledTraitsSet,//explicitlyEnabledTraits.flatMap { Set($0) }, + .init(node.item.manifest) ) - enabledTraitsMap[manifest.packageIdentity, default: []].formUnion(calculatedTraits ?? []) + + // TODO bp: precompute traits should take care of this, no longer need +// enabledTraitsMap[manifest.packageIdentity, default: []].formUnion(calculatedTraits ?? []) // we also compare the location as this function may attempt to load // dependencies that have the same identity but from a different location @@ -755,6 +779,8 @@ extension Workspace { } } + enabledTraitsMap = try precomputeTraits(root: root, topLevelManifests.values.map({ $0 }), loadedManifests) + let dependencyManifests = allNodes.filter { !$0.value.manifest.packageKind.isRoot } // TODO: this check should go away when introducing explicit overrides @@ -799,6 +825,94 @@ extension Workspace { ) } + public func precomputeTraits( + root: PackageGraphRoot, + _ topLevelManifests: [Manifest], + _ manifestMap: [PackageIdentity: Manifest] + ) throws -> [PackageIdentity: Set] { + var enabledTraits = root.enabledTraits + var visited: Set = [] + + func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { + let parentTraits = enabledTraits[parent.packageIdentity] + let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) + let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) + + _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in + return try manifestMap[dependency.identity].flatMap({ manifest in + + let explicitlyEnabledTraits = dependency.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: parentTraits) + }.map(\.name) + + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } + + // Check for existing traits; TODO bp see if these are the same? + if let depTraits = enabledTraits[dependency.identity] { + enabledTraitsSet?.formUnion(depTraits) + } + + let calculatedTraits = try manifest.enabledTraits( + using: enabledTraitsSet, + .init(parent) + ) + + enabledTraits[dependency.identity] = calculatedTraits + + let result = visited.insert(dependency.identity) + if result.inserted { + try dependencies(of: manifest, dependency.productFilter) + } + + return manifest + }) + }) + } + + for manifest in topLevelManifests { + let result = visited.insert(manifest.packageIdentity) + if result.inserted { + try dependencies(of: manifest) + } + } + + return enabledTraits + +// func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { +// let parentTraits = enabledTraits[parent.packageIdentity] +// let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) +// let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) +// +// _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in +// return try manifestMap[dependency.identity].flatMap({ manifest in +// +// let explicitlyEnabledTraits = dependency.traits?.filter { +// guard let condition = $0.condition else { return true } +// return condition.isSatisfied(by: parentTraits) +// }.map(\.name) +// +// let calculatedTraits = try manifest.enabledTraits( +// using: explicitlyEnabledTraits.flatMap { Set($0) }, +// parent.packageIdentity.description +// ) +// +// enabledTraits[dependency.identity, default: []].formUnion(calculatedTraits ?? []) +// +// try dependencies(of: manifest, dependency.productFilter) +// +// return manifest +// }) +// }) +// } +// +// for manifest in topLevelManifests { +// try dependencies(of: manifest) +// } +// +// return enabledTraits + } + /// Loads the given manifests, if it is present in the managed dependencies. /// diff --git a/Sources/_InternalTestSupport/ManifestExtensions.swift b/Sources/_InternalTestSupport/ManifestExtensions.swift index 225ae53d84c..0e4535f7a6f 100644 --- a/Sources/_InternalTestSupport/ManifestExtensions.swift +++ b/Sources/_InternalTestSupport/ManifestExtensions.swift @@ -32,14 +32,14 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "default")], + traits: Set = [],//[.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( displayName: displayName, path: path, packageKind: .root(path), - packageIdentity: .plain(displayName), + packageIdentity: .plain(displayName.lowercased()), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -73,14 +73,14 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "default")], + traits: Set = [],//[.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( displayName: displayName, path: path, packageKind: .fileSystem(path), - packageIdentity: .plain(displayName), + packageIdentity: .plain(displayName.lowercased()), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -120,7 +120,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .localSourceControl(path), - packageIdentity: .plain(displayName), + packageIdentity: .plain(displayName.lowercased()), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -161,7 +161,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .remoteSourceControl(url), - packageIdentity: .plain(displayName), + packageIdentity: .plain(displayName.lowercased()), packageLocation: url.absoluteString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -201,7 +201,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .registry(identity), - packageIdentity: .plain(displayName), + packageIdentity: .plain(displayName.lowercased()), packageLocation: identity.description, defaultLocalization: defaultLocalization, platforms: platforms, @@ -237,7 +237,7 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "default")], + traits: Set = [],//[.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { return Manifest( diff --git a/Sources/_InternalTestSupport/MockDependency.swift b/Sources/_InternalTestSupport/MockDependency.swift index 1553decf02b..c8a1dcaab90 100644 --- a/Sources/_InternalTestSupport/MockDependency.swift +++ b/Sources/_InternalTestSupport/MockDependency.swift @@ -22,13 +22,13 @@ public struct MockDependency { public let deprecatedName: String? public let location: Location public let products: ProductFilter - public let traits: Set + public let traits: Set? init( deprecatedName: String? = nil, location: Location, products: ProductFilter = .everything, - traits: Set = [] + traits: Set? = nil ) { self.deprecatedName = deprecatedName self.location = location @@ -132,35 +132,35 @@ public struct MockDependency { } - public static func fileSystem(path: String, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func fileSystem(path: String, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { try! MockDependency(location: .fileSystem(path: RelativePath(validating: path)), products: products, traits: traits) } - public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { try! .sourceControl(path: RelativePath(validating: path), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { MockDependency(location: .localSourceControl(path: path, requirement: requirement), products: products, traits: traits) } - public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { try! MockDependency(deprecatedName: name, location: .localSourceControl(path: RelativePath(validating: path), requirement: requirement), products: products, traits: traits) } - public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { .sourceControl(url: SourceControlURL(url), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { MockDependency(location: .remoteSourceControl(url: url, requirement: requirement), products: products, traits: traits) } - public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { .registry(identity: .plain(identity), requirement: requirement, traits: traits) } - public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { MockDependency(location: .registry(identity: identity, requirement: requirement), products: products, traits: traits) } diff --git a/Sources/_InternalTestSupport/MockPackage.swift b/Sources/_InternalTestSupport/MockPackage.swift index 0185514e486..5c82b05cd59 100644 --- a/Sources/_InternalTestSupport/MockPackage.swift +++ b/Sources/_InternalTestSupport/MockPackage.swift @@ -35,7 +35,7 @@ public struct MockPackage { targets: [MockTarget], products: [MockProduct] = [], dependencies: [MockDependency] = [], - traits: Set = [.init(name: "default")], + traits: Set = [],//[.init(name: "default")], versions: [String?] = [], revisionProvider: ((String) -> String)? = nil, toolsVersion: ToolsVersion? = nil diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index acf70042af0..519561a8215 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -323,7 +323,7 @@ public final class MockWorkspace { displayName: package.name, path: packagePath, packageKind: packageKind, - packageIdentity: .plain(package.name), + packageIdentity: .plain(package.name.lowercased()), packageLocation: packageLocation, platforms: package.platforms, version: v, diff --git a/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift b/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift index 471319c5dba..30c63beb2ae 100644 --- a/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift +++ b/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift @@ -22,7 +22,7 @@ package extension PackageDependency { deprecatedName: String? = nil, path: AbsolutePath, productFilter: ProductFilter = .everything, - traits: Set = [.init(name: "default")] + traits: Set? = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(path: path) return .fileSystem( @@ -40,7 +40,7 @@ package extension PackageDependency { path: AbsolutePath, requirement: SourceControl.Requirement, productFilter: ProductFilter = .everything, - traits: Set = [.init(name: "default")] + traits: Set? = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(path: path) return .localSourceControl( @@ -59,7 +59,7 @@ package extension PackageDependency { url: SourceControlURL, requirement: SourceControl.Requirement, productFilter: ProductFilter = .everything, - traits: Set = [.init(name: "default")] + traits: Set? = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(url: url) return .remoteSourceControl( @@ -76,7 +76,7 @@ package extension PackageDependency { identity: String, requirement: Registry.Requirement, productFilter: ProductFilter = .everything, - traits: Set = [.init(name: "default")] + traits: Set? = [.init(name: "default")] ) -> Self { return .registry( identity: .plain(identity), diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index a7af4641efa..993c77c97ac 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -523,7 +523,7 @@ struct TraitTests { } let expectedErr = """ - error: Disabled default traits by package 'disablingemptydefaultsexample' on package 'Package11' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + error: Disabled default traits by package 'disablingemptydefaultsexample' (DisablingEmptyDefaultsExample) on package 'package11' (Package11) that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. """ #expect(stderr.contains(expectedErr)) diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 384ae953c36..39370b360d2 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -4421,18 +4421,18 @@ final class ModulesGraphTests: XCTestCase { observabilityScope: observability.topScope ) - XCTAssertEqual(observability.diagnostics.count, 1) - testDiagnostics(observability.diagnostics) { result in - result.check( - diagnostic: "dependency 'package5' is not used by any target", - severity: .warning - ) - } + XCTAssertEqual(observability.diagnostics.count, 0) +// testDiagnostics(observability.diagnostics) { result in +// result.check( +// diagnostic: "dependency 'package5' is not used by any target", +// severity: .warning +// ) +// } PackageGraphTester(graph) { result in result.checkPackage("Package1") { package in XCTAssertEqual(package.enabledTraits, ["Package1Trait3"]) - XCTAssertEqual(package.dependencies.count, 3) + XCTAssertEqual(package.dependencies.count, 2) } result.checkTarget("Package1Target1") { target in target.check(dependencies: "Package2Target1", "Package4Target1") diff --git a/Tests/PackageModelTests/ManifestTests.swift b/Tests/PackageModelTests/ManifestTests.swift index 34a902cc470..a602167522c 100644 --- a/Tests/PackageModelTests/ManifestTests.swift +++ b/Tests/PackageModelTests/ManifestTests.swift @@ -208,7 +208,7 @@ class ManifestTests: XCTestCase { Trait '\( trait .name - )' is not declared by package 'Foo'. There are no available traits declared by this package. + )' is not declared by package 'foo' (Foo). There are no available traits declared by this package. """) } } @@ -255,28 +255,28 @@ class ManifestTests: XCTestCase { // Test `isTraitEnabled` when the trait we're querying for does not exist. XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), nil)) { error in XCTAssertEqual("\(error)", """ - Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + Trait 'IDontExist' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2. """) } // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package. XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "Trait1"), ["IDontExist"])) { error in XCTAssertEqual("\(error)", """ - Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + Trait 'IDontExist' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2. """) } // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package, and the queried trait is the same non-existant trait. XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), ["IDontExist"])) { error in XCTAssertEqual("\(error)", """ - Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + Trait 'IDontExist' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2. """) } // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package, and the queried trait is another non-existant trait. XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExistPart2"), ["IDontExist"])) { error in XCTAssertEqual("\(error)", """ - Trait 'IDontExistPart2' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + Trait 'IDontExistPart2' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2. """) } @@ -320,14 +320,14 @@ class ManifestTests: XCTestCase { // When passed .disableAllTraits configuration XCTAssertThrowsError(try manifest.enabledTraits(using: .disableAllTraits)) { error in XCTAssertEqual("\(error)", """ - Disabled default traits on package 'Foo' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + Disabled default traits on package 'foo' (Foo) that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. """) } // When passed .enableAllTraits configuration XCTAssertThrowsError(try manifest.enabledTraits(using: .enabledTraits(["Trait1"]))) { error in XCTAssertEqual("\(error)", """ - Traits [Trait1] have been enabled on package 'Foo' that declares no traits. + Traits [Trait1] have been enabled on package 'foo' (Foo) that declares no traits. """) } @@ -337,16 +337,16 @@ class ManifestTests: XCTestCase { // Enabled Traits when passed explicitly enabled traits list: // If given a parent package, and the enabled traits being passed don't exist: - XCTAssertThrowsError(try manifest.enabledTraits(using: ["Trait1"], "Qux")) { error in + XCTAssertThrowsError(try manifest.enabledTraits(using: ["Trait1"], .init(identity: "qux"))) { error in XCTAssertEqual("\(error)", """ - Package 'Qux' enables traits [Trait1] on package 'Foo' that declares no traits. + Package 'qux' enables traits [Trait1] on package 'foo' (Foo) that declares no traits. """) } // If given a parent package, and the default traits are disabled: - XCTAssertThrowsError(try manifest.enabledTraits(using: [], "Qux")) { error in + XCTAssertThrowsError(try manifest.enabledTraits(using: [], .init(identity: "qux"))) { error in XCTAssertEqual("\(error)", """ - Disabled default traits by package 'Qux' on package 'Foo' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + Disabled default traits by package 'qux' on package 'foo' (Foo) that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. """) } } @@ -388,11 +388,10 @@ class ManifestTests: XCTestCase { traits: traits ) - // Assure that the guarded dependencies aren't pruned, since we haven't enabled it for this manifest. + // Assure that the trait-guarded dependencies pruned. XCTAssertEqual( try manifest.dependenciesRequired(for: .everything, nil).map(\.identity.description).sorted(), [ - "baz", "buzz", ] ) diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 45444ce2a41..5e063c927e9 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -2927,8 +2927,7 @@ final class WorkspaceTests: XCTestCase { nameForTargetDependencyResolutionOnly: settings.nameForTargetDependencyResolutionOnly, location: settings.location, requirement: .exact("1.5.0"), - productFilter: settings.productFilter, - traits: [] + productFilter: settings.productFilter ) workspace.manifestLoader.manifests[fooKey] = Manifest.createManifest( @@ -16189,7 +16188,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraphFailure(roots: ["Foo"], deps: deps) { diagnostics in testDiagnostics(diagnostics) { result in - result.check(diagnostic: .equal("Trait 'TraitNotFound' enabled by parent package 'Foo' is not declared by package 'Baz'. The available traits declared by this package are: TraitFound."), severity: .error) + result.check(diagnostic: .equal("Trait 'TraitNotFound' enabled by parent package 'foo' (Foo) is not declared by package 'baz' (Baz). The available traits declared by this package are: TraitFound."), severity: .error) } } await workspace.checkManagedDependencies { result in @@ -16252,7 +16251,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraphFailure(roots: ["Foo"], deps: deps) { diagnostics in testDiagnostics(diagnostics) { result in - result.check(diagnostic: .equal("Trait 'TraitNotFound' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2, default."), severity: .error) + result.check(diagnostic: .equal("Trait 'TraitNotFound' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2, default."), severity: .error) } } } From 3b28262114994e40e4319dd3f3afdcfa7a347e65 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 3 Jul 2025 11:57:41 -0400 Subject: [PATCH 07/23] Remove instances of optional set of enabled traits Since there is a bit of redundancy when checking the conditions of an optional set of enabled traits, it makes more sense to simply have the set of enabled traits be non-optional, and to derive the checks for defaults by asserting whether the set contains the "default" keyword. --- Sources/PackageGraph/GraphLoadingNode.swift | 4 +- .../PackageGraph/ModulesGraph+Loading.swift | 10 ++--- Sources/PackageGraph/ModulesGraph.swift | 4 +- Sources/PackageGraph/PackageContainer.swift | 17 ++++--- Sources/PackageGraph/PackageGraphRoot.swift | 16 ++++--- .../PackageModel+Extensions.swift | 13 +++--- .../Resolution/DependencyResolutionNode.swift | 39 ++++++++-------- .../PubGrub/PubGrubDependencyResolver.swift | 6 +-- .../PubGrub/PubGrubPackageContainer.swift | 3 +- .../Manifest/Manifest+Traits.swift | 38 ++++++++-------- Sources/PackageModel/Manifest/Manifest.swift | 7 ++- .../FileSystemPackageContainer.swift | 6 +-- .../RegistryPackageContainer.swift | 6 +-- .../SourceControlPackageContainer.swift | 10 ++--- .../ResolverPrecomputationProvider.swift | 6 +-- .../Workspace/Workspace+Dependencies.swift | 6 ++- Sources/Workspace/Workspace+Manifests.swift | 26 ++++++----- Sources/Workspace/Workspace.swift | 3 +- .../MockPackageContainer.swift | 6 +-- Sources/swift-bootstrap/main.swift | 2 +- .../PackageGraphTests/ModulesGraphTests.swift | 2 +- Tests/PackageGraphTests/PubGrubTests.swift | 6 +-- Tests/PackageModelTests/ManifestTests.swift | 44 +++++++++---------- .../SourceControlPackageContainerTests.swift | 18 ++++---- 24 files changed, 156 insertions(+), 142 deletions(-) diff --git a/Sources/PackageGraph/GraphLoadingNode.swift b/Sources/PackageGraph/GraphLoadingNode.swift index 6b78b1d26ab..e0c4bb7a173 100644 --- a/Sources/PackageGraph/GraphLoadingNode.swift +++ b/Sources/PackageGraph/GraphLoadingNode.swift @@ -30,13 +30,13 @@ public struct GraphLoadingNode: Equatable, Hashable { public let productFilter: ProductFilter /// The enabled traits for this package. - package var enabledTraits: Set? + package var enabledTraits: Set public init( identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter, - enabledTraits: Set? + enabledTraits: Set ) throws { self.identity = identity self.manifest = manifest diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 45ab0a0ee63..abcb050686f 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -59,7 +59,7 @@ extension ModulesGraph { // TODO bp // If we have enabled traits passed then we start with those. If there are no enabled // traits passed then the default traits will be used. - let enabledTraits = root.enabledTraits[identity] + let enabledTraits = root.enabledTraits[identity] ?? ["default"] return try GraphLoadingNode( identity: identity, manifest: package.manifest, @@ -74,7 +74,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: $0.manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[dependency.identity] + enabledTraits: root.enabledTraits[dependency.identity] ?? ["default"] ) } } @@ -105,7 +105,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[manifest.packageIdentity]//calculatedTraits + enabledTraits: root.enabledTraits[manifest.packageIdentity] ?? ["default"]//calculatedTraits ), key: dependency.identity ) @@ -1438,7 +1438,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { var products: [ResolvedProductBuilder] = [] /// The enabled traits of this package. - var enabledTraits: Set? //= ["default"] TODO bp + var enabledTraits: Set //= ["default"] TODO bp /// The dependencies of this package. var dependencies: [ResolvedPackageBuilder] = [] @@ -1462,7 +1462,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { init( _ package: Package, productFilter: ProductFilter, - enabledTraits: Set?, + enabledTraits: Set, isAllowedToVendUnsafeProducts: Bool, allowedToOverride: Bool, platformVersionProvider: PlatformVersionProvider diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index bf2924fc747..879acb09b17 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -455,7 +455,7 @@ public func precomputeTraits( var visited: Set = [] func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] + let parentTraits = enabledTraits[parent.packageIdentity] ?? ["default"] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -475,7 +475,7 @@ public func precomputeTraits( } let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet, + using: enabledTraitsSet ?? [], .init(parent) ) diff --git a/Sources/PackageGraph/PackageContainer.swift b/Sources/PackageGraph/PackageContainer.swift index 515dfdbb3f4..c9086cd6e70 100644 --- a/Sources/PackageGraph/PackageContainer.swift +++ b/Sources/PackageGraph/PackageContainer.swift @@ -75,7 +75,7 @@ public protocol PackageContainer { /// - Precondition: `versions.contains(version)` /// - Throws: If the version could not be resolved; this will abort /// dependency resolution completely. - func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [PackageContainerConstraint] + func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set) async throws -> [PackageContainerConstraint] /// Fetch the declared dependencies for a particular revision. /// @@ -84,12 +84,12 @@ public protocol PackageContainer { /// /// - Throws: If the revision could not be resolved; this will abort /// dependency resolution completely. - func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [PackageContainerConstraint] + func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set) async throws -> [PackageContainerConstraint] /// Fetch the dependencies of an unversioned package container. /// /// NOTE: This method should not be called on a versioned container. - func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [PackageContainerConstraint] + func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set) async throws -> [PackageContainerConstraint] /// Get the updated identifier at a bound version. /// @@ -118,9 +118,14 @@ extension PackageContainer { return true } + // TODO bp may not need this anymore public func getEnabledTraits(traitConfiguration: TraitConfiguration, version: Version? = nil) async throws -> Set { return [] } + + func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [PackageContainerConstraint] { + return [] + } } public protocol CustomPackageContainer: PackageContainer { @@ -156,11 +161,11 @@ public struct PackageContainerConstraint: Equatable, Hashable { public let products: ProductFilter /// The traits that have been enabled for the package. - public let enabledTraits: Set? + public let enabledTraits: Set /// Create a constraint requiring the given `container` satisfying the /// `requirement`. - public init(package: PackageReference, requirement: PackageRequirement, products: ProductFilter, enabledTraits: Set? = nil) { + public init(package: PackageReference, requirement: PackageRequirement, products: ProductFilter, enabledTraits: Set = ["default"]) { self.package = package self.requirement = requirement self.products = products @@ -169,7 +174,7 @@ public struct PackageContainerConstraint: Equatable, Hashable { /// Create a constraint requiring the given `container` satisfying the /// `versionRequirement`. - public init(package: PackageReference, versionRequirement: VersionSetSpecifier, products: ProductFilter, enabledTraits: Set? = nil) { + public init(package: PackageReference, versionRequirement: VersionSetSpecifier, products: ProductFilter, enabledTraits: Set = ["default"]) { self.init(package: package, requirement: .versionSet(versionRequirement), products: products, enabledTraits: enabledTraits) } diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 74b8a05d1eb..15ef0ad054f 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -152,7 +152,7 @@ public struct PackageGraphRoot { // If not, then we can omit this dependency if pruning unused dependencies // is enabled. return manifests.values.reduce(false) { result, manifest in - let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] + let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] ?? ["default"] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { return result || isUsed } @@ -165,7 +165,7 @@ public struct PackageGraphRoot { // FIXME: `dependenciesRequired` modifies manifests and prevents conversion of `Manifest` to a value type let deps = try? manifests.values.lazy .map({ manifest -> [PackageDependency] in - let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] + let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] ?? ["default"] return try manifest.dependenciesRequired(for: .everything, enabledTraits) }) .flatMap({ $0 }) @@ -184,7 +184,7 @@ public struct PackageGraphRoot { public func constraints() throws -> [PackageContainerConstraint] { let constraints = self.packages.map { (identity, package) in // Since these are root packages, can apply trait configuration as this is a root package concept. - let enabledTraits = self.enabledTraits[identity] + let enabledTraits = self.enabledTraits[identity] ?? ["default"] return PackageContainerConstraint( package: package.reference, requirement: .unversioned, @@ -195,11 +195,13 @@ public struct PackageGraphRoot { let depend = try dependencies .map { dep in - var enabledTraits: Set? - if let traits = dep.traits { - enabledTraits = Set(traits.map(\.name)) - } + // TODO bp: refactor this +// var enabledTraits: Set? +// if let traits = dep.traits { +// enabledTraits = Set(traits.map(\.name)) +// } + var enabledTraits = Set(["default"]) return PackageContainerConstraint( package: dep.packageRef, requirement: try dep.toConstraintRequirement(), diff --git a/Sources/PackageGraph/PackageModel+Extensions.swift b/Sources/PackageGraph/PackageModel+Extensions.swift index 0fd06072ff3..c9c959e66bc 100644 --- a/Sources/PackageGraph/PackageModel+Extensions.swift +++ b/Sources/PackageGraph/PackageModel+Extensions.swift @@ -35,17 +35,18 @@ extension PackageDependency { extension Manifest { /// Constructs constraints of the dependencies in the raw package. - public func dependencyConstraints(productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func dependencyConstraints(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { return try self.dependenciesRequired(for: productFilter, enabledTraits).map({ - var explicitlyEnabledTraits: Set? - if let traits = $0.traits { - explicitlyEnabledTraits = Set(traits.map(\.name)) - } + // TODO bp: refactor this +// var explicitlyEnabledTraits: Set? +// if let traits = $0.traits { +// explicitlyEnabledTraits = Set(traits.map(\.name)) +// } return PackageContainerConstraint( package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: $0.productFilter, - enabledTraits: explicitlyEnabledTraits + enabledTraits: ["default"]//explicitlyEnabledTraits ) }) } diff --git a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift index 8865707b651..ae71b3504f0 100644 --- a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift +++ b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift @@ -44,7 +44,7 @@ public enum DependencyResolutionNode { /// Since a non‐existent product ends up with only its implicit dependency on its own package, /// only whichever package contains the product will end up adding additional constraints. /// See `ProductFilter` and `Manifest.register(...)`. - case product(String, package: PackageReference, enabledTraits: Set? = nil) + case product(String, package: PackageReference, enabledTraits: Set = ["default"]) /// A root node. /// @@ -58,7 +58,7 @@ public enum DependencyResolutionNode { /// It is a warning condition, and builds do not actually need these dependencies. /// However, forcing the graph to resolve and fetch them anyway allows the diagnostics passes access /// to the information needed in order to provide actionable suggestions to help the user stitch up the dependency declarations properly. - case root(package: PackageReference, traitConfiguration: TraitConfiguration = .default) + case root(package: PackageReference, enabledTraits: Set = ["default"]) /// The package. public var package: PackageReference { @@ -91,27 +91,28 @@ public enum DependencyResolutionNode { } /// Returns the enabled traits for this node's manifest. - public var traits: Set? { + public var enabledTraits: Set { switch self { - case .root(_, let config): - return config.enabledTraits - case .product(_, _, let enabledTraits): + case .root(_, let enabledTraits), .product(_, _, let enabledTraits): return enabledTraits +// case .product(_, _, let enabledTraits): +// return enabledTraits default: - return nil + return ["default"] } } - public var traitConfiguration: TraitConfiguration { - switch self { - case .root(_, let config): - return config - case .product(_, _, let enabledTraits): - return .init(enabledTraits: enabledTraits) - case .empty: - return .default - } - } + // TODO bp remove this, should precompute traits before here +// public var traitConfiguration: TraitConfiguration { +// switch self { +// case .root(_, let config): +// return config +// case .product(_, _, let enabledTraits): +// return .init(enabledTraits: enabledTraits) +// case .empty: +// return .default +// } +// } /// Returns the dependency that a product has on its own package, if relevant. /// @@ -123,7 +124,7 @@ public enum DependencyResolutionNode { package: self.package, versionRequirement: .exact(version), products: .specific([]), - enabledTraits: traits + enabledTraits: enabledTraits // TODO bp: the traits here should be already computed. ) } @@ -137,7 +138,7 @@ public enum DependencyResolutionNode { package: self.package, requirement: .revision(revision), products: .specific([]), - enabledTraits: traits + enabledTraits: enabledTraits ) } } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index 71e39debc1e..2e33a3b2cc7 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -165,12 +165,12 @@ public struct PubGrubDependencyResolver { } /// Execute the resolution algorithm to find a valid assignment of versions. - public func solve(constraints: [Constraint], traitConfiguration: TraitConfiguration = .default) async -> Result<[DependencyResolverBinding], Error> { + public func solve(constraints: [Constraint], enabledRootTraits: Set = ["default"]/*traitConfiguration: TraitConfiguration = .default*/) async -> Result<[DependencyResolverBinding], Error> { // the graph resolution root let root: DependencyResolutionNode if constraints.count == 1, let constraint = constraints.first, constraint.package.kind.isRoot { // root level package, use it as our resolution root - root = .root(package: constraint.package, traitConfiguration: traitConfiguration) + root = .root(package: constraint.package, enabledTraits: enabledRootTraits) } else { // more complex setup requires a synthesized root root = .root( @@ -178,7 +178,7 @@ public struct PubGrubDependencyResolver { identity: .plain(""), path: .root ), - traitConfiguration: traitConfiguration + enabledTraits: enabledRootTraits ) } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift index 37893363a04..dc7fee62b48 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift @@ -167,7 +167,8 @@ final class PubGrubPackageContainer { )] } - let enabledTraits = node.package.kind.isRoot ? try await self.underlying.getEnabledTraits(traitConfiguration: node.traitConfiguration) : node.traits + // TODO bp check that this is correct now + let enabledTraits = node.enabledTraits/*node.package.kind.isRoot ? try await self.underlying.getEnabledTraits(traitConfiguration: node.traitConfiguration) : node.traits*/ var unprocessedDependencies = try await self.underlying.getDependencies( at: version, productFilter: node.productFilter, diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index ac7aa4f361b..47fb89245cf 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -91,11 +91,11 @@ extension Manifest { /// set of enabled traits and whether the manifest defines these traits (or if it defines any traits at all), then an /// error indicating the issue will be thrown. private func validateEnabledTraits( - _ explicitlyEnabledTraits: Set?, + _ explicitlyEnabledTraits: Set, _ parentPackage: PackageIdentifier? = nil ) throws { guard supportsTraits else { - if let explicitlyEnabledTraits, !explicitlyEnabledTraits.contains("default") { + if explicitlyEnabledTraits != ["default"] /*!explicitlyEnabledTraits.contains("default")*/ { throw TraitError.traitsNotSupported( parent: parentPackage, package: .init(self), @@ -177,13 +177,13 @@ extension Manifest { /// Calculates the set of all transitive traits that are enabled for this manifest using the passed trait configuration. /// Since a trait configuration is only used for root packages, this method is intended for use with root packages only. - public func enabledTraits(using traitConfiguration: TraitConfiguration) throws -> Set? { + public func enabledTraits(using traitConfiguration: TraitConfiguration) throws -> Set { // If this manifest does not support traits, but the passed configuration either // disables default traits or enables non-default traits (i.e. traits that would // not exist for this manifest) then we must throw an error. try validateTraitConfiguration(traitConfiguration) guard supportsTraits, packageKind.isRoot else { - return nil + return ["default"] } var enabledTraits: Set = [] @@ -211,13 +211,13 @@ extension Manifest { /// Calculates the set of all transitive traits that are enabled for this manifest using the passed set of /// explicitly enabled traits, and the parent package that defines the enabled traits for this package. /// This method is intended for use with non-root packages. - public func enabledTraits(using explicitlyEnabledTraits: Set?, _ parentPackage: PackageIdentifier?) throws -> Set? { + public func enabledTraits(using explicitlyEnabledTraits: Set = ["default"], _ parentPackage: PackageIdentifier?) throws -> Set { // If this manifest does not support traits, but the passed configuration either // disables default traits or enables non-default traits (i.e. traits that would // not exist for this manifest) then we must throw an error. try validateEnabledTraits(explicitlyEnabledTraits, parentPackage) guard supportsTraits else { - return nil + return ["default"] } var enabledTraits: Set = [] @@ -230,7 +230,7 @@ extension Manifest { } /// Determines if a trait is enabled with a given set of enabled traits. - public func isTraitEnabled(_ trait: TraitDescription, _ enabledTraits: Set?) throws -> Bool { + public func isTraitEnabled(_ trait: TraitDescription, _ enabledTraits: Set) throws -> Bool { // First, check that the queried trait is valid. try validateTrait(trait) // Then, check that the list of enabled traits is valid. @@ -246,9 +246,9 @@ extension Manifest { // - If there is no existing list of enabled traits (nil), and we know that the // manifest has defined default traits, then just return true. // - If none of these conditions are met, then defaults aren't enabled and we return false. - if let enabledTraits, enabledTraits.contains(trait.name) { + if enabledTraits.contains(trait.name) { return true - } else if enabledTraits == nil { + } else if enabledTraits.isEmpty { return true } else { return false @@ -279,9 +279,9 @@ extension Manifest { // - If there is no existing list of enabled traits (nil), and we know that the // manifest has defined default traits, then just return true. // - If none of these conditions are met, then defaults aren't enabled and we return false. - if let enabledTraits, enabledTraits.contains(trait.name) { + if enabledTraits.contains(trait.name) { return true - } else if enabledTraits == nil { + } else if enabledTraits.isEmpty { return true } else { return false @@ -304,16 +304,16 @@ extension Manifest { /// Calculates and returns a set of all enabled traits, beginning with a set of explicitly enabled traits (which can either be the default traits of a manifest, or a configuration of enabled traits determined from a user-generated trait configuration) and determines which traits are transitively enabled. private func calculateAllEnabledTraits( - explictlyEnabledTraits: Set?, + explictlyEnabledTraits: Set, _ parentPackage: PackageIdentifier? = nil ) throws -> Set { try validateEnabledTraits(explictlyEnabledTraits, parentPackage) // This the point where we flatten the enabled traits and resolve the recursive traits - var enabledTraits = explictlyEnabledTraits ?? [] + var enabledTraits = explictlyEnabledTraits let areDefaultsEnabled = enabledTraits.remove("default") != nil // We have to enable all default traits if no traits are enabled or the defaults are explicitly enabled - if explictlyEnabledTraits == nil || areDefaultsEnabled { + if /*explictlyEnabledTraits == nil*//*enabledTraits.isEmpty && */explictlyEnabledTraits == ["default"] || areDefaultsEnabled { if let defaultTraits { enabledTraits.formUnion(defaultTraits.flatMap(\.enabledTraits)) } @@ -349,7 +349,7 @@ extension Manifest { } /// Computes the dependencies that are in use per target in this manifest. - public func usedTargetDependencies(withTraits enabledTraits: Set?) throws -> [String: Set] { + public func usedTargetDependencies(withTraits enabledTraits: Set) throws -> [String: Set] { try self.targets.reduce(into: [String: Set]()) { depMap, target in let nonTraitDeps = target.dependencies.filter { $0.condition?.traits?.isEmpty ?? true @@ -373,7 +373,7 @@ extension Manifest { } /// Computes the set of package dependencies that are used by targets of this manifest. - public func usedDependencies(withTraits enabledTraits: Set?) throws -> (knownPackage: Set, unknownPackage: Set) { + public func usedDependencies(withTraits enabledTraits: Set) throws -> (knownPackage: Set, unknownPackage: Set) { let deps = try self.usedTargetDependencies(withTraits: enabledTraits) .values .flatMap { $0 } @@ -437,7 +437,7 @@ extension Manifest { public func isTargetDependencyEnabled( target: String, _ dependency: TargetDescription.Dependency, - enabledTraits: Set?, + enabledTraits: Set, enableAllTraits: Bool = false // TODO bp: remove this parameter ) throws -> Bool { guard self.supportsTraits, !enableAllTraits else { return true } @@ -458,7 +458,7 @@ extension Manifest { return traitsToEnable.isEmpty || isEnabled } /// Determines whether a given package dependency is used by this manifest given a set of enabled traits. - public func isPackageDependencyUsed(_ dependency: PackageDependency, enabledTraits: Set?) throws -> Bool { + public func isPackageDependencyUsed(_ dependency: PackageDependency, enabledTraits: Set/* = ["default"]*/) throws -> Bool { if self.pruneDependencies { let usedDependencies = try self.usedDependencies(withTraits: enabledTraits) let foundKnownPackage = usedDependencies.knownPackage.contains(where: { @@ -479,7 +479,7 @@ extension Manifest { // if target deps is empty, default to returning true here. let isTraitGuarded = targetDependenciesForPackageDependency.isEmpty ? false : targetDependenciesForPackageDependency.compactMap({ $0.condition?.traits }).allSatisfy({ - let condition = $0.subtracting(enabledTraits ?? []) + let condition = $0.subtracting(enabledTraits) return !condition.isEmpty }) diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 9301b285c23..17cc309b1c1 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -199,7 +199,7 @@ public final class Manifest: Sendable { /// /// If we set the `enabledTraits` to be `["Trait1"]`, then the list of dependencies guarded by traits would be `[]`. /// Otherwise, if `enabledTraits` were `nil`, then the dependencies guarded by traits would be `["Bar"]`. - public func dependenciesTraitGuarded(withEnabledTraits enabledTraits: Set?) -> [PackageDependency] { + public func dependenciesTraitGuarded(withEnabledTraits enabledTraits: Set) -> [PackageDependency] { guard supportsTraits else { return [] } @@ -249,8 +249,7 @@ public final class Manifest: Sendable { continue } - if let enabledTraits, - guardingTraits.intersection(enabledTraits) != guardingTraits + if guardingTraits.intersection(enabledTraits) != guardingTraits { guardedDependencies.insert(dependency.identity) } @@ -267,7 +266,7 @@ public final class Manifest: Sendable { /// Returns the package dependencies required for a particular products filter and trait configuration. public func dependenciesRequired( for productFilter: ProductFilter, - _ enabledTraits: Set? + _ enabledTraits: Set = ["default"] ) throws -> [PackageDependency] { #if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION // If we have already calculated it, returned the cached value. diff --git a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index 473da456bdd..5ff57dc37cf 100644 --- a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -94,7 +94,7 @@ public struct FileSystemPackageContainer: PackageContainer { } } - public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [PackageContainerConstraint] { + public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [PackageContainerConstraint] { let manifest = try await self.loadManifest() return try manifest.dependencyConstraints(productFilter: productFilter, enabledTraits) } @@ -121,11 +121,11 @@ public struct FileSystemPackageContainer: PackageContainer { fatalError("This should never be called") } - public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { fatalError("This should never be called") } - public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { fatalError("This should never be called") } diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 4884dbdb562..1602c237ceb 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -104,16 +104,16 @@ public class RegistryPackageContainer: PackageContainer { return results } - public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [PackageContainerConstraint] { + public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [PackageContainerConstraint] { let manifest = try await self.loadManifest(version: version) return try manifest.dependencyConstraints(productFilter: productFilter, enabledTraits) } - public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { throw InternalError("getDependencies for revision not supported by RegistryPackageContainer") } - public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { throw InternalError("getUnversionedDependencies not supported by RegistryPackageContainer") } diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index d2c9dc52fa9..e08cbfdd362 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -241,7 +241,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri } } - public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [Constraint] { + public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [Constraint] { do { return try await self.getCachedDependencies(forIdentifier: version.description, productFilter: productFilter) { guard let tag = try self.knownVersions()[version] else { @@ -259,7 +259,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri } } - public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) async throws -> [Constraint] { + public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [Constraint] { do { return try await self.getCachedDependencies(forIdentifier: revision, productFilter: productFilter) { // resolve the revision identifier and return its dependencies. @@ -323,7 +323,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri tag: String, version: Version? = nil, productFilter: ProductFilter, - enabledTraits: Set? + enabledTraits: Set ) async throws -> (Manifest, [Constraint]) { let manifest = try await self.loadManifest(tag: tag, version: version) return (manifest, try manifest.dependencyConstraints(productFilter: productFilter, enabledTraits)) @@ -334,13 +334,13 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri at revision: Revision, version: Version? = nil, productFilter: ProductFilter, - enabledTraits: Set? + enabledTraits: Set ) async throws -> (Manifest, [Constraint]) { let manifest = try await self.loadManifest(at: revision, version: version) return (manifest, try manifest.dependencyConstraints(productFilter: productFilter, enabledTraits)) } - public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [Constraint] { + public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [Constraint] { // We just return an empty array if requested for unversioned dependencies. return [] } diff --git a/Sources/Workspace/ResolverPrecomputationProvider.swift b/Sources/Workspace/ResolverPrecomputationProvider.swift index 630c1dc56ae..9b047da57a9 100644 --- a/Sources/Workspace/ResolverPrecomputationProvider.swift +++ b/Sources/Workspace/ResolverPrecomputationProvider.swift @@ -122,7 +122,7 @@ private struct LocalPackageContainer: PackageContainer { try await self.versionsDescending() } - func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { // Because of the implementation of `reversedVersions`, we should only get the exact same version. switch dependency?.state { case .sourceControlCheckout(.version(version, revision: _)): @@ -134,7 +134,7 @@ private struct LocalPackageContainer: PackageContainer { } } - func getDependencies(at revisionString: String, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + func getDependencies(at revisionString: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { let revision = Revision(identifier: revisionString) switch dependency?.state { case .sourceControlCheckout(.branch(_, revision: revision)), .sourceControlCheckout(.revision(revision)): @@ -150,7 +150,7 @@ private struct LocalPackageContainer: PackageContainer { } } - func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { switch dependency?.state { case .none, .fileSystem, .edited: return try manifest.dependencyConstraints(productFilter: productFilter, enabledTraits) diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index dd824c11927..9725cc8c543 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -870,7 +870,8 @@ extension Workspace { let computedConstraints = try root.constraints() + // Include constraints from the manifests in the graph root. - root.manifests.values.flatMap { try $0.dependencyConstraints(productFilter: .everything, nil) } + + // TODO bp see if this is correct... + root.manifests.values.flatMap { try $0.dependencyConstraints(productFilter: .everything, ["default"]) } + dependencyManifests.dependencyConstraints + constraints @@ -1181,7 +1182,8 @@ extension Workspace { observabilityScope: ObservabilityScope ) async -> [DependencyResolverBinding] { os_signpost(.begin, name: SignpostName.pubgrub) - let result = await resolver.solve(constraints: constraints, traitConfiguration: configuration.traitConfiguration) + // TODO bp: see if traits should be passed here. + let result = await resolver.solve(constraints: constraints) os_signpost(.end, name: SignpostName.pubgrub) // Take an action based on the result. diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 1f93d07c1b8..069feb61e94 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -230,7 +230,7 @@ extension Workspace { let inputNodes: [GraphLoadingNode] = try root.packages.map { identity, package in inputIdentities.append(package.reference) - let traits: Set? = rootEnabledTraitsMap[package.reference.identity] + let traits: Set = rootEnabledTraitsMap[package.reference.identity] ?? ["default"] let node = try GraphLoadingNode( identity: identity, @@ -243,7 +243,7 @@ extension Workspace { let package = dependency.packageRef inputIdentities.append(package) return try manifestsMap[dependency.identity].map { manifest in - let traits: Set? = rootDependenciesEnabledTraitsMap[dependency.identity] + let traits: Set = rootDependenciesEnabledTraitsMap[dependency.identity] ?? ["default"] return try GraphLoadingNode( identity: dependency.identity, @@ -280,11 +280,12 @@ extension Workspace { enabledTraits: node.enabledTraits ) if !isDepUsed && workspace.configuration.pruneDependencies { - if let enabledTraits = node.enabledTraits { + // TODO bp: see if this produces expected result, since enabled traits is no longer optional + if !node.enabledTraits.isEmpty { observabilityScope.emit(debug: """ '\(package.identity)' from '\(package.locationString)' was omitted \ from required dependencies because it is being guarded by the following traits:' \ - \(enabledTraits.joined(separator: ", ")) + \(node.enabledTraits.joined(separator: ", ")) """) } else { observabilityScope.emit(debug: """ @@ -351,7 +352,8 @@ extension Workspace { return try manifestsMap[dependency.identity].map { manifest in // Calculate all transitively enabled traits for this manifest. - var allEnabledTraits: Set? + // TODO bp: see if this is corect + var allEnabledTraits: Set = ["default"] if let explicitlyEnabledTraits { allEnabledTraits = Set(explicitlyEnabledTraits) @@ -466,7 +468,7 @@ extension Workspace { case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: break } - let enabledTraits = rootDependenciesEnabledTraitsMap[managedDependency.packageRef.identity] + let enabledTraits = rootDependenciesEnabledTraitsMap[managedDependency.packageRef.identity] ?? ["default"] allConstraints += try externalManifest.dependencyConstraints( productFilter: productFilter, enabledTraits @@ -607,7 +609,7 @@ extension Workspace { let rootManifests = try root.manifests.mapValues { manifest in let deps = try manifest.dependencies.filter { dep in - let enabledTraits = root.enabledTraits[manifest.packageIdentity] + let enabledTraits = root.enabledTraits[manifest.packageIdentity] ?? ["default"] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed } @@ -646,7 +648,7 @@ extension Workspace { let firstLevelDependencies = try topLevelManifests.values.map { manifest in try manifest.dependencies.filter { dep in // Calculate conditional traits for dependencies here; add to enabled traits map. - let enabledTraits: Set? = root.enabledTraits[manifest.packageIdentity] + let enabledTraits: Set = root.enabledTraits[manifest.packageIdentity] ?? ["default"] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) let explicitlyEnabledTraits = dep.traits?.filter { guard let condition = $0.condition else { return true } @@ -727,7 +729,7 @@ extension Workspace { } let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet,//explicitlyEnabledTraits.flatMap { Set($0) }, + using: enabledTraitsSet ?? ["default"],//explicitlyEnabledTraits.flatMap { Set($0) }, .init(node.item.manifest) ) @@ -763,7 +765,7 @@ extension Workspace { identity: identity, manifest: manifest, productFilter: .everything, - enabledTraits: root.enabledTraits[identity] + enabledTraits: root.enabledTraits[identity] ?? ["default"] ), key: identity ) @@ -834,7 +836,7 @@ extension Workspace { var visited: Set = [] func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] + let parentTraits = enabledTraits[parent.packageIdentity] ?? ["default"] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -854,7 +856,7 @@ extension Workspace { } let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet, + using: enabledTraitsSet ?? ["default"], .init(parent) ) diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index c8e6c315a33..d4c8310b134 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -780,7 +780,8 @@ extension Workspace { defaultRequirement } - var dependencyEnabledTraits: Set? + // TODO bp ensure this is correct. + var dependencyEnabledTraits: Set = Set(["default"]) if let traits = root.dependencies.first(where: { $0.nameForModuleDependencyResolutionOnly == packageName })? .traits { diff --git a/Sources/_InternalTestSupport/MockPackageContainer.swift b/Sources/_InternalTestSupport/MockPackageContainer.swift index a6fcb46d605..27e779d2477 100644 --- a/Sources/_InternalTestSupport/MockPackageContainer.swift +++ b/Sources/_InternalTestSupport/MockPackageContainer.swift @@ -46,12 +46,12 @@ public class MockPackageContainer: CustomPackageContainer { return _versions } - public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) -> [MockPackageContainer.Constraint] { + public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) -> [MockPackageContainer.Constraint] { requestedVersions.insert(version) return getDependencies(at: version.description, productFilter: productFilter, enabledTraits) } - public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) -> [MockPackageContainer.Constraint] { + public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) -> [MockPackageContainer.Constraint] { let dependencies: [Dependency] if filteredMode { dependencies = filteredDependencies[productFilter]! @@ -64,7 +64,7 @@ public class MockPackageContainer: CustomPackageContainer { } } - public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) -> [MockPackageContainer.Constraint] { + public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) -> [MockPackageContainer.Constraint] { return unversionedDeps } diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index dfae051c72e..448daa9d762 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -409,7 +409,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { let input = loadedManifests.map { identity, manifest in KeyedPair(manifest, key: identity) } _ = try await topologicalSort(input) { pair in // When bootstrapping no special trait build configuration is used - let dependenciesRequired = try pair.item.dependenciesRequired(for: .everything, nil) + let dependenciesRequired = try pair.item.dependenciesRequired(for: .everything) let dependenciesToLoad = dependenciesRequired.map{ $0.packageRef }.filter { !loadedManifests.keys.contains($0.identity) } let dependenciesManifests = try await self.loadManifests(manifestLoader: manifestLoader, packages: dependenciesToLoad) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 39370b360d2..54784103d29 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -3052,7 +3052,7 @@ final class ModulesGraphTests: XCTestCase { ) // Make sure aliases are found properly and do not fall back to pre‐5.2 behavior, leaking across onto other // dependencies. - let required = try manifest.dependenciesRequired(for: .everything, nil) + let required = try manifest.dependenciesRequired(for: .everything) let unrelated = try XCTUnwrap( required .first(where: { $0.nameForModuleDependencyResolutionOnly == "Unrelated" }) diff --git a/Tests/PackageGraphTests/PubGrubTests.swift b/Tests/PackageGraphTests/PubGrubTests.swift index 43ac9d829df..5290db6e53c 100644 --- a/Tests/PackageGraphTests/PubGrubTests.swift +++ b/Tests/PackageGraphTests/PubGrubTests.swift @@ -3165,11 +3165,11 @@ public class MockContainer: PackageContainer { return version } - public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getDependencies(at version: Version, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { return try getDependencies(at: version.description, productFilter: productFilter, enabledTraits) } - public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { guard let revisionDependencies = dependencies[revision] else { throw _MockLoadingError.unknownRevision } @@ -3183,7 +3183,7 @@ public class MockContainer: PackageContainer { }) } - public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set?) throws -> [PackageContainerConstraint] { + public func getUnversionedDependencies(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { // FIXME: This is messy, remove unversionedDeps property. if !unversionedDeps.isEmpty { return unversionedDeps diff --git a/Tests/PackageModelTests/ManifestTests.swift b/Tests/PackageModelTests/ManifestTests.swift index a602167522c..12387c41a70 100644 --- a/Tests/PackageModelTests/ManifestTests.swift +++ b/Tests/PackageModelTests/ManifestTests.swift @@ -93,7 +93,7 @@ class ManifestTests: XCTestCase { ) XCTAssertEqual( - try manifest.dependenciesRequired(for: .everything, nil).map(\.identity.description).sorted(), + try manifest.dependenciesRequired(for: .everything).map(\.identity.description).sorted(), [ "bar1", "bar2", @@ -113,7 +113,7 @@ class ManifestTests: XCTestCase { ) XCTAssertEqual( - try manifest.dependenciesRequired(for: .specific(["Foo"]), nil).map(\.identity.description).sorted(), + try manifest.dependenciesRequired(for: .specific(["Foo"])).map(\.identity.description).sorted(), [ "bar1", // Foo → Foo1 → Bar1 "bar2", // Foo → Foo1 → Foo2 → Bar2 @@ -133,7 +133,7 @@ class ManifestTests: XCTestCase { ) XCTAssertEqual( - try manifest.dependenciesRequired(for: .everything, nil).map(\.identity.description).sorted(), + try manifest.dependenciesRequired(for: .everything).map(\.identity.description).sorted(), [ "bar1", "bar2", @@ -253,7 +253,7 @@ class ManifestTests: XCTestCase { ) // Test `isTraitEnabled` when the trait we're querying for does not exist. - XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), nil)) { error in + XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), ["default"])) { error in XCTAssertEqual("\(error)", """ Trait 'IDontExist' is not declared by package 'foo' (Foo). The available traits declared by this package are: Trait1, Trait2. """) @@ -390,7 +390,7 @@ class ManifestTests: XCTestCase { // Assure that the trait-guarded dependencies pruned. XCTAssertEqual( - try manifest.dependenciesRequired(for: .everything, nil).map(\.identity.description).sorted(), + try manifest.dependenciesRequired(for: .everything).map(\.identity.description).sorted(), [ "buzz", ] @@ -398,7 +398,7 @@ class ManifestTests: XCTestCase { // Assure that each trait is not enabled. for trait in traits { - XCTAssertEqual(try manifest.isTraitEnabled(trait, nil), false) + XCTAssertEqual(try manifest.isTraitEnabled(trait, ["default"]), false) } // Now, create a version of the same manifest but with the `pruneDependencies` flag set to true. @@ -415,7 +415,7 @@ class ManifestTests: XCTestCase { // Since we've enabled pruned dependencies for this manifest, we should only see "buzz" XCTAssertEqual( - try manifestPrunedDeps.dependenciesRequired(for: .everything, nil).map(\.identity.description).sorted(), + try manifestPrunedDeps.dependenciesRequired(for: .everything).map(\.identity.description).sorted(), [ "buzz", ] @@ -423,7 +423,7 @@ class ManifestTests: XCTestCase { // Assure that each trait is not enabled. for trait in traits { - XCTAssertEqual(try manifestPrunedDeps.isTraitEnabled(trait, nil), false) + XCTAssertEqual(try manifestPrunedDeps.isTraitEnabled(trait, ["default"]), false) } } } @@ -573,13 +573,13 @@ class ManifestTests: XCTestCase { // Calculate the enabled traits with an explicitly declared set of enabled traits. // This should override the default traits (since it isn't explicitly passed in here). - let allEnabledTraitsWithoutDefaults = try manifest.enabledTraits(using: .enabledTraits(["Trait3"]))?.sorted() + let allEnabledTraitsWithoutDefaults = try manifest.enabledTraits(using: .enabledTraits(["Trait3"])).sorted() XCTAssertEqual(allEnabledTraitsWithoutDefaults, ["Trait3"]) // Calculate the enabled traits with an explicitly declared set of enabled traits, // including the default traits. Since default traits are explicitly enabled in the // passed set of traits, this will be factored into the calculation. - let allEnabledTraitsWithDefaults = try manifest.enabledTraits(using: .enabledTraits(["default", "Trait3"]))?.sorted() + let allEnabledTraitsWithDefaults = try manifest.enabledTraits(using: .enabledTraits(["default", "Trait3"])).sorted() XCTAssertEqual(allEnabledTraitsWithDefaults, ["Trait1", "Trait2", "Trait3"]) } } @@ -612,7 +612,7 @@ class ManifestTests: XCTestCase { ) // Calculate the enabled traits with all traits enabled flag. - let allEnabledTraits = try manifest.enabledTraits(using: .enableAllTraits)?.sorted() + let allEnabledTraits = try manifest.enabledTraits(using: .enableAllTraits).sorted() XCTAssertEqual(allEnabledTraits, ["Trait1", "Trait2", "Trait3"]) } } @@ -743,7 +743,7 @@ class ManifestTests: XCTestCase { XCTAssertTrue(try manifest.isTargetDependencyEnabled( target: "Foo", unguardedTargetDependency, - enabledTraits: nil + enabledTraits: ["default"] )) // Test if a trait-guarded dependency is enabled when passed a set of enabled traits that @@ -759,7 +759,7 @@ class ManifestTests: XCTestCase { XCTAssertTrue(try manifest.isTargetDependencyEnabled( target: "Foo", trait3GuardedTargetDependency, - enabledTraits: nil, + enabledTraits: ["default"], enableAllTraits: true )) @@ -767,7 +767,7 @@ class ManifestTests: XCTestCase { XCTAssertFalse(try manifest.isTargetDependencyEnabled( target: "Foo", trait3GuardedTargetDependency, - enabledTraits: nil + enabledTraits: ["default"] )) // Test if a target dependency guarded by default traits is enabled when passed no explicitly @@ -775,7 +775,7 @@ class ManifestTests: XCTestCase { XCTAssertTrue(try manifest.isTargetDependencyEnabled( target: "Foo", defaultTraitGuardedTargetDependency, - enabledTraits: nil + enabledTraits: ["default"] )) // Test if a target dependency guarded by default traits is enabled when passed an empty set @@ -880,11 +880,11 @@ class ManifestTests: XCTestCase { traits: traits ) - XCTAssertTrue(try manifest.isPackageDependencyUsed(bar, enabledTraits: nil)) +// XCTAssertTrue(try manifest.isPackageDependencyUsed(bar)) XCTAssertTrue(try manifest.isPackageDependencyUsed(bar, enabledTraits: [])) - XCTAssertFalse(try manifest.isPackageDependencyUsed(baz, enabledTraits: nil)) +// XCTAssertFalse(try manifest.isPackageDependencyUsed(baz)) XCTAssertTrue(try manifest.isPackageDependencyUsed(baz, enabledTraits: ["Trait3"])) - XCTAssertTrue(try manifest.isPackageDependencyUsed(bam, enabledTraits: nil)) +// XCTAssertTrue(try manifest.isPackageDependencyUsed(bam)) XCTAssertFalse(try manifest.isPackageDependencyUsed(bam, enabledTraits: [])) XCTAssertFalse(try manifest.isPackageDependencyUsed(bam, enabledTraits: ["Trait3"])) @@ -892,7 +892,7 @@ class ManifestTests: XCTestCase { // dependency that depends on the same package as another target dependency, but // is unguarded by traits; therefore, this package dependency should be considered used // in every scenario, regardless of the passed trait configuration. - XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: nil)) +// XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: nil)) XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: [])) XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: ["Trait3"])) } @@ -942,7 +942,7 @@ class ManifestTests: XCTestCase { // The list of required dependencies should remain the same, since all depenencies are being // used in the current manifest. - let calculatedDependencies = try manifest.dependenciesRequired(for: .everything, nil) + let calculatedDependencies = try manifest.dependenciesRequired(for: .everything) XCTAssertEqual(calculatedDependencies.map(\.identity).sorted(), dependencies.map(\.identity).sorted()) } } @@ -1003,7 +1003,7 @@ class ManifestTests: XCTestCase { pruneDependencies: true ) - let calculatedDependenciesWithDefaultTraits = try manifest.dependenciesRequired(for: .everything, nil) + let calculatedDependenciesWithDefaultTraits = try manifest.dependenciesRequired(for: .everything) XCTAssertEqual( calculatedDependenciesWithDefaultTraits.map(\.identity).sorted(), [ @@ -1086,7 +1086,7 @@ class ManifestTests: XCTestCase { // When using default traits (since we omit a list of enabled traits here), // `Bar` should not be trait-guarded since `Trait1` is enabled by default. - let noTraitGuardedDependencies = manifest.dependenciesTraitGuarded(withEnabledTraits: nil) + let noTraitGuardedDependencies = manifest.dependenciesTraitGuarded(withEnabledTraits: ["default"]) XCTAssertEqual(noTraitGuardedDependencies, []) } } diff --git a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift index 45adb1a22f6..fce433b382f 100644 --- a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift +++ b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift @@ -265,7 +265,7 @@ final class SourceControlPackageContainerTests: XCTestCase { let container = try await provider.getContainer(for: ref) as! SourceControlPackageContainer let revision = try container.getRevision(forTag: "1.0.0") do { - _ = try await container.getDependencies(at: revision.identifier, productFilter: .nothing, nil) + _ = try await container.getDependencies(at: revision.identifier, productFilter: .nothing) } catch let error as SourceControlPackageContainer.GetDependenciesError { let error = error.underlyingError as! UnsupportedToolsVersion XCTAssertMatch(error.description, .and(.prefix("package '\(PackageIdentity(path: repoPath))' @"), .suffix("is using Swift tools version 3.1.0 which is no longer supported; consider using '// swift-tools-version:4.0' to specify the current tools version"))) @@ -438,7 +438,7 @@ final class SourceControlPackageContainerTests: XCTestCase { XCTAssertEqual( try manifest - .dependencyConstraints(productFilter: .everything, nil) + .dependencyConstraints(productFilter: .everything) .sorted(by: { $0.package.identity < $1.package.identity }), [ v5Constraints[0], @@ -460,7 +460,7 @@ final class SourceControlPackageContainerTests: XCTestCase { XCTAssertEqual( try manifest - .dependencyConstraints(productFilter: .everything, nil) + .dependencyConstraints(productFilter: .everything) .sorted(by: { $0.package.identity < $1.package.identity }), [ v5Constraints[0], @@ -482,7 +482,7 @@ final class SourceControlPackageContainerTests: XCTestCase { XCTAssertEqual( try manifest - .dependencyConstraints(productFilter: .everything, nil) + .dependencyConstraints(productFilter: .everything) .sorted(by: { $0.package.identity < $1.package.identity }), [ v5_2Constraints[0], @@ -504,7 +504,7 @@ final class SourceControlPackageContainerTests: XCTestCase { XCTAssertEqual( try manifest - .dependencyConstraints(productFilter: .specific(Set(products.map { $0.name })), nil) + .dependencyConstraints(productFilter: .specific(Set(products.map { $0.name }))) .sorted(by: { $0.package.identity < $1.package.identity }), [ v5_2Constraints[0], @@ -564,7 +564,7 @@ final class SourceControlPackageContainerTests: XCTestCase { let container = try await containerProvider.getContainer(for: packageRef) as! SourceControlPackageContainer // Simulate accessing a fictitious dependency on the `master` branch, and check that we get back the expected error. - do { _ = try await container.getDependencies(at: "master", productFilter: .everything, nil) } + do { _ = try await container.getDependencies(at: "master", productFilter: .everything) } catch let error as SourceControlPackageContainer.GetDependenciesError { // We expect to get an error message that mentions main. XCTAssertMatch(error.description, .and(.prefix("could not find a branch named ‘master’"), .suffix("(did you mean ‘main’?)"))) @@ -573,7 +573,7 @@ final class SourceControlPackageContainerTests: XCTestCase { } // Simulate accessing a fictitious dependency on some random commit that doesn't exist, and check that we get back the expected error. - do { _ = try await container.getDependencies(at: "535f4cb5b4a0872fa691473e82d7b27b9894df00", productFilter: .everything, nil) } + do { _ = try await container.getDependencies(at: "535f4cb5b4a0872fa691473e82d7b27b9894df00", productFilter: .everything) } catch let error as SourceControlPackageContainer.GetDependenciesError { // We expect to get an error message about the specific commit. XCTAssertMatch(error.description, .prefix("could not find the commit 535f4cb5b4a0872fa691473e82d7b27b9894df00")) @@ -729,8 +729,8 @@ final class SourceControlPackageContainerTests: XCTestCase { let packageReference = PackageReference.localSourceControl(identity: PackageIdentity(path: packageDirectory), path: packageDirectory) let container = try await containerProvider.getContainer(for: packageReference) - let forNothing = try await container.getDependencies(at: version, productFilter: .specific([]), nil) - let forProduct = try await container.getDependencies(at: version, productFilter: .specific(["Product"]), nil) + let forNothing = try await container.getDependencies(at: version, productFilter: .specific([]), ["default"]) + let forProduct = try await container.getDependencies(at: version, productFilter: .specific(["Product"]), ["default"]) #if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION // If the cache overlaps (incorrectly), these will be the same. XCTAssertNotEqual(forNothing, forProduct) From c1341139d8f2eed6e5d0feec72b3385b237fe079 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 3 Jul 2025 12:54:29 -0400 Subject: [PATCH 08/23] Introduce new EnabledTraitsMap struct To help with the computation of traits, a wrapper struct for a dictionary of traits has been created wherein the default value when a key isn't found is no longer nil but instead ["default"], since if we haven't defined a set of explicitly enabled traits for a given package, then it should default to the defaults. --- .../PackageGraph/ModulesGraph+Loading.swift | 7 ++-- Sources/PackageGraph/ModulesGraph.swift | 18 +++------ Sources/PackageGraph/PackageContainer.swift | 11 ------ Sources/PackageGraph/PackageGraphRoot.swift | 38 ++++++++++++++++++- .../Resolution/DependencyResolutionNode.swift | 18 +-------- .../PubGrub/PubGrubPackageContainer.swift | 2 +- .../FileSystemPackageContainer.swift | 9 ----- .../RegistryPackageContainer.swift | 12 ------ .../SourceControlPackageContainer.swift | 8 ---- .../ResolverPrecomputationProvider.swift | 19 ---------- Sources/Workspace/Workspace+Manifests.swift | 31 +++++++-------- .../MockPackageContainer.swift | 5 --- Tests/PackageGraphTests/PubGrubTests.swift | 5 --- 13 files changed, 64 insertions(+), 119 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index abcb050686f..36865e4aed3 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -59,12 +59,11 @@ extension ModulesGraph { // TODO bp // If we have enabled traits passed then we start with those. If there are no enabled // traits passed then the default traits will be used. - let enabledTraits = root.enabledTraits[identity] ?? ["default"] return try GraphLoadingNode( identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: enabledTraits + enabledTraits: root.enabledTraits[identity] ) } let rootDependencyNodes = try root.dependencies.lazy.filter { requiredDependencies.contains($0.packageRef) } @@ -74,7 +73,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: $0.manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[dependency.identity] ?? ["default"] + enabledTraits: root.enabledTraits[dependency.identity] ) } } @@ -105,7 +104,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[manifest.packageIdentity] ?? ["default"]//calculatedTraits + enabledTraits: root.enabledTraits[manifest.packageIdentity] ), key: dependency.identity ) diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 879acb09b17..489858c6f61 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -455,7 +455,7 @@ public func precomputeTraits( var visited: Set = [] func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] ?? ["default"] + let parentTraits = enabledTraits[parent.packageIdentity] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -470,9 +470,9 @@ public func precomputeTraits( var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } // Check for existing traits; TODO bp see if these are the same? - if let depTraits = enabledTraits[dependency.identity] { - enabledTraitsSet?.formUnion(depTraits) - } +// if let depTraits = enabledTraits[dependency.identity] { + enabledTraitsSet?.formUnion(enabledTraits[dependency.identity]) +// } let calculatedTraits = try manifest.enabledTraits( using: enabledTraitsSet ?? [], @@ -498,7 +498,7 @@ public func precomputeTraits( } } - return enabledTraits + return enabledTraits.dictionaryLiteral } @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) @@ -540,13 +540,7 @@ public func loadModulesGraph( let updatedTraitsMap = try precomputeTraits(root: graphRoot, manifests, manifestMap) // TODO bp: Post-process the trait computation here; a little hacky since we actually do this during dependency resolution... -// for manifest in manifests { -// var enabledTraits = graphRoot.enabledTraits[manifest.packageIdentity] -// enabledTraits = try manifest.enabledTraits(using: enabledTraits, nil) -// -// graphRoot.enabledTraits[manifest.packageIdentity] = enabledTraits -// } - graphRoot.enabledTraits = updatedTraitsMap + graphRoot.enabledTraits = .init(updatedTraitsMap) return try ModulesGraph.load( root: graphRoot, diff --git a/Sources/PackageGraph/PackageContainer.swift b/Sources/PackageGraph/PackageContainer.swift index c9086cd6e70..08d511620cf 100644 --- a/Sources/PackageGraph/PackageContainer.swift +++ b/Sources/PackageGraph/PackageContainer.swift @@ -97,12 +97,6 @@ public protocol PackageContainer { /// after the container is available. The updated identifier is returned in result of the /// dependency resolution. func loadPackageReference(at boundVersion: BoundVersion) async throws -> PackageReference - - - /// Fetch the enabled traits of a package container. - /// - /// NOTE: This method should only be called on root packages. - func getEnabledTraits(traitConfiguration: TraitConfiguration, version: Version?) async throws -> Set } extension PackageContainer { @@ -118,11 +112,6 @@ extension PackageContainer { return true } - // TODO bp may not need this anymore - public func getEnabledTraits(traitConfiguration: TraitConfiguration, version: Version? = nil) async throws -> Set { - return [] - } - func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) async throws -> [PackageContainerConstraint] { return [] } diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 15ef0ad054f..12ec8dab639 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -38,6 +38,40 @@ public struct PackageGraphRootInput { } } +//public typealias EnabledTraitsMap = [PackageIdentity: Set] +//@dynamicMemberLookup +public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { + public typealias Key = PackageIdentity + public typealias Value = Set + + var storage: [PackageIdentity: Set] = [:] + + public init(dictionaryLiteral elements: (Key, Value)...) { + for (key, value) in elements { + storage[key] = value + } + } + + public init() { } + + public init(_ dictionary: [Key: Value]) { + self.storage = dictionary + } + + public subscript(key: PackageIdentity) -> Set { + get { storage[key] ?? ["default"] } + set { storage[key] = newValue } + } + +// subscript(dynamicMember key: PackageIdentity) -> Set { +// get { storage[key] ?? ["default"] } +// set { storage[key] = newValue } +// } + public var dictionaryLiteral: [PackageIdentity: Set] { + return storage + } +} + /// Represents the inputs to the package graph. public struct PackageGraphRoot { @@ -50,7 +84,7 @@ public struct PackageGraphRoot { } /// The root manifest(s)'s enabled traits (and their transitively enabled traits). - public var enabledTraits: [PackageIdentity: Set] + public var enabledTraits: EnabledTraitsMap /// The root package references. public var packageReferences: [PackageReference] { @@ -137,7 +171,7 @@ public struct PackageGraphRoot { } } - self.enabledTraits = enableTraitsMap + self.enabledTraits = .init(enableTraitsMap) // FIXME: Deprecate special casing once the manifest supports declaring used executable products. // Special casing explicit products like this is necessary to pass the test suite and satisfy backwards compatibility. diff --git a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift index ae71b3504f0..11693a109c7 100644 --- a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift +++ b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift @@ -95,25 +95,11 @@ public enum DependencyResolutionNode { switch self { case .root(_, let enabledTraits), .product(_, _, let enabledTraits): return enabledTraits -// case .product(_, _, let enabledTraits): -// return enabledTraits default: return ["default"] } } - // TODO bp remove this, should precompute traits before here -// public var traitConfiguration: TraitConfiguration { -// switch self { -// case .root(_, let config): -// return config -// case .product(_, _, let enabledTraits): -// return .init(enabledTraits: enabledTraits) -// case .empty: -// return .default -// } -// } - /// Returns the dependency that a product has on its own package, if relevant. /// /// This is the constraint that requires all products from a package resolve to the same version. @@ -124,7 +110,7 @@ public enum DependencyResolutionNode { package: self.package, versionRequirement: .exact(version), products: .specific([]), - enabledTraits: enabledTraits // TODO bp: the traits here should be already computed. + enabledTraits: self.enabledTraits ) } @@ -138,7 +124,7 @@ public enum DependencyResolutionNode { package: self.package, requirement: .revision(revision), products: .specific([]), - enabledTraits: enabledTraits + enabledTraits: self.enabledTraits ) } } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift index dc7fee62b48..0f30e058d5f 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift @@ -168,7 +168,7 @@ final class PubGrubPackageContainer { } // TODO bp check that this is correct now - let enabledTraits = node.enabledTraits/*node.package.kind.isRoot ? try await self.underlying.getEnabledTraits(traitConfiguration: node.traitConfiguration) : node.traits*/ + let enabledTraits = node.enabledTraits var unprocessedDependencies = try await self.underlying.getDependencies( at: version, productFilter: node.productFilter, diff --git a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index 5ff57dc37cf..47a19c0bb73 100644 --- a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -128,15 +128,6 @@ public struct FileSystemPackageContainer: PackageContainer { public func getDependencies(at revision: String, productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { fatalError("This should never be called") } - - public func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version? = nil) async throws -> Set { - guard version == nil else { - throw InternalError("File system package container does not support versioning.") - } - let manifest = try await loadManifest() - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) - return enabledTraits ?? [] - } } extension FileSystemPackageContainer: CustomStringConvertible { diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 1602c237ceb..630ba49a280 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -210,18 +210,6 @@ public class RegistryPackageContainer: PackageContainer { self.availableManifestsCache[version] = (manifests: manifests, fileSystem: fileSystem) return (manifests: manifests, fileSystem: fileSystem) } - - public func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version?) async throws -> Set { - guard let version else { - throw InternalError("Version needed to compute enabled traits for registry package \(self.package.identity.description)") - } - let manifest = try await loadManifest(version: version) - guard manifest.packageKind.isRoot else { - return [] - } - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) - return enabledTraits ?? [] - } } // MARK: - CustomStringConvertible diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index e08cbfdd362..2444ec5bfae 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -412,14 +412,6 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri ) } - public func getEnabledTraits(traitConfiguration: TraitConfiguration, at revision: String?, version: Version?) async throws -> Set { - guard let version, let tag = getTag(for: version) else { - return [] - } - let manifest = try await self.loadManifest(tag: tag, version: version) - return try manifest.enabledTraits(using: traitConfiguration) ?? [] - } - public var isRemoteContainer: Bool? { return true } diff --git a/Sources/Workspace/ResolverPrecomputationProvider.swift b/Sources/Workspace/ResolverPrecomputationProvider.swift index 9b047da57a9..4ae40119dce 100644 --- a/Sources/Workspace/ResolverPrecomputationProvider.swift +++ b/Sources/Workspace/ResolverPrecomputationProvider.swift @@ -172,23 +172,4 @@ private struct LocalPackageContainer: PackageContainer { return .root(identity: self.package.identity, path: self.manifest.path) } } - - func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version? = nil) async throws -> Set { - guard manifest.packageKind.isRoot else { - return [] - } - - if let version { - switch dependency?.state { - case .sourceControlCheckout(.version(version, revision: _)): - return try manifest.enabledTraits(using: traitConfiguration) ?? [] - case .registryDownload(version: version): - return try manifest.enabledTraits(using: traitConfiguration) ?? [] - default: - throw InternalError("expected version based state, but state was \(String(describing: dependency?.state))") - } - } else { - return try manifest.enabledTraits(using: traitConfiguration) ?? [] - } - } } diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 069feb61e94..62a816e65d1 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -648,7 +648,7 @@ extension Workspace { let firstLevelDependencies = try topLevelManifests.values.map { manifest in try manifest.dependencies.filter { dep in // Calculate conditional traits for dependencies here; add to enabled traits map. - let enabledTraits: Set = root.enabledTraits[manifest.packageIdentity] ?? ["default"] + let enabledTraits: Set = root.enabledTraits[manifest.packageIdentity] let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) let explicitlyEnabledTraits = dep.traits?.filter { guard let condition = $0.condition else { return true } @@ -661,12 +661,13 @@ extension Workspace { // enabledTraitsMap[dep.identity] = // enabledTraitsMap[dep.identity, default: []].formUnion(explicitlyEnabledTraits.map(\.name)) // } - if let depTraits = enabledTraitsMap[dep.identity] { - enabledTraitsSet?.formUnion(depTraits) +// if let depTraits = enabledTraitsMap[dep.identity] { + enabledTraitsSet?.formUnion(enabledTraitsMap[dep.identity]) +// } + if let enabledTraitsSet { + enabledTraitsMap[dep.identity] = enabledTraitsSet } - enabledTraitsMap[dep.identity] = enabledTraitsSet - return isDepUsed }.map(\.packageRef) }.flatMap(\.self) @@ -724,9 +725,9 @@ extension Workspace { var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - if let depTraits = enabledTraitsMap[dependency.identity] { - enabledTraitsSet?.formUnion(depTraits) - } +// if let depTraits = enabledTraitsMap[dependency.identity] { + enabledTraitsSet?.formUnion(enabledTraitsMap[dependency.identity]) +// } let calculatedTraits = try manifest.enabledTraits( using: enabledTraitsSet ?? ["default"],//explicitlyEnabledTraits.flatMap { Set($0) }, @@ -765,7 +766,7 @@ extension Workspace { identity: identity, manifest: manifest, productFilter: .everything, - enabledTraits: root.enabledTraits[identity] ?? ["default"] + enabledTraits: root.enabledTraits[identity] ), key: identity ) @@ -781,7 +782,7 @@ extension Workspace { } } - enabledTraitsMap = try precomputeTraits(root: root, topLevelManifests.values.map({ $0 }), loadedManifests) + enabledTraitsMap = .init(try precomputeTraits(root: root, topLevelManifests.values.map({ $0 }), loadedManifests)) let dependencyManifests = allNodes.filter { !$0.value.manifest.packageKind.isRoot } @@ -836,7 +837,7 @@ extension Workspace { var visited: Set = [] func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] ?? ["default"] + let parentTraits = enabledTraits[parent.packageIdentity] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -851,9 +852,9 @@ extension Workspace { var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } // Check for existing traits; TODO bp see if these are the same? - if let depTraits = enabledTraits[dependency.identity] { - enabledTraitsSet?.formUnion(depTraits) - } +// if let depTraits = enabledTraits[dependency.identity] { + enabledTraitsSet?.formUnion(enabledTraits[dependency.identity]) +// } let calculatedTraits = try manifest.enabledTraits( using: enabledTraitsSet ?? ["default"], @@ -879,7 +880,7 @@ extension Workspace { } } - return enabledTraits + return enabledTraits.dictionaryLiteral // func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { // let parentTraits = enabledTraits[parent.packageIdentity] diff --git a/Sources/_InternalTestSupport/MockPackageContainer.swift b/Sources/_InternalTestSupport/MockPackageContainer.swift index 27e779d2477..9780ae4ab10 100644 --- a/Sources/_InternalTestSupport/MockPackageContainer.swift +++ b/Sources/_InternalTestSupport/MockPackageContainer.swift @@ -72,11 +72,6 @@ public class MockPackageContainer: CustomPackageContainer { return self.package } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?) async throws -> Set { - // This mock does not currently need support for traits. - return [] - } - public func isToolsVersionCompatible(at version: Version) -> Bool { return true } diff --git a/Tests/PackageGraphTests/PubGrubTests.swift b/Tests/PackageGraphTests/PubGrubTests.swift index 5290db6e53c..8f2bbcfa2dd 100644 --- a/Tests/PackageGraphTests/PubGrubTests.swift +++ b/Tests/PackageGraphTests/PubGrubTests.swift @@ -3198,11 +3198,6 @@ public class MockContainer: PackageContainer { return self.package } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?) async throws -> Set { - // FIXME: This mock does not currently support traits. - return [] - } - func appendVersion(_ version: BoundVersion) { self._versions.append(version) self._versions = self._versions From 2398e7df381d46cf97b72af884e599d5160a76b8 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 3 Jul 2025 16:42:16 -0400 Subject: [PATCH 09/23] Move enabled traits map into Workspace --- .../PackageGraph/ModulesGraph+Loading.swift | 14 +- Sources/PackageGraph/ModulesGraph.swift | 33 ++- Sources/PackageGraph/PackageGraphRoot.swift | 60 ++--- .../PackageModel+Extensions.swift | 14 +- .../PubGrub/PubGrubDependencyResolver.swift | 6 +- .../PubGrub/PubGrubPackageContainer.swift | 4 +- .../Manifest/Manifest+Traits.swift | 2 +- .../PackageDependencyDescription.swift | 4 +- .../Workspace/Workspace+Dependencies.swift | 27 +- Sources/Workspace/Workspace+Manifests.swift | 247 +++++++----------- Sources/Workspace/Workspace.swift | 31 ++- Sources/swift-bootstrap/main.swift | 3 +- Tests/FunctionalTests/TraitTests.swift | 10 +- .../PackageGraphPerfTests.swift | 3 +- 14 files changed, 202 insertions(+), 256 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 36865e4aed3..aa9426cbd7a 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -39,7 +39,8 @@ extension ModulesGraph { fileSystem: FileSystem, observabilityScope: ObservabilityScope, productsFilter: ((Product) -> Bool)? = nil, - modulesFilter: ((Module) -> Bool)? = nil + modulesFilter: ((Module) -> Bool)? = nil, + enabledTraitsMap: EnabledTraitsMap ) throws -> ModulesGraph { let observabilityScope = observabilityScope.makeChildScope(description: "Loading Package Graph") @@ -63,7 +64,7 @@ extension ModulesGraph { identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: root.enabledTraits[identity] + enabledTraits: enabledTraitsMap[identity] ) } let rootDependencyNodes = try root.dependencies.lazy.filter { requiredDependencies.contains($0.packageRef) } @@ -73,7 +74,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: $0.manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[dependency.identity] + enabledTraits: enabledTraitsMap[dependency.identity] ) } } @@ -104,7 +105,7 @@ extension ModulesGraph { identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: root.enabledTraits[manifest.packageIdentity] + enabledTraits: enabledTraitsMap[manifest.packageIdentity] ), key: dependency.identity ) @@ -292,9 +293,6 @@ private func checkAllDependenciesAreUsed( if dependency.manifest.supportsTraits { continue } -// if !dependency.enabledTraits.isEmpty { -// continue -// } // Make sure that any diagnostics we emit below are associated with the package. let packageDiagnosticsScope = observabilityScope.makeChildScope( @@ -1437,7 +1435,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { var products: [ResolvedProductBuilder] = [] /// The enabled traits of this package. - var enabledTraits: Set //= ["default"] TODO bp + var enabledTraits: Set /// The dependencies of this package. var dependencies: [ResolvedPackageBuilder] = [] diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 489858c6f61..74205481026 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -446,16 +446,16 @@ func topologicalSortIdentifiable( } public func precomputeTraits( - root: PackageGraphRoot, + _ enabledTraitsMap: EnabledTraitsMap, +// root: PackageGraphRoot, _ topLevelManifests: [Manifest], _ manifestMap: [PackageIdentity: Manifest] ) throws -> [PackageIdentity: Set] { - var enabledTraits = root.enabledTraits - +// var enabledTraits = root.enabledTraits var visited: Set = [] - func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] + func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws { + let parentTraits = enabledTraitsMap[parent.packageIdentity] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -469,17 +469,15 @@ public func precomputeTraits( var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - // Check for existing traits; TODO bp see if these are the same? -// if let depTraits = enabledTraits[dependency.identity] { - enabledTraitsSet?.formUnion(enabledTraits[dependency.identity]) -// } + // Form union with traits that have already been pre-computed, if they exist + enabledTraitsSet?.formUnion(enabledTraitsMap[dependency.identity]) let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet ?? [], + using: enabledTraitsSet ?? ["default"], .init(parent) ) - enabledTraits[dependency.identity] = calculatedTraits +// enabledTraitsMap[dependency.identity] = calculatedTraits let result = visited.insert(dependency.identity) if result.inserted { @@ -492,13 +490,14 @@ public func precomputeTraits( } for manifest in topLevelManifests { + // Track already-visited manifests to avoid cycles let result = visited.insert(manifest.packageIdentity) if result.inserted { try dependencies(of: manifest) } } - return enabledTraits.dictionaryLiteral + return enabledTraitsMap.dictionaryLiteral } @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) @@ -527,7 +526,7 @@ public func loadModulesGraph( let packages = Array(rootManifests.keys) let input = PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) - var graphRoot = try PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: input, manifests: rootManifests, explicitProduct: explicitProduct, @@ -538,9 +537,8 @@ public func loadModulesGraph( manifestMap[manifest.packageIdentity] = manifest } - let updatedTraitsMap = try precomputeTraits(root: graphRoot, manifests, manifestMap) - // TODO bp: Post-process the trait computation here; a little hacky since we actually do this during dependency resolution... - graphRoot.enabledTraits = .init(updatedTraitsMap) + // TODO bp + let updatedTraitsMap = try precomputeTraits([:], manifests, manifestMap) return try ModulesGraph.load( root: graphRoot, @@ -556,6 +554,7 @@ public func loadModulesGraph( fileSystem: fileSystem, observabilityScope: observabilityScope, productsFilter: nil, - modulesFilter: nil + modulesFilter: nil, + enabledTraitsMap: .init(updatedTraitsMap) ) } diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 12ec8dab639..ce2b376ee08 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -38,22 +38,20 @@ public struct PackageGraphRootInput { } } -//public typealias EnabledTraitsMap = [PackageIdentity: Set] -//@dynamicMemberLookup public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { public typealias Key = PackageIdentity public typealias Value = Set var storage: [PackageIdentity: Set] = [:] + public init() { } + public init(dictionaryLiteral elements: (Key, Value)...) { for (key, value) in elements { storage[key] = value } } - public init() { } - public init(_ dictionary: [Key: Value]) { self.storage = dictionary } @@ -63,10 +61,6 @@ public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { set { storage[key] = newValue } } -// subscript(dynamicMember key: PackageIdentity) -> Set { -// get { storage[key] ?? ["default"] } -// set { storage[key] = newValue } -// } public var dictionaryLiteral: [PackageIdentity: Set] { return storage } @@ -84,7 +78,7 @@ public struct PackageGraphRoot { } /// The root manifest(s)'s enabled traits (and their transitively enabled traits). - public var enabledTraits: EnabledTraitsMap +// public var enabledTraits: EnabledTraitsMap /// The root package references. public var packageReferences: [PackageReference] { @@ -139,8 +133,8 @@ public struct PackageGraphRoot { }) // Calculate the enabled traits for root. - var enableTraitsMap: [PackageIdentity: Set] = [:] - enableTraitsMap = try packages.reduce(into: [PackageIdentity: Set]()) { traitsMap, package in + var enableTraitsMap: EnabledTraitsMap = [:] + enableTraitsMap = try packages.reduce(into: EnabledTraitsMap()) { traitsMap, package in let manifest = package.value.manifest let traitConfiguration = input.traitConfiguration @@ -150,28 +144,22 @@ public struct PackageGraphRoot { // Calculate the enabled traits for each dependency of this root: manifest.dependencies.forEach { dependency in - // TODO bp: check for condition on the dependency traits here let explicitlyEnabledTraits = dependency.traits?.filter({ guard let condition = $0.condition else { return true } return condition.isSatisfied(by: enabledTraits) - }).map(\.name) //?? [] + }).map(\.name) var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - if let depTraits = traitsMap[dependency.identity] { - enabledTraitsSet?.formUnion(depTraits) - } + enabledTraitsSet?.formUnion(traitsMap[dependency.identity]) // to fix with precompute fix here - traitsMap[dependency.identity] = enabledTraitsSet -// -// if let traits = dependency.traits { -// let traitNames = traits.map(\.name) -// traitsMap[dependency.identity, default: []].formUnion(Set(traitNames)) -// } + if let enabledTraitsSet { + traitsMap[dependency.identity] = enabledTraitsSet + } } } - self.enabledTraits = .init(enableTraitsMap) +// self.enabledTraits = enableTraitsMap // FIXME: Deprecate special casing once the manifest supports declaring used executable products. // Special casing explicit products like this is necessary to pass the test suite and satisfy backwards compatibility. @@ -186,7 +174,7 @@ public struct PackageGraphRoot { // If not, then we can omit this dependency if pruning unused dependencies // is enabled. return manifests.values.reduce(false) { result, manifest in - let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] ?? ["default"] + let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { return result || isUsed } @@ -199,7 +187,7 @@ public struct PackageGraphRoot { // FIXME: `dependenciesRequired` modifies manifests and prevents conversion of `Manifest` to a value type let deps = try? manifests.values.lazy .map({ manifest -> [PackageDependency] in - let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] ?? ["default"] + let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] return try manifest.dependenciesRequired(for: .everything, enabledTraits) }) .flatMap({ $0 }) @@ -215,10 +203,11 @@ public struct PackageGraphRoot { } /// Returns the constraints imposed by root manifests + dependencies. - public func constraints() throws -> [PackageContainerConstraint] { + public func constraints(_ enabledTraitsMap: EnabledTraitsMap) throws -> [PackageContainerConstraint] { + var rootEnabledTraits: Set = [] let constraints = self.packages.map { (identity, package) in - // Since these are root packages, can apply trait configuration as this is a root package concept. - let enabledTraits = self.enabledTraits[identity] ?? ["default"] + let enabledTraits = enabledTraitsMap[identity] + rootEnabledTraits.formUnion(enabledTraits) return PackageContainerConstraint( package: package.reference, requirement: .unversioned, @@ -229,18 +218,19 @@ public struct PackageGraphRoot { let depend = try dependencies .map { dep in - // TODO bp: refactor this -// var enabledTraits: Set? -// if let traits = dep.traits { -// enabledTraits = Set(traits.map(\.name)) -// } + let enabledTraits = dep.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: rootEnabledTraits) + }.map(\.name) + + var enabledTraitsSet = enabledTraits.flatMap { Set($0) } ?? ["default"] + enabledTraitsSet.formUnion(enabledTraitsMap[dep.identity]) - var enabledTraits = Set(["default"]) return PackageContainerConstraint( package: dep.packageRef, requirement: try dep.toConstraintRequirement(), products: dep.productFilter, - enabledTraits: enabledTraits + enabledTraits: enabledTraitsSet ) } diff --git a/Sources/PackageGraph/PackageModel+Extensions.swift b/Sources/PackageGraph/PackageModel+Extensions.swift index c9c959e66bc..a32b352fa6a 100644 --- a/Sources/PackageGraph/PackageModel+Extensions.swift +++ b/Sources/PackageGraph/PackageModel+Extensions.swift @@ -37,16 +37,18 @@ extension Manifest { /// Constructs constraints of the dependencies in the raw package. public func dependencyConstraints(productFilter: ProductFilter, _ enabledTraits: Set = ["default"]) throws -> [PackageContainerConstraint] { return try self.dependenciesRequired(for: productFilter, enabledTraits).map({ - // TODO bp: refactor this -// var explicitlyEnabledTraits: Set? -// if let traits = $0.traits { -// explicitlyEnabledTraits = Set(traits.map(\.name)) -// } + let explicitlyEnabledTraits = $0.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: enabledTraits) + }.map(\.name) + + var enabledTraitsSet = explicitlyEnabledTraits.flatMap({ Set($0) }) ?? ["default"] + return PackageContainerConstraint( package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: $0.productFilter, - enabledTraits: ["default"]//explicitlyEnabledTraits + enabledTraits: enabledTraitsSet ) }) } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index 2e33a3b2cc7..801162636d8 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -165,12 +165,12 @@ public struct PubGrubDependencyResolver { } /// Execute the resolution algorithm to find a valid assignment of versions. - public func solve(constraints: [Constraint], enabledRootTraits: Set = ["default"]/*traitConfiguration: TraitConfiguration = .default*/) async -> Result<[DependencyResolverBinding], Error> { + public func solve(constraints: [Constraint]) async -> Result<[DependencyResolverBinding], Error> { // the graph resolution root let root: DependencyResolutionNode if constraints.count == 1, let constraint = constraints.first, constraint.package.kind.isRoot { // root level package, use it as our resolution root - root = .root(package: constraint.package, enabledTraits: enabledRootTraits) + root = .root(package: constraint.package, enabledTraits: constraint.enabledTraits) } else { // more complex setup requires a synthesized root root = .root( @@ -178,7 +178,7 @@ public struct PubGrubDependencyResolver { identity: .plain(""), path: .root ), - enabledTraits: enabledRootTraits + enabledTraits: ["default"] ) } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift index 0f30e058d5f..f82fa5d6d81 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift @@ -167,12 +167,10 @@ final class PubGrubPackageContainer { )] } - // TODO bp check that this is correct now - let enabledTraits = node.enabledTraits var unprocessedDependencies = try await self.underlying.getDependencies( at: version, productFilter: node.productFilter, - enabledTraits + node.enabledTraits ) if let sharedVersion = node.versionLock(version: version) { unprocessedDependencies.append(sharedVersion) diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index 47fb89245cf..c633f932235 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -106,7 +106,7 @@ extension Manifest { return } - let enabledTraits = explicitlyEnabledTraits ?? [] + let enabledTraits = explicitlyEnabledTraits // Validate each trait to assure it's defined in the current package. for trait in enabledTraits { diff --git a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift index 5ed36dc0637..2d24b6ac37d 100644 --- a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift +++ b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift @@ -30,9 +30,9 @@ public enum PackageDependency: Equatable, Hashable, Sendable { self.traits = traits } - public func isSatisfied(by enabledTraits: Set?) -> Bool { + public func isSatisfied(by enabledTraits: Set) -> Bool { guard let traits else { return true } - return !traits.intersection(enabledTraits ?? []).isEmpty + return !traits.intersection(enabledTraits).isEmpty } } diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 9725cc8c543..b55d77e4c62 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -106,7 +106,7 @@ extension Workspace { var updateConstraints = currentManifests.editedPackagesConstraints // Create constraints based on root manifest and `Package.resolved` for the update resolution. - updateConstraints += try graphRoot.constraints() + updateConstraints += try graphRoot.constraints(self.enabledTraitsMap) let resolvedPackages: ResolvedPackagesStore.ResolvedPackages if packages.isEmpty { @@ -352,7 +352,7 @@ extension Workspace { packages: root.packages, observabilityScope: observabilityScope ) - var graphRoot = try PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, @@ -370,7 +370,7 @@ extension Workspace { ) // TODO bp - graphRoot = dependencyManifests.root +// graphRoot = dependencyManifests.root return (dependencyManifests, .notRequired @@ -470,9 +470,7 @@ extension Workspace { automaticallyAddManagedDependencies: true, observabilityScope: observabilityScope ) - // TODO bp - graphRoot = currentManifests.root - + try await self.updateBinaryArtifacts( manifests: currentManifests, addedOrUpdatedPackages: [], @@ -523,7 +521,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - var graphRoot = try PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, @@ -531,13 +529,14 @@ extension Workspace { observabilityScope: observabilityScope ) + observabilityScope.emit(warning: "bp resolving + loading deps") + // Of the enabled dependencies of targets, only consider these for dependency resolution let currentManifests = try await self.loadDependencyManifests( root: graphRoot, observabilityScope: observabilityScope ) - // TODO bp - graphRoot = currentManifests.root + guard !observabilityScope.errorsReported else { return currentManifests } @@ -603,7 +602,7 @@ extension Workspace { // Create the constraints; filter unused dependencies. var computedConstraints = [PackageContainerConstraint]() computedConstraints += currentManifests.editedPackagesConstraints - computedConstraints += try graphRoot.constraints() + constraints + computedConstraints += try graphRoot.constraints(self.enabledTraitsMap) + constraints // Perform dependency resolution. let resolver = try self.createResolver(resolvedPackages: resolvedPackagesStore.resolvedPackages, observabilityScope: observabilityScope) @@ -638,7 +637,7 @@ extension Workspace { observabilityScope: observabilityScope ) // TODO bp - graphRoot = updatedDependencyManifests.root +// graphRoot = updatedDependencyManifests.root // If we still have missing packages, something is fundamentally wrong with the resolution of the graph let stillMissingPackages = try updatedDependencyManifests.missingPackages @@ -868,10 +867,9 @@ extension Workspace { observabilityScope: ObservabilityScope ) async throws -> ResolutionPrecomputationResult { let computedConstraints = - try root.constraints() + + try root.constraints(self.enabledTraitsMap) + // Include constraints from the manifests in the graph root. - // TODO bp see if this is correct... - root.manifests.values.flatMap { try $0.dependencyConstraints(productFilter: .everything, ["default"]) } + + root.manifests.values.flatMap { try $0.dependencyConstraints(productFilter: .everything, self.enabledTraitsMap[$0.packageIdentity]) } + dependencyManifests.dependencyConstraints + constraints @@ -1182,7 +1180,6 @@ extension Workspace { observabilityScope: ObservabilityScope ) async -> [DependencyResolverBinding] { os_signpost(.begin, name: SignpostName.pubgrub) - // TODO bp: see if traits should be passed here. let result = await resolver.solve(constraints: constraints) os_signpost(.end, name: SignpostName.pubgrub) diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 62a816e65d1..7dec151af72 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -202,54 +202,29 @@ extension Workspace { } } - let rootEnabledTraitsMap: [PackageIdentity: Set] = root.manifests - .reduce(into: [PackageIdentity: Set]()) { traitMap, manifest in - traitMap[manifest.key] = root.enabledTraits[manifest.key] - } - - let allRootEnabledTraits = rootEnabledTraitsMap.values.flatMap { $0 } - - let rootDependenciesEnabledTraitsMap = root.dependencies - .reduce(into: [PackageIdentity: Set]()) { traitMap, dependency in - let explicitlyEnabledTraits = dependency.traits?.filter { -// guard let conditionTraits = $0.condition?.traits else { -// return true -// } -// return !conditionTraits.intersection(allRootEnabledTraits).isEmpty - guard let condition = $0.condition else { return true } - return condition.isSatisfied(by: Set(allRootEnabledTraits)) - }.map(\.name) - - if let explicitlyEnabledTraits { - traitMap[dependency.identity] = Set(explicitlyEnabledTraits) - } - } - var unusedIdentities: OrderedCollections.OrderedSet = [] var inputIdentities: OrderedCollections.OrderedSet = [] let inputNodes: [GraphLoadingNode] = try root.packages.map { identity, package in inputIdentities.append(package.reference) - let traits: Set = rootEnabledTraitsMap[package.reference.identity] ?? ["default"] let node = try GraphLoadingNode( identity: identity, manifest: package.manifest, productFilter: .everything, - enabledTraits: traits + enabledTraits: workspace.enabledTraitsMap[package.reference.identity] ) return node } + root.dependencies.compactMap { dependency in let package = dependency.packageRef inputIdentities.append(package) return try manifestsMap[dependency.identity].map { manifest in - let traits: Set = rootDependenciesEnabledTraitsMap[dependency.identity] ?? ["default"] return try GraphLoadingNode( identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: traits + enabledTraits: workspace.enabledTraitsMap[dependency.identity] ) } } @@ -341,10 +316,6 @@ extension Workspace { // should calculate enabled traits here. let explicitlyEnabledTraits = dependency.traits?.filter { -// guard let conditionTraits = $0.condition?.traits else { -// return true -// } -// return !conditionTraits.intersection(node.enabledTraits ?? []).isEmpty guard let condition = $0.condition else { return true } return condition.isSatisfied(by: node.enabledTraits) }.map(\.name) @@ -431,23 +402,6 @@ extension Workspace { ) throws -> [PackageContainerConstraint] { var allConstraints = [PackageContainerConstraint]() - let rootDependenciesEnabledTraitsMap = root.dependencies - .reduce(into: [PackageIdentity: Set]()) { traitMap, dependency in - let explicitlyEnabledTraits = dependency.traits?.filter { -// guard let conditionTraits = $0.condition?.traits else { -// return true -// } -// return !conditionTraits -// .intersection(workspace.configuration.traitConfiguration.enabledTraits ?? []).isEmpty - guard let condition = $0.condition else { return true } - return condition.isSatisfied(by: workspace.configuration.traitConfiguration.enabledTraits) - }.map(\.name) - - if let explicitlyEnabledTraits { - traitMap[dependency.identity] = Set(explicitlyEnabledTraits) - } - } - for (externalManifest, managedDependency, productFilter, _) in dependencies { // For edited packages, add a constraint with unversioned requirement so the // resolver doesn't try to resolve it. @@ -468,10 +422,9 @@ extension Workspace { case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: break } - let enabledTraits = rootDependenciesEnabledTraitsMap[managedDependency.packageRef.identity] ?? ["default"] allConstraints += try externalManifest.dependencyConstraints( productFilter: productFilter, - enabledTraits + workspace.enabledTraitsMap[managedDependency.packageRef.identity] ) } return allConstraints @@ -608,9 +561,20 @@ extension Workspace { ) let rootManifests = try root.manifests.mapValues { manifest in + let parentEnabledTraits = self.enabledTraitsMap[manifest.packageIdentity] let deps = try manifest.dependencies.filter { dep in - let enabledTraits = root.enabledTraits[manifest.packageIdentity] ?? ["default"] - let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) + // compute enabled traits map; todo bp cleanup + let explicitlyEnabledTraits = dep.traits?.filter({ + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: parentEnabledTraits) + }).map(\.name) + + let enabledTraitsSet = explicitlyEnabledTraits.flatMap({ Set($0) }) + let enabledTraits = enabledTraitsSet?.union(self.enabledTraitsMap[dep.identity]) ?? self.enabledTraitsMap[dep.identity] + + self.enabledTraitsMap[dep.identity] = enabledTraits + + let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: parentEnabledTraits) return isDepUsed } @@ -642,33 +606,39 @@ extension Workspace { lhs // prefer roots! }) - var enabledTraitsMap = root.enabledTraits - // optimization: preload first level dependencies manifest (in parallel) let firstLevelDependencies = try topLevelManifests.values.map { manifest in - try manifest.dependencies.filter { dep in + let parentEnabledTraits = self.enabledTraitsMap[manifest.packageIdentity] + return try manifest.dependencies.filter { dep in // Calculate conditional traits for dependencies here; add to enabled traits map. - let enabledTraits: Set = root.enabledTraits[manifest.packageIdentity] - let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) - let explicitlyEnabledTraits = dep.traits?.filter { - guard let condition = $0.condition else { return true } - return condition.isSatisfied(by: enabledTraits) - }.map(\.name) - - var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - // Add enabled traits dictated by the parent package -// if let explicitlyEnabledTraits, !explicitlyEnabledTraits.isEmpty { -// enabledTraitsMap[dep.identity] = -// enabledTraitsMap[dep.identity, default: []].formUnion(explicitlyEnabledTraits.map(\.name)) -// } -// if let depTraits = enabledTraitsMap[dep.identity] { - enabledTraitsSet?.formUnion(enabledTraitsMap[dep.identity]) +// let enabledTraits: Set = self.enabledTraitsMap[manifest.packageIdentity] +// let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) +// let explicitlyEnabledTraits = dep.traits?.filter { +// guard let condition = $0.condition else { return true } +// return condition.isSatisfied(by: enabledTraits) +// }.map(\.name) +// +// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } +// enabledTraitsSet?.formUnion(self.enabledTraitsMap[dep.identity]) +// +// if let enabledTraitsSet { +// self.enabledTraitsMap[dep.identity] = enabledTraitsSet // } - if let enabledTraitsSet { - enabledTraitsMap[dep.identity] = enabledTraitsSet - } +// +// return isDepUsed + let explicitlyEnabledTraits = dep.traits?.filter({ + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: parentEnabledTraits) + }).map(\.name) + + let enabledTraitsSet = explicitlyEnabledTraits.flatMap({ Set($0) }) + let enabledTraits = enabledTraitsSet?.union(self.enabledTraitsMap[dep.identity]) ?? self.enabledTraitsMap[dep.identity] + self.enabledTraitsMap[dep.identity] = enabledTraits + + let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: parentEnabledTraits) return isDepUsed + }.map(\.packageRef) }.flatMap(\.self) @@ -677,21 +647,6 @@ extension Workspace { observabilityScope: observabilityScope ) - var manifestMap: [PackageIdentity: Manifest] = firstLevelManifests - - // Part of pre-computing unified traits is that we must load each dependency's manifest itself -- - // to avoid redundancy of this task, store a manifestMap that will later be referenced by the - // below graph - -// for parentManifest in topLevelManifests.values { -// try parentManifest.dependencies.forEach { dep in -// if let enabledDependencyTraits = enabledTraitsMap[dep.identity], let depManifest = manifestMap[dep.identity] { -// let calculatedTraits = try depManifest.enabledTraits(using: enabledDependencyTraits, parentManifest.packageIdentity.description) -// enabledTraitsMap[dep.identity]?.formUnion(calculatedTraits ?? []) -// } -// } -// } - // Continue to load the rest of the manifest for this graph // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. var loadedManifests = firstLevelManifests @@ -700,13 +655,11 @@ extension Workspace { PackageIdentity >] = { node in // optimization: preload manifest we know about in parallel - // avoid loading dependencies that are trait-guarded here. + // avoid loading dependencies that are trait-guarded here since this is redundant. let dependenciesRequired = try node.item.manifest.dependenciesRequired( for: node.item.productFilter, node.item.enabledTraits ) -// let dependenciesGuarded = node.item.manifest -// .dependenciesTraitGuarded(withEnabledTraits: node.item.enabledTraits) let dependenciesToLoad = dependenciesRequired.map(\.packageRef) .filter { !loadedManifests.keys.contains($0.identity) } try await prepopulateManagedDependencies(dependenciesToLoad) @@ -724,13 +677,21 @@ extension Workspace { }.map(\.name) var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - -// if let depTraits = enabledTraitsMap[dependency.identity] { - enabledTraitsSet?.formUnion(enabledTraitsMap[dependency.identity]) -// } + let precomputedTraits = self.enabledTraitsMap[dependency.identity] + // TODO bp shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits. + if precomputedTraits == ["default"], + let enabledTraitsSet { + self.enabledTraitsMap[dependency.identity] = enabledTraitsSet + } else { + // unify traits + enabledTraitsSet?.formUnion(precomputedTraits) + if let enabledTraitsSet { + self.enabledTraitsMap[dependency.identity] = enabledTraitsSet + } + } let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet ?? ["default"],//explicitlyEnabledTraits.flatMap { Set($0) }, + using: self.enabledTraitsMap[dependency.identity], .init(node.item.manifest) ) @@ -766,7 +727,7 @@ extension Workspace { identity: identity, manifest: manifest, productFilter: .everything, - enabledTraits: root.enabledTraits[identity] + enabledTraits: self.enabledTraitsMap[identity] ), key: identity ) @@ -782,7 +743,13 @@ extension Workspace { } } - enabledTraitsMap = .init(try precomputeTraits(root: root, topLevelManifests.values.map({ $0 }), loadedManifests)) + observabilityScope.emit(warning: "bp top level mans: \(topLevelManifests.map(\.key.description))") + observabilityScope.emit(warning: "bp loaded: \(loadedManifests.map(\.key.description))") + + // Update enabled traits map + self.enabledTraitsMap = .init(try precomputeTraits( topLevelManifests.values.map({ $0 }), loadedManifests)) + + observabilityScope.emit(warning: "bp !!!! enabled traits: \(self.enabledTraitsMap)") let dependencyManifests = allNodes.filter { !$0.value.manifest.packageKind.isRoot } @@ -815,13 +782,8 @@ extension Workspace { dependencies.append((node.manifest, dependency, node.productFilter, fileSystem ?? self.fileSystem)) } - // TODO bp: pass in new root here, with the updated enabledTraitsMap - var newRoot = root - newRoot.enabledTraits = enabledTraitsMap - return DependencyManifests( -// root: root, - root: newRoot, + root: root, dependencies: dependencies, workspace: self, observabilityScope: observabilityScope @@ -829,15 +791,13 @@ extension Workspace { } public func precomputeTraits( - root: PackageGraphRoot, _ topLevelManifests: [Manifest], _ manifestMap: [PackageIdentity: Manifest] ) throws -> [PackageIdentity: Set] { - var enabledTraits = root.enabledTraits var visited: Set = [] - func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { - let parentTraits = enabledTraits[parent.packageIdentity] + func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws { + let parentTraits = self.enabledTraitsMap[parent.packageIdentity] let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) @@ -848,21 +808,38 @@ extension Workspace { guard let condition = $0.condition else { return true } return condition.isSatisfied(by: parentTraits) }.map(\.name) - +// +// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } +// +// // Form union with traits that have already been pre-computed, if they exist +// enabledTraitsSet?.formUnion(self.enabledTraitsMap[dependency.identity]) +// +// let calculatedTraits = try manifest.enabledTraits( +// using: enabledTraitsSet ?? ["default"], +// .init(parent) +// ) +// +// self.enabledTraitsMap[dependency.identity] = calculatedTraits var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - - // Check for existing traits; TODO bp see if these are the same? -// if let depTraits = enabledTraits[dependency.identity] { - enabledTraitsSet?.formUnion(enabledTraits[dependency.identity]) -// } + let precomputedTraits = self.enabledTraitsMap[dependency.identity] + // TODO bp shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits. + if precomputedTraits == ["default"], + let enabledTraitsSet { + self.enabledTraitsMap[dependency.identity] = enabledTraitsSet + } else { + // unify traits + enabledTraitsSet?.formUnion(precomputedTraits) + if let enabledTraitsSet { + self.enabledTraitsMap[dependency.identity] = enabledTraitsSet + } + } let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet ?? ["default"], + using: self.enabledTraitsMap[dependency.identity], .init(parent) ) - enabledTraits[dependency.identity] = calculatedTraits - + self.enabledTraitsMap[dependency.identity] = calculatedTraits let result = visited.insert(dependency.identity) if result.inserted { try dependencies(of: manifest, dependency.productFilter) @@ -874,46 +851,14 @@ extension Workspace { } for manifest in topLevelManifests { + // Track already-visited manifests to avoid cycles let result = visited.insert(manifest.packageIdentity) if result.inserted { try dependencies(of: manifest) } } - return enabledTraits.dictionaryLiteral - -// func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws /*-> [Manifest]*/ { -// let parentTraits = enabledTraits[parent.packageIdentity] -// let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) -// let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) -// -// _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in -// return try manifestMap[dependency.identity].flatMap({ manifest in -// -// let explicitlyEnabledTraits = dependency.traits?.filter { -// guard let condition = $0.condition else { return true } -// return condition.isSatisfied(by: parentTraits) -// }.map(\.name) -// -// let calculatedTraits = try manifest.enabledTraits( -// using: explicitlyEnabledTraits.flatMap { Set($0) }, -// parent.packageIdentity.description -// ) -// -// enabledTraits[dependency.identity, default: []].formUnion(calculatedTraits ?? []) -// -// try dependencies(of: manifest, dependency.productFilter) -// -// return manifest -// }) -// }) -// } -// -// for manifest in topLevelManifests { -// try dependencies(of: manifest) -// } -// -// return enabledTraits + return self.enabledTraitsMap.dictionaryLiteral } /// Loads the given manifests, if it is present in the managed dependencies. diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index d4c8310b134..e1e1dfcc1f5 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -103,6 +103,9 @@ public class Workspace { /// to the store. package let resolvedPackagesStore: LoadableResult + /// Computed enabled traits per package in the workspace + public var enabledTraitsMap: EnabledTraitsMap = [:] + /// The file system on which the workspace will operate. package let fileSystem: any FileSystem @@ -781,19 +784,19 @@ extension Workspace { } // TODO bp ensure this is correct. - var dependencyEnabledTraits: Set = Set(["default"]) - if let traits = root.dependencies.first(where: { $0.nameForModuleDependencyResolutionOnly == packageName })? - .traits - { - dependencyEnabledTraits = Set(traits.map(\.name)) - } +// var dependencyEnabledTraits: Set = Set(["default"]) +// if let traits = root.dependencies.first(where: { $0.nameForModuleDependencyResolutionOnly == packageName })? +// .traits +// { +// dependencyEnabledTraits = Set(traits.map(\.name)) +// } // If any products are required, the rest of the package graph will supply those constraints. let constraint = PackageContainerConstraint( package: dependency.packageRef, requirement: requirement, products: .nothing, - enabledTraits: dependencyEnabledTraits + enabledTraits: self.enabledTraitsMap[dependency.packageRef.identity] ) // Run the resolution. @@ -952,6 +955,8 @@ extension Workspace { // such hosts processes call loadPackageGraph to make sure the workspace state is correct try await self.state.reload() + observabilityScope.emit(warning: "bp BEFORE COMP enabled traits map: \(self.enabledTraitsMap)") + // Perform dependency resolution, if required. let manifests = try await self._resolve( root: root, @@ -960,6 +965,8 @@ extension Workspace { observabilityScope: observabilityScope ) + observabilityScope.emit(warning: "bp enabled traits map: \(self.enabledTraitsMap)") + let binaryArtifacts = await self.state.artifacts .reduce(into: [PackageIdentity: [String: BinaryArtifact]]()) { partial, artifact in partial[artifact.packageRef.identity, default: [:]][artifact.targetName] = BinaryArtifact( @@ -998,7 +1005,8 @@ extension Workspace { customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, testEntryPointPath: testEntryPointPath, fileSystem: self.fileSystem, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: self.enabledTraitsMap ) try self.validateSignatures( @@ -1067,9 +1075,16 @@ extension Workspace { if let (package, manifest) = result { // Store the manifest. rootManifests[package] = manifest + + // TODO bp: compute the traits for roots here. + let traitConfiguration = self.configuration.traitConfiguration + let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) + self.enabledTraitsMap[manifest.packageIdentity] = enabledTraits } } + + // Check for duplicate root packages after all manifests are loaded. let duplicateRoots = rootManifests.values.spm_findDuplicateElements(by: \.displayName) if let firstDuplicateSet = duplicateRoots.first, let firstDuplicate = firstDuplicateSet.first { diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 448daa9d762..c830f9872e0 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -435,7 +435,8 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { binaryArtifacts: [:], prebuilts: [:], fileSystem: fileSystem, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: [:] // When bootstrapping no special trait build configuration is used ) } diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index 993c77c97ac..21ab9613f6e 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -81,7 +81,7 @@ struct TraitTests { let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), "Example", - extraArgs: ["--traits", "default,Package9,Package10"/*, "--experimental-prune-unused-dependencies"*/], + extraArgs: ["--traits", "default,Package9,Package10"], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. @@ -459,9 +459,9 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, ) async throws { try await fixture(name: "Traits") { fixturePath in - let (stdout, _) = try await executeSwiftPackage( + let (stdout, stderr) = try await executeSwiftPackage( fixturePath.appending("Package10"), - extraArgs: ["dump-symbol-graph"/*, "--experimental-prune-unused-dependencies"*/], + extraArgs: ["dump-symbol-graph"], buildSystem: buildSystem, ) let optionalPath = stdout @@ -487,9 +487,9 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, ) async throws { try await fixture(name: "Traits") { fixturePath in - let (stdout, _) = try await executeSwiftPackage( + let (stdout, stderr) = try await executeSwiftPackage( fixturePath.appending("Package10"), - extraArgs: ["plugin", "extract"/*, "--experimental-prune-unused-dependencies"*/], + extraArgs: ["plugin", "extract"], buildSystem: buildSystem, ) let path = String(stdout.split(whereSeparator: \.isNewline).first!) diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index 7858462a026..66c98c79c27 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -98,7 +98,8 @@ final class PackageGraphPerfTests: XCTestCasePerf { binaryArtifacts: [:], prebuilts: [:], fileSystem: fs, - observabilityScope: observability.topScope + observabilityScope: observability.topScope, + enabledTraitsMap: [:] ) XCTAssertEqual(g.packages.count, N) XCTAssertNoDiagnostics(observability.diagnostics) From adf28f7f0a7ed006ccc5116bcd80d31c69104382 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Fri, 4 Jul 2025 16:22:53 -0400 Subject: [PATCH 10/23] Cleanup; logging messages to help debug failing traits tests --- Sources/PackageGraph/ModulesGraph+Loading.swift | 5 +++-- Sources/Workspace/Workspace+Dependencies.swift | 2 +- Sources/Workspace/Workspace.swift | 2 +- Tests/FunctionalTests/TraitTests.swift | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index aa9426cbd7a..413cc3817dc 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -57,7 +57,6 @@ extension ModulesGraph { }) let rootManifestNodes = try root.packages.map { identity, package in - // TODO bp // If we have enabled traits passed then we start with those. If there are no enabled // traits passed then the default traits will be used. return try GraphLoadingNode( @@ -170,7 +169,7 @@ extension ModulesGraph { createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, fileSystem: fileSystem, observabilityScope: nodeObservabilityScope, - enabledTraits: node.enabledTraits ?? []//["default"] TODO bp + enabledTraits: node.enabledTraits ) let package = try builder.construct() manifestToPackage[manifest] = package @@ -192,6 +191,8 @@ extension ModulesGraph { .init(implementation: .minimumDeploymentTargetDefault) } + observabilityScope.emit(warning: "bp \(allNodes)") + // Resolve dependencies and create resolved packages. let resolvedPackages = try createResolvedPackages( nodes: Array(allNodes.values), diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index b55d77e4c62..19d7d26c6b0 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -529,7 +529,7 @@ extension Workspace { observabilityScope: observabilityScope ) - observabilityScope.emit(warning: "bp resolving + loading deps") +// observabilityScope.emit(warning: "bp resolving + loading deps") // Of the enabled dependencies of targets, only consider these for dependency resolution let currentManifests = try await self.loadDependencyManifests( diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index e1e1dfcc1f5..2dced9a4333 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -965,7 +965,7 @@ extension Workspace { observabilityScope: observabilityScope ) - observabilityScope.emit(warning: "bp enabled traits map: \(self.enabledTraitsMap)") +// observabilityScope.emit(warning: "bp enabled traits map: \(self.enabledTraitsMap)") let binaryArtifacts = await self.state.artifacts .reduce(into: [PackageIdentity: [String: BinaryArtifact]]()) { partial, artifact in diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index 363f94aa75f..7d2e6469c16 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -203,7 +203,6 @@ struct TraitTests { extraArgs: [ "--traits", "default,Package5,Package7,BuildCondition3", - //"--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) @@ -404,13 +403,15 @@ struct TraitTests { extraArgs: [ "--enable-all-traits", "--disable-default-traits", -// "--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) // We expect no warnings to be produced. Specifically no unused dependency warnings. let unusedDependencyRegex = try Regex("warning: '.*': dependency '.*' is not used by any target") #expect(!stderr.contains(unusedDependencyRegex)) + if buildSystem == .swiftbuild { + print(stderr) + } #expect(stdout == """ Package1Library1 trait1 enabled Package2Library1 trait2 enabled @@ -521,7 +522,6 @@ struct TraitTests { extraArgs: [ "--enable-all-traits", "--disable-default-traits", - // "--experimental-prune-unused-dependencies", ], buildSystem: buildSystem, ) @@ -599,7 +599,7 @@ struct TraitTests { let (stdout, _) = try await executeSwiftPackage( fixturePath.appending("Package10"), configuration: configuration, - extraArgs: ["plugin", "extract", "--experimental-prune-unused-dependencies"], + extraArgs: ["plugin", "extract"], buildSystem: buildSystem, ) let path = String(stdout.split(whereSeparator: \.isNewline).first!) From 6c2ce588e52795d862f4b0712a71d80130c0d838 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Tue, 8 Jul 2025 14:01:29 -0400 Subject: [PATCH 11/23] Add trait configuration to calls on getActiveWorkspace * Whenever this method is called, it will store a previously- created Workspace object in the SwiftCommandState. Some calls to this method were missing the traitConfiguration, thus instantiating the Workspace without the necessary trait config. --- Sources/Build/BuildOperation.swift | 1 + Sources/Commands/SwiftRunCommand.swift | 3 ++- Sources/CoreCommands/BuildSystemSupport.swift | 8 ++++---- Sources/CoreCommands/SwiftCommandState.swift | 5 ++--- Sources/PackageGraph/ModulesGraph+Loading.swift | 2 -- Sources/Workspace/Workspace+Dependencies.swift | 2 -- Sources/Workspace/Workspace+Manifests.swift | 5 ----- Sources/Workspace/Workspace.swift | 14 +------------- Tests/FunctionalTests/TraitTests.swift | 2 ++ 9 files changed, 12 insertions(+), 30 deletions(-) diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index a3708a0f56a..315b9148bcb 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -429,6 +429,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // delegate is only available after createBuildSystem is called progressTracker.buildStart(configuration: configuration) + // Perform the build. let llbuildTarget = try await computeLLBuildTargetName(for: subset) let success = buildSystem.build(target: llbuildTarget) diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index ac08161af22..4346d0b7345 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -219,7 +219,8 @@ public struct SwiftRunCommand: AsyncSwiftCommand { explicitProduct: options.executable, traitConfiguration: .init(traitOptions: self.options.traits) ) - let productName = try await findProductName(in: buildSystem.getPackageGraph()) + let modulesGraph = try await buildSystem.getPackageGraph() + let productName = try findProductName(in: modulesGraph) if options.shouldBuildTests { try await buildSystem.build(subset: .allIncludingTests) } else if options.shouldBuild { diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 8cd403ed867..f548199561b 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -58,8 +58,8 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { ) }, pluginConfiguration: .init( - scriptRunner: self.swiftCommandState.getPluginScriptRunner(), - workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory, + scriptRunner: self.swiftCommandState.getPluginScriptRunner(traitConfiguration: traitConfiguration), + workDirectory: try self.swiftCommandState.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory, disableSandbox: self.swiftCommandState.shouldDisableSandbox ), scratchDirectory: self.swiftCommandState.scratchDirectory, @@ -136,8 +136,8 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory { fileSystem: self.swiftCommandState.fileSystem, observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope, pluginConfiguration: .init( - scriptRunner: self.swiftCommandState.getPluginScriptRunner(), - workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory, + scriptRunner: self.swiftCommandState.getPluginScriptRunner(traitConfiguration: traitConfiguration), + workDirectory: try self.swiftCommandState.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory, disableSandbox: self.swiftCommandState.shouldDisableSandbox ), delegate: delegate diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index a6ec8110f62..2806f62a457 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -718,8 +718,8 @@ public final class SwiftCommandState { } } - public func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none) throws -> PluginScriptRunner { - let pluginsDir = try customPluginsDir ?? self.getActiveWorkspace().location.pluginWorkingDirectory + public func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none, traitConfiguration: TraitConfiguration = .default) throws -> PluginScriptRunner { + let pluginsDir = try customPluginsDir ?? self.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory let cacheDir = pluginsDir.appending("cache") let pluginScriptRunner = try DefaultPluginScriptRunner( fileSystem: self.fileSystem, @@ -796,7 +796,6 @@ public final class SwiftCommandState { var productsParameters = try productsBuildParameters ?? self.productsBuildParameters productsParameters.linkingParameters.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib - let buildSystem = try await buildSystemProvider.createBuildSystem( kind: explicitBuildSystem ?? self.options.build.buildSystem, explicitProduct: explicitProduct, diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 413cc3817dc..13b26684ca5 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -191,8 +191,6 @@ extension ModulesGraph { .init(implementation: .minimumDeploymentTargetDefault) } - observabilityScope.emit(warning: "bp \(allNodes)") - // Resolve dependencies and create resolved packages. let resolvedPackages = try createResolvedPackages( nodes: Array(allNodes.values), diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 19d7d26c6b0..6f209a33521 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -529,8 +529,6 @@ extension Workspace { observabilityScope: observabilityScope ) -// observabilityScope.emit(warning: "bp resolving + loading deps") - // Of the enabled dependencies of targets, only consider these for dependency resolution let currentManifests = try await self.loadDependencyManifests( root: graphRoot, diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 7dec151af72..0c6ec78707f 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -743,14 +743,9 @@ extension Workspace { } } - observabilityScope.emit(warning: "bp top level mans: \(topLevelManifests.map(\.key.description))") - observabilityScope.emit(warning: "bp loaded: \(loadedManifests.map(\.key.description))") - // Update enabled traits map self.enabledTraitsMap = .init(try precomputeTraits( topLevelManifests.values.map({ $0 }), loadedManifests)) - observabilityScope.emit(warning: "bp !!!! enabled traits: \(self.enabledTraitsMap)") - let dependencyManifests = allNodes.filter { !$0.value.manifest.packageKind.isRoot } // TODO: this check should go away when introducing explicit overrides diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 2dced9a4333..26932a86725 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -152,7 +152,7 @@ public class Workspace { let fingerprints: PackageFingerprintStorage? /// The workspace configuration settings - let configuration: WorkspaceConfiguration + public let configuration: WorkspaceConfiguration // MARK: State @@ -783,14 +783,6 @@ extension Workspace { defaultRequirement } - // TODO bp ensure this is correct. -// var dependencyEnabledTraits: Set = Set(["default"]) -// if let traits = root.dependencies.first(where: { $0.nameForModuleDependencyResolutionOnly == packageName })? -// .traits -// { -// dependencyEnabledTraits = Set(traits.map(\.name)) -// } - // If any products are required, the rest of the package graph will supply those constraints. let constraint = PackageContainerConstraint( package: dependency.packageRef, @@ -955,8 +947,6 @@ extension Workspace { // such hosts processes call loadPackageGraph to make sure the workspace state is correct try await self.state.reload() - observabilityScope.emit(warning: "bp BEFORE COMP enabled traits map: \(self.enabledTraitsMap)") - // Perform dependency resolution, if required. let manifests = try await self._resolve( root: root, @@ -965,8 +955,6 @@ extension Workspace { observabilityScope: observabilityScope ) -// observabilityScope.emit(warning: "bp enabled traits map: \(self.enabledTraitsMap)") - let binaryArtifacts = await self.state.artifacts .reduce(into: [PackageIdentity: [String: BinaryArtifact]]()) { partial, artifact in partial[artifact.packageRef.identity, default: [:]][artifact.targetName] = BinaryArtifact( diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index 7d2e6469c16..b105438b144 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -102,6 +102,8 @@ struct TraitTests { // We expect no warnings to be produced. Specifically no unused dependency warnings. let unusedDependencyRegex = try Regex("warning: '.*': dependency '.*' is not used by any target") #expect(!stderr.contains(unusedDependencyRegex)) + print("computing: \(buildSystem.rawValue) with configuration \(configuration.rawValue)") + print(stderr) #expect(stdout == """ Package1Library1 trait1 enabled Package2Library1 trait2 enabled From cb6723ea642d5928560eb650d5937301f57907f0 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 9 Jul 2025 11:47:56 -0400 Subject: [PATCH 12/23] Amend plugin command configuration of package graph --- Sources/Commands/PackageCommands/DumpCommands.swift | 2 +- Sources/Commands/PackageCommands/PluginCommand.swift | 4 +++- Sources/Commands/Utilities/PluginDelegate.swift | 7 ++++--- Sources/CoreCommands/SwiftCommandState.swift | 1 - Sources/PackageGraph/ModulesGraph.swift | 3 +-- Sources/PackageModel/Manifest/Manifest+Traits.swift | 3 +-- Sources/_InternalTestSupport/MockWorkspace.swift | 6 ++---- Tests/FunctionalTests/TraitTests.swift | 2 -- Tests/PackageModelTests/ManifestTests.swift | 5 ++--- 9 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index e5ccf3873fe..ad9295a2099 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -54,7 +54,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: .native, // We are enabling all traits for dumping the symbol graph. - traitConfiguration: .init(enableAllTraits: true), + traitConfiguration: .enableAllTraits, cacheBuildManifest: false ) try await buildSystem.build() diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index ec2243a6a71..09e9e83d5a7 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -184,7 +184,9 @@ struct PluginCommand: AsyncSwiftCommand { swiftCommandState: SwiftCommandState ) async throws { // Load the workspace and resolve the package graph. - let packageGraph = try await swiftCommandState.loadPackageGraph() + let packageGraph = try await swiftCommandState.loadPackageGraph( + traitConfiguration: .enableAllTraits + ) swiftCommandState.observabilityScope.emit(info: "Finding plugin for command ‘\(command)’") let matchingPlugins = PluginCommand.findPlugins(matching: command, in: packageGraph, limitedTo: options.packageIdentity) diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 98464b43227..83a9ebfb1c6 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -171,7 +171,7 @@ final class PluginDelegate: PluginInvocationDelegate { let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: buildSystem, explicitProduct: explicitProduct, - traitConfiguration: .init(), + traitConfiguration: .init(), // TODO bp cacheBuildManifest: false, productsBuildParameters: buildParameters, outputStream: outputStream, @@ -181,6 +181,7 @@ final class PluginDelegate: PluginInvocationDelegate { // Run the build. This doesn't return until the build is complete. let success = await buildSystem.buildIgnoringError(subset: buildSubset) + swiftCommandState.observabilityScope.emit(warning: "plugin delegate getting package graph") let packageGraph = try await buildSystem.getPackageGraph() var builtArtifacts: [PluginInvocationBuildResult.BuiltArtifact] = [] @@ -246,7 +247,7 @@ final class PluginDelegate: PluginInvocationDelegate { toolsBuildParameters.testingParameters.explicitlyEnabledTestability = true toolsBuildParameters.testingParameters.enableCodeCoverage = parameters.enableCodeCoverage let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(), + traitConfiguration: .init(), // TODO bp toolsBuildParameters: toolsBuildParameters ) try await buildSystem.build(subset: .allIncludingTests) @@ -410,7 +411,7 @@ final class PluginDelegate: PluginInvocationDelegate { // Create a build system for building the target., skipping the the cache because we need the build plan. let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: buildSystem, - traitConfiguration: TraitConfiguration(enableAllTraits: true), + traitConfiguration: .enableAllTraits, cacheBuildManifest: false ) diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 2806f62a457..f870e1e6c4c 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -793,7 +793,6 @@ public final class SwiftCommandState { guard let buildSystemProvider else { fatalError("build system provider not initialized") } - var productsParameters = try productsBuildParameters ?? self.productsBuildParameters productsParameters.linkingParameters.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib let buildSystem = try await buildSystemProvider.createBuildSystem( diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 74205481026..273ac86cac6 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -447,11 +447,9 @@ func topologicalSortIdentifiable( public func precomputeTraits( _ enabledTraitsMap: EnabledTraitsMap, -// root: PackageGraphRoot, _ topLevelManifests: [Manifest], _ manifestMap: [PackageIdentity: Manifest] ) throws -> [PackageIdentity: Set] { -// var enabledTraits = root.enabledTraits var visited: Set = [] func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws { @@ -477,6 +475,7 @@ public func precomputeTraits( .init(parent) ) + // TODO bp // enabledTraitsMap[dependency.identity] = calculatedTraits let result = visited.insert(dependency.identity) diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift index c633f932235..9f9364ea3a9 100644 --- a/Sources/PackageModel/Manifest/Manifest+Traits.swift +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -438,9 +438,8 @@ extension Manifest { target: String, _ dependency: TargetDescription.Dependency, enabledTraits: Set, - enableAllTraits: Bool = false // TODO bp: remove this parameter ) throws -> Bool { - guard self.supportsTraits, !enableAllTraits else { return true } + guard self.supportsTraits else { return true } guard let target = self.targetMap[target] else { return false } guard target.dependencies.contains(where: { $0 == dependency }) else { throw InternalError( diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index 519561a8215..a3ea8df34d2 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -685,7 +685,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - var root = try PackageGraphRoot( + let root = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope @@ -955,7 +955,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - var graphRoot = try PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope @@ -964,8 +964,6 @@ public final class MockWorkspace { root: graphRoot, observabilityScope: observability.topScope ) - // TODO bp -// graphRoot = manifests.root result(manifests, observability.diagnostics) } diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index b105438b144..7d2e6469c16 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -102,8 +102,6 @@ struct TraitTests { // We expect no warnings to be produced. Specifically no unused dependency warnings. let unusedDependencyRegex = try Regex("warning: '.*': dependency '.*' is not used by any target") #expect(!stderr.contains(unusedDependencyRegex)) - print("computing: \(buildSystem.rawValue) with configuration \(configuration.rawValue)") - print(stderr) #expect(stdout == """ Package1Library1 trait1 enabled Package2Library1 trait2 enabled diff --git a/Tests/PackageModelTests/ManifestTests.swift b/Tests/PackageModelTests/ManifestTests.swift index 12387c41a70..d42b450bdb1 100644 --- a/Tests/PackageModelTests/ManifestTests.swift +++ b/Tests/PackageModelTests/ManifestTests.swift @@ -754,13 +754,12 @@ class ManifestTests: XCTestCase { enabledTraits: ["Trait3"] )) - // Test if a trait-guarded dependency is enabled when passed a flag that enables all traits; + // Test if a trait-guarded dependency is enabled when passed all traits enabled; // should be true. XCTAssertTrue(try manifest.isTargetDependencyEnabled( target: "Foo", trait3GuardedTargetDependency, - enabledTraits: ["default"], - enableAllTraits: true + enabledTraits: ["Trait1", "Trait2", "Trait3"] )) // Test if a trait-guarded dependency is enabled when there are no enabled traits passsed. From 307867fee12943b8fdf93ba60bd1b0ab480eb153 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 9 Jul 2025 13:24:07 -0400 Subject: [PATCH 13/23] Allow workspace to update its trait configuration - Previous calls to getActiveWorkspace could have defaulted to using the default trait configuration (re: PluginCommand -> PluginDelegate.createSymbolGraphForPlugin case), therefore we should update the existing workspace's trait configuration if a new one is passed. --- .../Commands/PackageCommands/PluginCommand.swift | 4 +--- Sources/CoreCommands/SwiftCommandState.swift | 8 ++++++++ Sources/Workspace/Workspace.swift | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 09e9e83d5a7..ec2243a6a71 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -184,9 +184,7 @@ struct PluginCommand: AsyncSwiftCommand { swiftCommandState: SwiftCommandState ) async throws { // Load the workspace and resolve the package graph. - let packageGraph = try await swiftCommandState.loadPackageGraph( - traitConfiguration: .enableAllTraits - ) + let packageGraph = try await swiftCommandState.loadPackageGraph() swiftCommandState.observabilityScope.emit(info: "Finding plugin for command ‘\(command)’") let matchingPlugins = PluginCommand.findPlugins(matching: command, in: packageGraph, limitedTo: options.packageIdentity) diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index f870e1e6c4c..a536b9819ed 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -459,6 +459,14 @@ public final class SwiftCommandState { /// Returns the currently active workspace. public func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false, traitConfiguration: TraitConfiguration = .default) throws -> Workspace { if let workspace = _workspace { + // Update the Workspace's trait configuration to the latest known trait configuration. + // Note: Previous calls to getActiveWorkspace could have not had an explicit trait configuration (default), + // however for particular cases where a PluginDelegate's createSymbolGraphForPlugin is being called (which happens + // after PluginCommand has begun running), it will need the latest traitConfiguration overridding what is already + // available in WorkspaceConfiguration. + if workspace.traitConfiguration != traitConfiguration { + workspace.updateConfiguration(with: traitConfiguration) + } return workspace } diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 26932a86725..a980c2b7844 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -152,7 +152,12 @@ public class Workspace { let fingerprints: PackageFingerprintStorage? /// The workspace configuration settings - public let configuration: WorkspaceConfiguration + private(set) var configuration: WorkspaceConfiguration + + /// The trait configuration as described in the workspace's configuration. + public var traitConfiguration: TraitConfiguration { + configuration.traitConfiguration + } // MARK: State @@ -1367,6 +1372,13 @@ extension Workspace { } } +// MARK: - Workspace configuration update extensions +extension Workspace { + public func updateConfiguration(with traitConfiguration: TraitConfiguration) { + self.configuration.traitConfiguration = traitConfiguration + } +} + // MARK: - Utility extensions extension Workspace.ManagedArtifact { From eb36991d38af61db3b1b7d29a34a13643d0f5a2a Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 10 Jul 2025 15:12:13 -0400 Subject: [PATCH 14/23] Add trait configuration to SwiftCommandState Since the trait configuration is used for a variety of swift package subcommands, it makes it a lot easier to add the trait options as a part of the GlobalOptions which is consumed by SwiftCommandState and persisted across all SwiftCommands. This also allows us to omit passing a trait configuration to the build system factory methods, and instead replacing it with a flag that can override the trait configuration to enable all traits for edge-case scenarios (e.g. fetching symbol graphs) --- .../Commands/PackageCommands/APIDiff.swift | 7 +- .../PackageCommands/DumpCommands.swift | 2 +- .../Commands/PackageCommands/Install.swift | 2 +- .../Commands/PackageCommands/Migrate.swift | 1 - .../PackageCommands/PluginCommand.swift | 1 - .../Commands/PackageCommands/Resolve.swift | 10 +- .../Commands/Snippets/Cards/SnippetCard.swift | 2 +- Sources/Commands/SwiftBuildCommand.swift | 6 +- Sources/Commands/SwiftRunCommand.swift | 7 +- Sources/Commands/SwiftTestCommand.swift | 13 +- Sources/Commands/Utilities/APIDigester.swift | 1 - .../Commands/Utilities/PluginDelegate.swift | 4 +- Sources/CoreCommands/BuildSystemSupport.swift | 24 +-- Sources/CoreCommands/Options.swift | 13 +- Sources/CoreCommands/SwiftCommandState.swift | 56 +++---- .../PackageGraph/ModulesGraph+Loading.swift | 3 - .../BuildSystem/BuildSystem.swift | 6 +- .../Workspace/Workspace+Dependencies.swift | 11 +- Sources/Workspace/Workspace+Manifests.swift | 1 - Sources/Workspace/Workspace.swift | 145 +++++++++++++++--- 20 files changed, 194 insertions(+), 121 deletions(-) diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index 9b380d90e9d..7416b67feec 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -72,8 +72,8 @@ struct APIDiff: AsyncSwiftCommand { help: "One or more targets to include in the API comparison. If present, only the specified targets (and any products specified using `--products`) will be compared.") var targets: [String] = [] - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions @Option(name: .customLong("baseline-dir"), help: "The path to a directory used to store API baseline files. If unspecified, a temporary directory will be used.") @@ -106,7 +106,6 @@ struct APIDiff: AsyncSwiftCommand { ) } else { let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(traitOptions: self.traits), cacheBuildManifest: false, ) try await runWithSwiftPMCoordinatedDiffing( @@ -207,7 +206,6 @@ struct APIDiff: AsyncSwiftCommand { ) let delegate = DiagnosticsCapturingBuildSystemDelegate() let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(traitOptions: self.traits), cacheBuildManifest: false, productsBuildParameters: productsBuildParameters, delegate: delegate @@ -316,7 +314,6 @@ struct APIDiff: AsyncSwiftCommand { // Build the baseline module. // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the APIDigester. rdar://86112934 let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(), cacheBuildManifest: false, productsBuildParameters: productsBuildParameters, packageGraphLoader: { graph } diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index ad9295a2099..0f7e009a461 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -54,7 +54,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: .native, // We are enabling all traits for dumping the symbol graph. - traitConfiguration: .enableAllTraits, + enableAllTraits: true, cacheBuildManifest: false ) try await buildSystem.build() diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index 9572c402559..839f4f089d2 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -88,7 +88,7 @@ extension SwiftPackageCommand { commandState.preferredBuildConfiguration = .release } - try await commandState.createBuildSystem(explicitProduct: productToInstall.name, traitConfiguration: .init()) + try await commandState.createBuildSystem(explicitProduct: productToInstall.name) .build(subset: .product(productToInstall.name)) let binPath = try commandState.productsBuildParameters.buildPath.appending(component: productToInstall.name) diff --git a/Sources/Commands/PackageCommands/Migrate.swift b/Sources/Commands/PackageCommands/Migrate.swift index 95098638934..c612c0a7a75 100644 --- a/Sources/Commands/PackageCommands/Migrate.swift +++ b/Sources/Commands/PackageCommands/Migrate.swift @@ -215,7 +215,6 @@ extension SwiftPackageCommand { } return try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(), // Don't attempt to cache manifests with temporary // feature flags added just for migration purposes. cacheBuildManifest: false, diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index ec2243a6a71..3cefef02a7e 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -338,7 +338,6 @@ struct PluginCommand: AsyncSwiftCommand { // Build or bring up-to-date any executable host-side tools on which this plugin depends. Add them and any binary dependencies to the tool-names-to-path map. let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: buildSystemKind, - traitConfiguration: .init(), cacheBuildManifest: false, productsBuildParameters: swiftCommandState.productsBuildParameters, toolsBuildParameters: buildParameters, diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index 263184c385b..2f2931028f3 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -33,8 +33,8 @@ extension SwiftPackageCommand { var packageName: String? /// Specifies the traits to build. - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions } struct Resolve: AsyncSwiftCommand { @@ -50,10 +50,10 @@ extension SwiftPackageCommand { func run(_ swiftCommandState: SwiftCommandState) async throws { // If a package is provided, use that to resolve the dependencies. if let packageName = resolveOptions.packageName { - let workspace = try swiftCommandState.getActiveWorkspace(traitConfiguration: .init(traitOptions: resolveOptions.traits)) + let workspace = try swiftCommandState.getActiveWorkspace() try await workspace.resolve( packageName: packageName, - root: swiftCommandState.getWorkspaceRoot(traitConfiguration: .init(traitOptions: resolveOptions.traits)), + root: swiftCommandState.getWorkspaceRoot(), version: resolveOptions.version, branch: resolveOptions.branch, revision: resolveOptions.revision, @@ -64,7 +64,7 @@ extension SwiftPackageCommand { } } else { // Otherwise, run a normal resolve. - try await swiftCommandState.resolve(.init(traitOptions: resolveOptions.traits)) + try await swiftCommandState.resolve() } } } diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index d54b916ccf3..1e575b869ab 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -112,7 +112,7 @@ struct SnippetCard: Card { func runExample() async throws { print("Building '\(snippet.path)'\n") - let buildSystem = try await swiftCommandState.createBuildSystem(explicitProduct: snippet.name, traitConfiguration: .init()) + let buildSystem = try await swiftCommandState.createBuildSystem(explicitProduct: snippet.name) try await buildSystem.build(subset: .product(snippet.name)) let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: snippet.name) if let exampleTarget = try await buildSystem.getPackageGraph().module(for: snippet.name) { diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 8bc8e1aaccb..60db8569488 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -111,8 +111,8 @@ struct BuildCommandOptions: ParsableArguments { var testLibraryOptions: TestLibraryOptions /// Specifies the traits to build. - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions /// If should link the Swift stdlib statically. @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically.") @@ -144,7 +144,6 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { // FIXME: Doesn't seem ideal that we need an explicit build operation, but this concretely uses the `LLBuildManifest`. guard let buildOperation = try await swiftCommandState.createBuildSystem( explicitBuildSystem: .native, - traitConfiguration: .init(traitOptions: self.options.traits) ) as? BuildOperation else { throw StringError("asked for native build system but did not get it") } @@ -194,7 +193,6 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { ) async throws { let buildSystem = try await swiftCommandState.createBuildSystem( explicitProduct: options.product, - traitConfiguration: .init(traitOptions: self.options.traits), shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters, diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 4346d0b7345..5185b983bfe 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -89,8 +89,8 @@ struct RunCommandOptions: ParsableArguments { var executable: String? /// Specifies the traits to build the product with. - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions /// The arguments to pass to the executable. @Argument(parsing: .captureForPassthrough, @@ -139,7 +139,6 @@ public struct SwiftRunCommand: AsyncSwiftCommand { // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the REPL. rdar://86112934 let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: .native, - traitConfiguration: .init(traitOptions: self.options.traits), cacheBuildManifest: false, packageGraphLoader: asyncUnsafeGraphLoader ) @@ -161,7 +160,6 @@ public struct SwiftRunCommand: AsyncSwiftCommand { do { let buildSystem = try await swiftCommandState.createBuildSystem( explicitProduct: options.executable, - traitConfiguration: .init(traitOptions: self.options.traits) ) let productName = try await findProductName(in: buildSystem.getPackageGraph()) if options.shouldBuildTests { @@ -217,7 +215,6 @@ public struct SwiftRunCommand: AsyncSwiftCommand { do { let buildSystem = try await swiftCommandState.createBuildSystem( explicitProduct: options.executable, - traitConfiguration: .init(traitOptions: self.options.traits) ) let modulesGraph = try await buildSystem.getPackageGraph() let productName = try findProductName(in: modulesGraph) diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index a5b1e2cd919..7735663849b 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -221,8 +221,8 @@ struct TestCommandOptions: ParsableArguments { return testOutput == .experimentalSummary } - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions } /// Tests filtering specifier, which is used to filter tests to run. @@ -659,7 +659,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters, testProduct: self.options.sharedOptions.testProduct, - traitConfiguration: .init(traitOptions: self.options.traits) + traitConfiguration: .init(traitOptions: self.globalOptions.traits) ) } @@ -741,8 +741,8 @@ extension SwiftTestCommand { @OptionGroup() var testEventStreamOptions: TestEventStreamOptions - @OptionGroup(visibility: .hidden) - package var traits: TraitOptions +// @OptionGroup(visibility: .hidden) +// package var traits: TraitOptions // for deprecated passthrough from SwiftTestTool (parse will fail otherwise) @Flag(name: [.customLong("list-tests"), .customShort("l")], help: .hidden) @@ -850,7 +850,7 @@ extension SwiftTestCommand { productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters, testProduct: self.sharedOptions.testProduct, - traitConfiguration: .init(traitOptions: self.traits) + traitConfiguration: .init(traitOptions: self.globalOptions.traits) ) } } @@ -1561,7 +1561,6 @@ private func buildTestsIfNeeded( traitConfiguration: TraitConfiguration ) async throws -> [BuiltTestProduct] { let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: traitConfiguration, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters ) diff --git a/Sources/Commands/Utilities/APIDigester.swift b/Sources/Commands/Utilities/APIDigester.swift index be402306492..351783307c2 100644 --- a/Sources/Commands/Utilities/APIDigester.swift +++ b/Sources/Commands/Utilities/APIDigester.swift @@ -140,7 +140,6 @@ struct APIDigesterBaselineDumper { // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the APIDigester. rdar://86112934 let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: .native, - traitConfiguration: .init(), cacheBuildManifest: false, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters, diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 83a9ebfb1c6..e2357fa6440 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -171,7 +171,6 @@ final class PluginDelegate: PluginInvocationDelegate { let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: buildSystem, explicitProduct: explicitProduct, - traitConfiguration: .init(), // TODO bp cacheBuildManifest: false, productsBuildParameters: buildParameters, outputStream: outputStream, @@ -247,7 +246,6 @@ final class PluginDelegate: PluginInvocationDelegate { toolsBuildParameters.testingParameters.explicitlyEnabledTestability = true toolsBuildParameters.testingParameters.enableCodeCoverage = parameters.enableCodeCoverage let buildSystem = try await swiftCommandState.createBuildSystem( - traitConfiguration: .init(), // TODO bp toolsBuildParameters: toolsBuildParameters ) try await buildSystem.build(subset: .allIncludingTests) @@ -411,7 +409,7 @@ final class PluginDelegate: PluginInvocationDelegate { // Create a build system for building the target., skipping the the cache because we need the build plan. let buildSystem = try await swiftCommandState.createBuildSystem( explicitBuildSystem: buildSystem, - traitConfiguration: .enableAllTraits, + enableAllTraits: true, cacheBuildManifest: false ) diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index f548199561b..b579d11c3fe 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -29,7 +29,7 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, @@ -39,7 +39,7 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { observabilityScope: ObservabilityScope?, delegate: BuildSystemDelegate? ) async throws -> any BuildSystem { - _ = try await swiftCommandState.getRootPackageInformation(traitConfiguration: traitConfiguration) + _ = try await swiftCommandState.getRootPackageInformation(enableAllTraits) let testEntryPointPath = productsBuildParameters?.testProductStyle.explicitlySpecifiedEntryPointPath let cacheBuildManifest = if cacheBuildManifest { try await self.swiftCommandState.canUseCachedBuildManifest() @@ -53,17 +53,17 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { packageGraphLoader: packageGraphLoader ?? { try await self.swiftCommandState.loadPackageGraph( explicitProduct: explicitProduct, - traitConfiguration: traitConfiguration, + enableAllTraits: enableAllTraits, testEntryPointPath: testEntryPointPath ) }, pluginConfiguration: .init( - scriptRunner: self.swiftCommandState.getPluginScriptRunner(traitConfiguration: traitConfiguration), - workDirectory: try self.swiftCommandState.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory, + scriptRunner: self.swiftCommandState.getPluginScriptRunner(), + workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory, disableSandbox: self.swiftCommandState.shouldDisableSandbox ), scratchDirectory: self.swiftCommandState.scratchDirectory, - traitConfiguration: traitConfiguration, + traitConfiguration: enableAllTraits ? .enableAllTraits : self.swiftCommandState.traitConfiguration, additionalFileRules: FileRuleDescription.swiftpmFileTypes, pkgConfigDirectories: self.swiftCommandState.options.locations.pkgConfigDirectories, outputStream: outputStream ?? self.swiftCommandState.outputStream, @@ -79,7 +79,7 @@ private struct XcodeBuildSystemFactory: BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, @@ -94,7 +94,7 @@ private struct XcodeBuildSystemFactory: BuildSystemFactory { packageGraphLoader: packageGraphLoader ?? { try await self.swiftCommandState.loadPackageGraph( explicitProduct: explicitProduct, - traitConfiguration: traitConfiguration + enableAllTraits: enableAllTraits ) }, outputStream: outputStream ?? self.swiftCommandState.outputStream, @@ -111,7 +111,7 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, @@ -126,7 +126,7 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory { packageGraphLoader: packageGraphLoader ?? { try await self.swiftCommandState.loadPackageGraph( explicitProduct: explicitProduct, - traitConfiguration: traitConfiguration, + enableAllTraits: enableAllTraits, ) }, packageManagerResourcesDirectory: swiftCommandState.packageManagerResourcesDirectory, @@ -136,8 +136,8 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory { fileSystem: self.swiftCommandState.fileSystem, observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope, pluginConfiguration: .init( - scriptRunner: self.swiftCommandState.getPluginScriptRunner(traitConfiguration: traitConfiguration), - workDirectory: try self.swiftCommandState.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory, + scriptRunner: self.swiftCommandState.getPluginScriptRunner(), + workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory, disableSandbox: self.swiftCommandState.shouldDisableSandbox ), delegate: delegate diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 9dddd1f9c58..9330f43d92d 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -63,6 +63,9 @@ public struct GlobalOptions: ParsableArguments { @OptionGroup(title: "Build Options") public var linker: LinkerOptions + + @OptionGroup(title: "Trait Options") + public var traits: TraitOptions } public struct LocationOptions: ParsableArguments { @@ -683,8 +686,8 @@ public struct TestLibraryOptions: ParsableArguments { } } -package struct TraitOptions: ParsableArguments { - package init() {} +public struct TraitOptions: ParsableArguments { + public init() {} /// The traits to enable for the package. @Option( @@ -694,7 +697,7 @@ package struct TraitOptions: ParsableArguments { package var _enabledTraits: String? /// The set of enabled traits for the package. - package var enabledTraits: Set? { + public var enabledTraits: Set? { self._enabledTraits.flatMap { Set($0.components(separatedBy: ",")) } } @@ -703,7 +706,7 @@ package struct TraitOptions: ParsableArguments { name: .customLong("enable-all-traits"), help: "Enables all traits of the package." ) - package var enableAllTraits: Bool = false + public var enableAllTraits: Bool = false /// Disables all default traits of the package. @Flag( @@ -714,7 +717,7 @@ package struct TraitOptions: ParsableArguments { } extension TraitConfiguration { - package init(traitOptions: TraitOptions) { + public init(traitOptions: TraitOptions) { var enabledTraits = traitOptions.enabledTraits if traitOptions.disableDefaultTraits { // If there are no enabled traits specified we can disable the diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index a536b9819ed..88558ad1702 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -215,7 +215,7 @@ public final class SwiftCommandState { } /// Get the current workspace root object. - public func getWorkspaceRoot(traitConfiguration: TraitConfiguration = .default) throws -> PackageGraphRootInput { + public func getWorkspaceRoot() throws -> PackageGraphRootInput { let packages: [AbsolutePath] if let workspace = options.locations.multirootPackageDataFile { @@ -225,7 +225,7 @@ public final class SwiftCommandState { packages = try [self.getPackageRoot()] } - return PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) + return PackageGraphRootInput(packages: packages, traitConfiguration: self.traitConfiguration) } /// Scratch space (.build) directory. @@ -292,6 +292,8 @@ public final class SwiftCommandState { package var preferredBuildConfiguration = BuildConfiguration.debug + public var traitConfiguration: TraitConfiguration + /// Create an instance of this tool. /// /// - parameter options: The command line options to be passed to this tool. @@ -413,6 +415,9 @@ public final class SwiftCommandState { explicitDirectory: options.locations.swiftSDKsDirectory ?? options.locations.deprecatedSwiftSDKsDirectory ) + // Set the trait configuration from user-passed trait options. + self.traitConfiguration = .init(traitOptions: options.traits) + // set global process logging handler AsyncProcess.loggingHandler = { self.observabilityScope.emit(debug: $0) } } @@ -457,15 +462,12 @@ public final class SwiftCommandState { } /// Returns the currently active workspace. - public func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false, traitConfiguration: TraitConfiguration = .default) throws -> Workspace { - if let workspace = _workspace { - // Update the Workspace's trait configuration to the latest known trait configuration. - // Note: Previous calls to getActiveWorkspace could have not had an explicit trait configuration (default), - // however for particular cases where a PluginDelegate's createSymbolGraphForPlugin is being called (which happens - // after PluginCommand has begun running), it will need the latest traitConfiguration overridding what is already - // available in WorkspaceConfiguration. - if workspace.traitConfiguration != traitConfiguration { - workspace.updateConfiguration(with: traitConfiguration) + public func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false, enableAllTraits: Bool = false) throws -> Workspace { + if var workspace = _workspace { + // if we decide to override the trait configuration, we can resolve accordingly for + // calls like createSymbolGraphForPlugin. + if enableAllTraits { + workspace = workspace.updateConfiguration(with: .enableAllTraits) } return workspace } @@ -519,7 +521,7 @@ public final class SwiftCommandState { prebuiltsDownloadURL: options.caching.prebuiltsDownloadURL, prebuiltsRootCertPath: options.caching.prebuiltsRootCertPath, pruneDependencies: self.options.resolver.pruneDependencies, - traitConfiguration: traitConfiguration + traitConfiguration: self.traitConfiguration ), cancellator: self.cancellator, initializationWarningHandler: { self.observabilityScope.emit(warning: $0) }, @@ -532,9 +534,9 @@ public final class SwiftCommandState { return workspace } - public func getRootPackageInformation(traitConfiguration: TraitConfiguration = .default) async throws -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { - let workspace = try self.getActiveWorkspace(traitConfiguration: traitConfiguration) - let root = try self.getWorkspaceRoot(traitConfiguration: traitConfiguration) + public func getRootPackageInformation(_ enableAllTraits: Bool = false) async throws -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { + let workspace = try self.getActiveWorkspace(enableAllTraits: enableAllTraits) + let root = try self.getWorkspaceRoot() let rootManifests = try await workspace.loadRootManifests( packages: root.packages, observabilityScope: self.observabilityScope @@ -657,9 +659,9 @@ public final class SwiftCommandState { } /// Resolve the dependencies. - public func resolve(_ traitConfiguration: TraitConfiguration = .default) async throws { - let workspace = try getActiveWorkspace(traitConfiguration: traitConfiguration) - let root = try getWorkspaceRoot(traitConfiguration: traitConfiguration) + public func resolve() async throws { + let workspace = try getActiveWorkspace() + let root = try getWorkspaceRoot() try await workspace.resolve( root: root, @@ -687,7 +689,7 @@ public final class SwiftCommandState { ) async throws -> ModulesGraph { try await self.loadPackageGraph( explicitProduct: explicitProduct, - traitConfiguration: .default, + enableAllTraits: false, testEntryPointPath: testEntryPointPath ) } @@ -700,15 +702,15 @@ public final class SwiftCommandState { @discardableResult package func loadPackageGraph( explicitProduct: String? = nil, - traitConfiguration: TraitConfiguration = .default, + enableAllTraits: Bool = false, testEntryPointPath: AbsolutePath? = nil ) async throws -> ModulesGraph { do { - let workspace = try getActiveWorkspace(traitConfiguration: traitConfiguration) + let workspace = try getActiveWorkspace(enableAllTraits: enableAllTraits) // Fetch and load the package graph. let graph = try await workspace.loadPackageGraph( - rootInput: self.getWorkspaceRoot(traitConfiguration: traitConfiguration), + rootInput: self.getWorkspaceRoot(), explicitProduct: explicitProduct, forceResolvedVersions: self.options.resolver.forceResolvedVersions, testEntryPointPath: testEntryPointPath, @@ -726,8 +728,8 @@ public final class SwiftCommandState { } } - public func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none, traitConfiguration: TraitConfiguration = .default) throws -> PluginScriptRunner { - let pluginsDir = try customPluginsDir ?? self.getActiveWorkspace(traitConfiguration: traitConfiguration).location.pluginWorkingDirectory + public func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none) throws -> PluginScriptRunner { + let pluginsDir = try customPluginsDir ?? self.getActiveWorkspace().location.pluginWorkingDirectory let cacheDir = pluginsDir.appending("cache") let pluginScriptRunner = try DefaultPluginScriptRunner( fileSystem: self.fileSystem, @@ -772,7 +774,7 @@ public final class SwiftCommandState { // Perform steps for build manifest caching if we can enabled it. // // FIXME: We don't add edited packages in the package structure command yet (SR-11254). - let hasEditedPackages = try await self.getActiveWorkspace(traitConfiguration: traitConfiguration).state.dependencies.contains(where: \.isEdited) + let hasEditedPackages = try await self.getActiveWorkspace().state.dependencies.contains(where: \.isEdited) if hasEditedPackages { return false } @@ -787,7 +789,7 @@ public final class SwiftCommandState { public func createBuildSystem( explicitBuildSystem: BuildSystemProvider.Kind? = .none, explicitProduct: String? = .none, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool = false, cacheBuildManifest: Bool = true, shouldLinkStaticSwiftStdlib: Bool = false, productsBuildParameters: BuildParameters? = .none, @@ -806,7 +808,7 @@ public final class SwiftCommandState { let buildSystem = try await buildSystemProvider.createBuildSystem( kind: explicitBuildSystem ?? self.options.build.buildSystem, explicitProduct: explicitProduct, - traitConfiguration: traitConfiguration, + enableAllTraits: enableAllTraits, cacheBuildManifest: cacheBuildManifest, productsBuildParameters: productsParameters, toolsBuildParameters: toolsBuildParameters, diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 13b26684ca5..b93531d9473 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -96,9 +96,6 @@ extension ModulesGraph { // required. This checks the current node and then enables the conditional // dependencies of the dependency node. - // TODO bp: shouldn't need to do any traits computation here, - // if we've successfully computed them in the PackageGraphRoot. - return try KeyedPair( GraphLoadingNode( identity: dependency.identity, diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index 9f29a66fdbc..31ec0cb432c 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -123,7 +123,7 @@ public protocol BuildPlan { public protocol BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, @@ -152,7 +152,7 @@ public struct BuildSystemProvider { public func createBuildSystem( kind: Kind, explicitProduct: String? = .none, - traitConfiguration: TraitConfiguration, + enableAllTraits: Bool = false, cacheBuildManifest: Bool = true, productsBuildParameters: BuildParameters? = .none, toolsBuildParameters: BuildParameters? = .none, @@ -167,7 +167,7 @@ public struct BuildSystemProvider { } return try await buildSystemFactory.makeBuildSystem( explicitProduct: explicitProduct, - traitConfiguration: traitConfiguration, + enableAllTraits: enableAllTraits, cacheBuildManifest: cacheBuildManifest, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters, diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 6f209a33521..b3cc16637dc 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -81,7 +81,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - var graphRoot = try PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, dependencyMapper: self.dependencyMapper, @@ -91,8 +91,6 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) - // TODO bp: find less hacky way to do this - graphRoot = currentManifests.root // Abort if we're unable to load the `Package.resolved` store or have any diagnostics. guard let resolvedPackagesStore = observabilityScope.trap({ try self.resolvedPackagesStore.load() }) else { return nil } @@ -166,8 +164,6 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) - // TODO bp - graphRoot = updatedDependencyManifests.root // If we have missing packages, something is fundamentally wrong with the resolution of the graph let stillMissingPackages = try updatedDependencyManifests.missingPackages guard stillMissingPackages.isEmpty else { @@ -369,9 +365,6 @@ extension Workspace { observabilityScope: observabilityScope ) - // TODO bp -// graphRoot = dependencyManifests.root - return (dependencyManifests, .notRequired ) @@ -634,8 +627,6 @@ extension Workspace { root: graphRoot, observabilityScope: observabilityScope ) - // TODO bp -// graphRoot = updatedDependencyManifests.root // If we still have missing packages, something is fundamentally wrong with the resolution of the graph let stillMissingPackages = try updatedDependencyManifests.missingPackages diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 0c6ec78707f..8547a2d6395 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -323,7 +323,6 @@ extension Workspace { return try manifestsMap[dependency.identity].map { manifest in // Calculate all transitively enabled traits for this manifest. - // TODO bp: see if this is corect var allEnabledTraits: Set = ["default"] if let explicitlyEnabledTraits { diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index a980c2b7844..1a3dae5baaf 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -152,9 +152,9 @@ public class Workspace { let fingerprints: PackageFingerprintStorage? /// The workspace configuration settings - private(set) var configuration: WorkspaceConfiguration + let configuration: WorkspaceConfiguration - /// The trait configuration as described in the workspace's configuration. + /// The trait configuration as described in the Workspace's configuration. public var traitConfiguration: TraitConfiguration { configuration.traitConfiguration } @@ -416,7 +416,7 @@ public class Workspace { ) } - private init( + private convenience init( // core fileSystem: FileSystem, environment: Environment, @@ -576,6 +576,7 @@ public class Workspace { // register the binary artifacts downloader with the cancellation handler cancellator?.register(name: "binary artifacts downloads", handler: binaryArtifactsManager) + var prebuiltsManager: PrebuiltsManager? if configuration.usePrebuilts, let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform, let swiftCompilerVersion = hostToolchain.swiftCompilerVersion @@ -587,7 +588,7 @@ public class Workspace { rootCertPath = nil } - let prebuiltsManager = PrebuiltsManager( + let prebuiltsManagerObj = PrebuiltsManager( fileSystem: fileSystem, hostPlatform: hostPlatform, swiftCompilerVersion: customPrebuiltsManager?.swiftVersion ?? swiftCompilerVersion, @@ -600,13 +601,91 @@ public class Workspace { prebuiltsDownloadURL: configuration.prebuiltsDownloadURL, rootCertPath: customPrebuiltsManager?.rootCertPath ?? rootCertPath ) - cancellator?.register(name: "package prebuilts downloads", handler: prebuiltsManager) - self.prebuiltsManager = prebuiltsManager + cancellator?.register(name: "package prebuilts downloads", handler: prebuiltsManagerObj) + prebuiltsManager = prebuiltsManagerObj } else { - self.prebuiltsManager = nil + prebuiltsManager = nil } // initialize +// self.fileSystem = fileSystem +// self.configuration = configuration +// self.location = location +// self.delegate = delegate +// self.mirrors = mirrors +// +// self.hostToolchain = hostToolchain +// self.manifestLoader = manifestLoader +// self.currentToolsVersion = currentToolsVersion +// +// self.customPackageContainerProvider = customPackageContainerProvider +// self.repositoryManager = repositoryManager +// self.registryClient = registryClient +// self.registryDownloadsManager = registryDownloadsManager +// self.binaryArtifactsManager = binaryArtifactsManager +// +// self.identityResolver = identityResolver +// self.dependencyMapper = dependencyMapper +// self.fingerprints = fingerprints + let resolvedPackagesStore = LoadableResult { + try ResolvedPackagesStore( + packageResolvedFile: location.resolvedVersionsFile, + workingDirectory: location.scratchDirectory, + fileSystem: fileSystem, + mirrors: mirrors + ) + } + + let state = WorkspaceState( + fileSystem: fileSystem, + storageDirectory: location.scratchDirectory, + initializationWarningHandler: initializationWarningHandler + ) + + self.init( + fileSystem: fileSystem, + configuration: configuration, + location: location, + delegate: delegate, + mirrors: mirrors, + hostToolchain: hostToolchain, + manifestLoader: manifestLoader, + currentToolsVersion: currentToolsVersion, + customPackageContainerProvider: customPackageContainerProvider, + repositoryManager: repositoryManager, + registryClient: registryClient, + registryDownloadsManager: registryDownloadsManager, + binaryArtifactsManager: binaryArtifactsManager, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fingerprints: fingerprints, + resolvedPackagesStore: resolvedPackagesStore, + prebuiltsManager: prebuiltsManager, + state: state + ) + } + + private init( + fileSystem: any FileSystem, + configuration: WorkspaceConfiguration, + location: Location, + delegate: Delegate?, + mirrors: DependencyMirrors, + hostToolchain: UserToolchain, + manifestLoader: ManifestLoaderProtocol, + currentToolsVersion: ToolsVersion, + customPackageContainerProvider: PackageContainerProvider?, + repositoryManager: RepositoryManager, + registryClient: RegistryClient, + registryDownloadsManager: RegistryDownloadsManager, + binaryArtifactsManager: BinaryArtifactsManager, + identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, + fingerprints: PackageFingerprintStorage?, + resolvedPackagesStore: LoadableResult, + prebuiltsManager: PrebuiltsManager?, + state: WorkspaceState + ) { self.fileSystem = fileSystem self.configuration = configuration self.location = location @@ -627,20 +706,10 @@ public class Workspace { self.dependencyMapper = dependencyMapper self.fingerprints = fingerprints - self.resolvedPackagesStore = LoadableResult { - try ResolvedPackagesStore( - packageResolvedFile: location.resolvedVersionsFile, - workingDirectory: location.scratchDirectory, - fileSystem: fileSystem, - mirrors: mirrors - ) - } + self.resolvedPackagesStore = resolvedPackagesStore + self.prebuiltsManager = prebuiltsManager - self.state = WorkspaceState( - fileSystem: fileSystem, - storageDirectory: self.location.scratchDirectory, - initializationWarningHandler: initializationWarningHandler - ) + self.state = state } } @@ -1372,15 +1441,41 @@ extension Workspace { } } -// MARK: - Workspace configuration update extensions + +// MARK: - Utility extensions + extension Workspace { - public func updateConfiguration(with traitConfiguration: TraitConfiguration) { - self.configuration.traitConfiguration = traitConfiguration + /// Creates and returns a copy of the current workspace with an updated configuration using the passed parameters. + /// - Parameters: + /// - traitConfiguration: A configuration of traits that will override the existing trait configuration in the WorkspaceConfiguration. + public func updateConfiguration(with traitConfiguration: TraitConfiguration) -> Workspace { + var newConfig = self.configuration + newConfig.traitConfiguration = traitConfiguration + + return Workspace( + fileSystem: self.fileSystem, + configuration: newConfig, + location: self.location, + delegate: self.delegate, + mirrors: self.mirrors, + hostToolchain: self.hostToolchain, + manifestLoader: self.manifestLoader, + currentToolsVersion: self.currentToolsVersion, + customPackageContainerProvider: self.customPackageContainerProvider, + repositoryManager: self.repositoryManager, + registryClient: self.registryClient, + registryDownloadsManager: self.registryDownloadsManager, + binaryArtifactsManager: self.binaryArtifactsManager, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + fingerprints: self.fingerprints, + resolvedPackagesStore: self.resolvedPackagesStore, + prebuiltsManager: prebuiltsManager, + state: self.state + ) } } -// MARK: - Utility extensions - extension Workspace.ManagedArtifact { fileprivate var originURL: String? { switch self.source { From c0ecd4eb3d51e1e01ca2aac72ccd0ad4e62ac079 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 10 Jul 2025 15:58:54 -0400 Subject: [PATCH 15/23] Ensure PackageGraphRoot.init has latest enabledTraitsMap --- Sources/PackageGraph/ModulesGraph.swift | 143 ++++++++++-------- Sources/PackageGraph/PackageGraphRoot.swift | 58 +++---- .../Workspace/Workspace+Dependencies.swift | 9 +- Sources/Workspace/Workspace+Manifests.swift | 17 +-- Sources/Workspace/Workspace.swift | 2 +- .../_InternalTestSupport/MockWorkspace.swift | 14 +- Sources/swift-bootstrap/main.swift | 3 +- 7 files changed, 130 insertions(+), 116 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 273ac86cac6..5c9fe3ced47 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -445,60 +445,6 @@ func topologicalSortIdentifiable( return result.reversed() } -public func precomputeTraits( - _ enabledTraitsMap: EnabledTraitsMap, - _ topLevelManifests: [Manifest], - _ manifestMap: [PackageIdentity: Manifest] -) throws -> [PackageIdentity: Set] { - var visited: Set = [] - - func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws { - let parentTraits = enabledTraitsMap[parent.packageIdentity] - let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) - let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) - - _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in - return try manifestMap[dependency.identity].flatMap({ manifest in - - let explicitlyEnabledTraits = dependency.traits?.filter { - guard let condition = $0.condition else { return true } - return condition.isSatisfied(by: parentTraits) - }.map(\.name) - - var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - - // Form union with traits that have already been pre-computed, if they exist - enabledTraitsSet?.formUnion(enabledTraitsMap[dependency.identity]) - - let calculatedTraits = try manifest.enabledTraits( - using: enabledTraitsSet ?? ["default"], - .init(parent) - ) - - // TODO bp -// enabledTraitsMap[dependency.identity] = calculatedTraits - - let result = visited.insert(dependency.identity) - if result.inserted { - try dependencies(of: manifest, dependency.productFilter) - } - - return manifest - }) - }) - } - - for manifest in topLevelManifests { - // Track already-visited manifests to avoid cycles - let result = visited.insert(manifest.packageIdentity) - if result.inserted { - try dependencies(of: manifest) - } - } - - return enabledTraitsMap.dictionaryLiteral -} - @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) public func loadModulesGraph( identityResolver: IdentityResolver = DefaultIdentityResolver(), @@ -524,21 +470,92 @@ public func loadModulesGraph( } let packages = Array(rootManifests.keys) + + let manifestMap = manifests.reduce(into: [PackageIdentity: Manifest]()) { manifestMap, manifest in + manifestMap[manifest.packageIdentity] = manifest + } + + // Note: The following is a copy of the existing `Workspace.precomputeTraits` method + func precomputeTraits( + _ enabledTraitsMap: EnabledTraitsMap, + _ topLevelManifests: [Manifest], + _ manifestMap: [PackageIdentity: Manifest] + ) throws -> [PackageIdentity: Set] { + var visited: Set = [] + var enabledTraitsMap = enabledTraitsMap + + func dependencies(of parent: Manifest, _ productFilter: ProductFilter = .everything) throws { + let parentTraits = enabledTraitsMap[parent.packageIdentity] + let requiredDependencies = try parent.dependenciesRequired(for: productFilter, parentTraits) + let guardedDependencies = parent.dependenciesTraitGuarded(withEnabledTraits: parentTraits) + + _ = try (requiredDependencies + guardedDependencies).compactMap({ dependency in + return try manifestMap[dependency.identity].flatMap({ manifest in + + let explicitlyEnabledTraits = dependency.traits?.filter { + guard let condition = $0.condition else { return true } + return condition.isSatisfied(by: parentTraits) + }.map(\.name) + + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } + let precomputedTraits = enabledTraitsMap[dependency.identity] + + if precomputedTraits == ["default"], + let enabledTraitsSet { + enabledTraitsMap[dependency.identity] = enabledTraitsSet + } else { + // unify traits + enabledTraitsSet?.formUnion(precomputedTraits) + if let enabledTraitsSet { + enabledTraitsMap[dependency.identity] = enabledTraitsSet + } + } + + let calculatedTraits = try manifest.enabledTraits( + using: enabledTraitsSet ?? ["default"], + .init(parent) + ) + + enabledTraitsMap[dependency.identity] = calculatedTraits + let result = visited.insert(dependency.identity) + if result.inserted { + try dependencies(of: manifest, dependency.productFilter) + } + + return manifest + }) + }) + } + + for manifest in topLevelManifests { + // Track already-visited manifests to avoid cycles + let result = visited.insert(manifest.packageIdentity) + if result.inserted { + try dependencies(of: manifest) + } + } + + return enabledTraitsMap.dictionaryLiteral + } + + + // Precompute enabled traits for roots. + var enabledTraitsMap: EnabledTraitsMap = [:] + for root in rootManifests.values { + let enabledTraits = try root.enabledTraits(using: traitConfiguration) + enabledTraitsMap[root.packageIdentity] = enabledTraits + } + enabledTraitsMap = .init(try precomputeTraits(enabledTraitsMap, manifests, manifestMap)) + let input = PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) let graphRoot = try PackageGraphRoot( input: input, manifests: rootManifests, explicitProduct: explicitProduct, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: enabledTraitsMap ) - let manifestMap = manifests.reduce(into: [PackageIdentity: Manifest]()) { manifestMap, manifest in - manifestMap[manifest.packageIdentity] = manifest - } - - // TODO bp - let updatedTraitsMap = try precomputeTraits([:], manifests, manifestMap) - return try ModulesGraph.load( root: graphRoot, identityResolver: identityResolver, @@ -554,6 +571,6 @@ public func loadModulesGraph( observabilityScope: observabilityScope, productsFilter: nil, modulesFilter: nil, - enabledTraitsMap: .init(updatedTraitsMap) + enabledTraitsMap: enabledTraitsMap ) } diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index ce2b376ee08..aa398678820 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -122,7 +122,8 @@ public struct PackageGraphRoot { manifests: [AbsolutePath: Manifest], explicitProduct: String? = nil, dependencyMapper: DependencyMapper? = nil, - observabilityScope: ObservabilityScope + observabilityScope: ObservabilityScope, + enabledTraitsMap: EnabledTraitsMap = .init() ) throws { self.packages = input.packages.reduce(into: .init(), { partial, inputPath in if let manifest = manifests[inputPath] { @@ -132,32 +133,33 @@ public struct PackageGraphRoot { } }) + // TODO bp: remove // Calculate the enabled traits for root. - var enableTraitsMap: EnabledTraitsMap = [:] - enableTraitsMap = try packages.reduce(into: EnabledTraitsMap()) { traitsMap, package in - let manifest = package.value.manifest - let traitConfiguration = input.traitConfiguration - - // Should only ever have to use trait configuration here for roots. - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) - traitsMap[package.key] = enabledTraits - - // Calculate the enabled traits for each dependency of this root: - manifest.dependencies.forEach { dependency in - let explicitlyEnabledTraits = dependency.traits?.filter({ - guard let condition = $0.condition else { return true } - return condition.isSatisfied(by: enabledTraits) - }).map(\.name) - var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } - - enabledTraitsSet?.formUnion(traitsMap[dependency.identity]) - - // to fix with precompute fix here - if let enabledTraitsSet { - traitsMap[dependency.identity] = enabledTraitsSet - } - } - } +// var enableTraitsMap: EnabledTraitsMap = [:] +// enableTraitsMap = try packages.reduce(into: EnabledTraitsMap()) { traitsMap, package in +// let manifest = package.value.manifest +// let traitConfiguration = input.traitConfiguration +// +// // Should only ever have to use trait configuration here for roots. +// let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) +// traitsMap[package.key] = enabledTraits +// +// // Calculate the enabled traits for each dependency of this root: +// manifest.dependencies.forEach { dependency in +// let explicitlyEnabledTraits = dependency.traits?.filter({ +// guard let condition = $0.condition else { return true } +// return condition.isSatisfied(by: enabledTraits) +// }).map(\.name) +// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } +// +// enabledTraitsSet?.formUnion(traitsMap[dependency.identity]) +// +// // to fix with precompute fix here +// if let enabledTraitsSet { +// traitsMap[dependency.identity] = enabledTraitsSet +// } +// } +// } // self.enabledTraits = enableTraitsMap @@ -174,7 +176,7 @@ public struct PackageGraphRoot { // If not, then we can omit this dependency if pruning unused dependencies // is enabled. return manifests.values.reduce(false) { result, manifest in - let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] + let enabledTraits: Set = enabledTraitsMap[manifest.packageIdentity] if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { return result || isUsed } @@ -187,7 +189,7 @@ public struct PackageGraphRoot { // FIXME: `dependenciesRequired` modifies manifests and prevents conversion of `Manifest` to a value type let deps = try? manifests.values.lazy .map({ manifest -> [PackageDependency] in - let enabledTraits: Set = enableTraitsMap[manifest.packageIdentity] + let enabledTraits: Set = enabledTraitsMap[manifest.packageIdentity] return try manifest.dependenciesRequired(for: .everything, enabledTraits) }) .flatMap({ $0 }) diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index b3cc16637dc..64eb37227b8 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -85,7 +85,8 @@ extension Workspace { input: root, manifests: rootManifests, dependencyMapper: self.dependencyMapper, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: self.enabledTraitsMap ) let currentManifests = try await self.loadDependencyManifests( root: graphRoot, @@ -353,7 +354,8 @@ extension Workspace { manifests: rootManifests, explicitProduct: explicitProduct, dependencyMapper: self.dependencyMapper, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: self.enabledTraitsMap ) // Load the `Package.resolved` store or abort now. @@ -519,7 +521,8 @@ extension Workspace { manifests: rootManifests, explicitProduct: explicitProduct, dependencyMapper: self.dependencyMapper, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: self.enabledTraitsMap ) // Of the enabled dependencies of targets, only consider these for dependency resolution diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 8547a2d6395..54382ae1480 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -802,26 +802,15 @@ extension Workspace { guard let condition = $0.condition else { return true } return condition.isSatisfied(by: parentTraits) }.map(\.name) -// -// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } -// -// // Form union with traits that have already been pre-computed, if they exist -// enabledTraitsSet?.formUnion(self.enabledTraitsMap[dependency.identity]) -// -// let calculatedTraits = try manifest.enabledTraits( -// using: enabledTraitsSet ?? ["default"], -// .init(parent) -// ) -// -// self.enabledTraitsMap[dependency.identity] = calculatedTraits + var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } let precomputedTraits = self.enabledTraitsMap[dependency.identity] - // TODO bp shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits. + // Shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits, since we're meant to flatten the default traits. if precomputedTraits == ["default"], let enabledTraitsSet { self.enabledTraitsMap[dependency.identity] = enabledTraitsSet } else { - // unify traits + // Unify traits enabledTraitsSet?.formUnion(precomputedTraits) if let enabledTraitsSet { self.enabledTraitsMap[dependency.identity] = enabledTraitsSet diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 1a3dae5baaf..af6bd63b0e7 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -1138,7 +1138,7 @@ extension Workspace { // Store the manifest. rootManifests[package] = manifest - // TODO bp: compute the traits for roots here. + // Compute the enabled traits for roots. let traitConfiguration = self.configuration.traitConfiguration let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) self.enabledTraitsMap[manifest.packageIdentity] = enabledTraits diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index a3ea8df34d2..4e8b202654f 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -103,6 +103,7 @@ public final class MockWorkspace { .SourceControlToRegistryDependencyTransformation var defaultRegistry: Registry? public let traitConfiguration: TraitConfiguration + public var enabledTraitsMap: EnabledTraitsMap public let pruneDependencies: Bool public init( @@ -125,7 +126,8 @@ public final class MockWorkspace { defaultRegistry: Registry? = .none, customHostTriple: Triple = hostTriple, traitConfiguration: TraitConfiguration = .default, - pruneDependencies: Bool = false + pruneDependencies: Bool = false, + enabledTraitsMap: EnabledTraitsMap = .init() ) async throws { try fileSystem.createMockToolchain() @@ -164,6 +166,7 @@ public final class MockWorkspace { self.customHostToolchain = try UserToolchain.mockHostToolchain(fileSystem, hostTriple: customHostTriple) self.traitConfiguration = traitConfiguration self.pruneDependencies = pruneDependencies + self.enabledTraitsMap = enabledTraitsMap try await self.create() } @@ -688,7 +691,8 @@ public final class MockWorkspace { let root = try PackageGraphRoot( input: rootInput, manifests: rootManifests, - observabilityScope: observability.topScope + observabilityScope: observability.topScope, + enabledTraitsMap: workspace.enabledTraitsMap ) let dependencyManifests = try await workspace.loadDependencyManifests( @@ -696,9 +700,6 @@ public final class MockWorkspace { observabilityScope: observability.topScope ) - // TODO bp - // root.enabledTraits = dependencyManifests.root - let result = try await workspace.precomputeResolution( root: root, dependencyManifests: dependencyManifests, @@ -958,7 +959,8 @@ public final class MockWorkspace { let graphRoot = try PackageGraphRoot( input: rootInput, manifests: rootManifests, - observabilityScope: observability.topScope + observabilityScope: observability.topScope, + enabledTraitsMap: workspace.enabledTraitsMap ) let manifests = try await workspace.loadDependencyManifests( root: graphRoot, diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index a5e9e3e3031..fef4fc0bbc4 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -426,7 +426,8 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { let packageGraphRoot = try PackageGraphRoot( input: .init(packages: [packagePath]), manifests: [packagePath: rootPackageManifest], - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + enabledTraitsMap: .init() ) return try ModulesGraph.load( From e989978ffe4c892f23684e42ff963961648a9548 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 10 Jul 2025 16:10:13 -0400 Subject: [PATCH 16/23] Cleanup --- .../Commands/PackageCommands/APIDiff.swift | 3 - .../Commands/PackageCommands/Resolve.swift | 4 -- Sources/Commands/SwiftBuildCommand.swift | 4 -- Sources/Commands/SwiftRunCommand.swift | 4 -- Sources/Commands/SwiftTestCommand.swift | 6 -- .../PackageGraph/ModulesGraph+Loading.swift | 5 +- Sources/PackageGraph/PackageGraphRoot.swift | 58 ------------------- Sources/Workspace/Workspace+Manifests.swift | 32 ++-------- Sources/Workspace/Workspace.swift | 50 ++++++++++------ 9 files changed, 39 insertions(+), 127 deletions(-) diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index 7416b67feec..5c156d79212 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -72,9 +72,6 @@ struct APIDiff: AsyncSwiftCommand { help: "One or more targets to include in the API comparison. If present, only the specified targets (and any products specified using `--products`) will be compared.") var targets: [String] = [] -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions - @Option(name: .customLong("baseline-dir"), help: "The path to a directory used to store API baseline files. If unspecified, a temporary directory will be used.") var overrideBaselineDir: Basics.AbsolutePath? diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index 2f2931028f3..10c5dff9039 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -31,10 +31,6 @@ extension SwiftPackageCommand { @Argument(help: "The name of the package to resolve.") var packageName: String? - - /// Specifies the traits to build. -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions } struct Resolve: AsyncSwiftCommand { diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 60db8569488..b2e14fd0da5 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -110,10 +110,6 @@ struct BuildCommandOptions: ParsableArguments { @OptionGroup(visibility: .private) var testLibraryOptions: TestLibraryOptions - /// Specifies the traits to build. -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions - /// If should link the Swift stdlib statically. @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically.") public var shouldLinkStaticSwiftStdlib: Bool = false diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 5185b983bfe..d343b5a04aa 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -88,10 +88,6 @@ struct RunCommandOptions: ParsableArguments { @Argument(help: "The executable to run.", completion: .shellCommand("swift package completion-tool list-executables")) var executable: String? - /// Specifies the traits to build the product with. -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions - /// The arguments to pass to the executable. @Argument(parsing: .captureForPassthrough, help: "The arguments to pass to the executable.") diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 7735663849b..c2ae2ada153 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -220,9 +220,6 @@ struct TestCommandOptions: ParsableArguments { var enableExperimentalTestOutput: Bool { return testOutput == .experimentalSummary } - -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions } /// Tests filtering specifier, which is used to filter tests to run. @@ -741,9 +738,6 @@ extension SwiftTestCommand { @OptionGroup() var testEventStreamOptions: TestEventStreamOptions -// @OptionGroup(visibility: .hidden) -// package var traits: TraitOptions - // for deprecated passthrough from SwiftTestTool (parse will fail otherwise) @Flag(name: [.customLong("list-tests"), .customShort("l")], help: .hidden) var _deprecated_passthrough: Bool = false diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index b93531d9473..4956e7e7920 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -133,9 +133,8 @@ extension ModulesGraph { successors: nodeSuccessorProvider ) { allNodes[$0.key] = $0.item - } onDuplicate: { first, second in - // We are unifying the enabled traits on duplicate - // TODO bp: to remove this, precompute traits elsewhere + } onDuplicate: { _, _ in + // Nothing we need to compute here. } // Create the packages. diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index aa398678820..e16edf08316 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -38,34 +38,6 @@ public struct PackageGraphRootInput { } } -public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { - public typealias Key = PackageIdentity - public typealias Value = Set - - var storage: [PackageIdentity: Set] = [:] - - public init() { } - - public init(dictionaryLiteral elements: (Key, Value)...) { - for (key, value) in elements { - storage[key] = value - } - } - - public init(_ dictionary: [Key: Value]) { - self.storage = dictionary - } - - public subscript(key: PackageIdentity) -> Set { - get { storage[key] ?? ["default"] } - set { storage[key] = newValue } - } - - public var dictionaryLiteral: [PackageIdentity: Set] { - return storage - } -} - /// Represents the inputs to the package graph. public struct PackageGraphRoot { @@ -133,36 +105,6 @@ public struct PackageGraphRoot { } }) - // TODO bp: remove - // Calculate the enabled traits for root. -// var enableTraitsMap: EnabledTraitsMap = [:] -// enableTraitsMap = try packages.reduce(into: EnabledTraitsMap()) { traitsMap, package in -// let manifest = package.value.manifest -// let traitConfiguration = input.traitConfiguration -// -// // Should only ever have to use trait configuration here for roots. -// let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) -// traitsMap[package.key] = enabledTraits -// -// // Calculate the enabled traits for each dependency of this root: -// manifest.dependencies.forEach { dependency in -// let explicitlyEnabledTraits = dependency.traits?.filter({ -// guard let condition = $0.condition else { return true } -// return condition.isSatisfied(by: enabledTraits) -// }).map(\.name) -// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } -// -// enabledTraitsSet?.formUnion(traitsMap[dependency.identity]) -// -// // to fix with precompute fix here -// if let enabledTraitsSet { -// traitsMap[dependency.identity] = enabledTraitsSet -// } -// } -// } - -// self.enabledTraits = enableTraitsMap - // FIXME: Deprecate special casing once the manifest supports declaring used executable products. // Special casing explicit products like this is necessary to pass the test suite and satisfy backwards compatibility. // However, changing the dependencies based on the command line arguments may force `Package.resolved` to temporarily change, diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 54382ae1480..334983cbd33 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -255,7 +255,6 @@ extension Workspace { enabledTraits: node.enabledTraits ) if !isDepUsed && workspace.configuration.pruneDependencies { - // TODO bp: see if this produces expected result, since enabled traits is no longer optional if !node.enabledTraits.isEmpty { observabilityScope.emit(debug: """ '\(package.identity)' from '\(package.locationString)' was omitted \ @@ -562,7 +561,6 @@ extension Workspace { let rootManifests = try root.manifests.mapValues { manifest in let parentEnabledTraits = self.enabledTraitsMap[manifest.packageIdentity] let deps = try manifest.dependencies.filter { dep in - // compute enabled traits map; todo bp cleanup let explicitlyEnabledTraits = dep.traits?.filter({ guard let condition = $0.condition else { return true } return condition.isSatisfied(by: parentEnabledTraits) @@ -609,22 +607,6 @@ extension Workspace { let firstLevelDependencies = try topLevelManifests.values.map { manifest in let parentEnabledTraits = self.enabledTraitsMap[manifest.packageIdentity] return try manifest.dependencies.filter { dep in - // Calculate conditional traits for dependencies here; add to enabled traits map. -// let enabledTraits: Set = self.enabledTraitsMap[manifest.packageIdentity] -// let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) -// let explicitlyEnabledTraits = dep.traits?.filter { -// guard let condition = $0.condition else { return true } -// return condition.isSatisfied(by: enabledTraits) -// }.map(\.name) -// -// var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } -// enabledTraitsSet?.formUnion(self.enabledTraitsMap[dep.identity]) -// -// if let enabledTraitsSet { -// self.enabledTraitsMap[dep.identity] = enabledTraitsSet -// } -// -// return isDepUsed let explicitlyEnabledTraits = dep.traits?.filter({ guard let condition = $0.condition else { return true } return condition.isSatisfied(by: parentEnabledTraits) @@ -667,7 +649,7 @@ extension Workspace { observabilityScope: observabilityScope ) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } - return try (dependenciesRequired /*+ dependenciesGuarded*/).compactMap { dependency in + return try dependenciesRequired.compactMap { dependency in return try loadedManifests[dependency.identity].flatMap { manifest in let explicitlyEnabledTraits = dependency.traits?.filter { @@ -677,12 +659,12 @@ extension Workspace { var enabledTraitsSet = explicitlyEnabledTraits.flatMap { Set($0) } let precomputedTraits = self.enabledTraitsMap[dependency.identity] - // TODO bp shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits. + // Shouldn't union here if enabledTraitsMap returns "default" and we DO have explicitly enabled traits, since we're meant to flatten the default traits. if precomputedTraits == ["default"], let enabledTraitsSet { self.enabledTraitsMap[dependency.identity] = enabledTraitsSet } else { - // unify traits + // Unify traits enabledTraitsSet?.formUnion(precomputedTraits) if let enabledTraitsSet { self.enabledTraitsMap[dependency.identity] = enabledTraitsSet @@ -694,9 +676,7 @@ extension Workspace { .init(node.item.manifest) ) - - // TODO bp: precompute traits should take care of this, no longer need -// enabledTraitsMap[manifest.packageIdentity, default: []].formUnion(calculatedTraits ?? []) + self.enabledTraitsMap[dependency.identity] = calculatedTraits // we also compare the location as this function may attempt to load // dependencies that have the same identity but from a different location @@ -737,8 +717,8 @@ extension Workspace { successors: successorNodes ) { allNodes[$0.key] = $0.item - } onDuplicate: { old, new in - // TODO bp + } onDuplicate: { _, _ in + // Nothing we need to compute here. } } diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index af6bd63b0e7..55082183fdf 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -608,25 +608,6 @@ public class Workspace { } // initialize -// self.fileSystem = fileSystem -// self.configuration = configuration -// self.location = location -// self.delegate = delegate -// self.mirrors = mirrors -// -// self.hostToolchain = hostToolchain -// self.manifestLoader = manifestLoader -// self.currentToolsVersion = currentToolsVersion -// -// self.customPackageContainerProvider = customPackageContainerProvider -// self.repositoryManager = repositoryManager -// self.registryClient = registryClient -// self.registryDownloadsManager = registryDownloadsManager -// self.binaryArtifactsManager = binaryArtifactsManager -// -// self.identityResolver = identityResolver -// self.dependencyMapper = dependencyMapper -// self.fingerprints = fingerprints let resolvedPackagesStore = LoadableResult { try ResolvedPackagesStore( packageResolvedFile: location.resolvedVersionsFile, @@ -1682,3 +1663,34 @@ extension ContainerUpdateStrategy { } } } + +// MARK: - Enabled traits dictionary wrapper + +/// A wrapper for a dictionary that stores the transitively enabled traits for each package. +public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { + public typealias Key = PackageIdentity + public typealias Value = Set + + var storage: [PackageIdentity: Set] = [:] + + public init() { } + + public init(dictionaryLiteral elements: (Key, Value)...) { + for (key, value) in elements { + storage[key] = value + } + } + + public init(_ dictionary: [Key: Value]) { + self.storage = dictionary + } + + public subscript(key: PackageIdentity) -> Set { + get { storage[key] ?? ["default"] } + set { storage[key] = newValue } + } + + public var dictionaryLiteral: [PackageIdentity: Set] { + return storage + } +} From ad0b958795bfa24b18e66886a47e29736fdda8ff Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 10 Jul 2025 16:22:11 -0400 Subject: [PATCH 17/23] Move EnabledTraitsMap to its own file in PackageModel --- Sources/PackageGraph/PackageGraphRoot.swift | 3 -- Sources/PackageModel/EnabledTraitsMap.swift | 40 +++++++++++++++++++++ Sources/Workspace/Workspace.swift | 31 ---------------- 3 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 Sources/PackageModel/EnabledTraitsMap.swift diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index e16edf08316..7f7f3b56521 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -49,9 +49,6 @@ public struct PackageGraphRoot { return self.packages.compactMapValues { $0.manifest } } - /// The root manifest(s)'s enabled traits (and their transitively enabled traits). -// public var enabledTraits: EnabledTraitsMap - /// The root package references. public var packageReferences: [PackageReference] { return self.packages.values.map { $0.reference } diff --git a/Sources/PackageModel/EnabledTraitsMap.swift b/Sources/PackageModel/EnabledTraitsMap.swift new file mode 100644 index 00000000000..958aeb15219 --- /dev/null +++ b/Sources/PackageModel/EnabledTraitsMap.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A wrapper for a dictionary that stores the transitively enabled traits for each package. +public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { + public typealias Key = PackageIdentity + public typealias Value = Set + + var storage: [PackageIdentity: Set] = [:] + + public init() { } + + public init(dictionaryLiteral elements: (Key, Value)...) { + for (key, value) in elements { + storage[key] = value + } + } + + public init(_ dictionary: [Key: Value]) { + self.storage = dictionary + } + + public subscript(key: PackageIdentity) -> Set { + get { storage[key] ?? ["default"] } + set { storage[key] = newValue } + } + + public var dictionaryLiteral: [PackageIdentity: Set] { + return storage + } +} diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 55082183fdf..fdfa0e8ef1d 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -1663,34 +1663,3 @@ extension ContainerUpdateStrategy { } } } - -// MARK: - Enabled traits dictionary wrapper - -/// A wrapper for a dictionary that stores the transitively enabled traits for each package. -public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { - public typealias Key = PackageIdentity - public typealias Value = Set - - var storage: [PackageIdentity: Set] = [:] - - public init() { } - - public init(dictionaryLiteral elements: (Key, Value)...) { - for (key, value) in elements { - storage[key] = value - } - } - - public init(_ dictionary: [Key: Value]) { - self.storage = dictionary - } - - public subscript(key: PackageIdentity) -> Set { - get { storage[key] ?? ["default"] } - set { storage[key] = newValue } - } - - public var dictionaryLiteral: [PackageIdentity: Set] { - return storage - } -} From 7e713839597cda5e4ab637a74575f70214c6ba27 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Thu, 10 Jul 2025 16:24:15 -0400 Subject: [PATCH 18/23] Cleanup remaining comments/whitespace --- Sources/Build/BuildOperation.swift | 1 - Tests/PackageGraphTests/ModulesGraphTests.swift | 7 ------- 2 files changed, 8 deletions(-) diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 315b9148bcb..a3708a0f56a 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -429,7 +429,6 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // delegate is only available after createBuildSystem is called progressTracker.buildStart(configuration: configuration) - // Perform the build. let llbuildTarget = try await computeLLBuildTargetName(for: subset) let success = buildSystem.build(target: llbuildTarget) diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 54784103d29..be0df3aa4ac 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -4422,13 +4422,6 @@ final class ModulesGraphTests: XCTestCase { ) XCTAssertEqual(observability.diagnostics.count, 0) -// testDiagnostics(observability.diagnostics) { result in -// result.check( -// diagnostic: "dependency 'package5' is not used by any target", -// severity: .warning -// ) -// } - PackageGraphTester(graph) { result in result.checkPackage("Package1") { package in XCTAssertEqual(package.enabledTraits, ["Package1Trait3"]) From da91ac0d4108dd7933fcefb797d332f34b5aa47e Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Fri, 11 Jul 2025 13:40:35 -0400 Subject: [PATCH 19/23] Add EnabledTraitsMap.swift to CMakeList --- Sources/PackageModel/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 3d5f2f509d3..aa8848b4155 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(PackageModel BuildSettings.swift DependencyMapper.swift Diagnostics.swift + EnabledTraitsMap.swift IdentityResolver.swift InstalledSwiftPMConfiguration.swift Manifest/Manifest.swift From 886d2723701b8288be8e46b7add238a0cdc116c1 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Tue, 15 Jul 2025 15:08:55 -0400 Subject: [PATCH 20/23] Remnant cleanup --- .../ManifestExtensions.swift | 6 +++--- .../_InternalTestSupport/MockDependency.swift | 20 +++++++++---------- .../_InternalTestSupport/MockPackage.swift | 2 +- ...ckageDependencyDescriptionExtensions.swift | 8 ++++---- Tests/WorkspaceTests/WorkspaceTests.swift | 4 ---- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Sources/_InternalTestSupport/ManifestExtensions.swift b/Sources/_InternalTestSupport/ManifestExtensions.swift index 0e4535f7a6f..3891c033a11 100644 --- a/Sources/_InternalTestSupport/ManifestExtensions.swift +++ b/Sources/_InternalTestSupport/ManifestExtensions.swift @@ -32,7 +32,7 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [],//[.init(name: "default")], + traits: Set = [], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( @@ -73,7 +73,7 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [],//[.init(name: "default")], + traits: Set = [], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( @@ -237,7 +237,7 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [],//[.init(name: "default")], + traits: Set = [], pruneDependencies: Bool = false ) -> Manifest { return Manifest( diff --git a/Sources/_InternalTestSupport/MockDependency.swift b/Sources/_InternalTestSupport/MockDependency.swift index c8a1dcaab90..8e9f9f0dbef 100644 --- a/Sources/_InternalTestSupport/MockDependency.swift +++ b/Sources/_InternalTestSupport/MockDependency.swift @@ -22,13 +22,13 @@ public struct MockDependency { public let deprecatedName: String? public let location: Location public let products: ProductFilter - public let traits: Set? + public let traits: Set init( deprecatedName: String? = nil, location: Location, products: ProductFilter = .everything, - traits: Set? = nil + traits: Set = ["default"] ) { self.deprecatedName = deprecatedName self.location = location @@ -132,35 +132,35 @@ public struct MockDependency { } - public static func fileSystem(path: String, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func fileSystem(path: String, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { try! MockDependency(location: .fileSystem(path: RelativePath(validating: path)), products: products, traits: traits) } - public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { try! .sourceControl(path: RelativePath(validating: path), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { MockDependency(location: .localSourceControl(path: path, requirement: requirement), products: products, traits: traits) } - public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { try! MockDependency(deprecatedName: name, location: .localSourceControl(path: RelativePath(validating: path), requirement: requirement), products: products, traits: traits) } - public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { .sourceControl(url: SourceControlURL(url), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { MockDependency(location: .remoteSourceControl(url: url, requirement: requirement), products: products, traits: traits) } - public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { .registry(identity: .plain(identity), requirement: requirement, traits: traits) } - public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set? = nil) -> MockDependency { + public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = ["default"]) -> MockDependency { MockDependency(location: .registry(identity: identity, requirement: requirement), products: products, traits: traits) } diff --git a/Sources/_InternalTestSupport/MockPackage.swift b/Sources/_InternalTestSupport/MockPackage.swift index 5c82b05cd59..0185514e486 100644 --- a/Sources/_InternalTestSupport/MockPackage.swift +++ b/Sources/_InternalTestSupport/MockPackage.swift @@ -35,7 +35,7 @@ public struct MockPackage { targets: [MockTarget], products: [MockProduct] = [], dependencies: [MockDependency] = [], - traits: Set = [],//[.init(name: "default")], + traits: Set = [.init(name: "default")], versions: [String?] = [], revisionProvider: ((String) -> String)? = nil, toolsVersion: ToolsVersion? = nil diff --git a/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift b/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift index 30c63beb2ae..471319c5dba 100644 --- a/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift +++ b/Sources/_InternalTestSupport/PackageDependencyDescriptionExtensions.swift @@ -22,7 +22,7 @@ package extension PackageDependency { deprecatedName: String? = nil, path: AbsolutePath, productFilter: ProductFilter = .everything, - traits: Set? = [.init(name: "default")] + traits: Set = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(path: path) return .fileSystem( @@ -40,7 +40,7 @@ package extension PackageDependency { path: AbsolutePath, requirement: SourceControl.Requirement, productFilter: ProductFilter = .everything, - traits: Set? = [.init(name: "default")] + traits: Set = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(path: path) return .localSourceControl( @@ -59,7 +59,7 @@ package extension PackageDependency { url: SourceControlURL, requirement: SourceControl.Requirement, productFilter: ProductFilter = .everything, - traits: Set? = [.init(name: "default")] + traits: Set = [.init(name: "default")] ) -> Self { let identity = identity ?? PackageIdentity(url: url) return .remoteSourceControl( @@ -76,7 +76,7 @@ package extension PackageDependency { identity: String, requirement: Registry.Requirement, productFilter: ProductFilter = .everything, - traits: Set? = [.init(name: "default")] + traits: Set = [.init(name: "default")] ) -> Self { return .registry( identity: .plain(identity), diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 5e063c927e9..a75787a3f12 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -15872,16 +15872,12 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Foo"], deps: deps) { graph, diagnostics in PackageGraphTester(graph) { result in result.check(roots: "Foo") -// result.check(packages: "Baz", "Foo", "Boo") result.check(packages: "Baz", "Foo") result.check(modules: "Bar", "Baz", "Foo") result.checkTarget("Foo") { result in result.check(dependencies: "Baz") } result.checkTarget("Bar") { result in result.check(dependencies: "Baz") } } XCTAssertNoDiagnostics(diagnostics) -// testDiagnostics(diagnostics) { result in -// result.check(diagnostic: .contains("dependency 'boo' is not used by any target"), severity: .warning) -// } } await workspace.checkManagedDependencies { result in result.check(dependency: "baz", at: .checkout(.version("1.0.0"))) From 9113b3e13ac51bfc666c991e34ebf7a0dfc638c3 Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 16 Jul 2025 11:15:01 -0400 Subject: [PATCH 21/23] Fix ModulesGraph load method Since we'll have others consuming the ModulesGraph load method, create a version of the method without the enabledTraitsMap parameter. --- .../PackageGraph/ModulesGraph+Loading.swift | 41 +++++++++++++++++++ Sources/PackageGraph/PackageContainer.swift | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 4956e7e7920..f6bbb668bf4 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -21,6 +21,47 @@ import func TSCBasic.findCycle import struct TSCBasic.KeyedPair extension ModulesGraph { + package static func load( + root: PackageGraphRoot, + identityResolver: IdentityResolver, + additionalFileRules: [FileRuleDescription] = [], + externalManifests: OrderedCollections.OrderedDictionary, + requiredDependencies: [PackageReference] = [], + unsafeAllowedPackages: Set = [], + binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]], + prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], // Product name to library mapping + shouldCreateMultipleTestProducts: Bool = false, + createREPLProduct: Bool = false, + customPlatformsRegistry: PlatformRegistry? = .none, + customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, + testEntryPointPath: AbsolutePath? = nil, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + productsFilter: ((Product) -> Bool)? = nil, + modulesFilter: ((Module) -> Bool)? = nil + ) throws -> ModulesGraph { + try Self.load( + root: root, + identityResolver: identityResolver, + additionalFileRules: additionalFileRules, + externalManifests: externalManifests, + requiredDependencies: requiredDependencies, + unsafeAllowedPackages: unsafeAllowedPackages, + binaryArtifacts: binaryArtifacts, + prebuilts: prebuilts, + shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, + createREPLProduct: createREPLProduct, + customPlatformsRegistry: customPlatformsRegistry, + customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, + testEntryPointPath: testEntryPointPath, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + productsFilter: productsFilter, + modulesFilter: modulesFilter, + enabledTraitsMap: .init() + ) + } + /// Load the package graph for the given package path. package static func load( root: PackageGraphRoot, diff --git a/Sources/PackageGraph/PackageContainer.swift b/Sources/PackageGraph/PackageContainer.swift index 08d511620cf..031799a22da 100644 --- a/Sources/PackageGraph/PackageContainer.swift +++ b/Sources/PackageGraph/PackageContainer.swift @@ -182,7 +182,7 @@ public struct PackageContainerConstraint: Equatable, Hashable { extension PackageContainerConstraint: CustomStringConvertible { public var description: String { - return "Constraint(\(self.package), \(requirement), \(products), \(enabledTraits ?? [])" + return "Constraint(\(self.package), \(requirement), \(products), \(enabledTraits)" } } From fcc904ea9d80bf957a38f83d6a370ebf7e2d29be Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 16 Jul 2025 12:14:33 -0400 Subject: [PATCH 22/23] Fix remaining logs left in for debugging --- .../Commands/Utilities/PluginDelegate.swift | 1 - .../PackageGraph/ModulesGraph+Loading.swift | 51 ++++--------------- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index ecdf6db452f..34192308c3a 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -180,7 +180,6 @@ final class PluginDelegate: PluginInvocationDelegate { // Run the build. This doesn't return until the build is complete. let success = await buildSystem.buildIgnoringError(subset: buildSubset) - swiftCommandState.observabilityScope.emit(warning: "plugin delegate getting package graph") let packageGraph = try await buildSystem.getPackageGraph() var builtArtifacts: [PluginInvocationBuildResult.BuiltArtifact] = [] diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index f6bbb668bf4..2ea14951129 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -21,47 +21,6 @@ import func TSCBasic.findCycle import struct TSCBasic.KeyedPair extension ModulesGraph { - package static func load( - root: PackageGraphRoot, - identityResolver: IdentityResolver, - additionalFileRules: [FileRuleDescription] = [], - externalManifests: OrderedCollections.OrderedDictionary, - requiredDependencies: [PackageReference] = [], - unsafeAllowedPackages: Set = [], - binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]], - prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], // Product name to library mapping - shouldCreateMultipleTestProducts: Bool = false, - createREPLProduct: Bool = false, - customPlatformsRegistry: PlatformRegistry? = .none, - customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, - testEntryPointPath: AbsolutePath? = nil, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - productsFilter: ((Product) -> Bool)? = nil, - modulesFilter: ((Module) -> Bool)? = nil - ) throws -> ModulesGraph { - try Self.load( - root: root, - identityResolver: identityResolver, - additionalFileRules: additionalFileRules, - externalManifests: externalManifests, - requiredDependencies: requiredDependencies, - unsafeAllowedPackages: unsafeAllowedPackages, - binaryArtifacts: binaryArtifacts, - prebuilts: prebuilts, - shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, - createREPLProduct: createREPLProduct, - customPlatformsRegistry: customPlatformsRegistry, - customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, - testEntryPointPath: testEntryPointPath, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - productsFilter: productsFilter, - modulesFilter: modulesFilter, - enabledTraitsMap: .init() - ) - } - /// Load the package graph for the given package path. package static func load( root: PackageGraphRoot, @@ -193,6 +152,14 @@ extension ModulesGraph { let packagePath = manifest.path.parentDirectory nodeObservabilityScope.trap { // Create a package from the manifest and sources. + + // Special case to handle: if the traits enabled for this node is simply ["default"], + // this means that we don't have any defined traits for this package and should there + // flatten the set to be empty for the PackageBuilder. + var enabledTraits = node.enabledTraits + if enabledTraits == ["default"] { + enabledTraits = [] + } let builder = PackageBuilder( identity: node.identity, manifest: manifest, @@ -206,7 +173,7 @@ extension ModulesGraph { createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, fileSystem: fileSystem, observabilityScope: nodeObservabilityScope, - enabledTraits: node.enabledTraits + enabledTraits: enabledTraits ) let package = try builder.construct() manifestToPackage[manifest] = package From 7fad68ac6c6bcb9e617a94c2997905a80bebe21e Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 16 Jul 2025 17:01:41 -0400 Subject: [PATCH 23/23] Address PR comments --- Sources/CoreCommands/SwiftCommandState.swift | 2 +- Tests/FunctionalTests/TraitTests.swift | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 88558ad1702..a723cf99912 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -292,7 +292,7 @@ public final class SwiftCommandState { package var preferredBuildConfiguration = BuildConfiguration.debug - public var traitConfiguration: TraitConfiguration + package let traitConfiguration: TraitConfiguration /// Create an instance of this tool. /// diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index 3160599d2c5..e121b0988ca 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -409,9 +409,6 @@ struct TraitTests { // We expect no warnings to be produced. Specifically no unused dependency warnings. let unusedDependencyRegex = try Regex("warning: '.*': dependency '.*' is not used by any target") #expect(!stderr.contains(unusedDependencyRegex)) - if buildSystem == .swiftbuild { - print(stderr) - } #expect(stdout == """ Package1Library1 trait1 enabled Package2Library1 trait2 enabled