目次
はじめに
Swift Buildには、ソースコードのimportから依存関係をチェックする機能があり、 importされた依存関係がPackage.swiftなどで構築できていない場合、 デフォルトだと警告(設定によってはエラー)を発生させます。
おそらくXcode 26あたりからその機能がバグりまくっていて無駄に警告を出されていました。
Fix dependency diagnostics for dynamic target variants
https://github.com/swiftlang/swift-build/pull/1457
推測ですが、Xcode 27正式版でこのSwift Buildへの変更が反映されるはずで、 まだ現在のXcode 26.6やXcode 27βでもこの問題は再現すると思います。
どういう問題が起こるのか
Targetの依存関係を正しく設定していても、Swift Buildが次のような警告を出すことがあります。
warning: 'FirebaseCore' is missing a dependency on 'FirebaseCoreInternal' because dependency scan of 'FIRHeartbeatLogger.m' discovered a dependency on 'FirebaseCoreInternal'
この例では、FirebaseCoreはFirebaseCoreInternalへ実際に依存しています。
しかしSwift Buildは、ソースコードから検出したimportに対応する依存関係を見つけられず、依存関係が不足していると誤判定していました。
この問題は、Swift PackageのTargetがDynamic Link用のTargetへ置き換えられる構成で起こります。
実例
具体例として次の依存関係を使っているとしましょう。macOSFwkとしましたがDynamic LinkのTargetであれば何でもいいです。
App
├── macOSFwk // これはDynamicであればなんでもいい
│ └── PackageLibProduct
│ └── Common
│ └── Core
└── PackageLibProduct2
└── Common
└── Core
Common.swiftはCoreをimportしますのでコードは次のようなのがあるとします。
import Core
public func commonValue() -> String {
coreValue()
}
Swift Buildは依存関係のチェックの工程として、コードスキャンでimportでCommonからCoreへの依存関係があることを知ります。
これはあくまでチェックの工程であり、importがあるからそのターゲットをLinkするのではなく、
すでにターゲットの依存関係を全て解決した上でこのスキャンの工程があります(たぶん)。
そのため、コードをスキャンした結果のCommonからCoreへの依存と、
Pacakge.swiftをみて実際の依存関係を解決した結果のCommonからCoreへの依存ができているかを検証します。
そしてそれが問題なければ何もおこりませんが、
それに問題があるから警告だったり(設定によってはエラー)を出します。
ダイアモンド問題解決によりDynamic Linkへと変化することで問題が起こる
ただ、問題はSwift BuildにはLinkするターゲットをDynamic Linkのターゲットとして自動で変更する機能があります。 その際に、ターゲット名やIDが変わるため、正確に比較ができなくなっていました。
具体例を続けると、
Appは、Dynamic LinkされるmacOSFwkとPackageLibProduct2からCommonとCoreへ到達します。
CommonとCoreにダイアモンド状のリンク経路があるため、Swift Buildは両方をDynamic Link用のTargetへ置き換えます。
Swift BuildがCommonとCoreを次のように置き換えます。
App
├── macOSFwk
│ └── PackageLibProduct
│ └── Common-dynamic // Dynamic Linkになった
│ └── Core-dynamic // Dynamic Linkになった
└── PackageLibProduct2
└── Common-dynamic // Dynamic Linkになった
└── Core-dynamic // Dynamic Linkになった
Common-dynamicとCore-dynamicは、Dynamic Link用のTarget Variantを表します。
末尾に-dynamicとついていて、IDも変わります。別物になるってことです。
問題が起こる原因
Swift Buildが、Dynamic Link用のTargetへ置き換える前の情報だけで診断していたのが原因です。
Swift Buildは、最初にTargetBuildGraphでTargetの依存関係を表します。
Swift Buildは、Package Targetのリンク方法を決めた後、GlobalProductPlanでDynamic Link用のTargetを含むビルド計画を保持します。
修正前のBuildDescription.targetDependenciesには、TargetBuildGraph.targetDependenciesByGuidが渡されていました。
この依存関係グラフは、Dynamic Link用のTargetへ置き換える前のCommonとCoreを持っています。
一方、実際にタスクを実行するTargetは、置き換え後のCommon-dynamicです。
依存関係を確認するBuildOperation.transitiveDependencyExists(target:antecedent:)は、置き換え前の依存関係グラフからCommon-dynamicを探すことになるため、Coreへ到達できるとは判定できませんでした。
依存先のTargetをモジュール名から解決する処理にも、同じ食い違いがありました。
修正前のdefiningTargetsByModuleNameはbuildGraph.allTargetsから構築されていたため、Coreというモジュール名を置き換え前のCoreへ対応づけていました。
実際のビルド計画では、依存先もCore-dynamicへ置き換えられています。
修正前の診断は、次のように異なる段階のTargetを比較していました。
| 診断に使う情報 | 修正前 | 実際のビルド計画 |
|---|---|---|
| 依存元のTarget | 依存関係グラフにはCommonだけが存在する |
タスクはCommon-dynamicとして実行される |
Coreを定義するTarget |
Core |
Core-dynamic |
依存関係の設定が不足していたわけではありません。 診断が参照する依存関係グラフと、タスクが実行される依存関係グラフが一致していませんでした。
PRでは、GlobalProductPlanにtargetDependenciesByGuidを追加しました。
新しいtargetDependenciesByGuidは、GlobalProductPlan.allTargetsとGlobalProductPlan.resolvedDependencies(of:)から、Dynamic Link用のTargetへ置き換えた後の依存関係を構築します。
BuildDescriptionも、TargetBuildGraph.targetDependenciesByGuidではなくGlobalProductPlan.targetDependenciesByGuidを受け取るように変更しました。
definingTargetsByModuleNameもGlobalProductPlan.allTargetsから構築するように変更したため、依存元と依存先の両方が置き換え後のTargetで揃います。
回帰テストは、DIAGNOSE_MISSING_TARGET_DEPENDENCIESを有効にした状態で先ほどの依存関係をビルドし、診断が一件も出ないことを確認します。
おわりに
このPRは2026年6月17日にSwift Buildのmainブランチへマージされました。
今回の修正によって、Swift BuildはDynamic Link用のTargetへ置き換えた後の依存関係グラフを使って、ソースコードから検出した依存関係を確認するようになりました。 Package Targetの依存関係を正しく設定しているにもかかわらず、Dynamic Link用のTargetへの置き換えによって警告やエラーが出る誤診断を修正しています。
どのXcodeで修正が利用できるかは、Swift Buildへマージされた時点では確定していません。 修正を取り込んだXcodeがリリースされるまでは、正しい依存関係に対して同じ警告が出る可能性があります。
そもそもダイアモンド問題の解決方法もバグっており、 それを修正したPRもマージされています。
Swift Buildのダイアモンド問題解決を修正するPRがマージされた
このダイアモンド問題解決あたりはかなりバギーであり、 1つ直すと色々な気づきがあっておもろいです。 みなさんもここら辺を治す楽しみを知って欲しいです。