Swift Digest
SE-0480 | Swift Evolution

Warning Control Settings for SwiftPM

Proposal
SE-0480
Authors
Dmitrii Galimzianov
Review Manager
John McCall, Franz Busch
Status
Implemented (Swift 6.2)

01 何が問題だったのか

SE-0443 では、Swift コンパイラに対して警告の扱いを細かく制御するフラグ(-Werror <group> / -Wwarning <group> など)が導入されました。これにより、-warnings-as-errors を使いつつ特定の診断グループ(例: Deprecated)だけは警告のまま残す、といった運用がコンパイラレベルでは可能になりました。

一方で、SwiftPM のパッケージマニフェスト(Package.swift)からこれらのフラグを素直に指定する手段は用意されていませんでした。警告の扱いを細かく制御したい場合、パッケージ作者は次のどちらかを選ぶしかありませんでした。

  • unsafeFlags で生のコンパイラフラグを直接渡す(unsafeFlags が付いたターゲットは他パッケージから依存として使えなくなるという大きな制約があります)
  • SwiftPM のデフォルトの警告設定を受け入れる

また、Swift だけでなく C / C++ ターゲットについても、警告の有効化・無効化や「警告をエラーとして扱う」挙動を統一的に指定する API が存在しませんでした。結果として、「パッケージ全体で -warnings-as-errors を有効にしつつ、非推奨 API の警告だけは通常の警告のままにする」といったよくある要求を、unsafeFlags に頼らずに表現できない状態になっていました。

さらに、警告の扱いはあくまで そのパッケージを直接開発しているとき のポリシーであって、自分のパッケージを依存として取り込んだ利用側のビルドまで壊してしまうのは望ましくありません。依存側で勝手に -warnings-as-errors 相当の設定が効いてしまうと、依存ライブラリ側の新しい警告でビルドが落ちる、という問題が起きます。この「開発時の設定」と「依存として配られたときの挙動」の切り分けも、あわせて整理する必要がありました。

02 どのように解決されるのか

SwiftPM の SwiftSetting / CSetting / CXXSetting に、警告の扱いを制御するための新しい static メソッドを追加します。これにより、unsafeFlags を使わずにターゲット単位で警告の挙動を指定できるようになります。

警告レベルを表す WarningLevel

まず、警告をどのように扱うかを表す列挙型 WarningLevel が追加されます。

public enum WarningLevel: String {
    case warning
    case error
}

.warning は通常の警告として表示しビルドは継続、.error は警告をエラーに格上げしてビルドを失敗させます。

Swift / C / C++ 共通の API

SwiftSetting / CSetting / CXXSetting のいずれにも、次の2つのメソッドが追加されます。

extension SwiftSetting { // CSetting / CXXSetting も同じ形
    public static func treatAllWarnings(
        as level: WarningLevel,
        _ condition: BuildSettingCondition? = nil
    ) -> SwiftSetting

    public static func treatWarning(
        _ name: String,
        as level: WarningLevel,
        _ condition: BuildSettingCondition? = nil
    ) -> SwiftSetting
}

treatAllWarnings(as:) はすべての警告をまとめて扱いを切り替え、treatWarning(_:as:) は特定の警告グループ名(Swift なら SE-0443 の診断グループ名、C/C++ なら Clang の警告名)だけを対象にします。

C/C++ 専用の API

C/C++ では、警告グループを有効化・無効化するためのメソッドも追加されます。

extension CSetting { // CXXSetting も同じ形
    public static func enableWarning(
        _ name: String,
        _ condition: BuildSettingCondition? = nil
    ) -> CSetting

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

Swift 側に enableWarning / disableWarning が無いのは、Swift では警告をピンポイントで抑止する仕組みがないためです。C/C++ では Clang の警告がデフォルトで無効になっていることがあり、「そもそも有効化する」操作と「警告として出すかエラーに格上げするか」の操作を分ける必要があるため、別 API になっています。

利用例

.target(
    name: "MyLib",
    swiftSettings: [
        .treatAllWarnings(as: .error),
        .treatWarning("DeprecatedDeclaration", as: .warning),
    ],
    cSettings: [
        .enableWarning("all"),
        .disableWarning("unused-function"),
        .treatAllWarnings(as: .error),
        .treatWarning("unused-variable", as: .warning),
    ],
    cxxSettings: [
        .enableWarning("all"),
        .disableWarning("unused-function"),
        .treatAllWarnings(as: .error),
        .treatWarning("unused-variable", as: .warning),
    ]
)

Swift 側では、全警告をエラーに格上げしつつ、DeprecatedDeclaration グループだけは通常の警告のままにしています。C/C++ 側では -Wall 相当の警告を有効化したうえで unused-function を無効化し、さらに全警告をエラー扱いにしつつ unused-variable だけは警告として残す、という細かい指定ができます。

対応するコンパイラフラグ

それぞれのメソッドは、最終的に次のコンパイラフラグに展開されます。

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

指定の順序に意味がある

設定は配列に書いた順番でそのままコンパイラフラグの順番になります。複数のフラグが同じ警告に影響する場合、コンパイラは左から順に適用し 最後に指定されたものが勝つ(”last one wins”)ルールで解釈します。

たとえば C++ 設定で次の2パターンを比較すると、結果が変わります。

// パターン1: "unused-variable" を先に書く
cxxSettings: [
    .treatWarning("unused-variable", as: .error),
    .treatWarning("unused", as: .warning),
]
// 展開: -Werror=unused-variable -Wno-error=unused
// → "unused" がより広いグループで後勝ちになるため、
//   "unused-variable" も警告のままになる

// パターン2: "unused" を先に書く
cxxSettings: [
    .treatWarning("unused", as: .warning),
    .treatWarning("unused-variable", as: .error),
]
// 展開: -Wno-error=unused -Werror=unused-variable
// → "unused-variable" はエラー、その他の "unused" 系は警告

広めのグループから狭いグループへ上書きしていくか、逆にするかで効果が変わるので、書く順序に注意が必要です。

リモートターゲット(依存先)への扱い

ここで指定した警告設定は、そのパッケージを直接ビルドするとき にだけ適用されます。自分のパッケージが別のパッケージから依存として参照された場合、SwiftPM は上記の警告制御フラグを取り除き、代わりに警告を丸ごと抑止するフラグ(Clang なら -w、Swift なら -suppress-warnings)に置き換えてビルドします。

これにより、依存ライブラリが .treatAllWarnings(as: .error) のような厳しい設定を持っていたとしても、それを取り込んだ利用側のビルドが依存ライブラリ内の警告で落ちることはありません。警告制御設定はあくまで「開発しているパッケージ自身のコード品質を担保するためのもの」として働きます。

コマンドラインフラグとの関係

swift build には -Xswiftc / -Xcc / -Xcxx でコンパイラに追加フラグを渡すオプションがあります。これらのフラグはマニフェスト由来のフラグの 後ろ に付きます。つまり “last one wins” ルールにより、コマンドラインから渡した設定がマニフェストの設定を上書きできます。

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

のように実行すると、マニフェストで .treatAllWarnings(as: .error) を指定していても、そのビルドに限ってはエラーへの格上げを無効化できます。ただし、Swift の -suppress-warnings は他の警告制御フラグと排他で、-Xswiftc -suppress-warnings を渡すと -warnings-as-errors-Wwarning と競合してエラーになる点は注意が必要です。

既存パッケージへの影響

この API は、SwiftPM のこの機能が入ったバージョン以降を tools version として指定しているパッケージからのみ利用できます。既存のパッケージがこの変更で壊れることはありません。

将来への見通し

今回のスコープには入っていませんが、関連する検討事項として次のような方向が示されています(いずれも speculative で、実現を約束するものではありません)。

  • パッケージレベルの設定: 警告設定はターゲットごとに書くよりも、パッケージ全体で共通化してターゲット側で上書きしたいことが多いです。ただし同じ要望は他のビルド設定にも当てはまるため、警告に限らず設定全般の継承をどう扱うかを将来まとめて検討することになっています。
  • 他の C/C++ コンパイラへの対応: 今回の API は事実上 Clang のフラグ体系を前提に設計されていますが、MSVC など他のコンパイラへ拡張する余地があります。警告名の表記差は、BuildSettingCondition に「現在使っているコンパイラ」を条件として加える形で吸収することが想定されています。
  • 「開発時のみ」設定の明示化: 今回の警告制御設定のように「自分でビルドするときだけ効いて、依存として配るときは無効化される」性質を持つ設定を、devSwiftSettings のような別カテゴリとして明示的に分離する案が議論されています。