Swift Digest
SE-0318 | Swift Evolution

Package Creation

Proposal
SE-0318
Authors
Miguel Perez
Review Manager
Tom Doron
Status
Returned for revision

01 何が問題だったのか

SwiftPM で新しいパッケージを用意するためのコマンドは swift package init しかなく、このコマンドが次の 2 つの異なるユースケースを兼任していました。

  1. 空のディレクトリに、新しいパッケージを一から作る
  2. 既存のソースコードが入ったディレクトリを、パッケージ化する

両方を 1 つのコマンドで賄っているため、デフォルト挙動がどちらのユースケースにも中途半端になっており、特に Swift を触り始めたばかりのユーザーにとって直感に反する結果を生んでいました。

新規作成の場合、ユーザーはまず自分で空のディレクトリを作り、そこに cd してから init を実行する必要があります。

$ mkdir MyApp
$ cd MyApp
$ swift package init

生成されるディレクトリ構成は次のようなもので、デフォルトでは ライブラリ として、しかもパッケージ名にはカレントディレクトリ名が使われます。

.
├── .gitignore
├── Package.swift
├── README.md
├── Sources
│   └── MyApp
│       └── MyApp.swift
└── Tests
    └── MyAppTests
        └── MyAppTests.swift

ところが新規ユーザーの多くは「Swift を試すために、とりあえず動く小さなプログラムを作りたい」という動機で来るため、期待しているのは実行可能なプログラム(executable)であってライブラリではありません。また、「ディレクトリをあらかじめ作って、その名前がパッケージ名になる」という前提知識も、コマンド名や挙動からは読み取れません。

一方で、既存のソース群をパッケージ化したい既存ユーザーにとっては、ディレクトリ名をそのまま使って不足しているファイル(Sources/Tests/Package.swift など)を補う現在の挙動は理にかなっています。このため init の挙動を新規作成寄りに変えてしまうと、既存の自動化やワークフローを壊してしまうというジレンマもありました。

さらに、swift package init が生成するディレクトリ構成は固定で、ユーザーやチームが普段使っている独自のひな形(ライセンスファイル、独自の Sources レイアウト、依存パッケージの初期セットなど)を反映する仕組みもありませんでした。SwiftPM 自体はパッケージのディレクトリ構成についてかなり柔軟なのに、ひな形作成の段階ではその柔軟さを活かせていない状態でした。

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

新しいコマンド swift package create を追加し、swift package init と役割を明確に分けます。

  • swift package create新しいパッケージを一から作る ためのコマンド
  • swift package init既存のソースディレクトリをパッケージ化する ためのコマンド

加えて、両コマンドにユーザー定義の テンプレート機能 を導入し、ひな形のディレクトリ構成を自分たちのスタイルに揃えられるようにします。

swift package create の基本

create では、ディレクトリを作るところからコマンドが面倒を見ます。引数にパッケージ名を渡せば、その名前のディレクトリが新しく作られ、必要なファイルがその中に展開されます。

$ swift package create MyApp

生成される構成は次のとおりです。

.
├── Package.swift
├── Sources
│   └── MyApp
│       └── MyApp.swift
└── Tests
    └── MyAppTests
        └── MyAppTests.swift

init と違い、デフォルトでは executable として作られます。そのため、そのまますぐに実行して動作を確認できます。

$ cd MyApp
$ swift run
[3/3] Linking MyApp
Hello, world!

パッケージの種類は --type オプションで切り替えられます。指定できるのは librarysystem-moduleexecutable です。

$ swift package create MyLib --type library
$ swift package create SysMod --type system-module

ユーザー定義テンプレート

~/.swiftpm/configuration/templates/new-package/<テンプレート名> に Swift パッケージの形でひな形を置いておくと、--template オプションでそれを指定してパッケージを作れます。SwiftPM はテンプレートディレクトリをコピーし、テキストファイル内の特定のプレースホルダを置換し、テンプレート由来の .git 情報を取り除いた上で、新しいパッケージとして配置します。

使えるプレースホルダは次の 2 つです。

  1. ___NAME___: --name(または引数)で指定された名前にそのまま置換される
  2. ___NAME_AS_C99___: 上記を C99 の識別子として有効になるよう変換した名前

たとえば、次のようなテンプレートを test という名前で登録しておいたとします。

.
├── .git
├── .gitignore
├── Package.swift
├── README.md
├── LICENSE.md
└── src
    └── MyApp.swift

Package.swift(抜粋):

import PackageDescription

let package = Package(
    name: "___NAME___",
    dependencies: [
        .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "___NAME_AS_C99___",
            sources: "src",
            dependencies: [
                .product(name: "NIO", package: "swift-nio"),
                .product(name: "Crypto", package: "swift-crypto"),
            ]
        ),
    ]
)

この状態で次を実行すると、

$ swift package create HelloWorld --template test

.git を除いた構成がコピーされた上で、___NAME___ などが HelloWorld に置き換えられたパッケージが得られます。依存関係や独自のディレクトリレイアウト、README や LICENSE までまるごとテンプレート化できるのがポイントです。

デフォルトテンプレートと配布

テンプレート名を default にしておく(~/.swiftpm/configuration/templates/new-package/default)と、--template を指定せずに swift package create / swift package init を実行したときに自動的にそのテンプレートが使われます。デフォルトのひな形を自分たちのスタイルに置き換えられる、ということです。

テンプレートは Git リポジトリとして共有する前提で、次のコマンドも追加されます。

  • swift package add-template <url> [--name <name>]: URL からテンプレートを git clone して登録する
  • swift package update-template <name>: 登録済みテンプレートを git update で更新する

swift package init 側の変更

init は「既存のソースをパッケージ化する」用途に寄せるため、いくつか挙動が調整されます。

  1. README.md.gitignoreデフォルトでは生成しない(既存ソースのディレクトリへの影響を抑えるため)
  2. 空のディレクトリで init が実行されたときは、従来どおり新しいパッケージを作りつつ、「次からは swift package create のほうが適している」という diagnostics メッセージを表示する
  3. --template オプションを init 側でも受け付け、テンプレート機能を利用できるようにする

今後の展望(Future Iterations)

ディレクトリコピー + 文字列置換だけでは表現しきれない、より動的なパッケージ生成については、将来的に スクリプトで手続き的にパッケージを構築する仕組み を導入する案が挙げられています(speculative)。SwiftPM がパッケージ生成用の API を提供し、Swift スクリプトが入力オプションや外部条件に応じて生成内容を決める、というイメージです。今回の提案はあくまでコピーベースのテンプレートにとどめつつ、その先の拡張余地を残す位置づけになっています。

ステータスについての注記

本 Proposal は Returned for revision となっており、上記の設計そのままでは採用されていません。swift package create の導入というゴールと、テンプレート機能の具体的な位置づけ(デフォルトテンプレートの扱いや共有方法など)が再検討の対象となった、という点を踏まえて読む必要があります。