Skip to content

Commit ef2934d

Browse files
authored
[SE-0362] Add enableUpcomingFeature and enableExperimentalFeature Swift settings (#5632)
1 parent ca07c27 commit ef2934d

File tree

6 files changed

+108
-3
lines changed

6 files changed

+108
-3
lines changed

Sources/PackageDescription/BuildSettings.swift

+49
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,55 @@ public struct SwiftSetting: Encodable {
296296
public static func unsafeFlags(_ flags: [String], _ condition: BuildSettingCondition? = nil) -> SwiftSetting {
297297
return SwiftSetting(name: "unsafeFlags", value: flags, condition: condition)
298298
}
299+
300+
/// Enable an upcoming feature with the given name.
301+
///
302+
/// An upcoming feature is one that has been accepted into Swift as of a
303+
/// certain language version, but is not available by default in prior
304+
/// language modes because it has some impact on source compatibility.
305+
///
306+
/// Multiple upcoming features can be added to a given target, and can
307+
/// be used in a target without affecting its dependencies. An unknown
308+
/// upcoming feature will be ignored by the implementation.
309+
///
310+
/// - Since: First available in PackageDescription 5.8.
311+
///
312+
/// - Parameters:
313+
/// - name: The name of the upcoming feature, e.g., ConciseMagicFile.
314+
/// - condition: A condition that restricts the application of the build
315+
/// setting.
316+
@available(_PackageDescription, introduced: 5.8)
317+
public static func enableUpcomingFeature(
318+
_ name: String,
319+
_ condition: BuildSettingCondition? = nil
320+
) -> SwiftSetting {
321+
return SwiftSetting(
322+
name: "upcomingFeatures", value: [name], condition: condition)
323+
}
324+
325+
/// Enable an experimental feature with the given name.
326+
///
327+
/// An experimental feature is one that is in development, but
328+
/// has not been accepted into Swift as a language feature.
329+
///
330+
/// Multiple experimental features can be added to a given target, and can
331+
/// be used in a target without affecting its dependencies. An unknown
332+
/// experimental feature will be ignored by the implementation.
333+
///
334+
/// - Since: First available in PackageDescription 5.8.
335+
///
336+
/// - Parameters:
337+
/// - name: The name of the experimental feature, e.g., VariadicGenerics.
338+
/// - condition: A condition that restricts the application of the build
339+
/// setting.
340+
@available(_PackageDescription, introduced: 5.8)
341+
public static func enableExperimentalFeature(
342+
_ name: String,
343+
_ condition: BuildSettingCondition? = nil
344+
) -> SwiftSetting {
345+
return SwiftSetting(
346+
name: "experimentalFeatures", value: [name], condition: condition)
347+
}
299348
}
300349

301350
/// A linker build setting.

Sources/PackageLoading/ManifestJSONParser.swift

+4
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ enum ManifestJSONParser {
425425
kind = .linkedFramework(value)
426426
case "unsafeFlags":
427427
kind = .unsafeFlags(values)
428+
case "upcomingFeatures":
429+
kind = .upcomingFeatures(values)
430+
case "experimentalFeatures":
431+
kind = .experimentalFeatures(values)
428432
default:
429433
throw InternalError("invalid build setting \(name)")
430434
}

Sources/PackageLoading/PackageBuilder.swift

+44
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,30 @@ public final class PackageBuilder {
977977
case .linker:
978978
decl = .OTHER_LDFLAGS
979979
}
980+
981+
case .upcomingFeatures(let _values):
982+
switch setting.tool {
983+
case .c, .cxx, .linker:
984+
throw InternalError("only Swift supports future features")
985+
986+
case .swift:
987+
decl = .OTHER_SWIFT_FLAGS
988+
}
989+
990+
values = _values.precedeElements(with: "-enable-future-feature")
991+
992+
case .experimentalFeatures(let _values):
993+
switch setting.tool {
994+
case .c, .cxx, .linker:
995+
throw InternalError(
996+
"only Swift supports experimental features")
997+
998+
case .swift:
999+
decl = .OTHER_SWIFT_FLAGS
1000+
}
1001+
1002+
values = _values.precedeElements(
1003+
with: "-enable-experimental-feature")
9801004
}
9811005

9821006
// Create an assignment for this setting.
@@ -1455,3 +1479,23 @@ extension PackageBuilder {
14551479
}
14561480
}
14571481
}
1482+
1483+
fileprivate extension Sequence {
1484+
/// Construct a new array where each of the elements in the \c self
1485+
/// sequence is preceded by the \c prefixElement.
1486+
///
1487+
/// For example:
1488+
/// ```
1489+
/// ["Alice", "Bob", "Charlie"].precedeElements(with: "Hi")
1490+
/// ```
1491+
///
1492+
/// produces `["Hi", "Alice", "Hi", "Bob", "Hi", "Charlie"]`.
1493+
func precedeElements(with prefixElement: Element) -> [Element] {
1494+
var results: [Element] = []
1495+
for element in self {
1496+
results.append(prefixElement)
1497+
results.append(element)
1498+
}
1499+
return results
1500+
}
1501+
}

Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public enum TargetBuildSettingDescription {
2929
case linkedFramework(String)
3030

3131
case unsafeFlags([String])
32+
case upcomingFeatures([String])
33+
case experimentalFeatures([String])
3234
}
3335

3436
/// An individual build setting.

Sources/PackageModel/ManifestSourceGeneration.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ fileprivate extension SourceCodeFragment {
487487
params.append(SourceCodeFragment(from: condition))
488488
}
489489
self.init(enum: setting.kind.name, subnodes: params)
490-
case .unsafeFlags(let values):
490+
case .unsafeFlags(let values), .upcomingFeatures(let values), .experimentalFeatures(let values):
491491
params.append(SourceCodeFragment(strings: values))
492492
if let condition = setting.condition {
493493
params.append(SourceCodeFragment(from: condition))
@@ -639,6 +639,10 @@ extension TargetBuildSettingDescription.Kind {
639639
return "linkedFramework"
640640
case .unsafeFlags:
641641
return "unsafeFlags"
642+
case .upcomingFeatures:
643+
return "upcomingFeatures"
644+
case .experimentalFeatures:
645+
return "experimentalFeatures"
642646
}
643647
}
644648
}

Tests/BuildTests/BuildPlanTests.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,8 @@ final class BuildPlanTests: XCTestCase {
32193219
.init(tool: .swift, kind: .define("RLINUX"), condition: .init(platformNames: ["linux"], config: "release")),
32203220
.init(tool: .swift, kind: .define("DMACOS"), condition: .init(platformNames: ["macos"], config: "debug")),
32213221
.init(tool: .swift, kind: .unsafeFlags(["-Isfoo", "-L", "sbar"])),
3222+
.init(tool: .swift, kind: .upcomingFeatures(["BestFeature"])),
3223+
.init(tool: .swift, kind: .upcomingFeatures(["WorstFeature"]), condition: .init(platformNames: ["macos"], config: "debug"))
32223224
]
32233225
),
32243226
try TargetDescription(
@@ -3283,7 +3285,7 @@ final class BuildPlanTests: XCTestCase {
32833285
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])
32843286

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

32883290
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
32893291
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])
@@ -3299,7 +3301,7 @@ final class BuildPlanTests: XCTestCase {
32993301
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])
33003302

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

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

0 commit comments

Comments
 (0)