Skip to content

Commit 5b30b32

Browse files
authored
[Traits] Fix traits omitting used package dependencies (#8399)
Fixes #8398 Used package dependencies of a manifest were considered trait-guarded if a target dependency in the manifest: 1. used a product from said package and 2. was trait-guarded regardless as to whether the same package dependency was used elsewhere (whether in the same target or another in the same manifest) and was not guarded by traits. This change ensures that this is considered when doing the calculations for package dependencies that should be omitted if *every* target dependency that uses said package dependency is guarded.
1 parent e86ec5c commit 5b30b32

File tree

3 files changed

+135
-51
lines changed

3 files changed

+135
-51
lines changed

Sources/CoreCommands/SwiftCommandState.swift

+7-5
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,13 @@ public final class SwiftCommandState {
215215

216216
/// Get the current workspace root object.
217217
public func getWorkspaceRoot(traitConfiguration: TraitConfiguration? = nil) throws -> PackageGraphRootInput {
218-
let packages: [AbsolutePath] = if let workspace = options.locations.multirootPackageDataFile {
219-
try self.workspaceLoaderProvider(self.fileSystem, self.observabilityScope)
218+
let packages: [AbsolutePath]
219+
220+
if let workspace = options.locations.multirootPackageDataFile {
221+
packages = try self.workspaceLoaderProvider(self.fileSystem, self.observabilityScope)
220222
.load(workspace: workspace)
221223
} else {
222-
try [self.getPackageRoot()]
224+
packages = [try getPackageRoot()]
223225
}
224226

225227
return PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration)
@@ -748,7 +750,7 @@ public final class SwiftCommandState {
748750
try self._manifestLoader.get()
749751
}
750752

751-
public func canUseCachedBuildManifest() async throws -> Bool {
753+
public func canUseCachedBuildManifest(_ traitConfiguration: TraitConfiguration? = nil) async throws -> Bool {
752754
if !self.options.caching.cacheBuildManifest {
753755
return false
754756
}
@@ -765,7 +767,7 @@ public final class SwiftCommandState {
765767
// Perform steps for build manifest caching if we can enabled it.
766768
//
767769
// FIXME: We don't add edited packages in the package structure command yet (SR-11254).
768-
let hasEditedPackages = try await self.getActiveWorkspace().state.dependencies.contains(where: \.isEdited)
770+
let hasEditedPackages = try await self.getActiveWorkspace(traitConfiguration: traitConfiguration).state.dependencies.contains(where: \.isEdited)
769771
if hasEditedPackages {
770772
return false
771773
}

Sources/PackageModel/Manifest/Manifest.swift

+60-42
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,7 @@ public final class Manifest: Sendable {
184184
/// Returns a list of dependencies that are being guarded by unenabled traits, given a set of enabled traits.
185185
///
186186
/// If a trait that is guarding a dependency is enabled (and is reflected in the `enabledTraits` parameter) and
187-
/// results in
188-
/// that dependency being used, then that dependency is not considered trait-guarded.
187+
/// results in that dependency being used, then that dependency is not considered trait-guarded.
189188
///
190189
/// For example:
191190
///
@@ -203,38 +202,46 @@ public final class Manifest: Sendable {
203202
return []
204203
}
205204

206-
let traitGuardedDeps = self.traitGuardedDependencies(lowercasedKeys: true)
205+
let traitGuardedDeps = self.traitGuardedTargetDependencies(lowercasedKeys: true)
207206
let explicitlyEnabledTraits = try? self.enabledTraits(using: enabledTraits, enableAllTraits: enableAllTraits)
208207
guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else {
209208
let deps = self.dependencies.filter {
210-
if let guardTraits = traitGuardedDeps[$0.identity.description]?.values.flatMap({ $0 }),
211-
!guardTraits.isEmpty, let explicitlyEnabledTraits
212-
{
213-
return !guardTraits.allSatisfy { explicitlyEnabledTraits.contains($0) }
209+
var result = false
210+
for guardedTargetDeps in traitGuardedDeps[$0.identity.description] ?? [] {
211+
if let guardTraits = guardedTargetDeps.condition?.traits, !guardTraits.isEmpty,
212+
let explicitlyEnabledTraits
213+
{
214+
result = result || !guardTraits.allSatisfy { explicitlyEnabledTraits.contains($0) }
215+
}
214216
}
215217

216-
return false
218+
return result
217219
}
218220
return deps
219221
}
220222

221223
if let dependencies = self._requiredDependencies[.nothing] {
222224
let deps = dependencies.filter {
223-
if let guardTraits = traitGuardedDeps[$0.identity.description]?.values.flatMap({ $0 }),
224-
let explicitlyEnabledTraits
225-
{
226-
return !guardTraits.allSatisfy { explicitlyEnabledTraits.contains($0) }
225+
var result = false
226+
for guardedTargetDeps in traitGuardedDeps[$0.identity.description] ?? [] {
227+
if let guardTraits = guardedTargetDeps.condition?.traits, !guardTraits.isEmpty,
228+
let explicitlyEnabledTraits
229+
{
230+
result = result || !guardTraits.allSatisfy { explicitlyEnabledTraits.contains($0) }
231+
}
227232
}
228233

229-
return false
234+
return result
230235
}
231236
return deps
232237
} else {
233238
var guardedDependencies: Set<PackageIdentity> = []
234239
for target in self.targetsRequired(for: self.products) {
240+
let traitGuardedTargetDeps = traitGuardedTargetDependencies(for: target)
241+
235242
for targetDependency in target.dependencies {
236243
guard let dependency = self.packageDependency(referencedBy: targetDependency),
237-
let guardingTraits = traitGuardedDeps[dependency.identity.description]?[target.name]
244+
let guardingTraits = traitGuardedTargetDeps[targetDependency]
238245
else {
239246
continue
240247
}
@@ -245,19 +252,6 @@ public final class Manifest: Sendable {
245252
guardedDependencies.insert(dependency.identity)
246253
}
247254
}
248-
249-
target.pluginUsages?.forEach {
250-
guard let dependency = self.packageDependency(referencedBy: $0),
251-
let guardingTraits = traitGuardedDeps[dependency.identity.description]?[target.name]
252-
else {
253-
return
254-
}
255-
if let explicitlyEnabledTraits,
256-
guardingTraits.intersection(explicitlyEnabledTraits) != guardingTraits
257-
{
258-
guardedDependencies.insert(dependency.identity)
259-
}
260-
}
261255
}
262256

263257
let dependencies = self.dependencies.filter { guardedDependencies.contains($0.identity) }
@@ -301,8 +295,6 @@ public final class Manifest: Sendable {
301295
return dependencies
302296
}
303297

304-
// calculate explicitly enabled traits through config:
305-
306298
// using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false
307299
if var dependencies = self._requiredDependencies[.nothing] {
308300
if self.pruneDependencies {
@@ -877,20 +869,45 @@ extension Manifest {
877869
return (knownPackage: known, unknownPackage: unknown)
878870
}
879871

880-
/// Returns the set of package dependencies that are potentially guarded by traits, per target. This does not
881-
/// calculate enabled and disabled dependencies enforced by traits.
882-
public func traitGuardedDependencies(lowercasedKeys: Bool = false) -> [String: [String: Set<String>]] {
883-
self.targets.reduce(into: [String: [String: Set<String>]]()) { depMap, target in
884-
let traitGuardedTargetDependencies = target.dependencies.filter {
885-
!($0.condition?.traits?.isEmpty ?? true)
886-
}
872+
/// Computes the list of target dependencies per target that are guarded by traits.
873+
/// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a
874+
/// list of traits.
875+
/// - Parameters:
876+
/// - lowercasedKeys: A flag that determines whether the keys in the resulting dictionary are lowercased.
877+
/// - Returns: A dictionary that maps the name of a `TargetDescription` to a list of its dependencies that are
878+
/// guarded by traits.
879+
public func traitGuardedTargetDependencies(
880+
lowercasedKeys: Bool = false
881+
) -> [String: [TargetDescription.Dependency]] {
882+
self.targets.reduce(into: [String: [TargetDescription.Dependency]]()) { depMap, target in
883+
let traitGuardedTargetDependencies = traitGuardedTargetDependencies(
884+
for: target
885+
)
886+
887887
traitGuardedTargetDependencies.forEach {
888-
guard let package = lowercasedKeys ? $0.package?.lowercased() : $0.package else { return }
889-
depMap[package, default: [:]][target.name, default: []].formUnion($0.condition?.traits ?? [])
888+
guard let package = lowercasedKeys ? $0.key.package?.lowercased() : $0.key.package else { return }
889+
depMap[package, default: []].append($0.key)
890890
}
891891
}
892892
}
893893

894+
/// Computes the list of target dependencies that are guarded by traits for given target.
895+
/// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a
896+
/// list of traits.
897+
/// - Parameters:
898+
/// - target: A `TargetDescription` for which the trait-guarded target dependencies are calculated.
899+
/// - Returns: A dictionary that maps each trait-guarded `TargetDescription.Dependency` of the given
900+
/// `TargetDescription` to the list of traits that guard it.
901+
public func traitGuardedTargetDependencies(for target: TargetDescription)
902+
-> [TargetDescription.Dependency: Set<String>]
903+
{
904+
target.dependencies.filter {
905+
!($0.condition?.traits?.isEmpty ?? true)
906+
}.reduce(into: [TargetDescription.Dependency: Set<String>]()) { depMap, dep in
907+
depMap[dep, default: []].formUnion(dep.condition?.traits ?? [])
908+
}
909+
}
910+
894911
/// Computes the enabled traits for a given target dependency
895912
public func enabledTraits(forDependency dependency: TargetDescription.Dependency) -> Set<String>? {
896913
guard let package = self.packageDependency(referencedBy: dependency),
@@ -910,21 +927,22 @@ extension Manifest {
910927
enableAllTraits: Bool = false
911928
) throws -> Bool {
912929
guard self.supportsTraits, !enableAllTraits else { return true }
913-
guard let package = dependency.package, let target = self.targetMap[target] else { return false }
930+
guard let target = self.targetMap[target] else { return false }
914931
guard target.dependencies.contains(where: { $0 == dependency }) else {
915932
throw InternalError(
916933
"target dependency \(dependency.name) not found for target \(target.name) in package \(self.displayName)"
917934
)
918935
}
919-
let traitsThatEnableDependency = self.traitGuardedDependencies()[package]?[target.name] ?? []
920936

921-
let isEnabled = try traitsThatEnableDependency.allSatisfy { try self.isTraitEnabled(
937+
let traitsToEnable = self.traitGuardedTargetDependencies(for: target)[dependency] ?? []
938+
939+
let isEnabled = try traitsToEnable.allSatisfy { try self.isTraitEnabled(
922940
.init(stringLiteral: $0),
923941
enabledTraits,
924942
enableAllTraits
925943
) }
926944

927-
return traitsThatEnableDependency.isEmpty || isEnabled
945+
return traitsToEnable.isEmpty || isEnabled
928946
}
929947

930948
/// Determines whether a given package dependency is used by this manifest given a set of enabled traits.

Tests/PackageModelTests/ManifestTests.swift

+68-4
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,10 @@ class ManifestTests: XCTestCase {
459459
name: "Baz",
460460
package: "Baz"
461461
),
462+
.product(
463+
name: "Bar2",
464+
package: "Bar"
465+
)
462466
]
463467
),
464468
]
@@ -481,11 +485,14 @@ class ManifestTests: XCTestCase {
481485
traits: traits
482486
)
483487

484-
let traitGuardedDependencies = manifest.traitGuardedDependencies()
488+
let traitGuardedDependencies = manifest.traitGuardedTargetDependencies()
489+
485490
XCTAssertEqual(
486491
traitGuardedDependencies,
487492
[
488-
"Bar": ["Foo": ["Trait2"]],
493+
"Bar": [
494+
.product(name: "Bar", package: "Bar", condition: .init(traits: ["Trait2"]))
495+
]
489496
]
490497
)
491498
}
@@ -501,24 +508,35 @@ class ManifestTests: XCTestCase {
501508
ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]),
502509
]
503510

504-
let unguardedTargetDependency: TargetDescription.Dependency = .product(name: "Bar", package: "Blah")
511+
let unguardedTargetDependency: TargetDescription.Dependency = .product(
512+
name: "Bar",
513+
package: "Blah"
514+
)
515+
505516
let trait3GuardedTargetDependency: TargetDescription.Dependency = .product(
506517
name: "Baz",
507518
package: "Buzz",
508519
condition: .init(traits: ["Trait3"])
509520
)
521+
510522
let defaultTraitGuardedTargetDependency: TargetDescription.Dependency = .product(
511523
name: "Bam",
512524
package: "Boom",
513525
condition: .init(traits: ["Trait2"])
514526
)
515527

528+
let enabledTargetDependencyWithSamePackage: TargetDescription.Dependency = .product(
529+
name: "Kaboom",
530+
package: "Boom"
531+
)
532+
516533
let target = try TargetDescription(
517534
name: "Foo",
518535
dependencies: [
519536
unguardedTargetDependency,
520537
trait3GuardedTargetDependency,
521538
defaultTraitGuardedTargetDependency,
539+
enabledTargetDependencyWithSamePackage,
522540
]
523541
)
524542

@@ -586,6 +604,14 @@ class ManifestTests: XCTestCase {
586604
defaultTraitGuardedTargetDependency,
587605
enabledTraits: []
588606
))
607+
608+
// Test if a target dependency that isn't guarded by traits wherein it uses a product
609+
// from the same package as another target dependency that is guarded by traits; should be true.
610+
XCTAssertTrue(try manifest.isTargetDependencyEnabled(
611+
target: "Foo",
612+
enabledTargetDependencyWithSamePackage,
613+
enabledTraits: []
614+
))
589615
}
590616
}
591617

@@ -604,18 +630,28 @@ class ManifestTests: XCTestCase {
604630
ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]),
605631
]
606632

607-
let unguardedTargetDependency: TargetDescription.Dependency = .product(name: "Bar", package: "Bar")
633+
let unguardedTargetDependency: TargetDescription.Dependency = .product(
634+
name: "Bar",
635+
package: "Bar"
636+
)
637+
608638
let trait3GuardedTargetDependency: TargetDescription.Dependency = .product(
609639
name: "Baz",
610640
package: "Baz",
611641
condition: .init(traits: ["Trait3"])
612642
)
643+
613644
let defaultTraitGuardedTargetDependency: TargetDescription.Dependency = .product(
614645
name: "Bam",
615646
package: "Bam",
616647
condition: .init(traits: ["Trait2"])
617648
)
618649

650+
let unguardedTargetDependencyWithBamPackage: TargetDescription.Dependency = .product(
651+
name: "Qux",
652+
package: "Bam"
653+
)
654+
619655
let target = try TargetDescription(
620656
name: "Foo",
621657
dependencies: [
@@ -625,6 +661,16 @@ class ManifestTests: XCTestCase {
625661
]
626662
)
627663

664+
let targetWithUnguardedBamPackageDep = try TargetDescription(
665+
name: "Foo",
666+
dependencies: [
667+
unguardedTargetDependency,
668+
trait3GuardedTargetDependency,
669+
defaultTraitGuardedTargetDependency,
670+
unguardedTargetDependencyWithBamPackage,
671+
]
672+
)
673+
628674
let traits: Set<TraitDescription> = [
629675
TraitDescription(name: "default", enabledTraits: ["Trait1"]),
630676
TraitDescription(name: "Trait1", enabledTraits: ["Trait2"]),
@@ -643,13 +689,31 @@ class ManifestTests: XCTestCase {
643689
traits: traits
644690
)
645691

692+
let manifestWithBamDependencyAlwaysUsed = Manifest.createRootManifest(
693+
displayName: "Foo",
694+
path: "/Foo",
695+
toolsVersion: .v5_2,
696+
dependencies: dependencies,
697+
products: products,
698+
targets: [targetWithUnguardedBamPackageDep],
699+
traits: traits
700+
)
701+
646702
XCTAssertTrue(try manifest.isPackageDependencyUsed(bar, enabledTraits: nil))
647703
XCTAssertTrue(try manifest.isPackageDependencyUsed(bar, enabledTraits: []))
648704
XCTAssertFalse(try manifest.isPackageDependencyUsed(baz, enabledTraits: nil))
649705
XCTAssertTrue(try manifest.isPackageDependencyUsed(baz, enabledTraits: ["Trait3"]))
650706
XCTAssertTrue(try manifest.isPackageDependencyUsed(bam, enabledTraits: nil))
651707
XCTAssertFalse(try manifest.isPackageDependencyUsed(bam, enabledTraits: []))
652708
XCTAssertFalse(try manifest.isPackageDependencyUsed(bam, enabledTraits: ["Trait3"]))
709+
710+
// Configuration of the manifest that includes a case where there exists a target
711+
// dependency that depends on the same package as another target dependency, but
712+
// is unguarded by traits; therefore, this package dependency should be considered used
713+
// in every scenario, regardless of the passed trait configuration.
714+
XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: nil))
715+
XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: []))
716+
XCTAssertTrue(try manifestWithBamDependencyAlwaysUsed.isPackageDependencyUsed(bam, enabledTraits: ["Trait3"]))
653717
}
654718
}
655719

0 commit comments

Comments
 (0)