Skip to content

[SE-0362] Add enableUpcomingFeature and enableExperimentalFeature Swift settings #5632

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 3 commits into from
Dec 2, 2022
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
49 changes: 49 additions & 0 deletions Sources/PackageDescription/BuildSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,55 @@ public struct SwiftSetting: Encodable {
public static func unsafeFlags(_ flags: [String], _ condition: BuildSettingCondition? = nil) -> SwiftSetting {
return SwiftSetting(name: "unsafeFlags", value: flags, condition: condition)
}

/// Enable an upcoming feature with the given name.
///
/// An upcoming feature is one that has been accepted into Swift as of a
/// certain language version, but is not available by default in prior
/// language modes because it has some impact on source compatibility.
///
/// Multiple upcoming features can be added to a given target, and can
/// be used in a target without affecting its dependencies. An unknown
/// upcoming feature will be ignored by the implementation.
///
/// - Since: First available in PackageDescription 5.8.
///
/// - Parameters:
/// - name: The name of the upcoming feature, e.g., ConciseMagicFile.
/// - condition: A condition that restricts the application of the build
/// setting.
@available(_PackageDescription, introduced: 5.8)
public static func enableUpcomingFeature(
_ name: String,
_ condition: BuildSettingCondition? = nil
) -> SwiftSetting {
return SwiftSetting(
name: "upcomingFeatures", value: [name], condition: condition)
}

/// Enable an experimental feature with the given name.
///
/// An experimental feature is one that is in development, but
/// has not been accepted into Swift as a language feature.
///
/// Multiple experimental features can be added to a given target, and can
/// be used in a target without affecting its dependencies. An unknown
/// experimental feature will be ignored by the implementation.
///
/// - Since: First available in PackageDescription 5.8.
///
/// - Parameters:
/// - name: The name of the experimental feature, e.g., VariadicGenerics.
/// - condition: A condition that restricts the application of the build
/// setting.
@available(_PackageDescription, introduced: 5.8)
public static func enableExperimentalFeature(
_ name: String,
_ condition: BuildSettingCondition? = nil
) -> SwiftSetting {
return SwiftSetting(
name: "experimentalFeatures", value: [name], condition: condition)
}
}

/// A linker build setting.
Expand Down
4 changes: 4 additions & 0 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ enum ManifestJSONParser {
kind = .linkedFramework(value)
case "unsafeFlags":
kind = .unsafeFlags(values)
case "upcomingFeatures":
kind = .upcomingFeatures(values)
case "experimentalFeatures":
kind = .experimentalFeatures(values)
default:
throw InternalError("invalid build setting \(name)")
}
Expand Down
44 changes: 44 additions & 0 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,30 @@ public final class PackageBuilder {
case .linker:
decl = .OTHER_LDFLAGS
}

case .upcomingFeatures(let _values):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports future features")

case .swift:
decl = .OTHER_SWIFT_FLAGS
}

values = _values.precedeElements(with: "-enable-future-feature")

case .experimentalFeatures(let _values):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError(
"only Swift supports experimental features")

case .swift:
decl = .OTHER_SWIFT_FLAGS
}

values = _values.precedeElements(
with: "-enable-experimental-feature")
Comment on lines +1002 to +1003
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think precedeElements(with:) is needed, if the new APIs are implemented like the existing .define API, which only stores a single value.

case .enableExperimentalFeature(let value):
    values = ["-enable-experimental-feature", value]
    switch setting.tool {

}

// Create an assignment for this setting.
Expand Down Expand Up @@ -1455,3 +1479,23 @@ extension PackageBuilder {
}
}
}

fileprivate extension Sequence {
/// Construct a new array where each of the elements in the \c self
/// sequence is preceded by the \c prefixElement.
///
/// For example:
/// ```
/// ["Alice", "Bob", "Charlie"].precedeElements(with: "Hi")
/// ```
///
/// produces `["Hi", "Alice", "Hi", "Bob", "Hi", "Charlie"]`.
func precedeElements(with prefixElement: Element) -> [Element] {
var results: [Element] = []
for element in self {
results.append(prefixElement)
results.append(element)
}
return results
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public enum TargetBuildSettingDescription {
case linkedFramework(String)

case unsafeFlags([String])
case upcomingFeatures([String])
case experimentalFeatures([String])
}

/// An individual build setting.
Expand Down
6 changes: 5 additions & 1 deletion Sources/PackageModel/ManifestSourceGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ fileprivate extension SourceCodeFragment {
params.append(SourceCodeFragment(from: condition))
}
self.init(enum: setting.kind.name, subnodes: params)
case .unsafeFlags(let values):
case .unsafeFlags(let values), .upcomingFeatures(let values), .experimentalFeatures(let values):
params.append(SourceCodeFragment(strings: values))
if let condition = setting.condition {
params.append(SourceCodeFragment(from: condition))
Expand Down Expand Up @@ -639,6 +639,10 @@ extension TargetBuildSettingDescription.Kind {
return "linkedFramework"
case .unsafeFlags:
return "unsafeFlags"
case .upcomingFeatures:
return "upcomingFeatures"
case .experimentalFeatures:
return "experimentalFeatures"
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions Tests/BuildTests/BuildPlanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3219,6 +3219,8 @@ final class BuildPlanTests: XCTestCase {
.init(tool: .swift, kind: .define("RLINUX"), condition: .init(platformNames: ["linux"], config: "release")),
.init(tool: .swift, kind: .define("DMACOS"), condition: .init(platformNames: ["macos"], config: "debug")),
.init(tool: .swift, kind: .unsafeFlags(["-Isfoo", "-L", "sbar"])),
.init(tool: .swift, kind: .upcomingFeatures(["BestFeature"])),
.init(tool: .swift, kind: .upcomingFeatures(["WorstFeature"]), condition: .init(platformNames: ["macos"], config: "debug"))
]
),
try TargetDescription(
Expand Down Expand Up @@ -3283,7 +3285,7 @@ final class BuildPlanTests: XCTestCase {
XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", .end])

let bar = try result.target(for: "bar").swiftTarget().compileArguments()
XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", .end])
XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-enable-future-feature", "BestFeature", .end])

let exe = try result.target(for: "exe").swiftTarget().compileArguments()
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])
Expand All @@ -3299,7 +3301,7 @@ final class BuildPlanTests: XCTestCase {
XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", .end])

let bar = try result.target(for: "bar").swiftTarget().compileArguments()
XCTAssertMatch(bar, [.anySequence, "-DDMACOS", "-Isfoo", "-L", "sbar", .end])
XCTAssertMatch(bar, [.anySequence, "-DDMACOS", "-Isfoo", "-L", "sbar", "-enable-future-feature", "BestFeature", "-enable-future-feature", "WorstFeature", .end])

let exe = try result.target(for: "exe").swiftTarget().compileArguments()
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])
Expand Down