Skip to content

Commit

Permalink
Reduce XCTest minimum deployment target computation (#6886)
Browse files Browse the repository at this point in the history
We never need this for any platform that we're not building for and we also don't really need it for most commands. So we can just move the computation to SwiftTool and leave these empty for all other cases.

rdar://64596106

(cherry picked from commit 019a6fb)
  • Loading branch information
neonichu authored Sep 21, 2023
1 parent 076342a commit 5d6c0cb
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 146 deletions.
20 changes: 10 additions & 10 deletions Sources/Build/BuildDescription/ProductBuildDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,16 @@ 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, self.buildParameters.triple.isDarwin(),
let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS),
macOSSupportedPlatform.version.major < 12
{
let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib
.parentDirectory
.parentDirectory
.appending("swift-5.5")
.appending("macosx")
args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString]
if useStdlibRpath, self.buildParameters.triple.isDarwin() {
let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
if macOSSupportedPlatform.version.major < 12 {
let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib
.parentDirectory
.parentDirectory
.appending("swift-5.5")
.appending("macosx")
args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString]
}
}
}

Expand Down
18 changes: 6 additions & 12 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,7 @@ extension BuildParameters {
var args = ["-target"]
// Compute the triple string for Darwin platform using the platform version.
if triple.isDarwin() {
guard let macOSSupportedPlatform = target.platforms.getDerived(for: .macOS) else {
throw StringError("the target \(target) doesn't support building for macOS")
}
let macOSSupportedPlatform = target.platforms.getDerived(for: .macOS, usingXCTest: target.type == .test)
args += [triple.tripleString(forPlatformVersion: macOSSupportedPlatform.version.versionString)]
} else {
args += [triple.tripleString]
Expand Down Expand Up @@ -305,7 +303,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
target: discoveryTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) },
defaultLocalization: .none, // safe since this is a derived target
platforms: .init(declared: [], derived: []) // safe since this is a derived target
platforms: .init(declared: [], derivedXCTestPlatformProvider: .none) // safe since this is a derived target
)
let discoveryTargetBuildDescription = try SwiftTargetBuildDescription(
package: package,
Expand Down Expand Up @@ -338,7 +336,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
target: entryPointTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryResolvedTarget, conditions: [])],
defaultLocalization: .none, // safe since this is a derived target
platforms: .init(declared: [], derived: []) // safe since this is a derived target
platforms: .init(declared: [], derivedXCTestPlatformProvider: .none) // safe since this is a derived target
)
return try SwiftTargetBuildDescription(
package: package,
Expand Down Expand Up @@ -367,7 +365,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
target: entryPointTarget,
dependencies: entryPointResolvedTarget.dependencies + [.target(discoveryTargets.resolved, conditions: [])],
defaultLocalization: .none, // safe since this is a derived target
platforms: .init(declared: [], derived: []) // safe since this is a derived target
platforms: .init(declared: [], derivedXCTestPlatformProvider: .none) // safe since this is a derived target
)
let entryPointTargetBuildDescription = try SwiftTargetBuildDescription(
package: package,
Expand Down Expand Up @@ -574,12 +572,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.
guard let productPlatform = product.platforms.getDerived(for: .macOS) else {
throw StringError("Expected supported platform macOS in product \(product)")
}
guard let targetPlatform = target.platforms.getDerived(for: .macOS) else {
throw StringError("Expected supported platform macOS in target \(target)")
}
let productPlatform = product.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest)
let targetPlatform = target.platforms.getDerived(for: .macOS, usingXCTest: target.type == .test)

// Check if the version requirement is satisfied.
//
Expand Down
89 changes: 14 additions & 75 deletions Sources/PackageGraph/PackageGraph+Loading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@ extension PackageGraph {
rootManifests: root.manifests,
unsafeAllowedPackages: unsafeAllowedPackages,
platformRegistry: customPlatformsRegistry ?? .default,
xcTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets ?? MinimumDeploymentTarget.default.xcTestMinimumDeploymentTargets,
derivedXCTestPlatformProvider: { declared in
if let customXCTestMinimumDeploymentTargets {
return customXCTestMinimumDeploymentTargets[declared]
} else {
return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared)
}
},
fileSystem: fileSystem,
observabilityScope: observabilityScope
)
Expand Down Expand Up @@ -225,7 +231,7 @@ private func createResolvedPackages(
rootManifests: [PackageIdentity: Manifest],
unsafeAllowedPackages: Set<PackageReference>,
platformRegistry: PlatformRegistry,
xcTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion],
derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?,
fileSystem: FileSystem,
observabilityScope: ObservabilityScope
) throws -> [ResolvedPackage] {
Expand Down Expand Up @@ -348,16 +354,8 @@ private func createResolvedPackages(

packageBuilder.platforms = computePlatforms(
package: package,
usingXCTest: false,
platformRegistry: platformRegistry,
xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets
)

let testPlatforms = computePlatforms(
package: package,
usingXCTest: true,
platformRegistry: platformRegistry,
xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets
derivedXCTestPlatformProvider: derivedXCTestPlatformProvider
)

// Create target builders for each target in the package.
Expand All @@ -379,7 +377,7 @@ private func createResolvedPackages(
}
}
targetBuilder.defaultLocalization = packageBuilder.defaultLocalization
targetBuilder.platforms = targetBuilder.target.type == .test ? testPlatforms : packageBuilder.platforms
targetBuilder.platforms = packageBuilder.platforms
}

// 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 @@ -686,9 +684,8 @@ private class DuplicateProductsChecker {

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

// the supported platforms as declared in the manifest
Expand All @@ -702,67 +699,9 @@ private func computePlatforms(
)
}

// the derived platforms based on known minimum deployment target logic
var derivedPlatforms = [SupportedPlatform]()

/// Add each declared platform to the supported platforms list.
for platform in package.manifest.platforms {
let declaredPlatform = platformRegistry.platformByName[platform.platformName]
?? PackageModel.Platform.custom(name: platform.platformName, oldestSupportedVersion: platform.version)
var version = PlatformVersion(platform.version)

if usingXCTest, let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[declaredPlatform], version < xcTestMinimumDeploymentTarget {
version = xcTestMinimumDeploymentTarget
}

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

let supportedPlatform = SupportedPlatform(
platform: declaredPlatform,
version: version,
options: platform.options
)

derivedPlatforms.append(supportedPlatform)
}

// Find the undeclared platforms.
let remainingPlatforms = Set(platformRegistry.platformByName.keys).subtracting(derivedPlatforms.map({ $0.platform.name }))

/// Start synthesizing for each undeclared platform.
for platformName in remainingPlatforms.sorted() {
let platform = platformRegistry.platformByName[platformName]!

let minimumSupportedVersion: PlatformVersion
if usingXCTest, let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[platform], xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion {
minimumSupportedVersion = xcTestMinimumDeploymentTarget
} else {
minimumSupportedVersion = platform.oldestSupportedVersion
}

let oldestSupportedVersion: PlatformVersion
if platform == .macCatalyst, let iOS = derivedPlatforms.first(where: { $0.platform == .iOS }) {
// 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
}

let supportedPlatform = SupportedPlatform(
platform: platform,
version: oldestSupportedVersion,
options: []
)

derivedPlatforms.append(supportedPlatform)
}

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

Expand Down Expand Up @@ -886,7 +825,7 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
var defaultLocalization: String? = nil

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

init(
target: Target,
Expand Down Expand Up @@ -978,7 +917,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
var defaultLocalization: String? = nil

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

/// If the given package's source is a registry release, this provides additional metadata and signature information.
var registryMetadata: RegistryReleaseMetadata?
Expand Down
24 changes: 14 additions & 10 deletions Sources/PackageGraph/ResolvedProduct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public final class ResolvedProduct {
target: swiftTarget,
dependencies: targets.map { .target($0, conditions: []) },
defaultLocalization: .none, // safe since this is a derived product
platforms: .init(declared: [], derived: []) // safe since this is a derived product
platforms: .init(declared: [], derivedXCTestPlatformProvider: .none) // safe since this is a derived product
)
}

Expand Down Expand Up @@ -119,16 +119,13 @@ public final class ResolvedProduct {
merge(into: &partial, platforms: item.platforms.declared)
}

let derived = targets.reduce(into: [SupportedPlatform]()) { partial, item in
merge(into: &partial, platforms: item.platforms.derived)
}

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


declared: declared.sorted(by: { $0.platform.name < $1.platform.name })) { declared in
let platforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in
merge(into: &partial, platforms: [item.platforms.getDerived(for: declared, usingXCTest: item.type == .test)])
}
return platforms.first!.version
}
}
}

Expand All @@ -147,3 +144,10 @@ extension ResolvedProduct: CustomStringConvertible {
return "<ResolvedProduct: \(name)>"
}
}

extension ResolvedProduct {
public var isLinkingXCTest: Bool {
// To retain existing behavior, we have to check both the product type, as well as the types of all of its targets.
return self.type == .test || self.targets.contains(where: { $0.type == .test })
}
}
14 changes: 8 additions & 6 deletions Sources/PackageModel/MinimumDeploymentTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
//
//===----------------------------------------------------------------------===//

import Basics
import TSCBasic

public struct MinimumDeploymentTarget {
public let xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion]
public let xcTestMinimumDeploymentTargets = ThreadSafeKeyValueStore<PackageModel.Platform,PlatformVersion>()

public static let `default`: MinimumDeploymentTarget = .init()

public init() {
xcTestMinimumDeploymentTargets = PlatformRegistry.default.knownPlatforms.reduce([PackageModel.Platform:PlatformVersion]()) {
var dict = $0
dict[$1] = Self.computeXCTestMinimumDeploymentTarget(for: $1)
return dict
private init() {
}

public func computeXCTestMinimumDeploymentTarget(for platform: PackageModel.Platform) -> PlatformVersion {
self.xcTestMinimumDeploymentTargets.memoize(platform) {
return Self.computeXCTestMinimumDeploymentTarget(for: platform)
}
}

Expand Down
50 changes: 45 additions & 5 deletions Sources/PackageModel/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,56 @@ public struct Platform: Equatable, Hashable, Codable {

public struct SupportedPlatforms {
public let declared: [SupportedPlatform]
public let derived: [SupportedPlatform]
private let derivedXCTestPlatformProvider: ((Platform) -> PlatformVersion?)?

public init(declared: [SupportedPlatform], derived: [SupportedPlatform]) {
public init(declared: [SupportedPlatform], derivedXCTestPlatformProvider: ((_ declared: Platform) -> PlatformVersion?)?) {
self.declared = declared
self.derived = derived
self.derivedXCTestPlatformProvider = derivedXCTestPlatformProvider
}

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

if usingXCTest, let xcTestMinimumDeploymentTarget = 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 = derivedXCTestPlatformProvider?(platform), xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion {
minimumSupportedVersion = xcTestMinimumDeploymentTarget
} else {
minimumSupportedVersion = platform.oldestSupportedVersion
}

let oldestSupportedVersion: PlatformVersion
if platform == .macCatalyst {
let iOS = getDerived(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: []
)
}
}
}

Expand Down
Loading

0 comments on commit 5d6c0cb

Please sign in to comment.