Skip to content

Commit cf6f83c

Browse files
authored
Improve handling of minimum deployment target (#2710)
* Improve handling of minimum deployment target We have a bit of a muddled deployment target situation in 5.2: - anything declared by SwiftPM's own manifest defaulted to macOS 10.10, but the toolchain doesn't actually work on macOS < 10.15 anymore - libPackageDescription was built with minimum deployment target of 10.15, but we were explicitly compiling the manifest for 10.10 minimum - support for running tests on macOS is aligned with Xcode (so 10.15 minimum), but we were not enforcing that This change makes it more consistent for 5.3: - make the deployment target configurable in the manifest, we'll still default to 10.10 but set it to 10.15 in the bootstrap script, so toolchains will end up with a 10.15 minimum deployment target - build manifests with the same deployment target as `libPackageDescription`, defaulting to 10.15 - always build tests with the minimum deployment target of the corresponding XCTest framework rdar://problem/62121806
1 parent 8064187 commit cf6f83c

File tree

13 files changed

+194
-21
lines changed

13 files changed

+194
-21
lines changed

Package.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,21 @@
1111
*/
1212

1313
import PackageDescription
14+
import class Foundation.ProcessInfo
15+
16+
// We default to a 10.10 minimum deployment target for clients of libSwiftPM,
17+
// but allow overriding it when building for a toolchain.
18+
19+
let macOSPlatform: SupportedPlatform
20+
if let deploymentTarget = ProcessInfo.processInfo.environment["SWIFTPM_MACOS_DEPLOYMENT_TARGET"] {
21+
macOSPlatform = .macOS(deploymentTarget)
22+
} else {
23+
macOSPlatform = .macOS(.v10_10)
24+
}
1425

1526
let package = Package(
1627
name: "SwiftPM",
28+
platforms: [macOSPlatform],
1729
products: [
1830
// The `libSwiftPM` set of interfaces to programatically work with Swift
1931
// packages.
@@ -222,7 +234,6 @@ let package = Package(
222234
// package dependency like this but there is no other good way of expressing
223235
// this right now.
224236

225-
import class Foundation.ProcessInfo
226237

227238
if ProcessInfo.processInfo.environment["SWIFTPM_LLBUILD_FWK"] == nil {
228239
if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {

Sources/Commands/SwiftPackageTool.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
115115
let builder = PackageBuilder(
116116
manifest: manifest,
117117
path: try getPackageRoot(),
118+
xcTestMinimumDeploymentTargets: [:], // Minimum deployment target does not matter for this operation.
118119
diagnostics: diagnostics
119120
)
120121
let package = try builder.construct()
@@ -354,6 +355,7 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
354355
let builder = PackageBuilder(
355356
manifest: manifest,
356357
path: try getPackageRoot(),
358+
xcTestMinimumDeploymentTargets: MinimumDeploymentTarget.default.xcTestMinimumDeploymentTargets,
357359
diagnostics: diagnostics
358360
)
359361
let package = try builder.construct()

Sources/PackageGraph/PackageGraphLoader.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public struct PackageGraphLoader {
117117
requiredDependencies: Set<PackageReference> = [],
118118
unsafeAllowedPackages: Set<PackageReference> = [],
119119
remoteArtifacts: [RemoteArtifact] = [],
120+
xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion] = MinimumDeploymentTarget.default.xcTestMinimumDeploymentTargets,
120121
diagnostics: DiagnosticsEngine,
121122
fileSystem: FileSystem = localFileSystem,
122123
shouldCreateMultipleTestProducts: Bool = false,
@@ -172,6 +173,7 @@ public struct PackageGraphLoader {
172173
path: packagePath,
173174
additionalFileRules: additionalFileRules,
174175
remoteArtifacts: remoteArtifacts,
176+
xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets,
175177
fileSystem: fileSystem,
176178
diagnostics: diagnostics,
177179
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,

Sources/PackageLoading/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(PackageLoading
1010
Diagnostics.swift
1111
ManifestBuilder.swift
1212
ManifestLoader.swift
13+
MinimumDeploymentTarget.swift
1314
ModuleMapGenerator.swift
1415
PackageBuilder.swift
1516
PackageDescription4Loader.swift

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ public final class ManifestLoader: ManifestLoaderProtocol {
494494
}
495495
}
496496

497+
private static var _packageDescriptionMinimumDeploymentTarget: String?
498+
497499
/// Parse the manifest at the given path to JSON.
498500
fileprivate func parse(
499501
packageIdentity: String,
@@ -531,6 +533,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
531533
cmd += [resources.swiftCompiler.pathString]
532534
cmd += verbosity.ccArgs
533535

536+
let macOSPackageDescriptionPath: AbsolutePath
534537
// If we got the binDir that means we could be developing SwiftPM in Xcode
535538
// which produces a framework for dynamic package products.
536539
let packageFrameworkPath = runtimePath.appending(component: "PackageFrameworks")
@@ -540,13 +543,27 @@ public final class ManifestLoader: ManifestLoaderProtocol {
540543
"-framework", "PackageDescription",
541544
"-Xlinker", "-rpath", "-Xlinker", packageFrameworkPath.pathString,
542545
]
546+
547+
macOSPackageDescriptionPath = packageFrameworkPath.appending(RelativePath("PackageDescription.framework/PackageDescription"))
543548
} else {
544549
cmd += [
545550
"-L", runtimePath.pathString,
546551
"-lPackageDescription",
547552
"-Xlinker", "-rpath", "-Xlinker", runtimePath.pathString
548553
]
554+
555+
// note: this is not correct for all platforms, but we only actually use it on macOS.
556+
macOSPackageDescriptionPath = runtimePath.appending(RelativePath("libPackageDescription.dylib"))
557+
}
558+
559+
// Use the same minimum deployment target as the PackageDescription library (with a fallback of 10.15).
560+
#if os(macOS)
561+
if Self._packageDescriptionMinimumDeploymentTarget == nil {
562+
Self._packageDescriptionMinimumDeploymentTarget = (try MinimumDeploymentTarget.computeMinimumDeploymentTarget(of: macOSPackageDescriptionPath))?.versionString ?? "10.15"
549563
}
564+
let version = Self._packageDescriptionMinimumDeploymentTarget!
565+
cmd += ["-target", "x86_64-apple-macosx\(version)"]
566+
#endif
550567

551568
cmd += compilerFlags
552569
if let moduleCachePath = moduleCachePath {
@@ -680,7 +697,6 @@ public final class ManifestLoader: ManifestLoaderProtocol {
680697
cmd += ["-swift-version", toolsVersion.swiftLanguageVersion.rawValue]
681698
cmd += ["-I", runtimePath.pathString]
682699
#if os(macOS)
683-
cmd += ["-target", "x86_64-apple-macosx10.10"]
684700
if let sdkRoot = resources.sdkRoot ?? self.sdkRoot() {
685701
cmd += ["-sdk", sdkRoot.pathString]
686702
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import PackageModel
12+
import TSCBasic
13+
14+
public struct MinimumDeploymentTarget {
15+
public let xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion]
16+
17+
public static let `default`: MinimumDeploymentTarget = .init()
18+
19+
public init() {
20+
xcTestMinimumDeploymentTargets = PlatformRegistry.default.knownPlatforms.reduce([PackageModel.Platform:PlatformVersion]()) {
21+
var dict = $0
22+
dict[$1] = Self.computeXCTestMinimumDeploymentTarget(for: $1)
23+
return dict
24+
}
25+
}
26+
27+
static func computeMinimumDeploymentTarget(of binaryPath: AbsolutePath) throws -> PlatformVersion? {
28+
let runResult = try Process.popen(arguments: ["xcrun", "vtool", "-show-build", binaryPath.pathString])
29+
guard let versionString = try runResult.utf8Output().components(separatedBy: "\n").first(where: { $0.contains("minos") })?.components(separatedBy: " ").last else { return nil }
30+
return PlatformVersion(versionString)
31+
}
32+
33+
static func computeXCTestMinimumDeploymentTarget(for platform: PackageModel.Platform) -> PlatformVersion {
34+
guard let sdkName = platform.sdkName else {
35+
return platform.oldestSupportedVersion
36+
}
37+
38+
// On macOS, we are determining the deployment target by looking at the XCTest binary.
39+
#if os(macOS)
40+
do {
41+
let runResult = try Process.popen(arguments: ["xcrun", "--sdk", sdkName, "--show-sdk-platform-path"])
42+
let sdkPath = AbsolutePath(try runResult.utf8Output().spm_chuzzle() ?? "")
43+
let xcTestPath = sdkPath.appending(RelativePath("Developer/Library/Frameworks/XCTest.framework/XCTest"))
44+
45+
if let version = try computeMinimumDeploymentTarget(of: xcTestPath) {
46+
return version
47+
}
48+
} catch { } // we do not treat this a fatal and instead use the fallback minimum deployment target
49+
#endif
50+
51+
return platform.oldestSupportedVersion
52+
}
53+
}
54+
55+
private extension PackageModel.Platform {
56+
var sdkName: String? {
57+
switch self {
58+
case .macOS:
59+
return "macosx"
60+
case .iOS:
61+
return "iphoneos"
62+
case .tvOS:
63+
return "appletvos"
64+
case .watchOS:
65+
return "watchos"
66+
default:
67+
return nil
68+
}
69+
}
70+
}

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ public final class PackageBuilder {
223223
/// The additionla file detection rules.
224224
private let additionalFileRules: [FileRuleDescription]
225225

226+
/// Minimum deployment target of XCTest per platform.
227+
private let xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion]
228+
226229
/// Create a builder for the given manifest and package `path`.
227230
///
228231
/// - Parameters:
@@ -238,6 +241,7 @@ public final class PackageBuilder {
238241
path: AbsolutePath,
239242
additionalFileRules: [FileRuleDescription] = [],
240243
remoteArtifacts: [RemoteArtifact] = [],
244+
xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion],
241245
fileSystem: FileSystem = localFileSystem,
242246
diagnostics: DiagnosticsEngine,
243247
shouldCreateMultipleTestProducts: Bool = false,
@@ -247,6 +251,7 @@ public final class PackageBuilder {
247251
self.packagePath = path
248252
self.additionalFileRules = additionalFileRules
249253
self.remoteArtifacts = remoteArtifacts
254+
self.xcTestMinimumDeploymentTargets = xcTestMinimumDeploymentTargets
250255
self.fileSystem = fileSystem
251256
self.diagnostics = diagnostics
252257
self.shouldCreateMultipleTestProducts = shouldCreateMultipleTestProducts
@@ -263,6 +268,7 @@ public final class PackageBuilder {
263268
public static func loadPackage(
264269
packagePath: AbsolutePath,
265270
swiftCompiler: AbsolutePath,
271+
xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion],
266272
diagnostics: DiagnosticsEngine,
267273
kind: PackageReference.Kind = .root
268274
) throws -> Package {
@@ -273,6 +279,7 @@ public final class PackageBuilder {
273279
let builder = PackageBuilder(
274280
manifest: manifest,
275281
path: packagePath,
282+
xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets,
276283
diagnostics: diagnostics)
277284
return try builder.construct()
278285
}
@@ -716,7 +723,7 @@ public final class PackageBuilder {
716723
name: potentialModule.name,
717724
bundleName: bundleName,
718725
defaultLocalization: manifest.defaultLocalization,
719-
platforms: self.platforms(),
726+
platforms: self.platforms(isTest: potentialModule.isTest),
720727
isTest: potentialModule.isTest,
721728
sources: sources,
722729
resources: resources,
@@ -729,7 +736,7 @@ public final class PackageBuilder {
729736
name: potentialModule.name,
730737
bundleName: bundleName,
731738
defaultLocalization: manifest.defaultLocalization,
732-
platforms: self.platforms(),
739+
platforms: self.platforms(isTest: potentialModule.isTest),
733740
cLanguageStandard: manifest.cLanguageStandard,
734741
cxxLanguageStandard: manifest.cxxLanguageStandard,
735742
includeDir: publicHeadersPath,
@@ -836,19 +843,25 @@ public final class PackageBuilder {
836843
}
837844

838845
/// Returns the list of platforms supported by the manifest.
839-
func platforms() -> [SupportedPlatform] {
840-
if let platforms = _platforms {
846+
func platforms(isTest: Bool = false) -> [SupportedPlatform] {
847+
if let platforms = _platforms[isTest] {
841848
return platforms
842849
}
843850

844851
var supportedPlatforms: [SupportedPlatform] = []
845852

846853
/// Add each declared platform to the supported platforms list.
847854
for platform in manifest.platforms {
855+
let declaredPlatform = platformRegistry.platformByName[platform.platformName]!
856+
var version = PlatformVersion(platform.version)
857+
858+
if let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[declaredPlatform], isTest, version < xcTestMinimumDeploymentTarget {
859+
version = xcTestMinimumDeploymentTarget
860+
}
848861

849862
let supportedPlatform = SupportedPlatform(
850-
platform: platformRegistry.platformByName[platform.platformName]!,
851-
version: PlatformVersion(platform.version),
863+
platform: declaredPlatform,
864+
version: version,
852865
options: platform.options
853866
)
854867

@@ -862,19 +875,27 @@ public final class PackageBuilder {
862875
for platformName in remainingPlatforms {
863876
let platform = platformRegistry.platformByName[platformName]!
864877

878+
let oldestSupportedVersion: PlatformVersion
879+
if let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[platform], isTest {
880+
oldestSupportedVersion = xcTestMinimumDeploymentTarget
881+
} else {
882+
oldestSupportedVersion = platform.oldestSupportedVersion
883+
}
884+
865885
let supportedPlatform = SupportedPlatform(
866886
platform: platform,
867-
version: platform.oldestSupportedVersion,
887+
version: oldestSupportedVersion,
868888
options: []
869889
)
870890

871891
supportedPlatforms.append(supportedPlatform)
872892
}
873893

874-
_platforms = supportedPlatforms
875-
return _platforms!
894+
_platforms[isTest] = supportedPlatforms
895+
return supportedPlatforms
876896
}
877-
private var _platforms: [SupportedPlatform]? = nil
897+
// Keep two sets of supported platforms, based on the `isTest` parameter.
898+
private var _platforms = [Bool:[SupportedPlatform]]()
878899

879900
/// The platform registry instance.
880901
private var platformRegistry: PlatformRegistry {

Sources/XCBuildSupport/PIFBuilder.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
255255
settings[.SDKROOT] = "auto"
256256
settings[.SDK_VARIANT] = "auto"
257257
settings[.SKIP_INSTALL] = "YES"
258-
let firstTarget = package.targets.first?.underlyingTarget
258+
let firstTarget = package.targets.first(where: { $0.type != .test })?.underlyingTarget ?? package.targets.first?.underlyingTarget
259259
settings[.MACOSX_DEPLOYMENT_TARGET] = firstTarget?.deploymentTarget(for: .macOS)
260260
settings[.IPHONEOS_DEPLOYMENT_TARGET] = firstTarget?.deploymentTarget(for: .iOS)
261261
settings[.TVOS_DEPLOYMENT_TARGET] = firstTarget?.deploymentTarget(for: .tvOS)
@@ -385,6 +385,14 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
385385
settings[.SWIFT_FORCE_STATIC_LINK_STDLIB] = "NO"
386386
settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB] = "YES"
387387

388+
// Tests can have a custom deployment target based on the minimum supported by XCTest.
389+
if mainTarget.underlyingTarget.type == .test {
390+
settings[.MACOSX_DEPLOYMENT_TARGET] = mainTarget.underlyingTarget.deploymentTarget(for: .macOS)
391+
settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTarget.underlyingTarget.deploymentTarget(for: .iOS)
392+
settings[.TVOS_DEPLOYMENT_TARGET] = mainTarget.underlyingTarget.deploymentTarget(for: .tvOS)
393+
settings[.WATCHOS_DEPLOYMENT_TARGET] = mainTarget.underlyingTarget.deploymentTarget(for: .watchOS)
394+
}
395+
388396
if product.type == .executable {
389397
// Command-line tools are only supported for the macOS platforms.
390398
settings[.SDKROOT] = "macosx"

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import TSCBasic
1414
import TSCUtility
1515
import SPMTestSupport
1616
import PackageModel
17-
import PackageLoading
17+
@testable import PackageLoading
1818
import SPMBuildCore
1919
import Build
2020

@@ -836,14 +836,15 @@ final class BuildPlanTests: XCTestCase {
836836
XCTAssertMatch(fooTests, ["-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-module-cache-path", "/path/to/build/debug/ModuleCache", .anySequence])
837837

838838
#if os(macOS)
839+
let version = MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString
839840
XCTAssertEqual(try result.buildProduct(for: "PkgPackageTests").linkArguments(), [
840841
"/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o",
841842
"/path/to/build/debug/PkgPackageTests.xctest/Contents/MacOS/PkgPackageTests", "-module-name",
842843
"PkgPackageTests", "-Xlinker", "-bundle",
843844
"-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../../",
844845
"@/path/to/build/debug/PkgPackageTests.product/Objects.LinkFileList",
845846
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
846-
"-target", "x86_64-apple-macosx10.10",
847+
"-target", "x86_64-apple-macosx\(version)",
847848
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Foo.swiftmodule",
848849
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/FooTests.swiftmodule",
849850
])

0 commit comments

Comments
 (0)