🗂

Swift Package ManagerのBuild Tools

2022/10/12に公開

はじめに

Swift Pakcage Manager (以下 SwiftPM) Build Tools(Build Tool Plugin)についてハマりどころがあったので、使用方法について実例を交えて紹介します
なお、コードサンプルも用意したので適宜参考にしていただければ幸いです

https://github.com/yoshiysh/spm-plugin-sample/tree/build-tools

また、本機能についてのRelease Noteはこちら

本記事では説明しないこと

  • Swift Package Managerの使用方法
  • 実例に挙げるライブラリの使用方法

環境

  • OS: macOS Monterey 12.5.1
  • Xcode: 14.0 (14A309)
  • Swift: 5.7

例: SwiftGenを実行する

Resourceファイルを安全にアクセスするためのコードジェネレータ。タイプセーフに扱うことができるので類似のライブラリを使用、もしくは自作している人も多いんじゃないでしょうか

SwiftGen

  • version: 6.6.2
  • artifactbundle: github

Package.swiftの実装

Package.swiftの実装は以下のようになります

Pakcage.swift
.binaryTarget(
    name: "swiftgen", // ①
    url: "https://github.com/SwiftGen/SwiftGen/releases/download/6.6.2/swiftgen-6.6.2.artifactbundle.zip", // ②
    checksum: "7586363e24edcf18c2da3ef90f379e9559c1453f48ef5e8fbc0b818fbbc3a045" // ③
),

.plugin(
    name: "SwiftGenPlugin", // ④
    capability: .buildTool(),
    dependencies: ["swiftgen"] // ⑤
),

このPackage.swiftの実装について詳しく解説します

  1. swiftgen-6.6.2.artifactbundle.zipを解凍したあとの swiftgen.artifactbundle
  2. artifactbundleのリンク
  3. artifactbundle.zipをダウンロード後、SwiftPakcage.swiftがあるディレクトリ配下で以下のコマンド実行結果
$ swift package compute-checksum swiftgen-6.6.2.artifactbundle.zip
7586363e24edcf18c2da3ef90f379e9559c1453f48ef5e8fbc0b818fbbc3a045
  1. 後述するPluginのディレクトリ名
  2. 1.で記載した名前

おすすめはしませんが、checksumを空文字にして実行することで得られるエラーでchecksumの中身を確認することができます

BuildToolPluginの実装

SwiftGenで実行するコマンドを実装します。ディレクトリ名は上述したPackage.swiftのディレクトリ名を使用して下図のように追加します

Plugin.swift
import PackagePlugin

@main
struct Plugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        [
            .prebuildCommand(
                displayName: "Running SwiftGen",
                executable: try context.tool(named: "swiftgen").path,
                arguments: [
                    "config",
                    "run",
                    "--config", "\(context.package.directory.string)/../swiftgen.yml"
                ],
                environment: [
                    "OUTPUT_DIR": context.pluginWorkDirectory.string // ①
                ],
                outputFilesDirectory: context.pluginWorkDirectory
            )
        ]
    }
}

ここでは設定のほとんどをswiftgen.ymlに記載しています
①のようにenvironmentの中で変数を定義することで、今回であればswiftgen.ymlの中で使用することができます

swiftgen.ymlの実装

SwiftGenの設定ファイルです。細かい設定については公式を参照してください

swiftge.yml
input_dir: ./Package/Sources/App/Resources
output_dir: ${OUTPUT_DIR}

strings:
  inputs: ja.lproj
  filter: .+\.strings$
  outputs:
    templateName: structured-swift5
    output: L10n-Constants.swift

上記ではBuildToolPluginで指定したOUTPUT_DIRを使用して、コードの生成先を指定しています

以上でビルド時にSwiftGenが実行されるようになったと思います

ところで……

ライブラリを追加/更新の際に毎回artifactbundleをダウンロードして、checksumを計算してと少し手間に感じませんか?

なので、次章ではSwiftLintを例にurlを用いずにPluginを実行する方法について紹介します

例: SwiftLintを実行する

Swift用の静的解析ツール。PRのレビュー等で指摘することも減りコードの品質を保つことができるので静的解析ツールを導入する機会も多いかと思います

SwiftLint

  • version: 0.49.1
  • artifactbundle: github

Package.swiftの実装の前に

今回はurl/checksumを使用しない方法として、ローカルに配置されたartifactbundleを使用する方法について説明します
そのためローカルにあらかじめ上記のartifactbundle.zipをダウンロードの上解凍済みのものとします
コードサンプルではArtifacts配下に配置しています

Package.swiftの実装

Package.swiftの実装は以下のようになります

Package.swift
.binaryTarget(
    name: "SwiftLintBinary",
    path: "./../Artifacts/SwiftLintBinary.artifactbundle"
),

.plugin(
    name: "SwiftLintPlugin",
    capability: .buildTool(),
    dependencies: ["SwiftLintBinary"]
),

基本的にはSwiftGenの時に実装した内容と同じ内容です。異なる点としてbinaryTargetで指定しているのがurlではなくartifactbundleまでのpathになっている点ですね

BuildToolPluginの実装

SwiftLintで実行するコマンドを実装します。ディレクトリ名は上述したPackage.swiftのディレクトリ名を使用してSwiftLintPluginという名前で作成しています

Plugin.swift
import PackagePlugin

@main
struct Plugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        [
            .buildCommand(
                displayName: "Running SwiftLint for \(target.name)",
                executable: try context.tool(named: "swiftlint").path,
                arguments: [
                    "--config", "\(context.package.directory.string)/../.swiftlint.yml",
                    "--no-cache",
                    "--quiet",
                    "--format"
                ],
                environment: [:]
            )
        ]
    }
}

こちらもほぼSwiftGenの実装時と変わらないので説明は省略します

.swiftlint.ymlの実装

SwiftLintの設定ファイルです。細かい設定については公式を参照してください

以上でビルド時にSwiftLintが実行されるようになったと思います

おわりに

本記事では、SwiftGen, SwiftLintを例にSwiftPM Build Toolsの実装についてURL、Pathを使用して実装する方法について紹介しました
実装に少し癖のあるプラグイン機能ですが、慣れてしまえばとても便利な機能かと思います

個人的にはレビューが容易になったりと嬉しいことも多いのでぜひ使っていきたい機能ですね!

参考リンク

Discussion