LoginSignup
55
29

More than 1 year has passed since last update.

Swift Package Managerのプラグイン機能

Last updated at Posted at 2022-04-15

2022年3月にSwift 5.6がリリースされました。Swift 5.6で、Swift Package Manager(以下、SwiftPMと呼びます)に対してプラグイン機能が追加されました。このプラグイン機能について解説します。

SwiftPMのプラグイン機能

SwiftPMにはこれまでプラグインの機能はありませんでした。つまり、今回のSwift 5.6で新規に追加されたものです。

プラグイン機能について、SwiftPM 5.6の Release Notes に簡単な説明が書かれています。プラグインには2種類あります。

  • ビルドツールプラグイン(SE-0303
  • コマンドプラグイン(SE-0332

これまでのSwiftPMはビルド時の動作をカスタマイズできませんでしたが、このプラグイン機能で可能になりました。

ビルドツールプラグイン

ビルドツールプラグインは、SwiftPMのビルド時に外部ツールを実行するようにします。

先ほどのRelease Notesやプロポーザルの中で想定されている用途は、ビルド時のソースコードの自動生成です。たとえば SwiftGen を使ってソースコードを生成してから、ビルドを実行できます。

ただし、必ずしもそれだけの用途ではなく、ビルド時に好きな外部ツールを実行できます。

例:XcodeプロジェクトでSwiftLintを実行する

最近、Xcodeプロジェクトで実装コードをSwiftPMで管理するプロジェクト構成を見かけることが増えてきました。その構成のサンプルを紹介します。

サンプルリポジトリ:usami-k/XcodeSwiftPMSample

  • アプリのXcodeプロジェクト(Hello.xcodeproj)には、必要最小限のものだけ入れる。
  • アプリの実装コードは、Swiftパッケージ(AppFeatureCore)に入れる。
  • Hello.xcodeproj でアプリに AppFeature パッケージをリンクする。

この構成の場合に、ビルド時に SwiftLint を実行したいとします。

従来は、SwiftPMではビルド時の処理をカスタマイズできなかったため、Xcodeのビルドスクリプトで SwiftLint を実行する必要がありました。これが、ビルドツールプラグインを使うことで、SwiftPMで SwiftLint を実行できます。

なお、Swift 5.6が同梱されたのはXcode 13.3ですので、この方法を使うにはXcode 13.3以降が必要です。

Package.swiftの記述

SwiftLintのビルドツールプラグインはまだ公式には配布されていません。そこで、試しにビルドツールプラグインのパッケージを作りました。

usami-k/SwiftLintPlugin

このプラグインパッケージを使うには、SwiftPMの設定ファイルである Package.swift で、次のように書きます(ファイル全体はGitHubにあります → Package.swift)。

dependencies: [
    .package(url: "https://github.com/usami-k/SwiftLintPlugin", branch: "main"),
],
targets: [
    .target(
        name: "AppFeature",
        plugins: [
            .plugin(name: "SwiftLintPlugin", package: "SwiftLintPlugin"),
        ]
    ),
]

まず、dependencies: で使いたいプラグインパッケージを指定します。次に、ビルドするターゲットの .target() 定義の中の plugins: でビルド時に実行するビルドツールプラグインを指定します。

上述のサンプルリポジトリにあるXcodeプロジェクトは、このプラグインパッケージを使って SwiftLint を実行するよう設定済みです。Xcodeでビルドすると、SwiftLintの内容がXcode上で警告やエラーとして表示されます。

コマンドプラグイン

コマンドプラグインは、SwiftPMのコマンドを拡張して、外部ツールを実行できるようにします。

先ほどのRelease Notesやプロポーザルの中で想定されている用途は、ドキュメントの生成、ソースコードのフォーマット、アーカイブの作成、などのタスクの実行です。

make コマンドや npm run コマンドのような、タスクランナー機能が追加されたと考えることができそうです。

例:Swift-DocCを使う

Swiftのドキュメント生成ツールとして Swift-DocC が2021年にリリースされました。

Swift-DocCはXcodeプロジェクトのソースコードからドキュメントを生成できます。その機能がXcode 13に組み込まれており、簡単に利用できます。

しかし一方で、SwiftPMプロジェクトのソースコードからドキュメントを生成するには多少準備が必要でした。これが、コマンドプラグインを使うことで、SwiftPMで手軽にSwift-DocCを利用できます。

Package.swiftの記述

Swift-DocCのコマンドプラグインのパッケージが、Appleから配布されています。

apple/swift-docc-plugin

このプラグインパッケージを使うには、SwiftPMの設定ファイル Package.swift で次のように書きます。

dependencies: [
    .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
],

単に dependencies: で使いたいプラグインパッケージを指定すればよいです。

これにより、次のコマンドが使えるようになります。

  • swift package generate-documentation : ドキュメントの生成(.doccarchive 形式、または静的サイトホスティング形式)
  • swift package preview-documentation : ドキュメントのプレビュー

これらのコマンドでできることについては、Swift-DocC プラグインのドキュメント を参照してください。

自分でプラグインを実装する

ここまでの例では、すでに配布されているプラグインパッケージを使いました。代わりに、自分でプラグインを実装する方法もあります。プラグインの動作を自分の好みにカスタマイズしたい場合や、プロジェクト固有の設定をしたい場合は、この方法が便利です。

以下、ビルドツールプラグインを自分で実装する例を紹介します。これは ExampleSPMProjectWithSwiftLint を参考にしました。

Package.swiftの記述

あるSwiftPMターゲットのビルド時にSwiftLintを実行したいとします。

そのターゲットの設定ファイル Package.swift で、次のように書きます。

targets: [
    .binaryTarget(
        name: "SwiftLintBinary",
        url: "https://github.com/juozasvalancius/SwiftLint/releases/download/spm-accommodation/SwiftLintBinary-macos.artifactbundle.zip",
        checksum: "cdc36c26225fba80efc3ac2e67c2e3c3f54937145869ea5dbcaa234e57fc3724"
    ),
    .plugin(
        name: "SwiftLintPlugin",
        capability: .buildTool(),
        dependencies: ["SwiftLintBinary"]
    ),
    .target(
        name: "AppFeature",
        plugins: ["SwiftLintPlugin"]
    ),
]

上記の設定ファイルで、ビルドするターゲットは AppFeature です。この .target() 定義の中の plugins: でビルド時に実行するビルドツールプラグインを指定します。ここでは SwiftLintPlugin プラグインを指定しています。

SwiftLintPlugin プラグインもターゲットの一種として記述しています。この .plugin() 定義の中の dependencies: で実行したい外部ツールのバイナリターゲット SwiftLintBinary を指定しています。

バイナリターゲット SwiftLintBinary.binaryTarget() で定義しています。SwiftPMプラグインが使用するバイナリはartifact bundleという形式で準備する必要があります。上記の設定では、SwiftLintのartifact bundleは juozasvalancius さんが配布しているものを使用しています。なお、現時点では公式にはSwiftLintのartifact bundleは配布されていませんが、近いうちに公式で配布される予定です。

プラグインの実装コード

ビルドツールプラグインの処理内容はSwiftコードで書きます。コードは Plugins フォルダーに置き、次のように実装します。

import PackagePlugin

@main
struct SwiftLintPlugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        return [
            .buildCommand(
                displayName: "Linting \(target.name)",
                executable: try context.tool(named: "swiftlint").path,
                arguments: [
                    "lint",
                    "--in-process-sourcekit",
                    "--path",
                    target.directory.string
                ],
                environment: [:]
            )
        ]
    }
}

このように、実行したい処理を .buildCommand() で定義すればよいです。ビルドターゲットにこのビルドツールプラグインが指定されていれば、ビルド実行時に自動的にこの処理が実行されます。

なお、Xcode 13.3時点での問題点として、Xcode上でビルドを実行した場合はツールに environment: の内容が正しく渡されないようです。このため、環境変数を参照するツールは正しく動作しませんので注意が必要です。コマンドライン引数は利用可能です。

上記の例ではSwiftLintのIn Process SourceKit機能(Xcode 13との連携機能)を使っています。その指定に環境変数 IN_PROCESS_SOURCEKIT は使わず、コマンドライン引数 --in-process-sourcekit を使っています。

まとめ

Swift 5.6で追加されたSwiftPMのプラグイン機能について説明しました。そして、配布されているプラグインパッケージを使う方法と、自分でプラグインを実装する方法を説明しました。

SwiftPMのプラグイン機能、個人的には今後も注目していきたい機能です。

55
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
29