SwiftGenのセットアップ方法&使い方(SwiftPM)

## はじめに

SwiftGenをSwiftPMで使う方法を紹介します。

## 「SwiftGen」とは？

リソース（ローカライズされた文字列や画像など）に対してSwiftのコードを生成し、型安全にアクセスするためのCLIツールです。
R.swiftと競合するパッケージです。

## R.swiftとの比較

R.swiftとSwiftGenを比較します。
両方とも素晴らしいパッケージなので、どちらを使うか迷います。

- CLIツールのみで完結する  
R.swiftはCLIツールとライブラリの両方が必要なのに対し、SwiftGenはCLIツールのみで完結する
- 設定ファイルが必要  
R.swiftは設定ファイルが不要なのに対し、SwiftGenは作成する必要がある

個人的な感覚は以下の通りです。

- 簡単に使いたい → R.swift
- ライブラリを増やしたくない、またはカスタマイズしたい → SwiftGen

## SwiftGen導入のメリット

R.swiftと同様なので、以下の記事をご参照ください。

https://qiita.com/uhooi/items/82fbdd94bdc467a22422#rswift導入のメリット

## 環境

- OS：macOS Monterey 12.5.1
- Xcode：14.2 (14C18)
- Swift：5.7.2
- Swift Package Manager：5.7.1
- SwiftGen：6.6.2

## セットアップ方法

### SwiftGenプラグインのインストール

SwiftGenは公式でプラグインが用意されています。
CLIツールとプラグインでリポジトリが分かれているのが特徴です。

- CLIツール: https://github.com/SwiftGen/SwiftGen
- プラグイン: https://github.com/SwiftGen/SwiftGenPlugin

プラグインはSwiftGenのArtifact bundle（≒ビルド済みバイナリ）にのみ依存していることが明確になっており、他のパッケージと依存が被らないので使いやすいです。

https://github.com/SwiftGen/SwiftGenPlugin/blob/879b85a470cacd70c19e22eb7e11a3aed66f4068/Package.swift

インストールは `Package.swift` の `dependencies:` に追記するのみです。

```diff_swift:Package.swift
    dependencies: [
+       .package(url: "https://github.com/SwiftGen/SwiftGenPlugin", from: "6.6.2"),
    ],
```

### ビルド時にリソースファイルを生成するようにする

`Package.swift` のリソースが含まれているターゲットにプラグインを追加するだけで、ビルド時に自動でリソースファイルが生成されます。

```diff_swift:Package.swift
    targets: [
        .target(
            // ...
            plugins: [
+               .plugin(name: "SwiftGenPlugin", package: "SwiftGenPlugin"),
            ]),
    ]
```

リソースファイルを生成したい全ターゲットに追加する必要があります。

プラグインの追加が手間なので、リソース用のターゲット（モジュール）を作り、リソースを1つのモジュールで一元管理するのもありだと思います。
ただ私は今のところ、必要なリソースを必要なモジュールのみに含めるのが好みです。

### 設定ファイルを作成する

設定ファイルは各ターゲットのパスに `swiftgen.yml` を追加すると、自動的に読み込まれます。

以下は設定ファイルの一例です。

```yaml:swiftgen.yml
input_dir: Resources
output_dir: ${DERIVED_SOURCES_DIR}

strings:
  inputs:
    - ja.lproj/Localizable.strings
  outputs:
      templateName: structured-swift5
      output: Strings.swift

xcassets:
  inputs:
    - Assets.xcassets
  outputs:
    templateName: swift5
    output: Assets.swift
```

Swift Package Manager 5.7.1時点で、Build Tool Pluginは `DerivedData` （ `${DERIVED_SOURCES_DIR}` ） にしかファイルを生成できません。
そのため `output_dir` は変更できません。

1つの設定ファイルに複数のターゲットの設定をまとめて書くこともできます。
私はターゲットを削除するときに設定を消し忘れるのを防ぐため、ターゲットごとに設定を書くのが好みです。

設定ファイルの書き方の詳細は、公式ドキュメントをご参照ください。

https://github.com/SwiftGen/SwiftGenPlugin/tree/879b85a470cacd70c19e22eb7e11a3aed66f4068#supporting-multiple-targets

## 使い方

### ローカライズされた文字列

`structured-swift5` テンプレートを使う場合、 `L10n.○○` でローカライズされた文字列にアクセスできます。

```diff_swift
- String(localized: "Copy Sakatsu text", bundle: .module)
+ L10n.copySakatsuText
```

### アセット

`swift5` テンプレートを使う場合、 `Asset.○○.image` で画像に `UIImage` としてアクセスできます。

```diff_swift
- Image("foo_and_bar", bundle: .module)
+ Image(uiImage: Asset.fooAndBar.image)
```

`Asset.○○.swiftUIImage` で `SwiftUI.Image` としてアクセスできるので、SwiftUIの場合はこちらのほうがスマートです。

```diff_swift
- Image(uiImage: Asset.fooAndBar.image)
+ Asset.fooAndBar.swiftUIImage
```

## おまけ: プラグインを自作する

ソースを見ればわかりますが、プラグインはCLIツールをラップした薄いファイルです。

https://github.com/SwiftGen/SwiftGenPlugin/blob/879b85a470cacd70c19e22eb7e11a3aed66f4068/Plugins/SwiftGenPlugin/Plugin.swift

Build Tool Pluginの場合、CLIツールに引数を一切渡せないため、カスタマイズしたい場合はプラグインを自作する必要があります。

SwiftGenはArtifact bundleを提供しており、プラグインも100行ないため、簡単に自作できます。
プラグインを自作する方法は、以下の記事がわかりやすいです。

https://zenn.dev/yoshi991/articles/274f271cadbc0e

## おまけ: マルチモジュールにおけるリソースの管理方法

SwiftPMでモジュール分割するとき、リソースをどう管理するかの考えを、以下のツイートのツリーでまとめています。

https://twitter.com/the_uhooi/status/1622565221478072320

リソースを1つのモジュールで一元管理する場合、アクセス修飾子を `internal` から `public` にする必要があります。
`swiftgen.yml` から簡単に変更できます。

```diff_yaml:swiftgen.yml
input_dir: Resources
output_dir: ${DERIVED_SOURCES_DIR}

strings:
  inputs:
    - ja.lproj/Localizable.strings
  outputs:
    templateName: structured-swift5
    output: Strings.swift
+   params:
+     publicAccess: true

xcassets:
  inputs:
    - Assets.xcassets
  outputs:
    templateName: swift5
    output: Assets.swift
+   params:
+     publicAccess: true
```

## おわりに

Swift Package上で、ローカライズされた文字列をスッキリかつ安全にアクセスできるようになりました :relaxed:
SwiftPMのプラグインは便利なので、他のCLIツールでも積極的に使っていきたいです。

## 参考リンク

- [Setup SwiftGen plugin by uhooi · Pull Request #126 · uhooi/Loki](https://github.com/uhooi/Loki/pull/126)
- [SwiftGen/structured-swift5.md at 1cf6d7eebd70c2157f69d5a991bc57c1ef182ed1 · SwiftGen/SwiftGen](https://github.com/SwiftGen/SwiftGen/blob/1cf6d7eebd70c2157f69d5a991bc57c1ef182ed1/Documentation/templates/strings/structured-swift5.md)
- [SwiftGen/README.md at 1cf6d7eebd70c2157f69d5a991bc57c1ef182ed1 · SwiftGen/SwiftGen](https://github.com/SwiftGen/SwiftGen/blob/1cf6d7eebd70c2157f69d5a991bc57c1ef182ed1/README.md#asset-catalog)
- [Make output file scoped to Public? · Issue #837 · SwiftGen/SwiftGen](https://github.com/SwiftGen/SwiftGen/issues/837)
- [How to use SwiftGen for internal framework · Discussion #833 · SwiftGen/SwiftGen](https://github.com/SwiftGen/SwiftGen/discussions/833#discussioncomment-696278)