Binary Static Library Dependencies
01 何が問題だったのか
SwiftPM の binaryTarget は、ソースコードを公開せずにライブラリを配布するための仕組みですが、これまでサポートされていた形態は次の2つに限られていました。
Swift は組み込みデバイス向けからサーバサイドまで、幅広いプラットフォームで利用されるクロスプラットフォーム言語へと成長してきました。それにもかかわらず、Linux や Windows といった非 Apple プラットフォームに対しては、バイナリライブラリを配布する公式な手段が存在しませんでした。ライセンスや技術的な理由でソースを公開できない C ライブラリを Swift パッケージから使いたいというニーズはあっても、SwiftPM には受け皿がなかったのです。
素朴にバイナリを配るだけでは壊れやすい
もう一つの課題は、非 Apple プラットフォームでバイナリを安全に配るには単にファイルを置くだけでは足りないという点です。静的ライブラリは、C 標準ライブラリ以外の動的ライブラリに依存していたり、特定のバージョンの glibc を前提にしていたりすることがあります。そうした依存関係をアーティファクトの作者が把握し切れていないと、利用者側のプラットフォームで未解決シンボルや ABI 非互換が発生し、ビルドや実行時に壊れる原因になります。
つまり、非 Apple プラットフォーム向けのバイナリ配布を実用的に解禁するには、配布フォーマットの拡張だけでなく、「そのバイナリが本当にターゲットプラットフォームで安全に動くか」を検証する仕組みもセットで必要でした。
02 どのように解決されるのか
SE-0305 で導入された Artifact Bundle に、新しいアーティファクト種別 staticLibrary を追加し、C インターフェースを公開する静的ライブラリを非 Apple プラットフォームでも配布できるようにします。あわせて、配布されるバイナリが対象プラットフォームで安全に使えるかを検証するための監査ツールを導入します。
本 Proposal のスコープは「C 標準ライブラリと C ランタイム以外に依存しない、C インターフェースの静的ライブラリ」です。Swift の静的ライブラリや動的ライブラリの配布は今後の課題として残されています。
staticLibrary アーティファクト
Artifact Bundle のマニフェスト(info.json)に、新しい type: "staticLibrary" を指定できるようになります。各バリアントには、リンク対象のライブラリファイル本体に加えて、Swift / C コードから API を利用するために必要なヘッダ検索パスとモジュールマップを記述します。
path: アーティファクトバンドルのルートからの相対パス。Apple / Linux では.a、Windows では.libファイルを指します。supportedTriples: そのバリアントが対応するターゲットトリプルのリスト。staticLibraryMetadata.headerPaths: ヘッダが置かれたディレクトリのリスト。Swift / C コンパイラへ通常のヘッダ検索パスとして渡されます。staticLibraryMetadata.moduleMapPath: モジュールマップへのパス。Swift コードから import したい場合は必須です。
マニフェストの例は次のようになります(Linux aarch64 向けの例)。
{
"schemaVersion": "1.0",
"artifacts": {
"example": {
"type": "staticLibrary",
"version": "1.0.0",
"variants": [
{
"path": "libExample.a",
"supportedTriples": ["aarch64-unknown-linux-gnu"],
"staticLibraryMetadata": {
"headerPaths": ["include"],
"moduleMapPath": "include/example.modulemap"
}
}
]
}
}
}
パッケージ側は、これまでの binaryTarget と同じように、このアーティファクトバンドルを指す binaryTarget を Package.swift に記述することで依存として利用できます。
監査ツールによるバイナリの安全性チェック
静的ライブラリが C 標準ライブラリ以外の未解決シンボルを抱えていると、利用者のプラットフォームでリンクや実行が壊れる可能性があります。これを配布前にチェックするため、ツールチェインに llvm-objdump を同梱し、それを利用する監査ツールが新たに提供されます。
監査ツールの動作は次のような流れです。
- 静的ライブラリ内の各オブジェクトファイルを
llvm-objdumpで走査し、Mach-O(Apple)/ELF(Linux)/COFF(Windows)いずれのフォーマットでも、リロケーション情報から「定義されたシンボル」と「参照されているシンボル」の一覧を構築します。 - 同時に C コンパイラからデフォルトのリンカ起動コマンドを引き出し、C プログラムが何もしなくてもリンクされる標準ライブラリ群を特定します。それらも走査して「定義されたシンボル」集合に加えます。
- 参照シンボルの集合が定義シンボルの集合の部分集合になっていなければ、エラーを報告します。
Linux については、Python コミュニティの manylinux と同様の発想で、サポート対象の中で最も古い glibc を持つ Docker イメージを提供し、その中で監査を実行する運用が想定されています。glibc は後方互換なので、古い glibc に対して通るバイナリは新しい glibc でも動く、という形で実行時の互換性を保証します。
なお、アーティファクトの配布範囲が限定されていて、すべての利用パッケージが把握できているような状況では、追加の C ライブラリ依存を持つアーティファクトを配ること自体は禁じられていません。ただしその場合、利用側のターゲットが必要な依存ライブラリを明示的に自分で持ち込む必要があります。
セキュリティ上の注意
本提案は、SE-0272 で挙げられていたバイナリ依存のセキュリティ上の懸念(アーティファクト配布サーバと Package Manifest を管理するリポジトリの両方を攻撃者に掌握されると、悪意あるライブラリを差し込まれる可能性がある)を、そのまま非 Apple プラットフォームにも持ち込みます。バイナリ依存を導入する際は、ソース依存以上に配布元の信頼性を確認する必要があります。
既存パッケージへの影響
既存のパッケージには影響しません。この変更は SwiftPM の機能を純粋に追加するものであり、既存の Apple プラットフォーム向け XCFramework の扱いは変わりません。
Future Directions(今後の見通し)
今回は C ライブラリに限定していますが、将来的な拡張としていくつかの方向性が示されています。実現が約束されているものではなく、あくまで今後の検討対象です。
- Swift 静的ライブラリのサポート: マニフェストに
.swiftinterfaceを載せられるように拡張し、監査ツールもlibSwiftCoreなど Swift のランタイムシンボルを認識できるようにする方向性。 - バイナリ互換性の対象拡大: アーティファクト配布者が C 標準ライブラリ以外の追加のリンク依存を宣言できるようにし、監査ツールの判定基準と利用側の自動リンクの両方に反映する拡張。CMake の
INTERFACE_LINK_LIBRARIESに相当する伝搬を SwiftPM でも提供するイメージです。 - 動的ライブラリのサポート: Windows の import library や、Linux / Apple のリンク高速化用スタブを配布できるように、アーティファクトマニフェストに「リンク時依存」と「実行時依存」を区別して記述する仕組みを追加する方向性。