Swift Buildの依存グラフにDynamic Linkかどうかを表示するようにしたPRがマージされた

はじめに

Swift Buildは、ビルド対象となるTargetの依存関係グラフをビルドログとDerivedDataの下へtarget-graph.txtへ出力します。 しかし従来の依存関係グラフでは、各TargetがDynamic Linkされるかどうかを確認できませんでした。

特にSwift Buildがダイアモンド問題を解決するためにPackage TargetをDynamic Link用のTargetへ置き換えても、依存関係グラフには置き換えの結果が表示されませんでした。

そこで、Dynamic LinkされるTargetへ(Dynamic)を表示するPRを出しました。 このPRはSwift Buildのmainブランチへマージされています。

Display dynamic targets in the target dependency graph

https://github.com/swiftlang/swift-build/pull/1549

具体的にどういう出力だったのか

いきなり具体例ですが、 次のようにアプリがDynamicLinkPackageProductというDynamic LinkのTargetを用意しているとします。そのほかはDynamicではなく指定をしておらず、PackageLibPackageLib2などがあります。構成を図にすると次のような感じがあるとしましょう。

App
├── DynamicLinkPackageProduct (開発者がDynamicとして明示)
│   └── PackageLib
│        └── Common
│
└── PackageLibProduct2
    └── PackageLib2
         └── Common

Swift Buildは2つの経路からLinkされ重複を避けるため、ダイアモンド問題を解決する過程でCommonをDynamic Linkとして変更します。

そうなった場合でも、 Swift Buildはx次のようなログを出力します。

note: Target dependency graph (6 targets)
    Target 'App' in project 'AppProject'
        ➜ Explicit dependency on target 'DynamicLinkPackageProduct' in project 'Modules'
        ➜ Explicit dependency on target 'PackageLibProduct2' in project 'Modules'
    Target 'PackageLibProduct2' in project 'Modules'
        ➜ Explicit dependency on target 'PackageLib2' in project 'Modules'
    Target 'PackageLib2' in project 'Modules'
        ➜ Explicit dependency on target 'Common' in project 'Modules'
    Target 'DynamicLinkPackageProduct' in project 'Modules'
        ➜ Explicit dependency on target 'PackageLib' in project 'Modules'
    Target 'PackageLib' in project 'Modules'
        ➜ Explicit dependency on target 'Common' in project 'Modules'
    Target 'Common' in project 'Modules' (no dependencies)

しかし従来のログには、CommonがDynamic Linkされるという情報がありません。 従来の依存関係グラフはダイアモンド問題を解決するステップの前に生成されていたため、Swift Buildが後からDynamic Linkへ変更した結果を表示できていませんでした。

これはおそらく、Xcodeなどのクライアントから来る情報を組み立てて依存関係をグラフ化することが目的だったためだと推測します。

  • BuildRequest/PIF(Xcodeやxcodebuildなどのクライアントが送ってくる)
  • Swift Build
    • 入力としてBuildRequestやPIFから依存関係を組み立てる
    • ダイアモンド問題を解決しDynamic Linkに置き換える
  • リンカーでDynamic Linkしたりする

つまり、入力としてBuildRequestやPIFから依存関係を組み立ててどうなるかを表示している依存グラフであり、その後のステップについての結果ではなかったということでしょう。

もちろん、現状でもビルド時のリンカーログはDynamic LinkしたTargetのログを出力しているので、そのTargetの名前から推測できなくもないです。 たとえばCommonはリンカーのログではCommon-dynamicという名前のターゲットをLinkするようになっていて、Commonという名前が使われていません。なので推理しようと思えばできます。

ただ、あくまで推測になるのであまり良いとは思えません。

さらにビルド後のバイナリにotoolを使えば、生成されたバイナリがDynamic Linkされているかを確認できます。 ただし、バイナリの調査では再帰的にリンク構造を辿る必要があり面倒です。 コードでループを書いたり、手動だとメモしながらやるのが面倒。

また、Static Linkされたかどうかは内部をDumpしてデマングリングして文字列からSwiftのコードとして任意の文字列(たとえば型名や関数名や定数名)が埋め込まれているのを確認できます。

そういうバイナリの調査をやるためにビルドの完了を待つ必要もあり、時間がもったいないんですよね。ビルドのログに出りゃいいのに。

どうやって解決したのか

issueを出す

まずPRをいきなり出さずに、issueを出しました。

https://github.com/swiftlang/swift-build/issues/1535

需要があるのかないのか意見が聞きたかったのと、自分が考えているログの出し方で良いのか知りたかったためです。

issueでは依存元が依存先のTarget名を出す際に変化したTarget名を表示するのと、依存先ではTarget名の変化について表記するようにしていました。

note: Target dependency graph (6 targets)

    Target 'App' in project 'SampleProject'

        ➜ Explicit dependency on target 'DynamicLinkPackageProduct' in project 'Modules'

        ➜ Explicit dependency on target 'PackageLibProduct2' in project 'Modules'

    Target 'PackageLibProduct2' in project 'Modules'

        ➜ Explicit dependency on target 'PackageLib2' in project 'Modules'

        ➜ Explicit dependency on target 'Common' in project 'Modules'

    Target 'PackageLib2' in project 'Modules'

        ➜ Explicit dependency on target 'Common' in project 'Modules'

    Target 'DynamicLinkPackageProduct' in project 'Modules'

        ➜ Explicit dependency on target 'PackageLib' in project 'Modules'

-       ➜ Explicit dependency on target 'Common' in project 'Modules'
+       ➜ Explicit dependency on target 'Common-dynamic' in project 'Modules'

    Target 'PackageLib' in project 'Modules'

        ➜ Explicit dependency on target 'Common' in project 'Modules'

-    Target 'Common' in project 'Modules' (no dependencies)
+    Target 'Common-dynamic' in project 'Modules' (no dependencies, dynamic variant of 'Common')

具体的には依存元からCommonという名前でLinkするのではなくCommon-dynamicとなり、 依存先ではCommon-dynamnicCommoonという名前に変化した、という表現にしていました。

コメントでやり取りの結果、依存先へシンプルにしDynamicとだけつけるようにしました。

実装

依存関係グラフの生成を依存関係の解決後に行うようにし、Targetをループする際に単純にDynmic Linkされるかどうかをチェックするようにしました。それだけです。

Swift Buildのコード的には TargetBuildGraphからGlobalProductPlanへ移しました。 TargetBuildGraphはダイアモンド問題を解決する前の依存関係を持ちます。 一方、GlobalProductPlanはダイアモンド問題を解決した結果を持つため、どのTargetがDynamic Linkされるかを判定できます。

Dynamic Linkの判定には、次の2つの情報を使います。

let isDynamic =
  getTargetSettings($0).globalScope.evaluate(
    BuiltinMacros.MACH_O_TYPE
  ) == "mh_dylib"
  || dynamicallyBuildingTargetsWithDiamondLinkage[$0.target] != nil

MACH_O_TYPEmh_dylibであるTargetに加えて、ダイアモンド問題の解決によってDynamic LinkされるTargetも対象になります。

修正後のログでは、Dynamic LinkされるTargetへ(Dynamic)が追加されます。

Target 'DynamicLinkPackageProduct' in project 'Modules' (Dynamic)
    ➜ Explicit dependency on target 'PackageLib' in project 'Modules'
Target 'PackageLib' in project 'Modules'
    ➜ Explicit dependency on target 'Common' in project 'Modules'
Target 'Common' in project 'Modules' (no dependencies, Dynamic)

(Dynamic)は、ダイアモンド問題を解決した結果だけを表す印ではありません。 (Dynamic)は、このビルドでTargetがDynamic Linkされるという最終状態を表します。

表示対象には、次の3種類が含まれます。

  • Swift Buildがダイアモンド問題を解決するためにDynamic Linkへ変更したPackage Target
  • Package.swift.library(type: .dynamic, ...)でDynamic Linkを指定したTarget
  • XcodeプロジェクトでDynamic LinkされるFramework Target

レビューでは、ダイアモンド問題の解決によってDynamic LinkされるTargetだけを表示する案も議論されました。 しかし限定した表示では、開発者が最初からDynamic Linkを指定したTargetや、XcodeがDynamic LinkするFramework Targetに(Dynamic)が付きません。 同じ依存関係グラフの中で最終的なリンク方法を確認できるように、Dynamic Linkされる理由を問わず(Dynamic)を表示する形になりました。

生成した依存関係グラフはBuildDescriptionへ保持し、シリアライズの対象にも追加しました。 Swift Buildは同じ依存関係グラフをビルドログとtarget-graph.txtへ出力するため、2つの出力でDynamic Linkの表示が一致します。 ディスク上のキャッシュからBuildDescriptionを復元した場合も、同じ表示を利用できます。

おわりに

このPRは2026年7月21日にSwift Buildのmainブランチへマージされました。

今回の変更によって、開発者はビルドログまたはtarget-graph.txtを見るだけで、各TargetがDynamic Linkされるかどうかを確認できます。 ビルド結果を変更するPRではなく、Swift Buildが決定したリンク方法を依存関係グラフから確認できるようにするPRです。

どのXcodeで利用できるかは、Swift Buildへマージされた時点では確定していませんが、 Dynamic Linkまわりは伸び代があっていろいろ楽しいところです。

Swift Buildのダイアモンド問題解決を修正するPRがマージされた

Swift Buildの依存関係チェックの誤診断を修正するPRがマージされた