この記事の要点
- Swift 4 の Package Manager では、
Package.swiftのマニフェスト API が再設計されました。新しい API は API 設計ガイドラインに沿っており、Swift 3 で混乱の原因になっていた ターゲットの推論ルール を大幅に削減し、パッケージ構成をマニフェストに 明示的に書く 方針に変わりました。 - マニフェストの形式は、ファイル先頭の特別なコメント
// swift-tools-version:<バージョン>で指定する tools version によって選ばれます。この行を省いたパッケージは tools version 3.1.0 として扱われ、Swift 4 の Package Manager は 後方互換 を保つため、Swift 3 のパッケージもそのまま動作します。 - tools version は、パッケージのソースをコンパイルする際の既定の Swift 言語バージョンも決めます。
swiftLanguageVersionsプロパティを使えば、マニフェストを Swift 4 形式に上げつつソースは Swift 3 のまま(Swift 3 互換モードでコンパイル)にしておく、といった段階的な移行も可能です。
背景: Swift 3 の推論ルールは分かりにくかった
Swift 3 の Package Manager は、規約ベースの複雑なレイアウトルールと、ターゲットやその依存関係を自動推論する仕組みを持っていました。これは便利な反面、「どのディレクトリがどのターゲットになり、何に依存するのか」が暗黙的に決まるため、混乱の原因になっていました。
Swift 4 ではこの推論の大部分を取り除き、パッケージの構成をマニフェストに明示的に記述する方針へ切り替えました。あわせて、外部に公開するターゲットを product として明示的に書き出す API も導入されています。
tools version とマニフェストの選択
Swift 4 の Package Manager がどの形式のマニフェストとして解釈するかは、マニフェスト先頭の特別なコメント行で指定する tools version で決まります。
// swift-tools-version:4.0
- この行を省いたパッケージは tools version 3.1.0 として扱われます。
- tools version は、ソースをコンパイルする際の既定の Swift 言語バージョンも決めます。既存の Swift 3 パッケージは Swift 3 互換モードでコンパイルされます。
- 既定の言語バージョンを変えたい場合は、Swift 3 / Swift 4 いずれのマニフェストでも
swiftLanguageVersionsプロパティで指定できます。これにより、マニフェストだけを Swift 4 形式に更新し、ソースは Swift 4 へ書き換えない という移行も可能です。
依存解決の面でも互換性が保たれます。Swift 3 の Package Manager は Swift 4 形式のマニフェストを理解できないため、Swift 4 マニフェストを含む Git タグを自動的に無視し、Swift 3 マニフェストを含む最後のタグを選びます。一方、Swift 4 の Package Manager はマニフェスト形式に関わらず、利用可能な最新バージョンを選びます。
Swift 4 で新しいパッケージを作る
swift package init で新しいパッケージを作成します。
$ mkdir mytool && cd mytool
$ swift package init
$ swift build
$ swift test
生成される Package.swift は次のような形です。
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "mytool",
products: [
.library(
name: "mytool",
targets: ["mytool"]),
],
dependencies: [
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
.target(
name: "mytool",
dependencies: []),
.testTarget(
name: "mytoolTests",
dependencies: ["mytool"]),
]
)
旧形式との主な違いは次の 3 点です。
- tools version を
// swift-tools-version:4.0の行で指定します。 - すべてのターゲットとその依存関係を 明示的に宣言 します。
- 公開するターゲットを、新しい product API で product として書き出します。Swift 4 のパッケージのターゲットは、他パッケージの product か、同じパッケージのターゲットに依存できます。
カスタムなターゲットレイアウト
新しいマニフェストでは、パッケージのレイアウトを自由に指定できます。複雑な規約ベースのレイアウトに従う必要はなくなり、ルールは 1 つだけです。ターゲットのパスを指定しない場合、Sources、Source、src、srcs、Tests の各ディレクトリがこの順で探索されてターゲットが見つけられます。
カスタムレイアウトのおかげで、C ライブラリを Swift Package Manager に取り込むのも容易になりました。たとえばソースが src ディレクトリに置かれている C ライブラリは、path と sources を指定して次のように書けます。
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "LibYAML",
products: [
.library(
name: "libyaml",
targets: ["libyaml"]),
],
targets: [
.target(
name: "libyaml",
path: ".",
sources: ["src"])
]
)
特定のソースファイルだけを対象にしたり、公開ヘッダの場所を publicHeaders で指定したりもできます。
let package = Package(
name: "http-parser",
products: [
.library(
name: "httpparser",
targets: ["http-parser"]),
],
targets: [
.target(
name: "http-parser",
publicHeaders: ".",
sources: ["http_parser.c"])
]
)
既存パッケージを Swift 4 形式に更新する
既存のパッケージを Swift 4 マニフェスト形式へ移行する手順は次のとおりです。
tools version を更新する
tools-version サブコマンドで tools version を現在の値に設定します。
$ cd mypackage
$ swift package tools-version --set-current
依存関係の宣言を更新する
dependencies を targets より前に置き、依存関係の構文を新しい形式に直します。バージョン指定が majorVersion: から from: や .upToNextMinor(from:) などに変わっています。
dependencies: [
- .Package(url: "https://github.com/apple/example-package-fisheryates.git", majorVersion: 2),
+ .package(url: "https://github.com/apple/example-package-fisheryates.git", from: "2.0.0"),
- .Package(url: "https://github.com/apple/example-package-playingcard.git", majorVersion: 3, minor: 3),
+ .package(url: "https://github.com/apple/example-package-playingcard.git", .upToNextMinor(from: "3.3.0")),
]
すべてのターゲットを宣言する
通常のターゲットとテストターゲットの両方を、依存関係も含めて明示的に宣言します。
targets: [
.target(
name: "Foo"),
.testTarget(
name: "FooTests",
dependencies: ["Foo"]),
]
必要ならターゲットのパスを指定する
推奨レイアウトは、Sources の下にターゲットごとのディレクトリを置く(Sources/<ターゲット名>)形です。このレイアウトならパスは自動検出されます。それ以外のレイアウトでは path を指定します。
targets: [
.target(
name: "Foo",
path: "."), // ソースはパッケージのルートにある
.target(
name: "Bar",
path: "Sources") // ソースは Sources/ にある
]
公開ターゲットを product として書き出す
ライブラリパッケージは、他パッケージから import できるよう公開ターゲットを明示的に書き出します。サンプルコード用ターゲットやテスト補助ライブラリなど、外部に見せる必要のないものは書き出さないようにします。
products: [
.library(
name: "Foo",
targets: ["Foo", "Bar"]),
],
Swift 3 互換モードでコンパイルする
ソースを Swift 4 へ書き換える前にマニフェストだけを新形式に更新したい場合は、swiftLanguageVersions を 3 に設定して Swift 3 互換モードでビルドします。
swiftLanguageVersions: [3]
関連リンク
- Swift Package Manager — Package Manager の概要とドキュメント
- API 設計ガイドライン — 新しいマニフェスト API が従う設計指針