Skip to content

Commit 7430175

Browse files
committed
Filter out duplicate package manifest conditions
Multiple places in our codebase pass around and store `[any PackageConditionProtocol]` arrays of existentials with `FIXME` notes that those should be sets and not arrays. Adding `Hashable` requirement to `PackageConditionProtocol` doesn't fix the issue, since existentials of this protocol still wouldn't conform to `Hashable`. A simple alternative is to create `enum PackageCondition` with cases for each condition type. We only have two of those types and they're always declared in the same package. There's no use case for externally defined types for this protocol. That allows us to convert uses of `[any PackageConditionProtocol]` to `Set<PackageCondition>`. Existing protocol kept around until clients of SwiftPM can migrate to the new `PackageCondition` enum.
1 parent 8387798 commit 7430175

File tree

10 files changed

+105
-33
lines changed

10 files changed

+105
-33
lines changed

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,10 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
869869
enum Dependency {
870870

871871
/// Dependency to another target, with conditions.
872-
case target(_ target: ResolvedTargetBuilder, conditions: [PackageConditionProtocol])
872+
case target(_ target: ResolvedTargetBuilder, conditions: Set<PackageCondition>)
873873

874874
/// Dependency to a product, with conditions.
875-
case product(_ product: ResolvedProductBuilder, conditions: [PackageConditionProtocol])
875+
case product(_ product: ResolvedProductBuilder, conditions: Set<PackageCondition>)
876876
}
877877

878878
/// The target reference.
@@ -918,7 +918,10 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
918918
try self.target.validateDependency(target: targetBuilder.target)
919919
return .target(try targetBuilder.construct(), conditions: conditions)
920920
case .product(let productBuilder, let conditions):
921-
try self.target.validateDependency(product: productBuilder.product, productPackage: productBuilder.packageBuilder.package.identity)
921+
try self.target.validateDependency(
922+
product: productBuilder.product,
923+
productPackage: productBuilder.packageBuilder.package.identity
924+
)
922925
let product = try productBuilder.construct()
923926
if !productBuilder.packageBuilder.isAllowedToVendUnsafeProducts {
924927
try self.diagnoseInvalidUseOfUnsafeFlags(product)

Sources/PackageGraph/Resolution/ResolvedTarget.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ public final class ResolvedTarget {
1919
/// Represents dependency of a resolved target.
2020
public enum Dependency {
2121
/// Direct dependency of the target. This target is in the same package and should be statically linked.
22-
case target(_ target: ResolvedTarget, conditions: [PackageConditionProtocol])
22+
case target(_ target: ResolvedTarget, conditions: Set<PackageCondition>)
2323

2424
/// The target depends on this product.
25-
case product(_ product: ResolvedProduct, conditions: [PackageConditionProtocol])
25+
case product(_ product: ResolvedProduct, conditions: Set<PackageCondition>)
2626

2727
public var target: ResolvedTarget? {
2828
switch self {
@@ -38,7 +38,7 @@ public final class ResolvedTarget {
3838
}
3939
}
4040

41-
public var conditions: [PackageConditionProtocol] {
41+
public var conditions: Set<PackageCondition> {
4242
switch self {
4343
case .target(_, let conditions): return conditions
4444
case .product(_, let conditions): return conditions
@@ -141,7 +141,7 @@ public final class ResolvedTarget {
141141
/// The list of platforms that are supported by this target.
142142
public let platforms: SupportedPlatforms
143143

144-
/// Create a target instance.
144+
/// Create a resolved target instance.
145145
public init(
146146
target: Target,
147147
dependencies: [Dependency],

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ public final class PackageBuilder {
697697

698698
// The created targets mapped to their name.
699699
var targets = [String: Target]()
700-
// If a direcotry is empty, we don't create a target object for them.
700+
// If a directory is empty, we don't create a target object for them.
701701
var emptyModules = Set<String>()
702702

703703
// Start iterating the potential targets.
@@ -710,7 +710,7 @@ public final class PackageBuilder {
710710

711711
// Get the dependencies of this target.
712712
let dependencies: [Target.Dependency] = try manifestTarget.map {
713-
try $0.dependencies.compactMap { dependency in
713+
try $0.dependencies.compactMap { dependency -> Target.Dependency? in
714714
switch dependency {
715715
case .target(let name, let condition):
716716
// We don't create an object for targets which have no sources.
@@ -1136,7 +1136,7 @@ public final class PackageBuilder {
11361136
// Create an assignment for this setting.
11371137
var assignment = BuildSettings.Assignment()
11381138
assignment.values = values
1139-
assignment.conditions = self.buildConditions(from: setting.condition)
1139+
assignment.uniqueConditions = self.buildConditions(from: setting.condition)
11401140

11411141
// Finally, add the assignment to the assignment table.
11421142
table.add(assignment, for: decl)
@@ -1145,12 +1145,12 @@ public final class PackageBuilder {
11451145
return table
11461146
}
11471147

1148-
func buildConditions(from condition: PackageConditionDescription?) -> [PackageConditionProtocol] {
1149-
var conditions: [PackageConditionProtocol] = []
1148+
func buildConditions(from condition: PackageConditionDescription?) -> Set<PackageCondition> {
1149+
var conditions: Set<PackageCondition> = []
11501150

11511151
if let config = condition?.config.flatMap({ BuildConfiguration(rawValue: $0) }) {
11521152
let condition = ConfigurationCondition(configuration: config)
1153-
conditions.append(condition)
1153+
conditions.insert(.configuration(condition))
11541154
}
11551155

11561156
if let platforms = condition?.platformNames.map({
@@ -1163,7 +1163,7 @@ public final class PackageBuilder {
11631163
!platforms.isEmpty
11641164
{
11651165
let condition = PlatformsCondition(platforms: platforms)
1166-
conditions.append(condition)
1166+
conditions.insert(.platforms(condition))
11671167
}
11681168

11691169
return conditions

Sources/PackageModel/BuildSettings.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,25 @@ public enum BuildSettings {
4343
/// The assignment value.
4444
public var values: [String]
4545

46-
// FIXME: This should be a set but we need Equatable existential (or AnyEquatable) for that.
4746
/// The condition associated with this assignment.
47+
@available(*, deprecated, renamed: "uniqueConditions")
4848
public var conditions: [PackageConditionProtocol] {
4949
get {
5050
return _conditions.map{ $0.condition }
5151
}
5252
set {
5353
_conditions = newValue.map{ PackageConditionWrapper($0) }
5454
}
55+
}
56+
57+
/// A set of unique conditions associated with this assignment.
58+
public var uniqueConditions: Set<PackageCondition> {
59+
get {
60+
return Set(_conditions.map(\.underlying))
61+
}
62+
set {
63+
_conditions = newValue.map { PackageConditionWrapper($0) }
64+
}
5565
}
5666

5767
private var _conditions: [PackageConditionWrapper]
@@ -64,16 +74,15 @@ public enum BuildSettings {
6474

6575
/// Build setting assignment table which maps a build setting to a list of assignments.
6676
public struct AssignmentTable: Codable {
67-
public private(set) var assignments: [Declaration: [Assignment]]
77+
public private(set) var assignments: [Declaration: Set<Assignment>]
6878

6979
public init() {
7080
assignments = [:]
7181
}
7282

7383
/// Add the given assignment to the table.
7484
mutating public func add(_ assignment: Assignment, for decl: Declaration) {
75-
// FIXME: We should check for duplicate assignments.
76-
assignments[decl, default: []].append(assignment)
85+
assignments[decl, default: []].insert(assignment)
7786
}
7887
}
7988

@@ -102,7 +111,7 @@ public enum BuildSettings {
102111
// Add values from each assignment if it satisfies the build environment.
103112
let values = assignments
104113
.lazy
105-
.filter { $0.conditions.allSatisfy { $0.satisfies(self.environment) } }
114+
.filter { $0.uniqueConditions.allSatisfy { $0.satisfies(self.environment) } }
106115
.flatMap { $0.values }
107116

108117
return Array(values)

Sources/PackageModel/Manifest/PackageConditionDescription.swift

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -27,11 +27,12 @@ public protocol PackageConditionProtocol: Codable {
2727
func satisfies(_ environment: BuildEnvironment) -> Bool
2828
}
2929

30-
/// Wrapper for package condition so it can be conformed to Codable.
30+
/// Wrapper for package condition so it can conform to Codable.
3131
struct PackageConditionWrapper: Codable, Equatable, Hashable {
3232
var platform: PlatformsCondition?
3333
var config: ConfigurationCondition?
3434

35+
@available(*, deprecated, renamed: "underlying")
3536
var condition: PackageConditionProtocol {
3637
if let platform {
3738
return platform
@@ -42,6 +43,17 @@ struct PackageConditionWrapper: Codable, Equatable, Hashable {
4243
}
4344
}
4445

46+
var underlying: PackageCondition {
47+
if let platform {
48+
return .platforms(platform)
49+
} else if let config {
50+
return .configuration(config)
51+
} else {
52+
fatalError("unreachable")
53+
}
54+
}
55+
56+
@available(*, deprecated, message: "Use .init(_ condition: PackageCondition) instead")
4557
init(_ condition: PackageConditionProtocol) {
4658
switch condition {
4759
case let platform as PlatformsCondition:
@@ -52,6 +64,53 @@ struct PackageConditionWrapper: Codable, Equatable, Hashable {
5264
fatalError("unknown condition \(condition)")
5365
}
5466
}
67+
68+
init(_ condition: PackageCondition) {
69+
switch condition {
70+
case let .platforms(platforms):
71+
self.platform = platforms
72+
case let .configuration(configuration):
73+
self.config = configuration
74+
}
75+
}
76+
}
77+
78+
public enum PackageCondition: Hashable {
79+
case platforms(PlatformsCondition)
80+
case configuration(ConfigurationCondition)
81+
82+
public func satisfies(_ environment: BuildEnvironment) -> Bool {
83+
switch self {
84+
case .configuration(let configuration):
85+
return configuration.satisfies(environment)
86+
case .platforms(let platforms):
87+
return platforms.satisfies(environment)
88+
}
89+
}
90+
91+
public var platformsCondition: PlatformsCondition? {
92+
guard case let .platforms(platformsCondition) = self else {
93+
return nil
94+
}
95+
96+
return platformsCondition
97+
}
98+
99+
public var configurationCondition: ConfigurationCondition? {
100+
guard case let .configuration(configurationCondition) = self else {
101+
return nil
102+
}
103+
104+
return configurationCondition
105+
}
106+
107+
public init(platforms: [Platform]) {
108+
self = .platforms(.init(platforms: platforms))
109+
}
110+
111+
public init(configuration: BuildConfiguration) {
112+
self = .configuration(.init(configuration: configuration))
113+
}
55114
}
56115

57116
/// Platforms condition implies that an assignment is valid on these platforms.

Sources/PackageModel/Target/Target.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public class Target: PolymorphicCodableProtocol {
7979
/// A target dependency to a target or product.
8080
public enum Dependency {
8181
/// A dependency referencing another target, with conditions.
82-
case target(_ target: Target, conditions: [PackageConditionProtocol])
82+
case target(_ target: Target, conditions: Set<PackageCondition>)
8383

8484
/// A dependency referencing a product, with conditions.
85-
case product(_ product: ProductReference, conditions: [PackageConditionProtocol])
85+
case product(_ product: ProductReference, conditions: Set<PackageCondition>)
8686

8787
/// The target if the dependency is a target dependency.
8888
public var target: Target? {
@@ -103,7 +103,7 @@ public class Target: PolymorphicCodableProtocol {
103103
}
104104

105105
/// The dependency conditions.
106-
public var conditions: [PackageConditionProtocol] {
106+
public var conditions: Set<PackageCondition> {
107107
switch self {
108108
case .target(_, let conditions):
109109
return conditions

Sources/SPMBuildCore/Plugins/PluginInvocation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ public extension PluginTarget {
584584
}
585585

586586
fileprivate extension Target.Dependency {
587-
var conditions: [PackageConditionProtocol] {
587+
var conditions: Set<PackageCondition> {
588588
switch self {
589589
case .target(_, let conditions): return conditions
590590
case .product(_, let conditions): return conditions

Sources/XCBuildSupport/PIF.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,8 @@ public enum PIF {
10021002
}
10031003

10041004
public var conditions: [String] {
1005-
let filters = [PlatformsCondition(platforms: [packageModelPlatform])].toPlatformFilters().map { (filter: PIF.PlatformFilter) -> String in
1005+
let filters = Set([.init(platforms: [packageModelPlatform])])
1006+
.toPlatformFilters().map { (filter: PIF.PlatformFilter) -> String in
10061007
if filter.environment.isEmpty {
10071008
return filter.platform
10081009
} else {

Sources/XCBuildSupport/PIFBuilder.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
781781
private func addDependency(
782782
to target: ResolvedTarget,
783783
in pifTarget: PIFTargetBuilder,
784-
conditions: [PackageConditionProtocol],
784+
conditions: Set<PackageCondition>,
785785
linkProduct: Bool
786786
) {
787787
// Only add the binary target as a library when we want to link against the product.
@@ -803,7 +803,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
803803
private func addDependency(
804804
to product: ResolvedProduct,
805805
in pifTarget: PIFTargetBuilder,
806-
conditions: [PackageConditionProtocol],
806+
conditions: Set<PackageCondition>,
807807
linkProduct: Bool
808808
) {
809809
pifTarget.addDependency(
@@ -1498,15 +1498,15 @@ private extension BuildSettings.AssignmentTable {
14981498

14991499
private extension BuildSettings.Assignment {
15001500
var configurations: [BuildConfiguration] {
1501-
if let configurationCondition = conditions.lazy.compactMap({ $0 as? ConfigurationCondition }).first {
1501+
if let configurationCondition = uniqueConditions.lazy.compactMap(\.configurationCondition).first {
15021502
return [configurationCondition.configuration]
15031503
} else {
15041504
return BuildConfiguration.allCases
15051505
}
15061506
}
15071507

15081508
var pifPlatforms: [PIF.BuildSettings.Platform]? {
1509-
if let platformsCondition = conditions.lazy.compactMap({ $0 as? PlatformsCondition }).first {
1509+
if let platformsCondition = uniqueConditions.lazy.compactMap(\.platformsCondition).first {
15101510
return platformsCondition.platforms.compactMap { PIF.BuildSettings.Platform(rawValue: $0.name) }
15111511
} else {
15121512
return nil
@@ -1537,10 +1537,10 @@ public struct DelayedImmutable<Value> {
15371537
}
15381538
}
15391539

1540-
extension Array where Element == PackageConditionProtocol {
1540+
extension Set<PackageCondition> {
15411541
func toPlatformFilters() -> [PIF.PlatformFilter] {
15421542
var result: [PIF.PlatformFilter] = []
1543-
let platformConditions = self.compactMap{ $0 as? PlatformsCondition }.flatMap{ $0.platforms }
1543+
let platformConditions = self.compactMap(\.platformsCondition).flatMap { $0.platforms }
15441544

15451545
for condition in platformConditions {
15461546
switch condition {

Tests/PackageLoadingTests/PackageBuilderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2990,7 +2990,7 @@ class PackageBuilderTests: XCTestCase {
29902990

29912991
var assignment = BuildSettings.Assignment()
29922992
assignment.values = ["YOLO"]
2993-
assignment.conditions = [PlatformsCondition(platforms: [PackageModel.Platform.custom(name: "bestOS", oldestSupportedVersion: .unknown)])]
2993+
assignment.uniqueConditions = Set([.init(platforms: [PackageModel.Platform.custom(name: "bestOS", oldestSupportedVersion: .unknown)])])
29942994

29952995
var settings = BuildSettings.AssignmentTable()
29962996
settings.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS)

0 commit comments

Comments
 (0)