Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NFC] Remove SupportedPlatforms, add PlatformVersionProvider #7161

Merged
merged 15 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
// When deploying to macOS prior to macOS 12, add an rpath to the
// back-deployed concurrency libraries.
if useStdlibRpath, triple.isMacOSX {
let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
let macOSSupportedPlatform = self.package.getSupportedPlatform(for: .macOS, usingXCTest: product.isLinkingXCTest)

if macOSSupportedPlatform.version.major < 12 {
let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib
.parentDirectory
Expand Down
9 changes: 6 additions & 3 deletions Sources/Build/BuildPlan/BuildPlan+Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ extension BuildPlan {
target: discoveryTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) },
defaultLocalization: testProduct.defaultLocalization,
platforms: testProduct.platforms
supportedPlatforms: testProduct.supportedPlatforms,
platformVersionProvider: testProduct.platformVersionProvider
)
let discoveryTargetBuildDescription = try SwiftTargetBuildDescription(
package: package,
Expand Down Expand Up @@ -124,7 +125,8 @@ extension BuildPlan {
target: entryPointTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) } + resolvedTargetDependencies,
defaultLocalization: testProduct.defaultLocalization,
platforms: testProduct.platforms
supportedPlatforms: testProduct.supportedPlatforms,
platformVersionProvider: testProduct.platformVersionProvider
)
return try SwiftTargetBuildDescription(
package: package,
Expand Down Expand Up @@ -166,7 +168,8 @@ extension BuildPlan {
target: entryPointTarget,
dependencies: entryPointResolvedTarget.dependencies + resolvedTargetDependencies,
defaultLocalization: testProduct.defaultLocalization,
platforms: testProduct.platforms
supportedPlatforms: testProduct.supportedPlatforms,
platformVersionProvider: testProduct.platformVersionProvider
)
let entryPointTargetBuildDescription = try SwiftTargetBuildDescription(
package: package,
Expand Down
6 changes: 3 additions & 3 deletions Sources/Build/BuildPlan/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ extension BuildParameters {
// Compute the triple string for Darwin platform using the platform version.
if self.triple.isDarwin() {
let platform = buildEnvironment.platform
let supportedPlatform = target.platforms.getDerived(for: platform, usingXCTest: target.type == .test)
let supportedPlatform = target.getSupportedPlatform(for: platform, usingXCTest: target.type == .test)
args += [self.triple.tripleString(forPlatformVersion: supportedPlatform.version.versionString)]
} else {
args += [self.triple.tripleString]
Expand Down Expand Up @@ -455,8 +455,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
) throws {
// Supported platforms are defined at the package level.
// This will need to become a bit complicated once we have target-level or product-level platform support.
let productPlatform = product.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
let targetPlatform = target.platforms.getDerived(for: .macOS, usingXCTest: target.type == .test)
let productPlatform = product.getSupportedPlatform(for: .macOS, usingXCTest: product.isLinkingXCTest)
let targetPlatform = target.getSupportedPlatform(for: .macOS, usingXCTest: target.type == .test)

// Check if the version requirement is satisfied.
//
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageGraph/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_library(PackageGraph
Resolution/DependencyResolverBinding.swift
Resolution/DependencyResolverDelegate.swift
Resolution/DependencyResolverError.swift
Resolution/PlatformVersionProvider.swift
Resolution/ResolvedPackage.swift
Resolution/ResolvedProduct.swift
Resolution/ResolvedTarget.swift
Expand Down
75 changes: 46 additions & 29 deletions Sources/PackageGraph/PackageGraph+Loading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ extension PackageGraph {
}
}

let platformVersionProvider: PlatformVersionProvider
if let customXCTestMinimumDeploymentTargets {
platformVersionProvider = .init(implementation: .customXCTestMinimumDeploymentTargets(customXCTestMinimumDeploymentTargets))
} else {
platformVersionProvider = .init(implementation: .minimumDeploymentTargetDefault)
}

// Resolve dependencies and create resolved packages.
let resolvedPackages = try createResolvedPackages(
nodes: allNodes,
Expand All @@ -153,13 +160,7 @@ extension PackageGraph {
rootManifests: root.manifests,
unsafeAllowedPackages: unsafeAllowedPackages,
platformRegistry: customPlatformsRegistry ?? .default,
derivedXCTestPlatformProvider: { declared in
if let customXCTestMinimumDeploymentTargets {
return customXCTestMinimumDeploymentTargets[declared]
} else {
return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared)
}
},
platformVersionProvider: platformVersionProvider,
fileSystem: fileSystem,
observabilityScope: observabilityScope
)
Expand Down Expand Up @@ -240,7 +241,7 @@ private func createResolvedPackages(
rootManifests: [PackageIdentity: Manifest],
unsafeAllowedPackages: Set<PackageReference>,
platformRegistry: PlatformRegistry,
derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?,
platformVersionProvider: PlatformVersionProvider,
fileSystem: FileSystem,
observabilityScope: ObservabilityScope
) throws -> [ResolvedPackage] {
Expand All @@ -257,7 +258,8 @@ private func createResolvedPackages(
package,
productFilter: node.productFilter,
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts,
allowedToOverride: allowedToOverride
allowedToOverride: allowedToOverride,
platformVersionProvider: platformVersionProvider
)
}

Expand Down Expand Up @@ -361,14 +363,19 @@ private func createResolvedPackages(

packageBuilder.defaultLocalization = package.manifest.defaultLocalization

packageBuilder.platforms = computePlatforms(
packageBuilder.supportedPlatforms = computePlatforms(
package: package,
platformRegistry: platformRegistry,
derivedXCTestPlatformProvider: derivedXCTestPlatformProvider
platformRegistry: platformRegistry
)

// Create target builders for each target in the package.
let targetBuilders = package.targets.map{ ResolvedTargetBuilder(target: $0, observabilityScope: packageObservabilityScope) }
let targetBuilders = package.targets.map {
ResolvedTargetBuilder(
target: $0,
observabilityScope: packageObservabilityScope,
platformVersionProvider: platformVersionProvider
)
}
packageBuilder.targets = targetBuilders

// Establish dependencies between the targets. A target can only depend on another target present in the same package.
Expand All @@ -386,7 +393,7 @@ private func createResolvedPackages(
}
}
targetBuilder.defaultLocalization = packageBuilder.defaultLocalization
targetBuilder.platforms = packageBuilder.platforms
targetBuilder.supportedPlatforms = packageBuilder.supportedPlatforms
}

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

private func computePlatforms(
package: Package,
platformRegistry: PlatformRegistry,
derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?
) -> SupportedPlatforms {

platformRegistry: PlatformRegistry
) -> [SupportedPlatform] {
// the supported platforms as declared in the manifest
let declaredPlatforms: [SupportedPlatform] = package.manifest.platforms.map { platform in
let declaredPlatform = platformRegistry.platformByName[platform.platformName]
Expand All @@ -758,10 +763,7 @@ private func computePlatforms(
)
}

return SupportedPlatforms(
declared: declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name }),
derivedXCTestPlatformProvider: derivedXCTestPlatformProvider
)
return declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name })
}

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

/// The platforms supported by this package.
var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none)
var supportedPlatforms: [SupportedPlatform] = []

let platformVersionProvider: PlatformVersionProvider

init(
target: Target,
observabilityScope: ObservabilityScope
observabilityScope: ObservabilityScope,
platformVersionProvider: PlatformVersionProvider
) {
self.target = target
self.diagnosticsEmitter = observabilityScope.makeDiagnosticsEmitter() {
var metadata = ObservabilityMetadata()
metadata.targetName = target.name
return metadata
}
self.platformVersionProvider = platformVersionProvider
}

func diagnoseInvalidUseOfUnsafeFlags(_ product: ResolvedProduct) throws {
Expand Down Expand Up @@ -934,7 +940,8 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
target: self.target,
dependencies: dependencies,
defaultLocalization: self.defaultLocalization,
platforms: self.platforms
supportedPlatforms: self.supportedPlatforms,
platformVersionProvider: self.platformVersionProvider
)
}
}
Expand Down Expand Up @@ -983,27 +990,37 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
var defaultLocalization: String? = nil

/// The platforms supported by this package.
var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none)
var supportedPlatforms: [SupportedPlatform] = []

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

init(_ package: Package, productFilter: ProductFilter, isAllowedToVendUnsafeProducts: Bool, allowedToOverride: Bool) {
let platformVersionProvider: PlatformVersionProvider

init(
_ package: Package,
productFilter: ProductFilter,
isAllowedToVendUnsafeProducts: Bool,
allowedToOverride: Bool,
platformVersionProvider: PlatformVersionProvider
) {
self.package = package
self.productFilter = productFilter
self.isAllowedToVendUnsafeProducts = isAllowedToVendUnsafeProducts
self.allowedToOverride = allowedToOverride
self.platformVersionProvider = platformVersionProvider
}

override func constructImpl() throws -> ResolvedPackage {
return ResolvedPackage(
package: self.package,
defaultLocalization: self.defaultLocalization,
platforms: self.platforms,
supportedPlatforms: self.supportedPlatforms,
dependencies: try self.dependencies.map{ try $0.construct() },
targets: try self.targets.map{ try $0.construct() },
products: try self.products.map{ try $0.construct() },
registryMetadata: self.registryMetadata
registryMetadata: self.registryMetadata,
platformVersionProvider: self.platformVersionProvider
)
}
}
Expand Down
114 changes: 114 additions & 0 deletions Sources/PackageGraph/Resolution/PlatformVersionProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import struct PackageModel.MinimumDeploymentTarget
import struct PackageModel.Platform
import struct PackageModel.PlatformVersion
import struct PackageModel.SupportedPlatform

/// Merging two sets of supported platforms, preferring the max constraint
func merge(into partial: inout [SupportedPlatform], platforms: [SupportedPlatform]) {
for platformSupport in platforms {
if let existing = partial.firstIndex(where: { $0.platform == platformSupport.platform }) {
if partial[existing].version < platformSupport.version {
partial.remove(at: existing)
partial.append(platformSupport)
}
} else {
partial.append(platformSupport)
}
}
}

public struct PlatformVersionProvider: Hashable {
public enum Implementation: Hashable {
case mergingFromTargets([ResolvedTarget])
case customXCTestMinimumDeploymentTargets([PackageModel.Platform: PlatformVersion])
case minimumDeploymentTargetDefault
}

private let implementation: Implementation

public init(implementation: Implementation) {
self.implementation = implementation
}

func derivedXCTestPlatformProvider(_ declared: PackageModel.Platform) -> PlatformVersion? {
switch self.implementation {
case .mergingFromTargets(let targets):
let platforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in
merge(
into: &partial,
platforms: [item.getSupportedPlatform(for: declared, usingXCTest: item.type == .test)]
)
}
return platforms.first!.version

case .customXCTestMinimumDeploymentTargets(let customXCTestMinimumDeploymentTargets):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are creating the implementation enum here, I wonder if we should make custom and default two separate cases?

Copy link
Contributor Author

@MaxDesiatov MaxDesiatov Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I also found that Implementation.empty case is unused. Ready for review 🙂

return customXCTestMinimumDeploymentTargets[declared]

case .minimumDeploymentTargetDefault:
return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared)
}
}

/// Returns the supported platform instance for the given platform.
func getDerived(declared: [SupportedPlatform], for platform: Platform, usingXCTest: Bool) -> SupportedPlatform {
// derived platform based on known minimum deployment target logic
if let declaredPlatform = declared.first(where: { $0.platform == platform }) {
var version = declaredPlatform.version

if usingXCTest,
let xcTestMinimumDeploymentTarget = self.derivedXCTestPlatformProvider(platform),
version < xcTestMinimumDeploymentTarget
{
version = xcTestMinimumDeploymentTarget
}

// If the declared version is smaller than the oldest supported one, we raise the derived version to that.
if version < platform.oldestSupportedVersion {
version = platform.oldestSupportedVersion
}

return SupportedPlatform(
platform: declaredPlatform.platform,
version: version,
options: declaredPlatform.options
)
} else {
let minimumSupportedVersion: PlatformVersion
if usingXCTest,
let xcTestMinimumDeploymentTarget = self.derivedXCTestPlatformProvider(platform),
xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion
{
minimumSupportedVersion = xcTestMinimumDeploymentTarget
} else {
minimumSupportedVersion = platform.oldestSupportedVersion
}

let oldestSupportedVersion: PlatformVersion
if platform == .macCatalyst {
let iOS = self.getDerived(declared: declared, for: .iOS, usingXCTest: usingXCTest)
// If there was no deployment target specified for Mac Catalyst, fall back to the iOS deployment target.
oldestSupportedVersion = max(minimumSupportedVersion, iOS.version)
} else {
oldestSupportedVersion = minimumSupportedVersion
}

return SupportedPlatform(
platform: platform,
version: oldestSupportedVersion,
options: []
)
}
}
}
Loading