Skip to content

[6.0][SE-0435] Implement per target swift version selection #7567

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

Merged
merged 9 commits into from
May 16, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,6 @@ package final class SwiftTargetBuildDescription {
/// Any addition flags to be added. These flags are expected to be computed during build planning.
var additionalFlags: [String] = []

/// The swift version for this target.
var swiftVersion: SwiftLanguageVersion {
self.swiftTarget.swiftVersion
}

/// Describes the purpose of a test target, including any special roles such as containing a list of discovered
/// tests or serving as the manifest target which contains the main entry point.
package enum TestTargetRole {
Expand Down Expand Up @@ -458,7 +453,6 @@ package final class SwiftTargetBuildDescription {
package func compileArguments() throws -> [String] {
var args = [String]()
args += try self.buildParameters.targetTripleArgs(for: self.target)
args += ["-swift-version", self.swiftVersion.rawValue]

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

// A custom swift version.
flags += scope.evaluate(.SWIFT_VERSION).flatMap { ["-swift-version", $0] }

// Swift defines.
let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS)
flags += swiftDefines.map { "-D" + $0 }
Expand Down
1 change: 0 additions & 1 deletion Sources/Build/BuildPlan/BuildPlan+Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ private extension PackageModel.SwiftTarget {
sources: sources,
dependencies: dependencies,
packageAccess: packageAccess,
swiftVersion: .v5,
usesUnsafeFlags: false
)
}
Expand Down
17 changes: 17 additions & 0 deletions Sources/PackageDescription/BuildSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,23 @@ public struct SwiftSetting: Sendable {
return SwiftSetting(
name: "interoperabilityMode", value: [mode.rawValue], condition: condition)
}

/// Defines a `-swift-version` to pass to the
/// corresponding build tool.
///
/// - Since: First available in PackageDescription 6.0.
///
/// - Parameters:
/// - version: The Swift language version to use.
/// - condition: A condition that restricts the application of the build setting.
@available(_PackageDescription, introduced: 6.0)
public static func swiftLanguageVersion(
_ version: SwiftVersion,
_ condition: BuildSettingCondition? = nil
) -> SwiftSetting {
return SwiftSetting(
name: "swiftLanguageVersion", value: [.init(describing: version)], condition: condition)
}
}

/// A linker build setting.
Expand Down
17 changes: 17 additions & 0 deletions Sources/PackageDescription/LanguageStandardSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,25 @@ public enum SwiftVersion {
@available(_PackageDescription, introduced: 5)
case v5

/// The identifier for the Swift 6 language version.
@available(_PackageDescription, introduced: 6)
case v6

/// A user-defined value for the Swift version.
///
/// The value is passed as-is to the Swift compiler's `-swift-version` flag.
case version(String)
}

extension SwiftVersion: CustomStringConvertible {
public var description: String {
switch self {
case .v3: "3"
case .v4: "4"
case .v4_2: "4.2"
case .v5: "5"
case .v6: "6"
case .version(let version): version
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ enum Serialization {
case v4
case v4_2
case v5
case v6
case version(String)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ extension Serialization.SwiftVersion {
case .v4: self = .v4
case .v4_2: self = .v4_2
case .v5: self = .v5
case .v6: self = .v6
case .version(let version): self = .version(version)
}
}
Expand Down
16 changes: 16 additions & 0 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ enum ManifestJSONParser {
case .v4: languageVersionString = "4"
case .v4_2: languageVersionString = "4.2"
case .v5: languageVersionString = "5"
case .v6: languageVersionString = "6"
case .version(let version): languageVersionString = version
}
guard let languageVersion = SwiftLanguageVersion(string: languageVersionString) else {
Expand Down Expand Up @@ -533,6 +534,21 @@ extension TargetBuildSettingDescription.Kind {
return .enableExperimentalFeature(value)
case "unsafeFlags":
return .unsafeFlags(values)

case "swiftLanguageVersion":
guard let rawVersion = values.first else {
throw InternalError("invalid (empty) build settings value")
}

if values.count > 1 {
throw InternalError("invalid build settings value")
}

guard let version = SwiftLanguageVersion(string: rawVersion) else {
throw InternalError("unknown swift language version: \(rawVersion)")
}

return .swiftLanguageVersion(version)
default:
throw InternalError("invalid build setting \(name)")
}
Expand Down
32 changes: 25 additions & 7 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,8 @@ public final class PackageBuilder {
let buildSettings = try self.buildSettings(
for: manifestTarget,
targetRoot: potentialModule.path,
cxxLanguageStandard: self.manifest.cxxLanguageStandard
cxxLanguageStandard: self.manifest.cxxLanguageStandard,
toolsSwiftVersion: self.toolsSwiftVersion()
)

// Compute the path to public headers directory.
Expand Down Expand Up @@ -982,7 +983,6 @@ public final class PackageBuilder {
others: others,
dependencies: dependencies,
packageAccess: potentialModule.packageAccess,
swiftVersion: self.swiftVersion(),
declaredSwiftVersions: self.declaredSwiftVersions(),
buildSettings: buildSettings,
buildSettingsDescription: manifestTarget.settings,
Expand Down Expand Up @@ -1040,11 +1040,18 @@ public final class PackageBuilder {
func buildSettings(
for target: TargetDescription?,
targetRoot: AbsolutePath,
cxxLanguageStandard: String? = nil
cxxLanguageStandard: String? = nil,
toolsSwiftVersion: SwiftLanguageVersion
) throws -> BuildSettings.AssignmentTable {
var table = BuildSettings.AssignmentTable()
guard let target else { return table }

// First let's add a default assignments for tools swift version.
var versionAssignment = BuildSettings.Assignment(default: true)
versionAssignment.values = [toolsSwiftVersion.rawValue]

table.add(versionAssignment, for: .SWIFT_VERSION)

// Process each setting.
for setting in target.settings {
let decl: BuildSettings.Declaration
Expand Down Expand Up @@ -1154,6 +1161,17 @@ public final class PackageBuilder {
}

values = ["-enable-experimental-feature", value]

case .swiftLanguageVersion(let version):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports swift language version")

case .swift:
decl = .SWIFT_VERSION
}

values = [version.rawValue]
}

// Create an assignment for this setting.
Expand Down Expand Up @@ -1211,7 +1229,7 @@ public final class PackageBuilder {
}

/// Computes the swift version to use for this manifest.
private func swiftVersion() throws -> SwiftLanguageVersion {
private func toolsSwiftVersion() throws -> SwiftLanguageVersion {
if let swiftVersion = self.swiftVersionCache {
return swiftVersion
}
Expand Down Expand Up @@ -1702,17 +1720,17 @@ extension PackageBuilder {
)
buildSettings = try self.buildSettings(
for: targetDescription,
targetRoot: sourceFile.parentDirectory
targetRoot: sourceFile.parentDirectory,
toolsSwiftVersion: self.toolsSwiftVersion()
)

return try SwiftTarget(
return SwiftTarget(
name: name,
type: .snippet,
path: .root,
sources: sources,
dependencies: dependencies,
packageAccess: false,
swiftVersion: self.swiftVersion(),
buildSettings: buildSettings,
buildSettingsDescription: targetDescription.settings,
usesUnsafeFlags: false
Expand Down
34 changes: 23 additions & 11 deletions Sources/PackageModel/BuildSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@

/// Namespace for build settings.
public enum BuildSettings {

/// Build settings declarations.
public struct Declaration: Hashable, Codable {
// Swift.
public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration = .init("SWIFT_ACTIVE_COMPILATION_CONDITIONS")
public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration =
.init("SWIFT_ACTIVE_COMPILATION_CONDITIONS")
public static let OTHER_SWIFT_FLAGS: Declaration = .init("OTHER_SWIFT_FLAGS")
public static let SWIFT_VERSION: Declaration = .init("SWIFT_VERSION")

// C family.
public static let GCC_PREPROCESSOR_DEFINITIONS: Declaration = .init("GCC_PREPROCESSOR_DEFINITIONS")
Expand Down Expand Up @@ -47,18 +48,23 @@ public enum BuildSettings {
/// The condition associated with this assignment.
public var conditions: [PackageCondition] {
get {
return _conditions.map { $0.underlying }
self._conditions.map(\.underlying)
}
set {
_conditions = newValue.map { PackageConditionWrapper($0) }
self._conditions = newValue.map { PackageConditionWrapper($0) }
}
}

private var _conditions: [PackageConditionWrapper]

public init() {
/// Indicates whether this assignment represents a default
/// that should be used only if no other assignments match.
public let `default`: Bool

public init(default: Bool = false) {
self._conditions = []
self.values = []
self.default = `default`
}
}

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

public init() {
assignments = [:]
self.assignments = [:]
}

/// Add the given assignment to the table.
mutating public func add(_ assignment: Assignment, for decl: Declaration) {
public mutating func add(_ assignment: Assignment, for decl: Declaration) {
// FIXME: We should check for duplicate assignments.
assignments[decl, default: []].append(assignment)
self.assignments[decl, default: []].append(assignment)
}
}

Expand All @@ -100,12 +106,18 @@ public enum BuildSettings {
}

// Add values from each assignment if it satisfies the build environment.
let values = assignments
let allViableAssignments = assignments
.lazy
.filter { $0.conditions.allSatisfy { $0.satisfies(self.environment) } }
.flatMap { $0.values }

return Array(values)
let nonDefaultAssignments = allViableAssignments.filter { !$0.default }

// If there are no non-default assignments, let's fallback to defaults.
if nonDefaultAssignments.isEmpty {
return allViableAssignments.filter(\.default).flatMap(\.values)
}

return nonDefaultAssignments.flatMap(\.values)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

/// A namespace for target-specific build settings.
public enum TargetBuildSettingDescription {

/// The tool for which a build setting is declared.
public enum Tool: String, Codable, Hashable, CaseIterable, Sendable {
case c
Expand Down Expand Up @@ -40,12 +39,15 @@ public enum TargetBuildSettingDescription {

case unsafeFlags([String])

case swiftLanguageVersion(SwiftLanguageVersion)

public var isUnsafeFlags: Bool {
switch self {
case .unsafeFlags(let flags):
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
return !flags.isEmpty
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, .enableUpcomingFeature, .enableExperimentalFeature:
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode,
.enableUpcomingFeature, .enableExperimentalFeature, .swiftLanguageVersion:
return false
}
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/PackageModel/ManifestSourceGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,12 @@ fileprivate extension SourceCodeFragment {
params.append(SourceCodeFragment(from: condition))
}
self.init(enum: setting.kind.name, subnodes: params)
case .swiftLanguageVersion(let version):
params.append(SourceCodeFragment(from: version))
if let condition = setting.condition {
params.append(SourceCodeFragment(from: condition))
}
self.init(enum: setting.kind.name, subnodes: params)
}
}
}
Expand Down Expand Up @@ -677,6 +683,8 @@ extension TargetBuildSettingDescription.Kind {
return "enableUpcomingFeature"
case .enableExperimentalFeature:
return "enableExperimentalFeature"
case .swiftLanguageVersion:
return "swiftLanguageVersion"
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/PackageModel/SwiftLanguageVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ public struct SwiftLanguageVersion: Hashable, Sendable {
/// Swift language version 5.
public static let v5 = SwiftLanguageVersion(uncheckedString: "5")

/// Swift language version 6.
public static let v6 = SwiftLanguageVersion(uncheckedString: "6")

/// The list of known Swift language versions.
public static let knownSwiftLanguageVersions = [
v3, v4, v4_2, v5,
v3, v4, v4_2, v5, v6
]

/// The raw value of the language version.
Expand Down
Loading