Swift Digest
SE-0082 | Swift Evolution

Package Manager Editable Packages

Proposal
SE-0082
Authors
Daniel Dunbar
Review Manager
Anders Bertelrud
Status
Implemented (Swift 3.1)

01 何が問題だったのか

Swift Package Manager(SwiftPM)では、パッケージがビルドされるときに依存パッケージのソースが自動的にチェックアウトされます。この提案以前は、そのチェックアウト先がプロジェクト直下の Packages/ ディレクトリで、ディレクトリ名は「パッケージ名とタグを組み合わせたもの」でした。ユーザーは Packages/ の中身を直接編集すればビルドに反映されるため、依存パッケージをその場で修正する手段としてはそのまま使えていたものの、実際のワークフローを考えると次のような問題がありました。

一貫性のあるビルドと、依存パッケージを編集したい要求が両立しない

SwiftPMには、本来満たしたい二つの目標があります。

  • 決定的なビルド: 特定のタグに対して、誰がどこでビルドしても同じ結果になること。これはデプロイやチーム開発での再現性に直結します。したがって、依存解決で選ばれたタグ以外の状態で知らずにビルドしてしまう状況は、基本的にはエラーや警告で拾いたい挙動です。
  • 依存パッケージ自体の反復開発: 自分のプロジェクトが依存しているパッケージに手を入れ、SwiftPMの外に切り出しにくい形で一緒に開発したい、あるいは上流パッケージへフィードバックするために手元で試したい、という需要があります。

従来の Packages/ の仕組みは、この二つの要求を明確に切り分けていませんでした。同じディレクトリが「依存解決で取ってきたソース」と「ユーザーが編集中のソース」の両方を兼ねていて、SwiftPMから見るとどちらの意図なのかを区別できません。

Packages/ 直下での編集が開発体験に合わない

依存パッケージの作者が自分のリポジトリを編集したいとき、Packages/<パッケージ名>-<タグ> のディレクトリ名は違和感があります。ふだんその作者がリポジトリを置いている場所とも違いますし、git の状態としても特定のタグを指しているだけで、反復開発を行うブランチ上ではありません。ユーザーがその場でブランチを切り替えることはできますが、今度はディレクトリ名(タグを埋め込んだ名前)と中身(別ブランチの状態)の食い違いが混乱の元になります。

依存ソースへの他の操作と干渉する

SwiftPMは、依存パッケージのソースに対して「新しいバージョンに更新する」などの操作も担う必要があります。ユーザーによる編集と、SwiftPM自身による更新とが同じディレクトリ上で衝突すると、「ユーザーの変更をどう尊重するか」「依存解決の結果をどう適用するか」といったワークフロー上の難問が生まれ、挙動を素直に定義しにくくなっていました。

結果として、Packages/ を単純に編集すると動いてはいるものの、決定的なビルドと反復開発のどちらに寄せるかが曖昧で、利用者にとってもSwiftPMの実装にとっても扱いにくい状態でした。

02 どのように解決されるのか

依存ソースの置き場所を「SwiftPMが内部的に管理する場所」と「ユーザーが明示的に編集するための場所」に分け、後者への切り替えを swift build --edit <PACKAGE> という明示的な操作で行うようにします。これにより、「ふだんは決定的なビルドを保証する」「編集したいときだけユーザーが宣言して切り替える」という二つのモードを切り分けます。

デフォルトの依存ソースは隠す

依存解決で取ってきたソースは、プロジェクト直下ではなく .build ディレクトリの中(当面はその配下、将来的には共有キャッシュに移す余地を残す位置)にチェックアウトされるようになります。ユーザーが直接触ることは想定されず、swift build は常に「依存解決で選ばれたタグそのままのソース」を使ってビルドします。

どうしてもソースの場所を知りたい場合は、専用のコマンドでパスを取得できるようにします。

swift build --get-package-path <PACKAGE>

これにより、SwiftPMはソースの物理的な置き場所を後から変更しても、ユーザーやツールが依存しているインターフェイスを壊さずに済みます。

--edit で明示的に編集モードに入る

依存パッケージを編集したくなったら、次のように宣言します。

swift build --edit Foo

この操作は「依存解決で選ばれているタグをそのまま使って、Packages/Foo にリポジトリをチェックアウトする」という挙動です。重要な不変条件として、

swift build
swift build --edit Foo
swift build

の3ステップを通して、いずれの swift buildまったく同じ結果を生むようになっています。--edit は、ビルドの内容を変えずに「これ以降このパッケージは編集対象である」という宣言を与える操作だ、と捉えられます。

編集モードでのビルド挙動

Packages/<NAME> に編集対象のパッケージが存在すると、swift build はそのディレクトリのソースをそのまま使ってビルドします。git の状態やタグ、依存解決が本来選ぶはずだったタグは一切考慮されません。「そこにあるものを素直にビルドする」という単純な規則です。

さらに、依存グラフの中に同じパッケージが複数回登場していても、編集対象が存在する場合はその編集中のソースがすべてのインスタンスに使われます。あるパッケージを編集するかどうかはパッケージ単位で独立しており、依存グラフのうち「いくつかだけ」を編集モードにすることにも制約はありません。

編集対象として認識されるのはルートパッケージ直下の Packages/ のみで、他のパッケージが持つ Packages/ は無視されます。

編集モードを抜ける

編集モードを解除するコマンド(--end-edit など)も想定されていますが、初期実装では省略される可能性があり、その場合は Packages/<NAME> を手作業で削除(rm -rf Packages/<NAME>)するとドキュメントされます。将来的には、未コミット・未プッシュの変更が残っていないかのチェックなど、安全策を組み込んだ専用コマンドを提供することが想定されています。

名前ベースでの差し替え

SwiftPMは、Packages/ 配下に見つかった各リポジトリを依存グラフに突き合わせるとき、パッケージ名で対応付けます。origin(リポジトリのURL)の一致までは要求しません。これは、まだどこにもpushしていない新しいパッケージを手元で組み合わせて開発できるようにするための配慮です。

既存パッケージへの影響

この変更は既存の Packages/ ディレクトリの意味づけをがらりと変えるため、既に Packages/ を持っているプロジェクトは、swift build から見ると「依存グラフに無い大量の編集対象パッケージが存在する」状態になります。SwiftPMは、この状況を検出して警告や移行案内を出す方針です。

今後の拡張として検討される方向性

Future Directionsとして、次のような発展が議論されています。いずれもスコープ外であり、具体的な仕様を約束するものではありません。

  • 編集中のパッケージに「次にタグ付けされる予定のセマンティックバージョン」を割り当て、そのバージョンで既にタグ付けされたかのように依存グラフを解決する仕組み。編集を完了してコミット・タグ付けした後の状態を、編集中にも再現できるようにする狙いです。
  • 編集モードを抜ける際に、変更がコミット・プッシュ・タグ付けまで到達しているかをチェックする安全策。
  • 編集中のパッケージのメタデータ(依存タグなど)への変更を検出して、ビルドに反映されない種類の変更であることをユーザーに警告する機能。
  • どのパッケージが編集状態にあるかを記録するメタデータファイルの導入。ファイルシステム上の状態を常に「正」として扱いつつ、診断や「編集対象の実体を別の場所に置きたい」といった要求に応える用途を想定しています。
  • すべての依存パッケージを一括で編集モードに切り替える --edit-all フラグ。