Skip to content

Commit 11f0e9e

Browse files
committedMar 26, 2025
Warning Control Settings for SwiftPM
1 parent 089e543 commit 11f0e9e

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed
 
+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# Warning Control Settings for SwiftPM
2+
3+
* Proposal: [SE-NNNN](NNNN-swiftpm-warning-control.md)
4+
* Authors: [Dmitrii Galimzianov](https://github.com/DmT021)
5+
* Review Manager: TBD
6+
* Status: **Awaiting review**
7+
* Implementation: [swiftlang/swift-package-manager#8315](https://github.com/swiftlang/swift-package-manager/pull/8315)
8+
* Review: ([Pitch](https://forums.swift.org/t/pitch-warning-control-settings-for-swiftpm/78666))
9+
* Previous Proposal: [SE-0443](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0443-warning-control-flags.md)
10+
11+
## Introduction
12+
13+
This proposal adds new settings to SwiftPM to control how the Swift, C, and C++ compilers treat warnings during the build process. It builds on [SE-0443](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0443-warning-control-flags.md), which introduced warning control flags for the Swift compiler but left SwiftPM support as a future direction.
14+
15+
## Motivation
16+
17+
The Swift Package Manager currently lacks a unified way to control warnings across Swift, C, and C++ compilation. This limitation forces developers to either use `unsafeFlags` or accept the default warning settings.
18+
19+
## Proposed solution
20+
21+
This proposal introduces new methods to SwiftPM's build settings API, allowing fine-grained control over warnings.
22+
23+
### API
24+
25+
#### Cross-language API (Swift, C, and C++)
26+
27+
```swift
28+
/// The level at which a compiler warning should be treated.
29+
public enum WarningLevel: String {
30+
/// Treat as a warning.
31+
///
32+
/// Warnings will be displayed during compilation but will not cause the build to fail.
33+
case warning
34+
35+
/// Treat as an error.
36+
///
37+
/// Warnings will be elevated to errors, causing the build to fail if any such warnings occur.
38+
case error
39+
}
40+
41+
public static func treatAllWarnings(
42+
as level: WarningLevel,
43+
_ condition: BuildSettingCondition? = nil
44+
) -> SwiftSetting // or CSetting or CXXSetting
45+
46+
public static func treatWarning(
47+
_ name: String,
48+
as level: WarningLevel,
49+
_ condition: BuildSettingCondition? = nil
50+
) -> SwiftSetting // or CSetting or CXXSetting
51+
```
52+
53+
#### C/C++-specific API
54+
55+
In C/C++ targets, we can also enable or disable specific warning groups, in addition to controlling their severity.
56+
57+
```swift
58+
public static func enableWarning(
59+
_ name: String,
60+
_ condition: BuildSettingCondition? = nil
61+
) -> CSetting // or CXXSetting
62+
63+
public static func disableWarning(
64+
_ name: String,
65+
_ condition: BuildSettingCondition? = nil
66+
) -> CSetting // or CXXSetting
67+
```
68+
_The necessity of these functions is also explained below in the Alternatives considered section._
69+
70+
### Example usage
71+
72+
```swift
73+
.target(
74+
name: "MyLib",
75+
swiftSettings: [
76+
.treatAllWarnings(as: .error),
77+
.treatWarning("DeprecatedDeclaration", as: .warning),
78+
],
79+
cSettings: [
80+
.enableWarning("all"),
81+
.disableWarning("unused-function"),
82+
83+
.treatAllWarnings(as: .error),
84+
.treatWarning("unused-variable", as: .warning),
85+
],
86+
cxxSettings: [
87+
.enableWarning("all"),
88+
.disableWarning("unused-function"),
89+
90+
.treatAllWarnings(as: .error),
91+
.treatWarning("unused-variable", as: .warning),
92+
]
93+
)
94+
```
95+
96+
## Detailed design
97+
98+
### Settings and their corresponding compiler flags
99+
100+
| Method | Swift | C/C++ |
101+
|--------|-------|-------|
102+
| `treatAllWarnings(as: .error)` | `-warnings-as-errors` | `-Werror` |
103+
| `treatAllWarnings(as: .warning)` | `-no-warnings-as-errors` | `-Wno-error` |
104+
| `treatWarning("XXXX", as: .error)` | `-Werror XXXX` | `-Werror=XXXX` |
105+
| `treatWarning("XXXX", as: .warning)` | `-Wwarning XXXX` | `-Wno-error=XXXX` |
106+
| `enableWarning("XXXX")` | N/A | `-WXXXX` |
107+
| `disableWarning("XXXX")` | N/A | `-Wno-XXXX` |
108+
109+
### Order of settings evaluation
110+
111+
The order in which warning control settings are specified in a target's settings array directly affects the order of the resulting compiler flags. This is critical because when multiple flags affect the same warning group, compilers apply them sequentially with the last flag taking precedence.
112+
113+
For example, consider these two different orderings for C++ settings:
114+
115+
```swift
116+
// Example 1: "unused-variable" in front of "unused"
117+
cxxSettings: [
118+
.treatWarning("unused-variable", as: .error),
119+
.treatWarning("unused", as: .warning),
120+
]
121+
122+
// Example 2: "unused" in front of "unused-variable"
123+
cxxSettings: [
124+
.treatWarning("unused", as: .warning),
125+
.treatWarning("unused-variable", as: .error),
126+
]
127+
```
128+
129+
In Example 1, the compiler will receive flags in this order:
130+
```
131+
-Werror=unused-variable -Wno-error=unused
132+
```
133+
Since "unused-variable" is a specific subgroup of the broader "unused" group, and the "unused" flag is applied last, all unused warnings (including unused-variable) will be treated as warnings.
134+
135+
In Example 2, the compiler will receive flags in this order:
136+
```
137+
-Wno-error=unused -Werror=unused-variable
138+
```
139+
Due to the "last one wins" rule, unused-variable warnings will be treated as errors, while other unused warnings remain as warnings.
140+
141+
The same principle applies when combining any of the new build settings:
142+
143+
```swift
144+
cxxSettings: [
145+
.enableWarning("all"), // Enable the "all" warning group
146+
.enableWarning("extra"), // Enable the "extra" warning group
147+
.disableWarning("unused-parameter"), // Disable the "unused-parameter" warning group
148+
.treatAllWarnings(as: .error), // Treat all warnings as errors
149+
.treatWarning("unused", as: .warning), // Keep warnings of the "unused" group as warnings
150+
]
151+
```
152+
153+
This will result in compiler flags:
154+
```
155+
-Wall -Wextra -Wno-unused-parameter -Werror -Wno-error=unused
156+
```
157+
158+
When configuring warnings, be mindful of the order to achieve the desired behavior.
159+
160+
### Remote targets behavior
161+
162+
When a target is remote (pulled from a package dependency rather than defined in the local package), the warning control settings specified in the manifest do not apply to it. SwiftPM will strip all of the warning control flags for remote targets and substitute them with options for suppressing warnings (`-w` for Clang and `-suppress-warnings` for Swift).
163+
164+
This behavior is already in place but takes into account only `-warnings-as-errors` (for Swift) and `-Werror` (for Clang) flags. We expand this list to include the following warning-related flags:
165+
166+
**For C/C++:**
167+
* `-Wxxxx`
168+
* `-Wno-xxxx`
169+
* `-Werror`
170+
* `-Werror=xxxx`
171+
* `-Wno-error`
172+
* `-Wno-error=xxxx`
173+
174+
**For Swift:**
175+
* `-warnings-as-errors`
176+
* `-no-warnings-as-errors`
177+
* `-Wwarning xxxx`
178+
* `-Werror xxxx`
179+
180+
This approach ensures that warning control settings are applied only to the targets you directly maintain in your package, while dependencies remain buildable without warnings regardless of their warning settings.
181+
182+
### Interaction with command-line flags
183+
184+
SwiftPM allows users to pass additional flags to the compilers using the `-Xcc`, `-Xswiftc`, and `-Xcxx` options with the `swift build` command. These flags are appended **after** the flags generated from the package manifest.
185+
186+
This ordering enables users to modify or override package-defined warning settings without modifying the package manifest.
187+
188+
#### Example
189+
190+
```swift
191+
let package = Package(
192+
name: "MyExecutable",
193+
targets: [
194+
// C target with warning settings
195+
.target(
196+
name: "cfoo",
197+
cSettings: [
198+
.enableWarning("all"),
199+
.treatAllWarnings(as: .error),
200+
.treatWarning("unused-variable", as: .warning),
201+
]
202+
),
203+
// Swift target with warning settings
204+
.executableTarget(
205+
name: "swiftfoo",
206+
swiftSettings: [
207+
.treatAllWarnings(as: .error),
208+
.treatWarning("DeprecatedDeclaration", as: .warning),
209+
]
210+
),
211+
]
212+
)
213+
```
214+
215+
When built with additional command-line flags:
216+
217+
```sh
218+
swift build -Xcc -Wno-error -Xswiftc -no-warnings-as-errors
219+
```
220+
221+
The resulting compiler invocations will include both sets of flags:
222+
223+
```
224+
# C compiler invocation
225+
clang ... -Wall -Werror -Wno-error=unused-variable ... -Wno-error ...
226+
227+
# Swift compiler invocation
228+
swiftc ... -warnings-as-errors -Wwarning DeprecatedDeclaration ... -no-warnings-as-errors -Xcc -Wno-error ...
229+
```
230+
231+
Flags are processed from left to right, and since `-no-warnings-as-errors` and `-Wno-error` apply globally to all warnings, they override the warning treating flags defined in the package manifest.
232+
233+
#### Limitations
234+
235+
This approach has a limitation when used with `-suppress-warnings`, which is mutually exclusive with other warning control flags:
236+
237+
```sh
238+
swift build -Xswiftc -suppress-warnings
239+
```
240+
241+
Results in compiler errors:
242+
243+
```
244+
error: conflicting options '-warnings-as-errors' and '-suppress-warnings'
245+
error: conflicting options '-Wwarning' and '-suppress-warnings'
246+
```
247+
248+
249+
## Security
250+
251+
This change has no impact on security, safety, or privacy.
252+
253+
## Impact on existing packages
254+
255+
The proposed API will only be available to packages that specify a tools version equal to or later than the SwiftPM version in which this functionality is implemented.
256+
257+
## Alternatives considered
258+
259+
### Disabling a warning via a treat level
260+
261+
Clang allows users to completely disable a specific warning, so for C/C++ settings we could implement that as a new case in the `WarningLevel` enum:
262+
263+
```swift
264+
public enum WarningLevel {
265+
case warning
266+
case error
267+
case ignored
268+
}
269+
```
270+
271+
_(Since Swift doesn't allow selective warning suppression, we would actually have to split the enum into two: `SwiftWarningLevel` and `CFamilyWarningLevel`)_
272+
273+
But some warnings in Clang are disabled by default. If we simply pass `-Wno-error=unused-variable`, the compiler won't actually produce a warning for an unused variable. It only makes sense to use it if we have enabled the warning: `-Wunused-variable -Werror -Wno-error=unused-variable`.
274+
275+
This necessitates separate functions to enable and disable warnings. Therefore, instead of `case ignored`, we propose the functions `enableWarning` and `disableWarning`.
276+
277+
## Future directions
278+
279+
### Package-level settings
280+
281+
It has been noted that warning control settings are often similar across all targets. It makes sense to declare them at the package level while allowing target-level customizations. However, many other settings would also likely benefit from such inheritance, and SwiftPM doesn't currently provide such an option. Therefore, it was decided to factor this improvement out and look at all the settings holistically in the future.
282+
283+
## Acknowledgments
284+
285+
Thank you to [Doug Gregor](https://github.com/douggregor) for the motivation, and to both [Doug Gregor](https://github.com/douggregor) and [Holly Borla](https://github.com/hborla) for their guidance during the implementation of this API.

0 commit comments

Comments
 (0)
Please sign in to comment.