Swift SDKs for Cross-Compilation
01 何が問題だったのか
Swiftのクロスコンパイルは、もともと「destination files」と呼ばれる設定ファイルをSwiftPMに渡す仕組みで行われてきました。しかしこの方式には、利用する側にとって次のような使いにくさがありました。
- destination filesはホスト/ターゲットの組み合わせごとに場当たり的に用意されていました。たとえばmacOSからLinux向けにクロスコンパイルするためのスクリプトは、Swiftチーム公式のものとコミュニティ製のもの(SPMDestinations/homebrew-tap など)が別々に存在していました。
- destinationのメタデータがハードコードされた絶対パスに依存しており、ファイルシステム上に展開されたツリーをそのまま前提としていました。そのため、配布物として他のマシンに持っていくのが困難でした。
- 必要なアセット(コンパイラ、リンカ、ターゲットSDKのライブラリやヘッダなど)を一式まとめて配布・インストールする、統一されたフォーマットやCLIが存在しませんでした。
想定される典型的なユースケースも、この不便さを強く受けていました。
- Raspberry Piのようなシングルボードコンピュータ向けのLinuxバイナリを作りたいが、実機上でのビルドは遅すぎるかメモリが足りない。別マシンからクロスコンパイルしたい。
- Swift AWS Lambda Runtime のように、macOSからLinuxバイナリを作りたい。DockerコンテナでビルドすればLinuxバイナリは作れるものの、「ビルドのためだけにDockerを入れる」ことが新規ユーザーにとって大きな参入障壁になっていました。
つまり、クロスコンパイルに必要な一式を相対パスで完結した配布可能なアーカイブにまとめ、それを標準のCLIで取得・インストール・利用できる仕組みが欠けていました。加えて、ここで扱う「ツールチェーンの差し替え」自体は、クロスコンパイル以外の場面(たとえばビルドごとに特定のコンパイラやリンカ、追加フラグを使いたい場合)でも役立つ設定項目であり、共通の形で表現できる場所もありませんでした。
02 どのように解決されるのか
クロスコンパイルに必要なツールチェーンとSDKを一つのアーカイブにまとめた Swift SDK という概念を導入し、それを扱うための swift sdk CLI と設定ファイル群を整備します。Swift SDKバンドルは SE-0305 で導入された .artifactbundle の上に構築され、中身はすべて相対パスで自己完結します。
用語は次のように使い分けます。
- toolchain: アプリやライブラリをビルドするためのツール一式。
- triple: CPUアーキテクチャ・ベンダー・OSなどを表すLLVMのトリプル。
- host triple: コードをビルドするマシン、target triple: コードが動くマシン、build triple: toolchain自体がビルドされたマシンを指します(通常の利用ではhostとtargetのみを意識すれば十分です)。
- SDK: 対象triple向けのコードを生成するのに必要なライブラリ・ヘッダ・リソースの集合。
- Swift SDK: 上記のSwift toolchainとSDKをartifact bundleとしてまとめたもの。
Swift SDKバンドルの構造
バンドルは .artifactbundle ディレクトリで、ルートに info.json(SE-0305 のバンドルマニフェストを流用)を置きます。artifactごとに swift-sdk.json(Swift SDK本体の設定)と toolset.json(ツール設定)が入り、さらにトリプルごとのサブディレクトリに差分の設定とファイルを置く構成です。
swift-5.9_ubuntu.artifactbundle
├ info.json
├ toolset.json
├ ubuntu_jammy
│ ├ swift-sdk.json
│ ├ toolset.json
│ ├ aarch64-unknown-linux-gnu
│ │ ├ toolset.json
│ │ └ <triple固有のファイル>
│ └ x86_64-unknown-linux-gnu
│ ├ toolset.json
│ └ <triple固有のファイル>
├ ubuntu_focal
┆
info.json のartifactでは "type": "swiftSDK" を指定し、supportedTriples にはその Swift SDK を使えるホスト側のトリプルを列挙します。1つのバンドルに複数のartifact(例: Ubuntu 22.04版と20.04版)を同梱できます。
{
"artifacts": {
"swift-5.9_ubuntu22.04": {
"type": "swiftSDK",
"version": "0.0.1",
"variants": [
{
"path": "ubuntu_jammy",
"supportedTriples": ["arm64-apple-darwin", "x86_64-apple-darwin"]
}
]
}
},
"schemaVersion": "1.0"
}
toolset.json: ツール設定の共通フォーマット
ツールチェーンのツール(Swiftコンパイラ、C/C++コンパイラ、リンカ、librarian、デバッガ、test runner)のパスと追加フラグを記述する共通フォーマットです。CMakeのtoolchain fileに近い位置づけで、SWIFT_EXEC や CC といった環境変数ベースの場当たり的な指定を置き換えることを狙っています。
{
"schemaVersion": "1.0",
"rootPath": "ツール実行ファイルがあるディレクトリ(任意、以下の相対パスの基準)",
"swiftCompiler": { "path": "...", "extraCLIOptions": ["..."] },
"cCompiler": { "path": "...", "extraCLIOptions": ["..."] },
"cxxCompiler": { "path": "...", "extraCLIOptions": ["..."] },
"linker": { "path": "...", "extraCLIOptions": ["..."] },
"librarian": { "path": "...", "extraCLIOptions": ["..."] },
"debugger": { "path": "...", "extraCLIOptions": ["..."] },
"testRunner": { "path": "...", "extraCLIOptions": ["..."] }
}
各ツールの設定は任意で、swift build・swift test・swift run に --toolset <path> オプションを複数回渡して重ね合わせできます。後から渡した設定が同名ツールを上書きし、path を持たず extraCLIOptions だけのエントリは先行ツールの引数に追記されます。
# toolset1.json の swiftCompiler を toolset2.json で差し替え、
# cCompiler の設定は toolset1.json のものを維持する
swift build --toolset toolset1.json --toolset toolset2.json
バンドルに同梱する toolset.json では、絶対パスやバンドル外に出るシンボリックリンクは禁止です。これにより Swift SDK をどこに展開してもそのまま動作します。ユーザーが追加のツールをローカルから足したい場合は、バンドル外に別の toolset.json を用意して併用します。
swift-sdk.json: SDKルートと検索パス
各artifactの swift-sdk.json には、対象triple向けのSDKルートや各種検索パスを記述します。従来の destination.json を発展させた形で、schemaVersion は "4.0" です(互換のため "1"・"2"・"3.0" も読めます)。
{
"schemaVersion": "4.0",
"targetTriples": {
"aarch64-unknown-linux-gnu": {
"sdkRootPath": "aarch64-unknown-linux-gnu/ubuntu-jammy.sdk",
"toolsetPaths": ["aarch64-unknown-linux-gnu/toolset.json"]
},
"x86_64-unknown-linux-gnu": {
"sdkRootPath": "x86_64-unknown-linux-gnu/ubuntu-jammy.sdk",
"toolsetPaths": ["x86_64-unknown-linux-gnu/toolset.json"]
}
}
}
sdkRootPath は必須で、その他(swiftResourcesPath / swiftStaticResourcesPath / includeSearchPaths / librarySearchPaths / toolsetPaths)は任意です。省略された項目は sdkRootPath からの慣習的な位置(usr/lib/swift、usr/include、usr/lib など)が使われます。
toolset.json と同様、swift-sdk.json 内のパスも ../ やシンボリックリンクでバンドル外に出ることは禁止されます。
swift sdk CLI
インストールと管理のために swift sdk サブコマンドが追加されます(ドラフト時の名称は swift experimental-sdk で、現在は swift sdk として提供されています)。
swift sdk install <URL または ローカルパス>— バンドルを取得してSwiftPMが探せる場所にインストールします。リモートURLの場合は--checksumが必須で、公開者はswift package compute-checksum(SE-0272)でチェックサムを生成できます。既に同じartifact IDで同等以上のバージョンが入っている場合はエラーになり、更新したいときは--updateを付けます。swift sdk list— インストール済みSwift SDKの識別子一覧を表示します。swift sdk configure <identifier> <target-triple>— 特定のSwift SDKに対して、バンドル外の追加検索パスやtoolsetを登録します。--swift-resources-path/--include-search-path/--library-search-path/--toolsetを複数回指定でき、--show-configurationで現在の設定、--resetで全リセットができます。swift sdk remove <identifier>— Swift SDKを削除します。
Swift SDKを使ったビルド
インストール後は --swift-sdk オプションで識別子または target triple を指定してビルドします。
swift build --swift-sdk ubuntu_focal
swift build --swift-sdk x86_64-unknown-linux-gnu
同じtripleをサポートするSwift SDKが複数インストールされている場合は、曖昧性を避けるためエラーとなり、識別子での指定を求められます。
swift build 実行時は、バンドルに同梱されたツール(Swiftコンパイラ、リンカ、C/C++コンパイラなど)がトップレベルtoolchainのツールを上書きする形で使われます。上書きされていないツールは従来通りトップレベルtoolchainから解決されます。
バンドルの作り方
本Proposalではバンドルの生成手順自体は規定しません(host/targetの組合せやユーザーの目的により多様になるため)。著者らは macOS → Linux 向けのジェネレータを公開予定で、Docker で目的のLinux環境を作ってから .artifactbundle ツリーへファイルをコピーする構成が想定されています。重要なのは、Dockerが使われるのはバンドル生成時だけで、バンドル利用者の手元にDockerは不要という点です。
たとえば Ubuntu 22.04 向けバンドルにPostgreSQLのライブラリを追加したい場合、ジェネレータ内の Dockerfile を次のように変更するだけで済みます。
FROM swift:5.9-jammy
apt-get install -y \
libpq-dev
# 必要なライブラリを追記
既存コードへの影響
純粋な追加機能であり、既存パッケージに対する互換性の影響はありません。
Future Directions(今後の見通し)
本Proposalのスコープは「ビルド時の体験」に限定されており、実行・デバッグ・配布まわりは意図的に別Proposalに切り出されています。今後の方向性として以下が挙げられていますが、いずれもspeculativeな議論であり、実現を約束するものではありません。
- トリプルに代わるプラットフォーム記述:
aarch64-unknown-linuxではサポート外のディストリビューションへのインストールを防げないため、kernel/libcFlavor/libcMinVersion/cpuArchitectureといったキーを持つ辞書形式でプラットフォームを表す案。 - リモート実行・テスト・デバッグのSwiftPMプラグイン:
swift runやswift testを target 上で行うプラグインの仕組み(Linuxターゲットなら Docker 経由など)。 swift sdk selectサブコマンド: デフォルトで使うSwift SDKを選択しておける機能。ただしswift run/swift testがリモート実行機能に依存するため、上記プラグインが前提になります。- SwiftPM / SourceKit-LSP の改善: 異なるtriple向けの並行ビルド、複数プラットフォームの同時インデックス、
#if下のコードに対するセマンティクス解析の改善など。 - ソースベースのSwift SDK: Zigのように最小のベースSDKをソース配布し、必要に応じてバイナリを生成するアプローチ。本Proposalと排他ではありません。
- パッケージレジストリでの配布:
info.jsonにバージョンがあることを活かし、Swift SDKバンドルをパッケージレジストリでホストする方向性。