From 36e87f56a239f612ee249bb4c11a3c4da35826e3 Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Tue, 12 May 2026 16:19:03 -0400 Subject: [PATCH 1/4] Initial draft --- proposals/0NNN-default-target-settings.md | 225 ++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 proposals/0NNN-default-target-settings.md diff --git a/proposals/0NNN-default-target-settings.md b/proposals/0NNN-default-target-settings.md new file mode 100644 index 0000000000..40b547bf51 --- /dev/null +++ b/proposals/0NNN-default-target-settings.md @@ -0,0 +1,225 @@ +# Default Target Settings + +* Proposal: [SE-0NNN](0NNN-default-target-sesttings.md) +* Authors: [Matt Massicotte](https://github.com/mattmassicotte) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Implementation: [swiftlang/swift-package-manager#10033](https://github.com/swiftlang/swift-package-manager/pull/10033) +* Review: ([pitch](https://forums.swift.org/t/default-package-swift-settings/71872)) + +## Introduction + +It is very common for packages to using the same settings flags across all their targets. +A built-in mechanism to apply these base settings +offers improved readability and convenience for package manifests. + +## Motivation + +The default SwiftPM package template generates a manifest with two targets: primary and test. +There are many examples of packages that go far beyond this default of two, +but single-target packages are quite rare. + +That same default template, as of Swift 6.4, +also includes the same setting for both of these targets. +Here's snippet of the code: + +```swift +let package = Package( + // ... + targets: [ + .target( + name: "MyPackage", + swiftSettings: [ + .enableUpcomingFeature("ApproachableConcurrency"), + ], + ), + .testTarget( + name: "MyPackageTests", + dependencies: ["MyPackage"], + swiftSettings: [ + .enableUpcomingFeature("ApproachableConcurrency"), + ], + ), + ] +) +``` + +This pattern comes up over and over again across the package ecosystem. +Selectively adopting upcoming language features across all targets is very common. +For the prototypical primary-test target pair, +duplicating one or two settings might not be ideal, but it is feasible. +For packages with many targets and/or complex manifest files, +it came become quite challenging to reason about the setting being applied. + +A common solution involves applying a constant array to each target. +This is sufficient as long as all targets use identical settings. +But, if a package author does happen to need slightly different settings for even +one target, additional logic needs to be introduced. + +```swift +let swiftSettings: [SwiftSetting] = [ + .enableUpcomingFeature("ApproachableConcurrency"), +] + +let package = Package( + // ... + targets: [ + .target( + name: "MyPackage", + swiftSettings: swiftSettings + [.enableUpcomingFeature("Lifetimes")], + ), + .testTarget( + name: "MyPackageTests", + dependencies: ["MyPackage"], + swiftSettings: swiftSettings, + ), + ] +) +``` + +These approaches are inconvenient, verbose, and error-prone. +And because of the subtleties that can arise from compiler behavior differences, +errors here can be particularly painful. + +## Proposed solution + +The desired configuration for the vast majority of package authors is the same. +Begin with a core list of settings that define baseline behaviors, +along with per-target refinements to that list, if needed. + +The package manifest API should provide a way to express this directly. + +## Detailed design + +There are two core components to this change. +The first is the ability to define and apply a base list of settings. +The second is a well-defined and reasonable merging strategy to handle per-target overrides. + +### Manifest APIs + +The `Package` class is extended to define a set of default settings: + +```swift +public final class Package { + // ... + + public var defaultSwiftSettings: Set + public var defaultCSettings: Set + public var defaultCXXSettings: Set + public var defaultLinkerSettings: Set + + public init( + name: String, + defaultLocalization: LanguageTag? = nil, + platforms: [SupportedPlatform]? = nil, + pkgConfig: String? = nil, + providers: [SystemPackageProvider]? = nil, + products: [Product] = [], + traits: Set = [], + dependencies: [Dependency] = [], + targets: [Target] = [], + swiftLanguageVersions: [SwiftVersion]? = nil, + defaultSwiftSettings: Set = [], + cLanguageStandard: CLanguageStandard? = nil, + defaultCSettings: Set = [], + cxxLanguageStandard: CXXLanguageStandard? = nil, + defaultCXXSettings: Set = [], + defaultLinkerSettings: Set = [] + ) +} +``` + +### Settings Resolution + +It is important that it be possible to control defaults on a per-target basis. +There is a bit of subtly here because this merge operation requires some special logic. + +Handling some of these is straightforward, because the setting can have only one possible value. +Examples of this are `interoperabilityMode`, `strictMemorySafety`, `swiftLanguageMode`, and `treatAllWarnings`. +In these cases, a value defined by the target takes precedence and can simply overwrite any default. + +Other settings are used to form a set of possible values. +Here, forming a union of all values can often make intuitive sense. +An example of this would be `headerSearchPath`. +The default could be used to apply a common path to all targets, +and then additional paths could be added targets as needed. +This strategy can be used for `headerSearchPath`, `linkedLibrary`, +`linkedFramework`, `enableUpcomingFeature`, and `enableExperimentalFeature`. + +A very similar approach can be used to merge the four settings that apply a named value. +The warnings controls work this way, and some examples can help visualize how it would work. + +|: Default |: Target |: Resolved | +|----------------------------------|--------------------------------|----------------------------------| +| `.treatWarning("Foo", .warning)` | - | ["Foo": .warning] | +| `.treatWarning("Foo", .warning)` | `.treatWarning("Foo", .error)` | ["Foo": .error] | +| `.treatWarning("Foo", .warning)` | `.treatWarning("Bar", .error)` | ["Foo": .warning, "Bar": .error] | +| `.enableWarning("Foo")` | - | ["Foo": .warning] | +| `.enableWarning("Foo")` | `.disableWarning("Foo") | [] | +| `.disableWarning("Foo")` | - | ["Foo": .disabled] | +| `.disableWarning("Foo")` | `.enableWarning("Foo")` | ["Foo": .warning] | + +The `define` property presents more of a challenge. +The argument to this setting is not really a truly free-form string, +but any merging could require at least some parsing. + +To side step this problem, `define` can be treated as a unconditional override. +It is true that this could potentially require some duplication across defaults and targets. +However, given the non-trivial nature, that seems much preferable to incorrect resolution. + +Here's a summary of the strategies by setting type: + +|: Setting |: Merge Strategy | +|-----------------------------|------------------| +| `headerSearchPath` | Union | +| `define` | Union | +| `linkedLibrary` | Union | +| `linkedFramework` | Union | +| `interoperabilityMode` | Override | +| `enableUpcomingFeature` | Union | +| `enableExperimentalFeature` | Union | +| `strictMemorySafety` | Override | +| `unsafeFlags` | Override | +| `swiftLanguageMode` | Override | +| `treatAllWarnings` | Override | +| `treatWarning` | Override by name | +| `enableWarning` | Override by name | +| `disableWarning` | Override by name | +| `defaultIsolation` | Override by name | + +### Adding Settings + +This change does impose a constraint on any new settings types added to SwiftPM. +New settings must carefully consider and implement an appropriate merge strategy. + +Given the breadth of existing settings, it seems likely that new settings will be able to use one of the existing approaches. +However, if it turns out that a reasonable merge is impossible, +a fallback should be to produce an error if such a conflict is detected. + +## Source compatibility + +Because this is a purely additive change, +there will be no impact on existing packages. + +## ABI compatibility + +This change does not have any effect on ABI. + +## Implications on adoption + +This change impacts manifest authors, but should have no effects at all on package consumers. +Authors will be able to adopt default settings freely without concern for compatibility. + +## Future directions + +The most obvious avenue for future work is more advanced merging strategies, +particularly for `define`. +Altering merging behavior, however, would be a source-incompatible change. + +## Alternatives considered + +? + +## Acknowledgments + +Max Desiatov provided some much-appreciated general guidance that helped get this idea off the ground. Boris G. immediately noticed and provided great feedback on the need for merging. From c6364d4c18e95cf52967a39a2b7fcf6d6db08997 Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Wed, 13 May 2026 06:03:12 -0400 Subject: [PATCH 2/4] Fix name in Acknowledgments --- proposals/0NNN-default-target-settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0NNN-default-target-settings.md b/proposals/0NNN-default-target-settings.md index 40b547bf51..3b3f69f1df 100644 --- a/proposals/0NNN-default-target-settings.md +++ b/proposals/0NNN-default-target-settings.md @@ -222,4 +222,4 @@ Altering merging behavior, however, would be a source-incompatible change. ## Acknowledgments -Max Desiatov provided some much-appreciated general guidance that helped get this idea off the ground. Boris G. immediately noticed and provided great feedback on the need for merging. +Max Desiatov provided some much-appreciated general guidance that helped get this idea off the ground. Boris Buegling immediately noticed and provided great feedback on the need for merging. From b5188115b260664cd725082dd1fbc9d83914efa7 Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Thu, 14 May 2026 10:58:11 -0400 Subject: [PATCH 3/4] Update to discuss inheritance --- proposals/0NNN-default-target-settings.md | 175 +++++++++++++--------- 1 file changed, 102 insertions(+), 73 deletions(-) diff --git a/proposals/0NNN-default-target-settings.md b/proposals/0NNN-default-target-settings.md index 3b3f69f1df..dadb23766b 100644 --- a/proposals/0NNN-default-target-settings.md +++ b/proposals/0NNN-default-target-settings.md @@ -92,8 +92,8 @@ The package manifest API should provide a way to express this directly. ## Detailed design There are two core components to this change. -The first is the ability to define and apply a base list of settings. -The second is a well-defined and reasonable merging strategy to handle per-target overrides. +The first is the ability to define and apply a base list of default settings. +The second is mechanism to control how these defaults apply on a per-target basis. ### Manifest APIs @@ -129,77 +129,95 @@ public final class Package { } ``` -### Settings Resolution +With this in place, the default package template could look like this: + +```swift +let package = Package( + // ... + targets: [ + .target( + name: "MyPackage" + ), + .testTarget( + name: "MyPackageTests", + dependencies: ["MyPackage"] + ), + ], + defaultSwiftSettings: [ + .enableUpcomingFeature("ApproachableConcurrency"), + ] +) +``` + +### Settings Inheritance It is important that it be possible to control defaults on a per-target basis. -There is a bit of subtly here because this merge operation requires some special logic. - -Handling some of these is straightforward, because the setting can have only one possible value. -Examples of this are `interoperabilityMode`, `strictMemorySafety`, `swiftLanguageMode`, and `treatAllWarnings`. -In these cases, a value defined by the target takes precedence and can simply overwrite any default. - -Other settings are used to form a set of possible values. -Here, forming a union of all values can often make intuitive sense. -An example of this would be `headerSearchPath`. -The default could be used to apply a common path to all targets, -and then additional paths could be added targets as needed. -This strategy can be used for `headerSearchPath`, `linkedLibrary`, -`linkedFramework`, `enableUpcomingFeature`, and `enableExperimentalFeature`. - -A very similar approach can be used to merge the four settings that apply a named value. -The warnings controls work this way, and some examples can help visualize how it would work. - -|: Default |: Target |: Resolved | -|----------------------------------|--------------------------------|----------------------------------| -| `.treatWarning("Foo", .warning)` | - | ["Foo": .warning] | -| `.treatWarning("Foo", .warning)` | `.treatWarning("Foo", .error)` | ["Foo": .error] | -| `.treatWarning("Foo", .warning)` | `.treatWarning("Bar", .error)` | ["Foo": .warning, "Bar": .error] | -| `.enableWarning("Foo")` | - | ["Foo": .warning] | -| `.enableWarning("Foo")` | `.disableWarning("Foo") | [] | -| `.disableWarning("Foo")` | - | ["Foo": .disabled] | -| `.disableWarning("Foo")` | `.enableWarning("Foo")` | ["Foo": .warning] | - -The `define` property presents more of a challenge. -The argument to this setting is not really a truly free-form string, -but any merging could require at least some parsing. - -To side step this problem, `define` can be treated as a unconditional override. -It is true that this could potentially require some duplication across defaults and targets. -However, given the non-trivial nature, that seems much preferable to incorrect resolution. - -Here's a summary of the strategies by setting type: - -|: Setting |: Merge Strategy | -|-----------------------------|------------------| -| `headerSearchPath` | Union | -| `define` | Union | -| `linkedLibrary` | Union | -| `linkedFramework` | Union | -| `interoperabilityMode` | Override | -| `enableUpcomingFeature` | Union | -| `enableExperimentalFeature` | Union | -| `strictMemorySafety` | Override | -| `unsafeFlags` | Override | -| `swiftLanguageMode` | Override | -| `treatAllWarnings` | Override | -| `treatWarning` | Override by name | -| `enableWarning` | Override by name | -| `disableWarning` | Override by name | -| `defaultIsolation` | Override by name | - -### Adding Settings - -This change does impose a constraint on any new settings types added to SwiftPM. -New settings must carefully consider and implement an appropriate merge strategy. - -Given the breadth of existing settings, it seems likely that new settings will be able to use one of the existing approaches. -However, if it turns out that a reasonable merge is impossible, -a fallback should be to produce an error if such a conflict is detected. +This is supported with a new `inherited` placeholder setting. +When setting are evaluated, this placeholder is substituted with the corresponding default values. + +Here are four possible target configurations that demonstrate the functionality. + +```swift +let package = Package( + // ... + targets: [ + .target( + name: "A", + ), + .target( + name: "B", + swiftSettings: [ + .inherited(), + ] + ), + .target( + name: "C", + swiftSettings: [ + ] + ), + .target( + name: "D", + swiftSettings: [ + .enableExperimentalFeature("Lifetimes"), + .inherited(), + ] + ), + ], + defaultSwiftSettings: [ + .defaultIsolation(MainActor.self), + ] +) +``` + +- Target `A`: `swiftSettings` is omitted, so defaults apply +- Target `B`: explicitly opts into inheriting the defaults +- Target `C`: defines settings without inheriting, no defaults are applied +- Target `D`: defines settings that control the order of the inheritance + +The behavior is identical for the `cSettings`, `cxxSettings`, and `linkerSettings` properties. + +For compatibility with conditional compilation, +empty default settings arrays are accepted and do not have any special meaning. + +### Restrictions + +There are two cases that present extra complexity. + +The `unsafeFlags` setting has special semantic meaning and plays an important role in dependency resolution. +Settings inheritance, even with its simplistic model, +makes the existing implementation more complex. +To avoid needing to update this logic, `unsafeFlags` are considered an invalid default. + +Another area of complexity is conditional inheritance. +Default settings can have conditions, just like regular target settings. +But the **inheritance** marker itself does not accept conditions. ## Source compatibility -Because this is a purely additive change, -there will be no impact on existing packages. +Because this is a purely additive change. + +It is worth noting that there is now a semantic difference between a target omitting a settings array and an empty array. +However, because this difference only matters when defaults are present, so it will not have any impact on existing package manifests. ## ABI compatibility @@ -212,14 +230,25 @@ Authors will be able to adopt default settings freely without concern for compat ## Future directions -The most obvious avenue for future work is more advanced merging strategies, -particularly for `define`. -Altering merging behavior, however, would be a source-incompatible change. +It would be useful to allow `unsafeFlags` in default settings. +Unsafe flags themselves already impose restrictions on package use, +so this helps to limit the impact. +But if this turns out to be an area of interest, +support can be added without any API changes. + +Conditional inheritance could also be something that package authors find useful. +And similarly, support for this can be added with the existing APIs. ## Alternatives considered -? +An earlier version of this proposal suggested an automatic, +predefined merging strategy without the `inherited` placeholder. + +With some settings, such as `defaultIsolation`, +the results of a merge seem quite unambiguous. +But, this is not the case for all value, and the merging logic can be involved. +The `inherited` mechanism is both intuitive and more powerful. ## Acknowledgments -Max Desiatov provided some much-appreciated general guidance that helped get this idea off the ground. Boris Buegling immediately noticed and provided great feedback on the need for merging. +Max Desiatov provided some much-appreciated general guidance that helped get this idea off the ground. Boris Buegling, Tony Allevato, Owen Voorhees, and Allen Humphreys all provided great feedback on the concept of inheritance. From a14cbbd80a0aaf7a22f8fad31727fb6a33130c07 Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Fri, 15 May 2026 07:00:22 -0400 Subject: [PATCH 4/4] Typos and some clarifications --- proposals/0NNN-default-target-settings.md | 81 ++++++++++++++++++----- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/proposals/0NNN-default-target-settings.md b/proposals/0NNN-default-target-settings.md index dadb23766b..deaacd0abe 100644 --- a/proposals/0NNN-default-target-settings.md +++ b/proposals/0NNN-default-target-settings.md @@ -1,6 +1,6 @@ # Default Target Settings -* Proposal: [SE-0NNN](0NNN-default-target-sesttings.md) +* Proposal: [SE-0NNN](0NNN-default-target-settings.md) * Authors: [Matt Massicotte](https://github.com/mattmassicotte) * Review Manager: TBD * Status: **Awaiting implementation** @@ -9,7 +9,7 @@ ## Introduction -It is very common for packages to using the same settings flags across all their targets. +It is very common for Swift packages to using the same settings flags across all their targets. A built-in mechanism to apply these base settings offers improved readability and convenience for package manifests. @@ -21,7 +21,7 @@ but single-target packages are quite rare. That same default template, as of Swift 6.4, also includes the same setting for both of these targets. -Here's snippet of the code: +Here's a snippet from the Package.swift file: ```swift let package = Package( @@ -77,7 +77,11 @@ let package = Package( ) ``` -These approaches are inconvenient, verbose, and error-prone. +And, all of this is just discussing how a uniform list of settings could be applied. +But there are packages that have much more complex requirements. +Typically, this requires at least some logic within the manifest file. + +These existing solutiuons are inconvenient, verbose, and error-prone. And because of the subtleties that can arise from compiler behavior differences, errors here can be particularly painful. @@ -85,7 +89,7 @@ errors here can be particularly painful. The desired configuration for the vast majority of package authors is the same. Begin with a core list of settings that define baseline behaviors, -along with per-target refinements to that list, if needed. +along with per-target refinements to that list as needed. The package manifest API should provide a way to express this directly. @@ -129,7 +133,41 @@ public final class Package { } ``` -With this in place, the default package template could look like this: +```swift +struct SwiftSettings { + // ... + + public static func inherited() -> SwiftSettings { + // ... + } +} + +struct CSettings { + // ... + + public static func inherited() -> CSettings { + // ... + } +} + +struct CXXSettings { + // ... + + public static func inherited() -> CXXSettings { + // ... + } +} + +struct LinkerSettings { + // ... + + public static func inherited() -> LinkerSettings { + // ... + } +} +``` + +With these changes in place, the default package template could look like this: ```swift let package = Package( @@ -199,25 +237,33 @@ The behavior is identical for the `cSettings`, `cxxSettings`, and `linkerSetting For compatibility with conditional compilation, empty default settings arrays are accepted and do not have any special meaning. +This inheritance mechanism matches the existing behaivor of the settings definition APIs. +This means that duplicates and invalid combinations are perimitted. +This situations are handled either by later stages of package validation or by the build tools themselves. +In many cases, this results in "last entry wins" semantics. + ### Restrictions -There are two cases that present extra complexity. +There are two cases that present extra complexity: unsafe flags and conditions. The `unsafeFlags` setting has special semantic meaning and plays an important role in dependency resolution. Settings inheritance, even with its simplistic model, -makes the existing implementation more complex. -To avoid needing to update this logic, `unsafeFlags` are considered an invalid default. +makes the existing implementation more complex and has nontrivial security implications. +To avoid needing to rework this logic, `unsafeFlags` are considered an invalid default +and are rejected during manifest validation. Another area of complexity is conditional inheritance. Default settings can have conditions, just like regular target settings. -But the **inheritance** marker itself does not accept conditions. +However, the `inherited` placeholder setting it self does not accept conditions. ## Source compatibility Because this is a purely additive change. -It is worth noting that there is now a semantic difference between a target omitting a settings array and an empty array. -However, because this difference only matters when defaults are present, so it will not have any impact on existing package manifests. +It is worth noting that there is now a semantic difference between +a target omitting a settings array and an empty array. +However, because this difference only matters when defaults are present, +it will not have any impact on existing package manifests. ## ABI compatibility @@ -230,11 +276,12 @@ Authors will be able to adopt default settings freely without concern for compat ## Future directions -It would be useful to allow `unsafeFlags` in default settings. +The two most obvious avenues for future work are supporting `unsafeFlags` and inheritance conditions. + Unsafe flags themselves already impose restrictions on package use, -so this helps to limit the impact. -But if this turns out to be an area of interest, -support can be added without any API changes. +so this helps to limit the impact of their omission. +But if this limitation turns out to be a problem for package authors, +support can be added in a source-compatible way. Conditional inheritance could also be something that package authors find useful. And similarly, support for this can be added with the existing APIs. @@ -246,7 +293,7 @@ predefined merging strategy without the `inherited` placeholder. With some settings, such as `defaultIsolation`, the results of a merge seem quite unambiguous. -But, this is not the case for all value, and the merging logic can be involved. +But, this is not the case for all values, and the merging logic can be involved. The `inherited` mechanism is both intuitive and more powerful. ## Acknowledgments