Statically link Swift runtime libraries by default on supported platforms
01 何が問題だったのか
Swiftで書かれたプログラムをLinuxなどのサーバ環境に配置する際、Swiftランタイムライブラリ(libswiftCore.so、libswift_Concurrency.so、libdispatch.so、libFoundation.so など)をどう扱うかは悩みどころでした。Darwin系のプラットフォームではランタイムライブラリがOSに同梱されているため、動的リンクで十分に小さいバイナリを配布できます。一方で、Linuxをはじめとする多くのプラットフォームではOSにランタイムが含まれないため、利用者が次のいずれかを選ぶ必要がありました。
- ビルド済みのバイナリと一緒に
libswift*.so群を「shared objectの詰め合わせ」として同梱する --static-swift-stdlibフラグを指定してランタイムライブラリを静的にリンクする- ランタイムを含む「runtime」Dockerイメージ(コンパイラのバージョンと厳密に一致するもの)を使う
1 は ldd や readelf で依存を手探りする必要があり、3 はコンパイラとランタイムのバージョンが厳密に一致することが求められるため、実運用では 2 の静的リンクが最も扱いやすい選択肢でした。ただし --static-swift-stdlib の存在はビルドフラグを知っている人にしか使えず、Swiftを始めたばかりのユーザーにとっては初期設定のまま配布したバイナリを別マシンに持っていっても動かない、という落とし穴になっていました。
Goや Rust はデフォルトで静的リンクを採用しており、これがサーバサイド用途での配布の容易さにつながっています。SwiftPMでもサポートされているプラットフォームではこの挙動をデフォルトに揃えたい、というのが本Proposalの動機です。
02 どのように解決されるのか
本Proposalは、SwiftPMが実行可能ファイルをリリースモードでビルドする際のデフォルトのリンク戦略を、プラットフォームに応じて切り替えるように変更します。言語機能ではなくSwiftPMの挙動の変更です。
プラットフォームごとのデフォルト
| プラットフォーム | デフォルト |
|---|---|
| Darwin(macOS、iOSなど) | 動的リンク(従来通り) |
| Linux | 静的リンク(新しいデフォルト) |
| WASI | 静的リンク(新しいデフォルト) |
| Windows | 動的リンク(静的リンクは現時点で技術的に未サポート) |
| その他 | 動的リンク |
デバッグビルドは従来どおり動的リンクのままです。デバッグにはそもそもSwiftツールチェインがインストールされている必要があり、ランタイムライブラリが存在することが前提にできるためです。
なお「静的リンク」といっても完全な静的バイナリにはなりません。Swift自身のランタイムライブラリ(stdlib、Foundation、Dispatch など)が実行ファイルに埋め込まれるだけで、次のような外部依存は引き続き動的リンクされ、実行環境に適切なバージョンがインストールされている必要があります。
- Glibc(
libc.so、libm.so、libdl.so、libutil.so) libstdc++、libgcc_s.soFoundationXMLが依存するlibxml2、FoundationNetworkingが依存するlibcurl- プログラム自身が使う
libsqlite、zlibなどのシステムライブラリ
新しいフラグ --disable-static-swift-runtime
これまでの --static-swift-stdlib は「静的リンクにオプトインするためのフラグ」でしたが、デフォルトが静的リンクに変わることでその役割を終えます。本Proposalでは --static-swift-stdlib をdeprecateし、代わりに「新しいデフォルトからオプトアウトするためのフラグ」として --disable-static-swift-runtime を導入します。
# Linuxでは何もしなくても静的リンクされる
swift build -c release
# 動的リンクに戻したい場合
swift build -c release --disable-static-swift-runtime
# 強制的に静的リンクしたい場合(従来どおりlong formは有効)
swift build -c release -Xswiftc -static-stdlib
バイナリサイズとデプロイ単位
静的リンクに切り替えると、単純な print("Hello, world!") 程度のバイナリでも17KB程度から35MB程度に膨らみます。数字だけ見ると驚きますが、動的リンク版もランタイムライブラリ一式(ICUを含めて40MB超)がなければ動かないため、配布物全体としてのサイズはほぼ変わりません。むしろ同梱漏れによる実行時エラーやバージョン不一致のトラブルが減り、起動時の動的ロードが無くなることでコールドスタートも改善します。
ライブラリの重複リンク検査
これまでのSwiftPMは、ランタイムライブラリを静的リンクした実行ファイルに、ランタイムライブラリを既に静的リンク済みのライブラリをさらにリンクしても検知してくれませんでした。ランタイムのバージョンが食い違うと実行時エラーの原因になるため、本Proposalに合わせてSwiftPMはビルド後にこの条件を検査し、警告を出すようになります。
既存パッケージへの影響
新しい挙動は新しいバージョンのSwiftPMから適用されます。前述の3つのデプロイ方式(shared objectの同梱、明示的な --static-swift-stdlib、runtime Dockerイメージ)はいずれも引き続き動作しますが、新しいデフォルトと冗長になります。特に --static-swift-stdlib を明示していたビルドには「もう不要です」という警告が出ます。
今後の方向性
以下は speculative な見通しで、実現を約束するものではありません。
Goのようにプログラム全体を静的リンクできれば配布はさらに単純になりますが、現状のSwift on LinuxはGlibcと libstdc++ に依存しており、これらが完全な静的リンクに対応していないため実現できていません。将来 musl libc や LLVM の libc++ への対応が進めば、-Xswiftc -static-stdlib がデフォルトの現状から、さらに -Xswiftc -static-executable(完全静的リンク)をデフォルトにする変更も検討されうる、と示唆されています。また、FoundationXML / FoundationNetworking のシステム依存(libxml2 / libcurl)をネイティブ実装に置き換えることで、これらを使うプログラムの配布も楽になる、という方向性も言及されています。