Swift Package ManagerでBuildToolPluginを利用する

# Swift Package Manager で BuildToolPlugin を利用する
* Siwft 5.6 から [Package Manager Extensible Build Tools](https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-extensible-build-tools.md) が利用できるようになりました
* これまで SwiftPM では、ビルド時に任意のスクリプトを実行する事ができませんでした
* なので、例えば SwiftPM のモジュールでビルド時に SwiftGen などでコード生成するという事ができませんでした
* しかし、Swift 5.6 から Plugin を利用する事で、ビルド時にコード生成などできるようになりました

# Swift Package Manager の Plugin を作る

* Makefile でコードを生成して、そのコードを利用する場合の Plugin を試してみました

## やりたいこと

* ビルド時に、下の Makefile の hoge を実行するようにします
* `DST` にファイルを作成して、Hoge 構造体を書き込みます
* この Hoge 構造体をモジュール内で利用します

```make
hoge:
	echo ${DST}
	touch ${DST}
	echo "public struct Hoge {\n    public let num: Int = 12\n} " > ${DST}
```

## Package.swift の定義
* plugin のターゲットを作成し、Hoge 構造体を利用したいターゲットの plugins に、plugin のターゲットを追加します

```swift
let package = Package(
    name: "MyLibrary",
    products: [
        .library(name: "MyLibrary",
                 targets: ["MyLibrary"]),
    ],
    targets: [
        .plugin(name: "MyPlugin",
                capability: .buildTool()),
        .target(name: "MyLibrary",
                plugins: [.plugin(name: "MyPlugin")]),
    ]
)
```

## Plugins/MyPlugin/MyPlugin.swift
* Plugins/MyPlugin/MyPlugin.swift を作成し、BuildToolPlugin を継承した　MyPlugin を作成します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/9dbdddcb-a34d-e5fb-2dc9-e68b6eaa6766.png)

* `context.pluginWorkDirectory` に Hoge.swift を作成します
* `context.pluginWorkDirectory` 以外に書き込む事はできませんでした
* `outputFilesDirectory` で指定したディレクトリ以下がビルドの対象にされるようでした

```swift
import PackagePlugin

@main struct MyPlugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        return [
            .prebuildCommand(displayName: "Run MyPlugin",
                             executable: Path("/usr/bin/make"),
                             arguments: [
                                "hoge",
                                "-C", "\(context.package.directory)",
                                "DST=\(context.pluginWorkDirectory.string)/Hoge.swift",
                             ],
                             outputFilesDirectory: context.pluginWorkDirectory),
        ]
    }
}
```

## 作成したコードを利用する
* DerivedData ↓ を見ると Hoge.swift が作成され、Hoge 構造体が書き込まれていることがわかります
  `/xxx/Xcode/DerivedData/xxx-xxxxxxxxxx/SourcePackages/mylibrary/MyLibrary/Hoge.swift`
* Xcode の Navigator Area には表示されないのがむず痒いですが、Hoge 構造体が利用できるようになってます

```swift
public struct MyLibrary {
    public private(set) var text = "num: \(Hoge().num)"
    public init() {
    }
}
```

* MyLibrary を表示するアプリを作ります

```swift
struct ContentView: View {
    var body: some View {
        Text(MyLibrary().text)
            .font(.system(size: 32))
            .padding()
    }
}
```

## 動作の様子
* Makefile で書き込む Hoge 構造体の中身を変更して、再度アプリをビルドすると、変更した内容に表示が変わる事が確認できました
* アプリを際ビルドすると、MyLibrary の plugin が実行され、そこで生成されたコードを使って、MyLibrary がちゃんとビルドし直されているようです

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">swift pmのplugin使ってビルド時に任意のスクリプト動かす事できた <a href="https://t.co/TSr9QugzhY">pic.twitter.com/TSr9QugzhY</a></p>&mdash; ふじき (@fzkqi) <a href="https://twitter.com/fzkqi/status/1503724697048915972?ref_src=twsrc%5Etfw">March 15, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ
* Swift 5.6 で追加された Plugin を使って make を実行してみました
* Xcode 13.3 では、生成したコードの Hoge の補完は効くのですが、jump to definition できなかったり、エラーがよくわからなかったり、使いにくさはありました
* しかし、SwiftPM でモジュールを作成しているアプリでは、SwiftGen など自動生成系をビルド時に実行できるようになるメリットは大きそうだと感じました