Skip to content

Commit f8aee43

Browse files
committed
[NFC] Remove SupportedPlatforms, add PlatformVersionProvider
Depends on #7117, unblocks #7160. The `PackageModel` module has no business of defining supported platforms computation for resolved targets, products, and packages. This belongs to `PackageGraph`, but was previously leaking out into `PackageModel` by passing the `derivedXCTestPlatformProvider` closure around. That prevented us from converting marking `SupportedPlatforms` as `Hashable` and marking any of `Resolved*` types as value types and also `Hashable`. In the future, when we'll also need to make them `Sendable`, this could become problematic, passing so much state captured in a closure, mostly by accident.
1 parent a6760d9 commit f8aee43

File tree

13 files changed

+244
-149
lines changed

13 files changed

+244
-149
lines changed

Sources/Build/BuildDescription/ProductBuildDescription.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
284284
// When deploying to macOS prior to macOS 12, add an rpath to the
285285
// back-deployed concurrency libraries.
286286
if useStdlibRpath, self.buildParameters.targetTriple.isMacOSX {
287-
let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
287+
let macOSSupportedPlatform = self.package.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
288288
if macOSSupportedPlatform.version.major < 12 {
289289
let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib
290290
.parentDirectory

Sources/Build/BuildPlan/BuildPlan+Test.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ extension BuildPlan {
8787
target: discoveryTarget,
8888
dependencies: testProduct.targets.map { .target($0, conditions: []) },
8989
defaultLocalization: testProduct.defaultLocalization,
90-
platforms: testProduct.platforms
90+
supportedPlatforms: testProduct.supportedPlatforms,
91+
platformVersionProvider: testProduct.platformVersionProvider
9192
)
9293
let discoveryTargetBuildDescription = try SwiftTargetBuildDescription(
9394
package: package,
@@ -120,7 +121,8 @@ extension BuildPlan {
120121
target: entryPointTarget,
121122
dependencies: testProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryResolvedTarget, conditions: [])],
122123
defaultLocalization: testProduct.defaultLocalization,
123-
platforms: testProduct.platforms
124+
supportedPlatforms: testProduct.supportedPlatforms,
125+
platformVersionProvider: testProduct.platformVersionProvider
124126
)
125127
return try SwiftTargetBuildDescription(
126128
package: package,
@@ -149,7 +151,8 @@ extension BuildPlan {
149151
target: entryPointTarget,
150152
dependencies: entryPointResolvedTarget.dependencies + [.target(discoveryTargets.resolved, conditions: [])],
151153
defaultLocalization: testProduct.defaultLocalization,
152-
platforms: testProduct.platforms
154+
supportedPlatforms: testProduct.supportedPlatforms,
155+
platformVersionProvider: testProduct.platformVersionProvider
153156
)
154157
let entryPointTargetBuildDescription = try SwiftTargetBuildDescription(
155158
package: package,

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ extension BuildParameters {
133133
// Compute the triple string for Darwin platform using the platform version.
134134
if targetTriple.isDarwin() {
135135
let platform = buildEnvironment.platform
136-
let supportedPlatform = target.platforms.getDerived(for: platform, usingXCTest: target.type == .test)
136+
let supportedPlatform = target.getDerived(for: platform, usingXCTest: target.type == .test)
137137
args += [targetTriple.tripleString(forPlatformVersion: supportedPlatform.version.versionString)]
138138
} else {
139139
args += [targetTriple.tripleString]
@@ -409,8 +409,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
409409
) throws {
410410
// Supported platforms are defined at the package level.
411411
// This will need to become a bit complicated once we have target-level or product-level platform support.
412-
let productPlatform = product.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
413-
let targetPlatform = target.platforms.getDerived(for: .macOS, usingXCTest: target.type == .test)
412+
let productPlatform = product.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
413+
let targetPlatform = target.getDerived(for: .macOS, usingXCTest: target.type == .test)
414414

415415
// Check if the version requirement is satisfied.
416416
//

Sources/PackageGraph/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_library(PackageGraph
3131
Resolution/DependencyResolverBinding.swift
3232
Resolution/DependencyResolverDelegate.swift
3333
Resolution/DependencyResolverError.swift
34+
Resolution/PlatformVersionProvider.swift
3435
Resolution/ResolvedPackage.swift
3536
Resolution/ResolvedProduct.swift
3637
Resolution/ResolvedTarget.swift

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,9 @@ extension PackageGraph {
153153
rootManifests: root.manifests,
154154
unsafeAllowedPackages: unsafeAllowedPackages,
155155
platformRegistry: customPlatformsRegistry ?? .default,
156-
derivedXCTestPlatformProvider: { declared in
157-
if let customXCTestMinimumDeploymentTargets {
158-
return customXCTestMinimumDeploymentTargets[declared]
159-
} else {
160-
return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared)
161-
}
162-
},
156+
platformVersionProvider: .init(
157+
implementation: .customXCTestMinimumDeploymentTargets(customXCTestMinimumDeploymentTargets)
158+
),
163159
fileSystem: fileSystem,
164160
observabilityScope: observabilityScope
165161
)
@@ -240,7 +236,7 @@ private func createResolvedPackages(
240236
rootManifests: [PackageIdentity: Manifest],
241237
unsafeAllowedPackages: Set<PackageReference>,
242238
platformRegistry: PlatformRegistry,
243-
derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?,
239+
platformVersionProvider: PlatformVersionProvider,
244240
fileSystem: FileSystem,
245241
observabilityScope: ObservabilityScope
246242
) throws -> [ResolvedPackage] {
@@ -257,7 +253,8 @@ private func createResolvedPackages(
257253
package,
258254
productFilter: node.productFilter,
259255
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts,
260-
allowedToOverride: allowedToOverride
256+
allowedToOverride: allowedToOverride,
257+
platformVersionProvider: platformVersionProvider
261258
)
262259
}
263260

@@ -361,14 +358,19 @@ private func createResolvedPackages(
361358

362359
packageBuilder.defaultLocalization = package.manifest.defaultLocalization
363360

364-
packageBuilder.platforms = computePlatforms(
361+
packageBuilder.supportedPlatforms = computePlatforms(
365362
package: package,
366-
platformRegistry: platformRegistry,
367-
derivedXCTestPlatformProvider: derivedXCTestPlatformProvider
363+
platformRegistry: platformRegistry
368364
)
369365

370366
// Create target builders for each target in the package.
371-
let targetBuilders = package.targets.map{ ResolvedTargetBuilder(target: $0, observabilityScope: packageObservabilityScope) }
367+
let targetBuilders = package.targets.map {
368+
ResolvedTargetBuilder(
369+
target: $0,
370+
observabilityScope: packageObservabilityScope,
371+
platformVersionProvider: platformVersionProvider
372+
)
373+
}
372374
packageBuilder.targets = targetBuilders
373375

374376
// Establish dependencies between the targets. A target can only depend on another target present in the same package.
@@ -386,7 +388,7 @@ private func createResolvedPackages(
386388
}
387389
}
388390
targetBuilder.defaultLocalization = packageBuilder.defaultLocalization
389-
targetBuilder.platforms = packageBuilder.platforms
391+
targetBuilder.supportedPlatforms = packageBuilder.supportedPlatforms
390392
}
391393

392394
// Create product builders for each product in the package. A product can only contain a target present in the same package.
@@ -743,10 +745,8 @@ private class DuplicateProductsChecker {
743745

744746
private func computePlatforms(
745747
package: Package,
746-
platformRegistry: PlatformRegistry,
747-
derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?
748-
) -> SupportedPlatforms {
749-
748+
platformRegistry: PlatformRegistry
749+
) -> [SupportedPlatform] {
750750
// the supported platforms as declared in the manifest
751751
let declaredPlatforms: [SupportedPlatform] = package.manifest.platforms.map { platform in
752752
let declaredPlatform = platformRegistry.platformByName[platform.platformName]
@@ -758,10 +758,7 @@ private func computePlatforms(
758758
)
759759
}
760760

761-
return SupportedPlatforms(
762-
declared: declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name }),
763-
derivedXCTestPlatformProvider: derivedXCTestPlatformProvider
764-
)
761+
return declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name })
765762
}
766763

767764
// Track and override module aliases specified for targets in a package graph
@@ -888,18 +885,22 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
888885
var defaultLocalization: String? = nil
889886

890887
/// The platforms supported by this package.
891-
var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none)
888+
var supportedPlatforms: [SupportedPlatform] = []
889+
890+
let platformVersionProvider: PlatformVersionProvider
892891

893892
init(
894893
target: Target,
895-
observabilityScope: ObservabilityScope
894+
observabilityScope: ObservabilityScope,
895+
platformVersionProvider: PlatformVersionProvider
896896
) {
897897
self.target = target
898898
self.diagnosticsEmitter = observabilityScope.makeDiagnosticsEmitter() {
899899
var metadata = ObservabilityMetadata()
900900
metadata.targetName = target.name
901901
return metadata
902902
}
903+
self.platformVersionProvider = platformVersionProvider
903904
}
904905

905906
func diagnoseInvalidUseOfUnsafeFlags(_ product: ResolvedProduct) throws {
@@ -934,7 +935,8 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
934935
target: self.target,
935936
dependencies: dependencies,
936937
defaultLocalization: self.defaultLocalization,
937-
platforms: self.platforms
938+
supportedPlatforms: self.supportedPlatforms,
939+
platformVersionProvider: self.platformVersionProvider
938940
)
939941
}
940942
}
@@ -983,27 +985,37 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
983985
var defaultLocalization: String? = nil
984986

985987
/// The platforms supported by this package.
986-
var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none)
988+
var supportedPlatforms: [SupportedPlatform] = []
987989

988990
/// If the given package's source is a registry release, this provides additional metadata and signature information.
989991
var registryMetadata: RegistryReleaseMetadata?
990992

991-
init(_ package: Package, productFilter: ProductFilter, isAllowedToVendUnsafeProducts: Bool, allowedToOverride: Bool) {
993+
let platformVersionProvider: PlatformVersionProvider
994+
995+
init(
996+
_ package: Package,
997+
productFilter: ProductFilter,
998+
isAllowedToVendUnsafeProducts: Bool,
999+
allowedToOverride: Bool,
1000+
platformVersionProvider: PlatformVersionProvider
1001+
) {
9921002
self.package = package
9931003
self.productFilter = productFilter
9941004
self.isAllowedToVendUnsafeProducts = isAllowedToVendUnsafeProducts
9951005
self.allowedToOverride = allowedToOverride
1006+
self.platformVersionProvider = platformVersionProvider
9961007
}
9971008

9981009
override func constructImpl() throws -> ResolvedPackage {
9991010
return ResolvedPackage(
10001011
package: self.package,
10011012
defaultLocalization: self.defaultLocalization,
1002-
platforms: self.platforms,
1013+
supportedPlatforms: self.supportedPlatforms,
10031014
dependencies: try self.dependencies.map{ try $0.construct() },
10041015
targets: try self.targets.map{ try $0.construct() },
10051016
products: try self.products.map{ try $0.construct() },
1006-
registryMetadata: self.registryMetadata
1017+
registryMetadata: self.registryMetadata,
1018+
platformVersionProvider: self.platformVersionProvider
10071019
)
10081020
}
10091021
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct PackageModel.MinimumDeploymentTarget
14+
import struct PackageModel.Platform
15+
import struct PackageModel.PlatformVersion
16+
import struct PackageModel.SupportedPlatform
17+
18+
/// Merging two sets of supported platforms, preferring the max constraint
19+
func merge(into partial: inout [SupportedPlatform], platforms: [SupportedPlatform]) {
20+
for platformSupport in platforms {
21+
if let existing = partial.firstIndex(where: { $0.platform == platformSupport.platform }) {
22+
if partial[existing].version < platformSupport.version {
23+
partial.remove(at: existing)
24+
partial.append(platformSupport)
25+
}
26+
} else {
27+
partial.append(platformSupport)
28+
}
29+
}
30+
}
31+
32+
public struct PlatformVersionProvider: Hashable {
33+
public enum Implementation: Hashable {
34+
case mergingFromTargets([ResolvedTarget])
35+
case customXCTestMinimumDeploymentTargets([PackageModel.Platform: PlatformVersion]?)
36+
case empty
37+
}
38+
39+
private let implementation: Implementation
40+
41+
public init(implementation: Implementation) {
42+
self.implementation = implementation
43+
}
44+
45+
func derivedXCTestPlatformProvider(_ declared: PackageModel.Platform) -> PlatformVersion? {
46+
switch implementation {
47+
case .mergingFromTargets(let targets):
48+
let platforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in
49+
merge(into: &partial, platforms: [item.getDerived(for: declared, usingXCTest: item.type == .test)])
50+
}
51+
return platforms.first!.version
52+
53+
case .customXCTestMinimumDeploymentTargets(let customXCTestMinimumDeploymentTargets):
54+
if let customXCTestMinimumDeploymentTargets {
55+
return customXCTestMinimumDeploymentTargets[declared]
56+
} else {
57+
return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared)
58+
}
59+
60+
case .empty:
61+
return nil
62+
}
63+
}
64+
65+
/// Returns the supported platform instance for the given platform.
66+
func getDerived(declared: [SupportedPlatform], for platform: Platform, usingXCTest: Bool) -> SupportedPlatform {
67+
// derived platform based on known minimum deployment target logic
68+
if let declaredPlatform = declared.first(where: { $0.platform == platform }) {
69+
var version = declaredPlatform.version
70+
71+
if usingXCTest, let xcTestMinimumDeploymentTarget = self.derivedXCTestPlatformProvider(platform), version < xcTestMinimumDeploymentTarget {
72+
version = xcTestMinimumDeploymentTarget
73+
}
74+
75+
// If the declared version is smaller than the oldest supported one, we raise the derived version to that.
76+
if version < platform.oldestSupportedVersion {
77+
version = platform.oldestSupportedVersion
78+
}
79+
80+
return SupportedPlatform(
81+
platform: declaredPlatform.platform,
82+
version: version,
83+
options: declaredPlatform.options
84+
)
85+
} else {
86+
let minimumSupportedVersion: PlatformVersion
87+
if usingXCTest, let xcTestMinimumDeploymentTarget = self.derivedXCTestPlatformProvider(platform), xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion {
88+
minimumSupportedVersion = xcTestMinimumDeploymentTarget
89+
} else {
90+
minimumSupportedVersion = platform.oldestSupportedVersion
91+
}
92+
93+
let oldestSupportedVersion: PlatformVersion
94+
if platform == .macCatalyst {
95+
let iOS = self.getDerived(declared: declared, for: .iOS, usingXCTest: usingXCTest)
96+
// If there was no deployment target specified for Mac Catalyst, fall back to the iOS deployment target.
97+
oldestSupportedVersion = max(minimumSupportedVersion, iOS.version)
98+
} else {
99+
oldestSupportedVersion = minimumSupportedVersion
100+
}
101+
102+
return SupportedPlatform(
103+
platform: platform,
104+
version: oldestSupportedVersion,
105+
options: []
106+
)
107+
}
108+
}
109+
}

Sources/PackageGraph/Resolution/ResolvedPackage.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,39 @@ public final class ResolvedPackage {
4646
public let defaultLocalization: String?
4747

4848
/// The list of platforms that are supported by this target.
49-
public let platforms: SupportedPlatforms
49+
public let supportedPlatforms: [SupportedPlatform]
5050

5151
/// If the given package's source is a registry release, this provides additional metadata and signature information.
5252
public let registryMetadata: RegistryReleaseMetadata?
5353

54+
private let platformVersionProvider: PlatformVersionProvider
55+
5456
public init(
5557
package: Package,
5658
defaultLocalization: String?,
57-
platforms: SupportedPlatforms,
59+
supportedPlatforms: [SupportedPlatform],
5860
dependencies: [ResolvedPackage],
5961
targets: [ResolvedTarget],
6062
products: [ResolvedProduct],
61-
registryMetadata: RegistryReleaseMetadata?
63+
registryMetadata: RegistryReleaseMetadata?,
64+
platformVersionProvider: PlatformVersionProvider
6265
) {
6366
self.underlyingPackage = package
6467
self.defaultLocalization = defaultLocalization
65-
self.platforms = platforms
68+
self.supportedPlatforms = supportedPlatforms
6669
self.dependencies = dependencies
6770
self.targets = targets
6871
self.products = products
6972
self.registryMetadata = registryMetadata
73+
self.platformVersionProvider = platformVersionProvider
74+
}
75+
76+
public func getDerived(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform {
77+
self.platformVersionProvider.getDerived(
78+
declared: self.supportedPlatforms,
79+
for: platform,
80+
usingXCTest: usingXCTest
81+
)
7082
}
7183
}
7284

0 commit comments

Comments
 (0)