Skip to content

Commit d05b594

Browse files
authored
[6.0][SE-0435] Implement per target swift version selection (#7567)
- Explanation: Implementation of SE-0435 proposal. Add a new Swift target setting API, similar to `enable{Upcoming, Experimental}Feature`, to specify a Swift language version that should be used to build the target, if such version is not specified, fallback to the current language version determination logic. - Scope: TargetDescription API and build system. - Main Branch PRs: #7439, #7544, #7550, #7557 - Radar: rdar://125732014 - Risk: Low - Reviewed By: @MaxDesiatov @bnbarham - Testing: Added new test-cases to the test suite.
1 parent d192ad0 commit d05b594

20 files changed

+480
-99
lines changed

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,6 @@ package final class SwiftTargetBuildDescription {
141141
/// Any addition flags to be added. These flags are expected to be computed during build planning.
142142
var additionalFlags: [String] = []
143143

144-
/// The swift version for this target.
145-
var swiftVersion: SwiftLanguageVersion {
146-
self.swiftTarget.swiftVersion
147-
}
148-
149144
/// Describes the purpose of a test target, including any special roles such as containing a list of discovered
150145
/// tests or serving as the manifest target which contains the main entry point.
151146
package enum TestTargetRole {
@@ -458,7 +453,6 @@ package final class SwiftTargetBuildDescription {
458453
package func compileArguments() throws -> [String] {
459454
var args = [String]()
460455
args += try self.buildParameters.targetTripleArgs(for: self.target)
461-
args += ["-swift-version", self.swiftVersion.rawValue]
462456

463457
// pass `-v` during verbose builds.
464458
if self.buildParameters.outputParameters.isVerbose {
@@ -800,6 +794,9 @@ package final class SwiftTargetBuildDescription {
800794
let scope = self.buildParameters.createScope(for: self.target)
801795
var flags: [String] = []
802796

797+
// A custom swift version.
798+
flags += scope.evaluate(.SWIFT_VERSION).flatMap { ["-swift-version", $0] }
799+
803800
// Swift defines.
804801
let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS)
805802
flags += swiftDefines.map { "-D" + $0 }

Sources/Build/BuildPlan/BuildPlan+Test.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ private extension PackageModel.SwiftTarget {
245245
sources: sources,
246246
dependencies: dependencies,
247247
packageAccess: packageAccess,
248-
swiftVersion: .v5,
249248
usesUnsafeFlags: false
250249
)
251250
}

Sources/PackageDescription/BuildSettings.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,23 @@ public struct SwiftSetting: Sendable {
387387
return SwiftSetting(
388388
name: "interoperabilityMode", value: [mode.rawValue], condition: condition)
389389
}
390+
391+
/// Defines a `-swift-version` to pass to the
392+
/// corresponding build tool.
393+
///
394+
/// - Since: First available in PackageDescription 6.0.
395+
///
396+
/// - Parameters:
397+
/// - version: The Swift language version to use.
398+
/// - condition: A condition that restricts the application of the build setting.
399+
@available(_PackageDescription, introduced: 6.0)
400+
public static func swiftLanguageVersion(
401+
_ version: SwiftVersion,
402+
_ condition: BuildSettingCondition? = nil
403+
) -> SwiftSetting {
404+
return SwiftSetting(
405+
name: "swiftLanguageVersion", value: [.init(describing: version)], condition: condition)
406+
}
390407
}
391408

392409
/// A linker build setting.

Sources/PackageDescription/LanguageStandardSettings.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,25 @@ public enum SwiftVersion {
167167
@available(_PackageDescription, introduced: 5)
168168
case v5
169169

170+
/// The identifier for the Swift 6 language version.
171+
@available(_PackageDescription, introduced: 6)
172+
case v6
173+
170174
/// A user-defined value for the Swift version.
171175
///
172176
/// The value is passed as-is to the Swift compiler's `-swift-version` flag.
173177
case version(String)
174178
}
179+
180+
extension SwiftVersion: CustomStringConvertible {
181+
public var description: String {
182+
switch self {
183+
case .v3: "3"
184+
case .v4: "4"
185+
case .v4_2: "4.2"
186+
case .v5: "5"
187+
case .v6: "6"
188+
case .version(let version): version
189+
}
190+
}
191+
}

Sources/PackageDescription/PackageDescriptionSerialization.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ enum Serialization {
9999
case v4
100100
case v4_2
101101
case v5
102+
case v6
102103
case version(String)
103104
}
104105

Sources/PackageDescription/PackageDescriptionSerializationConversion.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ extension Serialization.SwiftVersion {
112112
case .v4: self = .v4
113113
case .v4_2: self = .v4_2
114114
case .v5: self = .v5
115+
case .v6: self = .v6
115116
case .version(let version): self = .version(version)
116117
}
117118
}

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ enum ManifestJSONParser {
150150
case .v4: languageVersionString = "4"
151151
case .v4_2: languageVersionString = "4.2"
152152
case .v5: languageVersionString = "5"
153+
case .v6: languageVersionString = "6"
153154
case .version(let version): languageVersionString = version
154155
}
155156
guard let languageVersion = SwiftLanguageVersion(string: languageVersionString) else {
@@ -533,6 +534,21 @@ extension TargetBuildSettingDescription.Kind {
533534
return .enableExperimentalFeature(value)
534535
case "unsafeFlags":
535536
return .unsafeFlags(values)
537+
538+
case "swiftLanguageVersion":
539+
guard let rawVersion = values.first else {
540+
throw InternalError("invalid (empty) build settings value")
541+
}
542+
543+
if values.count > 1 {
544+
throw InternalError("invalid build settings value")
545+
}
546+
547+
guard let version = SwiftLanguageVersion(string: rawVersion) else {
548+
throw InternalError("unknown swift language version: \(rawVersion)")
549+
}
550+
551+
return .swiftLanguageVersion(version)
536552
default:
537553
throw InternalError("invalid build setting \(name)")
538554
}

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,8 @@ public final class PackageBuilder {
891891
let buildSettings = try self.buildSettings(
892892
for: manifestTarget,
893893
targetRoot: potentialModule.path,
894-
cxxLanguageStandard: self.manifest.cxxLanguageStandard
894+
cxxLanguageStandard: self.manifest.cxxLanguageStandard,
895+
toolsSwiftVersion: self.toolsSwiftVersion()
895896
)
896897

897898
// Compute the path to public headers directory.
@@ -982,7 +983,6 @@ public final class PackageBuilder {
982983
others: others,
983984
dependencies: dependencies,
984985
packageAccess: potentialModule.packageAccess,
985-
swiftVersion: self.swiftVersion(),
986986
declaredSwiftVersions: self.declaredSwiftVersions(),
987987
buildSettings: buildSettings,
988988
buildSettingsDescription: manifestTarget.settings,
@@ -1040,11 +1040,18 @@ public final class PackageBuilder {
10401040
func buildSettings(
10411041
for target: TargetDescription?,
10421042
targetRoot: AbsolutePath,
1043-
cxxLanguageStandard: String? = nil
1043+
cxxLanguageStandard: String? = nil,
1044+
toolsSwiftVersion: SwiftLanguageVersion
10441045
) throws -> BuildSettings.AssignmentTable {
10451046
var table = BuildSettings.AssignmentTable()
10461047
guard let target else { return table }
10471048

1049+
// First let's add a default assignments for tools swift version.
1050+
var versionAssignment = BuildSettings.Assignment(default: true)
1051+
versionAssignment.values = [toolsSwiftVersion.rawValue]
1052+
1053+
table.add(versionAssignment, for: .SWIFT_VERSION)
1054+
10481055
// Process each setting.
10491056
for setting in target.settings {
10501057
let decl: BuildSettings.Declaration
@@ -1154,6 +1161,17 @@ public final class PackageBuilder {
11541161
}
11551162

11561163
values = ["-enable-experimental-feature", value]
1164+
1165+
case .swiftLanguageVersion(let version):
1166+
switch setting.tool {
1167+
case .c, .cxx, .linker:
1168+
throw InternalError("only Swift supports swift language version")
1169+
1170+
case .swift:
1171+
decl = .SWIFT_VERSION
1172+
}
1173+
1174+
values = [version.rawValue]
11571175
}
11581176

11591177
// Create an assignment for this setting.
@@ -1211,7 +1229,7 @@ public final class PackageBuilder {
12111229
}
12121230

12131231
/// Computes the swift version to use for this manifest.
1214-
private func swiftVersion() throws -> SwiftLanguageVersion {
1232+
private func toolsSwiftVersion() throws -> SwiftLanguageVersion {
12151233
if let swiftVersion = self.swiftVersionCache {
12161234
return swiftVersion
12171235
}
@@ -1702,17 +1720,17 @@ extension PackageBuilder {
17021720
)
17031721
buildSettings = try self.buildSettings(
17041722
for: targetDescription,
1705-
targetRoot: sourceFile.parentDirectory
1723+
targetRoot: sourceFile.parentDirectory,
1724+
toolsSwiftVersion: self.toolsSwiftVersion()
17061725
)
17071726

1708-
return try SwiftTarget(
1727+
return SwiftTarget(
17091728
name: name,
17101729
type: .snippet,
17111730
path: .root,
17121731
sources: sources,
17131732
dependencies: dependencies,
17141733
packageAccess: false,
1715-
swiftVersion: self.swiftVersion(),
17161734
buildSettings: buildSettings,
17171735
buildSettingsDescription: targetDescription.settings,
17181736
usesUnsafeFlags: false

Sources/PackageModel/BuildSettings.swift

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212

1313
/// Namespace for build settings.
1414
public enum BuildSettings {
15-
1615
/// Build settings declarations.
1716
public struct Declaration: Hashable, Codable {
1817
// Swift.
19-
public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration = .init("SWIFT_ACTIVE_COMPILATION_CONDITIONS")
18+
public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration =
19+
.init("SWIFT_ACTIVE_COMPILATION_CONDITIONS")
2020
public static let OTHER_SWIFT_FLAGS: Declaration = .init("OTHER_SWIFT_FLAGS")
21+
public static let SWIFT_VERSION: Declaration = .init("SWIFT_VERSION")
2122

2223
// C family.
2324
public static let GCC_PREPROCESSOR_DEFINITIONS: Declaration = .init("GCC_PREPROCESSOR_DEFINITIONS")
@@ -47,18 +48,23 @@ public enum BuildSettings {
4748
/// The condition associated with this assignment.
4849
public var conditions: [PackageCondition] {
4950
get {
50-
return _conditions.map { $0.underlying }
51+
self._conditions.map(\.underlying)
5152
}
5253
set {
53-
_conditions = newValue.map { PackageConditionWrapper($0) }
54+
self._conditions = newValue.map { PackageConditionWrapper($0) }
5455
}
5556
}
5657

5758
private var _conditions: [PackageConditionWrapper]
5859

59-
public init() {
60+
/// Indicates whether this assignment represents a default
61+
/// that should be used only if no other assignments match.
62+
public let `default`: Bool
63+
64+
public init(default: Bool = false) {
6065
self._conditions = []
6166
self.values = []
67+
self.default = `default`
6268
}
6369
}
6470

@@ -67,13 +73,13 @@ public enum BuildSettings {
6773
public private(set) var assignments: [Declaration: [Assignment]]
6874

6975
public init() {
70-
assignments = [:]
76+
self.assignments = [:]
7177
}
7278

7379
/// Add the given assignment to the table.
74-
mutating public func add(_ assignment: Assignment, for decl: Declaration) {
80+
public mutating func add(_ assignment: Assignment, for decl: Declaration) {
7581
// FIXME: We should check for duplicate assignments.
76-
assignments[decl, default: []].append(assignment)
82+
self.assignments[decl, default: []].append(assignment)
7783
}
7884
}
7985

@@ -100,12 +106,18 @@ public enum BuildSettings {
100106
}
101107

102108
// Add values from each assignment if it satisfies the build environment.
103-
let values = assignments
109+
let allViableAssignments = assignments
104110
.lazy
105111
.filter { $0.conditions.allSatisfy { $0.satisfies(self.environment) } }
106-
.flatMap { $0.values }
107112

108-
return Array(values)
113+
let nonDefaultAssignments = allViableAssignments.filter { !$0.default }
114+
115+
// If there are no non-default assignments, let's fallback to defaults.
116+
if nonDefaultAssignments.isEmpty {
117+
return allViableAssignments.filter(\.default).flatMap(\.values)
118+
}
119+
120+
return nonDefaultAssignments.flatMap(\.values)
109121
}
110122
}
111123
}

Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
/// A namespace for target-specific build settings.
1414
public enum TargetBuildSettingDescription {
15-
1615
/// The tool for which a build setting is declared.
1716
public enum Tool: String, Codable, Hashable, CaseIterable, Sendable {
1817
case c
@@ -40,12 +39,15 @@ public enum TargetBuildSettingDescription {
4039

4140
case unsafeFlags([String])
4241

42+
case swiftLanguageVersion(SwiftLanguageVersion)
43+
4344
public var isUnsafeFlags: Bool {
4445
switch self {
4546
case .unsafeFlags(let flags):
4647
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
4748
return !flags.isEmpty
48-
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, .enableUpcomingFeature, .enableExperimentalFeature:
49+
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode,
50+
.enableUpcomingFeature, .enableExperimentalFeature, .swiftLanguageVersion:
4951
return false
5052
}
5153
}

Sources/PackageModel/ManifestSourceGeneration.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,12 @@ fileprivate extension SourceCodeFragment {
525525
params.append(SourceCodeFragment(from: condition))
526526
}
527527
self.init(enum: setting.kind.name, subnodes: params)
528+
case .swiftLanguageVersion(let version):
529+
params.append(SourceCodeFragment(from: version))
530+
if let condition = setting.condition {
531+
params.append(SourceCodeFragment(from: condition))
532+
}
533+
self.init(enum: setting.kind.name, subnodes: params)
528534
}
529535
}
530536
}
@@ -677,6 +683,8 @@ extension TargetBuildSettingDescription.Kind {
677683
return "enableUpcomingFeature"
678684
case .enableExperimentalFeature:
679685
return "enableExperimentalFeature"
686+
case .swiftLanguageVersion:
687+
return "swiftLanguageVersion"
680688
}
681689
}
682690
}

Sources/PackageModel/SwiftLanguageVersion.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ public struct SwiftLanguageVersion: Hashable, Sendable {
3131
/// Swift language version 5.
3232
public static let v5 = SwiftLanguageVersion(uncheckedString: "5")
3333

34+
/// Swift language version 6.
35+
public static let v6 = SwiftLanguageVersion(uncheckedString: "6")
36+
3437
/// The list of known Swift language versions.
3538
public static let knownSwiftLanguageVersions = [
36-
v3, v4, v4_2, v5,
39+
v3, v4, v4_2, v5, v6
3740
]
3841

3942
/// The raw value of the language version.

0 commit comments

Comments
 (0)