diff --git a/Sources/PackageDescription/BuildSettings.swift b/Sources/PackageDescription/BuildSettings.swift index 84516838ca7..d73b72547cf 100644 --- a/Sources/PackageDescription/BuildSettings.swift +++ b/Sources/PackageDescription/BuildSettings.swift @@ -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. diff --git a/Sources/PackageLoading/ManifestJSONParser.swift b/Sources/PackageLoading/ManifestJSONParser.swift index fb116469708..76497dd2d5c 100644 --- a/Sources/PackageLoading/ManifestJSONParser.swift +++ b/Sources/PackageLoading/ManifestJSONParser.swift @@ -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)") } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 001f30bd578..146b54302e3 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -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") } // Create an assignment for this setting. @@ -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 + } +} diff --git a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift index cdc30b4789f..9d01368230b 100644 --- a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift +++ b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift @@ -29,6 +29,8 @@ public enum TargetBuildSettingDescription { case linkedFramework(String) case unsafeFlags([String]) + case upcomingFeatures([String]) + case experimentalFeatures([String]) } /// An individual build setting. diff --git a/Sources/PackageModel/ManifestSourceGeneration.swift b/Sources/PackageModel/ManifestSourceGeneration.swift index f957c339508..c906d8e6969 100644 --- a/Sources/PackageModel/ManifestSourceGeneration.swift +++ b/Sources/PackageModel/ManifestSourceGeneration.swift @@ -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)) @@ -639,6 +639,10 @@ extension TargetBuildSettingDescription.Kind { return "linkedFramework" case .unsafeFlags: return "unsafeFlags" + case .upcomingFeatures: + return "upcomingFeatures" + case .experimentalFeatures: + return "experimentalFeatures" } } } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 9cf9df3a92d..13fdb1b5e51 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -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( @@ -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]) @@ -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])