Skip to content
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

Build settings for warning treating rules (SE-0443, draft) #8315

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

DmT021
Copy link

@DmT021 DmT021 commented Feb 27, 2025

This change adds warning control settings. The flags for Swift targets are described by SE-0443, for Clang targets - here.

Common API for all three languages (Swift, C, and C++)

/// The level at which a compiler warning should be treated.
public enum WarningTreatLevel: String {
    /// Treat as a warning.
    ///
    /// Warnings will be displayed during compilation but will not cause the build to fail.
    case warning

    /// Treat as an error.
    ///
    /// Warnings will be elevated to errors, causing the build to fail if any such warnings occur.
    case error
}

public static func treatAllWarnings(
    as level: WarningTreatLevel,
    _ condition: BuildSettingCondition? = nil
) -> SwiftSetting // or CSetting or CXXSetting

public static func treatWarning(
    name: String,
    as level: WarningTreatLevel,
    _ condition: BuildSettingCondition? = nil
) -> SwiftSetting // or CSetting or CXXSetting

Warning control methods for C and C++ only

public static func enableWarning(
    name: String,
    _ condition: BuildSettingCondition? = nil
) -> CSetting // or CXXSetting

public static func disableWarning(
    name: String,
    _ condition: BuildSettingCondition? = nil
) -> CSetting // or CXXSetting

Settings and their corresponding compiler flags

Method Swift C/C++
treatAllWarnings(as: .error) -warnings-as-errors -Werror
treatAllWarnings(as: .warning) -no-warnings-as-errors -Wno-error
treatWarning(name: "XXXX", as: .error) -Werror XXXX -Werror=XXXX
treatWarning(name: "XXXX", as: .warning) -Wwarning XXXX -Wno-error=XXXX
enableWarning(name: "XXXX") N/A -WXXXX
disableWarning(name: "XXXX") N/A -Wno-XXXX

Notes

  • The order of settings is preserved when passing the flags to the compilers.
  • Warning control settings have no effect when a target being built is remote. SwiftPM will strip all of the warning control flags and substitute them with an option for suppressing warnings (-w for Clang and -suppress-warnings for Swift).

@DougGregor
Copy link
Member

@swift-ci test

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

Aside from the two places where we can go quadratic in the number of command-line arguments, this is looking good to me. But I'm not super familiar with the SwiftPM code base.

Comment on lines 367 to 378
// `-w` (suppress warnings) and the other warning control flags are mutually exclusive
for index in args.indices.reversed() {
let arg = args[index]
if arg.starts(with: "-W"), arg.count > 2 {
// we consider the following flags:
// -Wxxxx
// -Wno-xxxx
// -Werror
// -Werror=xxxx
// -Wno-error
// -Wno-error=xxxx
args.remove(at: index)
Copy link
Member

Choose a reason for hiding this comment

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

This loop could be a filter, which would make it linear-time rather than quadratic.

Comment on lines 626 to 639
for index in args.indices.reversed() {
let arg = args[index]
switch arg {
case "-warnings-as-errors", "-no-warnings-as-errors":
args.remove(at: index)
case "-Wwarning", "-Werror":
guard args.indices.contains(index + 1) else {
throw InternalError("Unexpected '\(arg)' at the end of args")
}
args.remove(at: index + 1)
args.remove(at: index)
default:
break
}
Copy link
Member

Choose a reason for hiding this comment

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

Also technically quadratic. Even though it's a little ugly, I think I'd still rather do it with a filter

var removeNextArg = false
args = args.filter { arg in
  if removeNextArg {
    removeNextArg = true
    return false
  }
  switch arg {
  case "-warnings-as-errors", "-no-warnings-as-errors":
    return false
  case "-Wwarning", "-Werror":
    removeNextArg = true
    return false
  default:
    return true
  }
}

Copy link
Author

@DmT021 DmT021 Mar 12, 2025

Choose a reason for hiding this comment

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

Yes, the quadratic complexity is an oversight on my part. Good thing you noticed.
TBH, using an external state in a filter makes me a bit uneasy. The documentation for filter doesn't guarantee that the predicate will be invoked in any particular order, so I'd try to avoid it.
I don't insist though. If you think a filter here is fine, I don't mind. But what do you think about something more "manual", e.g:

var readIndex = args.startIndex
var writeIndex = args.startIndex
let endIndex = args.endIndex
while readIndex < endIndex {
    let arg = args[readIndex]
    switch arg {
    case "-warnings-as-errors", "-no-warnings-as-errors":
        break
    case "-Wwarning", "-Werror":
        readIndex += 1  // -Wwarning and -Werror have an argument
        if readIndex >= endIndex {
            throw InternalError("Unexpected '\(arg)' at the end of args")
        }
    default:
        if readIndex != writeIndex {
            args[writeIndex] = args[readIndex]
        }
        writeIndex += 1
    }
    readIndex += 1
}
if writeIndex < endIndex {
    args.removeSubrange(writeIndex..<endIndex)
}

@dschaefer2
Copy link
Member

A couple of general questions. Placing these flags in the package manifest, means all consumers of this package will build with the same settings. I was just wondering if it's a concern that consumers may want to set them differently. For example, if they are specifying additional warning setting flags on the swift build command lines, which ones take precedence.

Also, as we're working on adopting Swift Build to replace our native build system, we would need to make sure this is handled there as well.

@dschaefer2
Copy link
Member

Mind you the pitch and evolution proposal predates my participation so maybe this has already been addressed. I'll need to catch up.

@DmT021
Copy link
Author

DmT021 commented Mar 12, 2025

I was just wondering if it's a concern that consumers may want to set them differently. For example, if they are specifying additional warning setting flags on the swift build command lines, which ones take precedence.

That's a good question. AFAIK, the additional flags (-Xcc, -Xcxx, -Xswiftc) are placed after the internally generated flags, but I didn't really check that. I'll dive into it later. If it's not the case, I think we should make it so. However, I need a confirmation from the maintainers that that's really the desired behavior.
If that's the guaranteed order of flags, then users will be able to either override or extend the warning treating rules. The rules are evaluated left to right in the compilers. So appending -[no-]warning-as-errors overrides everything that is earlier in the command line.

Also, as we're working on adopting Swift Build to replace our native build system, we would need to make sure this is handled there as well.

I've raised a discussion about that, but it seems like the Swift Build maintainers prefer a simpler approach to these flags. swiftlang/swift-build#248

Mind you the pitch and evolution proposal predates my participation so maybe this has already been addressed. I'll need to catch up.

The original proposal (SE-0443) doesn't address this API, but we're working on fixing that. We will probably write a new pitch.

@DmT021
Copy link
Author

DmT021 commented Mar 15, 2025

@swift-ci Please test

@dschaefer2
Copy link
Member

@swift-ci please test windows

@DmT021
Copy link
Author

DmT021 commented Mar 18, 2025

For example, if they are specifying additional warning setting flags on the swift build command lines, which ones take precedence.

Following up on this question. The additional arguments the used provided are placed after the ones SwiftPM generates.
The code responsible for this behavior has comments about that, which leads me to believe it was intentional.
For C/C++
For Swift

So if we consider the following Package.swift:

let package = Package(
    name: "MyExecutable",
    platforms: [
        .macOS(.v13),
    ],
    targets: [
        .target(
            name: "cfoo",
            cSettings: [
                .enableWarning(name: "all"),
                .disableWarning(name: "unused-parameter"),
                .treatAllWarnings(as: .error),
                .treatWarning(name: "unused-variable", as: .warning),
            ]
        ),
        .target(
            name: "cxxfoo",
            cxxSettings: [
                .enableWarning(name: "all"),
                .disableWarning(name: "unused-parameter"),
                .treatAllWarnings(as: .error),
                .treatWarning(name: "unused-variable", as: .warning),
            ]
        ),
        .executableTarget(
            name: "swiftfoo",
            swiftSettings: [
                .treatAllWarnings(as: .error),
                .treatWarning(name: "DeprecatedDeclaration", as: .warning),
            ]
        ),
    ]
)

And build it with additional arguments like this:

swift-build --very-verbose -Xcc -Wno-error -Xswiftc -no-warnings-as-errors

We will get the following invocations of the compilers:

For cfoo:
clang ... -Wall -Wno-unused-parameter -Werror -Wno-error=unused-variable ... -Wno-error ...
For cxxfoo:
clang ... -Wall -Wno-unused-parameter -Werror -Wno-error=unused-variable ... -Wno-error ...
For swiftfoo:
swiftc ... -warnings-as-errors -Wwarning DeprecatedDeclaration ... -no-warnings-as-errors -Xcc -Wno-error ...

Given that these flags are evaluated left-to-right it gives the user some control here at the build stage.
However, it doesn't compose well with -suppress-warnings, as it's mutually exclusive with the other flags:

swift-build --very-verbose -Xswiftc -suppress-warnings

swiftc ... -warnings-as-errors -Wwarning DeprecatedDeclaration ... -suppress-warnings ...

error: conflicting options '-warnings-as-errors' and '-suppress-warnings'
error: conflicting options '-Wwarning' and '-suppress-warnings'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants