Package Editor Commands
01 何が問題だったのか
Swift Package Manager の Package.swift(マニフェスト)は、JSON や TOML といった宣言的データ形式ではなく、PackageDescription API を用いた Swift コード として書かれます。表現力が高い反面、プロダクト・ターゲット・依存関係の追加といった「よくある編集作業」を自動化したいときには、構文木を扱う必要があり手軽にはいきません。結果として、次のような場面で不便が生じていました。
- パッケージの作者が README で依存関係の追加方法を案内したくても、「
Package.swiftを開いてdependencies:配列にこういう要素を足してください」と文章で説明するしかなく、ユーザー側の手作業ミスが起きがちです。 - 新しいライブラリターゲットを足すには、マニフェストの編集に加えて
Sources/<ターゲット名>/のようなディレクトリをパッケージ規約に沿って作る必要があり、慣れていないユーザーにとって覚えることが多くなります。 - IDE や libSwiftPM を利用するツールから「足りない依存を自動で追加する」「新しいターゲットを作るついでにマニフェストを更新する」といった支援を提供しにくい状況でした。
- パッケージコレクション(package collections)に登録されているパッケージを URL ではなく名前で指定して追加する、といった高レベルな操作の基盤もありませんでした。
他の言語のパッケージマネージャでは、こうした編集操作をコマンドラインから行う仕組みが一般的です。npm の npm install、Rust 向けの cargo-edit、Elm の elm install などが該当します。Swift Package Manager にも、マニフェスト編集を自動化するための公式な入り口が求められていました。
02 どのように解決されるのか
swift package に、マニフェストを編集するための3つのサブコマンド add-product / add-target / add-dependency を追加します。これらはいずれもカレントパッケージの Package.swift を構文レベルで書き換え、既存のコメントや条件付きコンパイルブロックなどを保ったまま必要な箇所だけを編集します。
対象となるのは swift-tools-version が 5.2 以降のマニフェストです。これは Swift 5.2 での PackageDescription API の大きな変更を踏まえ、初期実装のスコープを絞るための制限です。
swift package add-product
プロダクトを追加するコマンドです。
swift package add-product <name> [--type <type>] [--targets <targets>]
- name: 追加するプロダクト名。
- –type: executable / library / static-library / dynamic-library のいずれか。省略時は library。
- –targets: プロダクトに含めるターゲット名をスペース区切りで指定します。
swift package add-target
ターゲットを追加するコマンドです。マニフェストを書き換えるだけでなく、対応する Sources/<ターゲット名>/ や Tests/<ターゲット名>/ ディレクトリも併せて作成します。
swift package add-target <name> [--type <type>] [--no-test-target]
[--dependencies <dependencies>]
[--url <url>] [--path <path>] [--checksum <checksum>]
- name: 追加するターゲット名。
- –type: library / executable / test / binary。省略時は library。system library ターゲットは設定項目が多いため、この初期バージョンでは対象外です。
- –no-test-target: 通常、library ターゲットを追加すると対応するテストターゲットが自動で追加されますが、このフラグを付けると追加されません。
- –dependencies: 依存ターゲット名をスペース区切りで指定します。
- –url / –path: binary ターゲットの配布元(リモート URL またはローカルパス)。
- –checksum: リモート binary ターゲットのチェックサム。
swift package add-dependency
パッケージ依存関係を追加するコマンドです。
swift package add-dependency <dependency> [--exact <version>]
[--revision <revision>]
[--branch <branch>]
[--from <version>]
[--up-to-next-minor-from <version>]
<dependency> には、リモートパッケージの URL、ローカルパッケージへのパス、あるいはユーザーのパッケージコレクションに含まれるパッケージ名のいずれかを指定できます。
バージョン要件は次のオプションで指定します。
- –exact:
.exact(<version>)に相当します。 - –revision:
.revision(<revision>)に相当します。 - –branch:
.branch(<branch>)に相当します。 - –up-to-next-minor-from:
.upToNextMinor(<version>)に相当します。 - –from: 単独で使うと
.upToNextMajor(<version>)を生成します。--to <version>と組み合わせれば任意の半開区間、--through <version>と組み合わせれば閉区間の要件を指定できます。
要件を何も指定しなかった場合は、当該パッケージの最新バージョンに対する .upToNextMajor がデフォルトで使われます。
使用例
次のような初期状態のマニフェストがあるとします。
// swift-tools-version:5.3
import PackageDescription
// Description of my package
let package = Package(
name: "MyPackage",
targets: [
.target(
name: "MyLibrary", // Utilities
dependencies: []
),
]
)
以下のコマンドを順に実行します。
swift package add-dependency https://github.com/apple/swift-argument-parser
swift package add-target MyLibraryTests --type test --dependencies MyLibrary
swift package add-target MyExecutable --type executable --dependencies MyLibrary ArgumentParser
swift package add-product MyLibrary --targets MyLibrary
結果、マニフェストは次のように書き換えられます。既存のコメント(// Description of my package や // Utilities)はそのまま保たれている点に注目してください。
// swift-tools-version:5.3
import PackageDescription
// Description of my package
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyLibrary",
targets: [
"MyLibrary",
]
),
],
dependencies: [
.package(name: "swift-argument-parser", url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "0.3.1")),
],
targets: [
.target(
name: "MyLibrary", // Utilities
dependencies: []
),
.testTarget(
name: "MyLibraryTests",
dependencies: [
"MyLibrary",
]
),
.target(
name: "MyExecutable",
dependencies: [
"MyLibrary",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
]
)
併せて、新しいターゲット用の Sources/MyExecutable/main.swift と Tests/MyLibraryTests/MyLibraryTests.swift も作成されます。
宣言的でないマニフェストの扱い
これらのコマンドは、マニフェストのうち fully declarative な部分(リテラルと、PackageDescription のファクトリメソッド・イニシャライザ呼び出しのみで構成される部分)を編集することを想定しています。実際のマニフェストの products / targets / dependencies の大半はこの条件を満たします。
マニフェストには任意の Swift コードを書けるため、条件分岐などで動的に構築されたエントリも存在します。コマンドはそうしたケースでも可能な範囲で best-effort に編集を試みますが、安全に書き換えられないと判断した場合はエラーを報告し、マニフェストを変更しないまま終了します。
Future Directions
今回の提案では追加操作だけが対象で、プロダクト/ターゲット/依存関係の削除や名前変更はスコープ外とされています。これらは利用頻度が比較的低いと見られるため、実運用での需要を見てから検討されることが示唆されています。
また、npm upgrade に相当する swift package upgrade(依存パッケージのバージョン指定を新しいバージョンへ自動更新するコマンド)も、将来的な拡張として言及されています。いずれも構想段階の話題で、実現が約束されているものではありません。