Swift Package コマンドプラグインの --allow-writing-to-(package-)directory オプションの動作

## 概要

`swift package plugin` コマンドの help を見ると、ファイルの書き込みを許可するオプションとして `--allow-writing-to-package-directory` と `--allow-writing-to-directory` があることがわかります。
```sh
❯ swift package plugin --help
# ...
  --allow-writing-to-package-directory
                          Allow the plugin to write to the package directory
  --allow-writing-to-directory <allow-writing-to-directory>
                          Allow the plugin to write to an additional directory
```
これらのオプションの具体的な動作について、 help を見るだけではよくわからなかったので調査しました。

注意点として、これらのオプションについてはプロポーザルに詳しい記載を見つけることができず、また Swift Package の実装を読んだわけではなく試したことをベースにこの記事を書いているので、もしかしたら勘違いが含まれているかもしれません。また、検証は以下の環境で行なっており、今後動作が変わる可能性があります。

- Xcode 14.3 Beta 2
- Swift 5.8

なにか指摘点を見つけられた方は、コメント等で教えていただけると助かります。

Swift Package プラグインについての基礎的な知識は以下の記事にまとめたので、よければ参照ください。

https://qiita.com/maiyama18/items/3d0f7b288c6858b751ce

## TL;DR

- `--allow-writing-to-package-directory` オプションをつけることで、**プラグインが permission を指定しているかどうかに関わらず** Package 内に書き込みができるようになる
- `--allow-writing-to-directory <directory>` オプションをつけることで、**Package 外にも書き込みができるようになる**

## サンプルプラグイン
`--allow-writing-to-package-directory` / `--allow-writing-to-directory` オプションの効果を検証するために、何か適当なファイル書き込みを行うプラグインが必要なので、指定されたパスに空のディレクトリを作るだけの plugin を作りました。

[maiyama18/ZatsuPlugins/CreateDirectoryPlugin](https://github.com/maiyama18/ZatsuPlugins/blob/4e769598f4928d2582c956a3f888e1f7f84d95a4/Plugins/CreateDirectoryPlugin/CreateDirectoryPlugin.swift)

このサンプルプラグインを別の適当な Swift Package から使ってみます。 `swift package init` で Package を作成し、 `Package.swift` にプラグインを依存として追加します。

```swift
let package = Package(
    name: "SomeSwiftPackage",
    dependencies: [
        .package(url: "https://github.com/maiyama18/ZatsuPlugins", branch: "main")
    ],
    // ...
)

```

この状態で `swift package plugin --list` コマンドを打つと、 `CreateDirectoryPlugin` が使えるようになっていることがわかります。

```sh
❯ swift package plugin --list
‘create-directory’ (plugin ‘CreateDirectoryPlugin’ in package ‘ZatsuPlugins’)
```

## `--allow-writing-to-package-directory` オプション

それでは、実際にプラグインを使ってみます。まずはなにもオプションを指定せずに package 直下に `tmp` ディレクトリを作ろうとしてみましょう。

```sh
❯ swift package plugin create-directory --path tmp
error: Failed to create directory: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “tmp”." UserInfo={NSFilePath=tmp, NSUnderlyingError=0x600001ddc090 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
```

権限不足のために失敗してしまいました。 Swift Package コマンドプラグインは Package 内に書き込むために明示的な許可が必要になるためです。続いて、 `--allow-writing-to-package-directory` オプションを指定して実行してみます。
```sh
❯ swift package plugin --allow-writing-to-package-directory create-directory --path tmp
Directory created at tmp
```
今度はディレクトリの作成に成功しました。これはオプションを指定したので当然のようにも思えますが、自分には少し驚きでした。というのも、プラグイン側の `Package.swift` に package 内書き込みのための permission を記述する仕組みがありますが、現時点では以下のようにこの permission を指定していないためです。

https://github.com/maiyama18/ZatsuPlugins/blob/4e769598f4928d2582c956a3f888e1f7f84d95a4/Package.swift#L12-L20

プラグイン側で package 内に書き込むための permission を指定するには、 `capability` の `permissions` に `.writeToPackageDirectory` を追加します。

```diff_swift
        .plugin(
            name: "CreateDirectoryPlugin",
            capability: .command(
                intent: .custom(
                    verb: "create-directory",
                    description: "Create a directory"
                ),  
+               permissions: [.writeToPackageDirectory(reason: "To create a directory")]
            )
        )
```

プラグイン定義側でこの permission を付けることで、利用側で `--allow-writing-to-package-directory` なしでプラグインを実行した時の振る舞いが変わります。

```sh
❯ swift package plugin create-directory --path tmp
Plugin ‘CreateDirectoryPlugin’ wants permission to write to the package directory.
Stated reason: “To create a directory”.
Allow this plugin to write to the package directory? (yes/no) yes
Directory created at tmp
```

package 内に書き込むための理由を表示してくれ、 yes/no でインタラクティブにプラグインの実行判断ができるようになっていることがわかります。

当然ですが、 `--allow-writing-to-package-directory` オプションをつけた場合はファイル書き込みが成功するのでプラグイン側で permission を付ける前と変わりません。

つまり、プラグイン側で付与する `.writeToPackageDirectory` の permission は、あくまでプラグイン実行側で `--allow-writing-to-package-directory` をつけない場合にのみ効果があり、 `--allow-writing-to-package-directory` をつける場合はプラグイン側で permission を付与しているかどうかに関わらず package 内への書き込みを許すことになります。

ただ、 `--allow-writing-to-package-directory` というオプション名から想像がつくかもしれませんが、書き込みができるのは package 内だけで、 package 外に書き込もうとすると権限エラーになります。

```sh
❯ swift package plugin --allow-writing-to-package-directory create-directory --path ../tmp
error: Failed to create directory: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “tmp” in the folder “maiyama18”." UserInfo={NSFilePath=../tmp, NSUnderlyingError=0x600001674300 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
```

## `--allow-writing-to-directory` オプション

続いて、`--allow-writing-to-directory` オプションについても見ていきます。 `--allow-writing-to-package-directory` は package 内のどこでも書き込めるようにするオプションでしたが、 `--allow-writing-to-directory` は指定したディレクトリ内にのみ書き込めるようにするオプションです。
Package 内に dir1 / dir2 というディレクトリを作成し、 `--allow-writing-to-directory dir2` というオプションを指定すると、 dir1 への書き込みは失敗し、 dir2 への書き込みのみ成功するようになります。

```sh
❯ mkdir dir1 && mkdir dir2

❯ swift package plugin --allow-writing-to-directory dir2 create-directory --path dir1/tmp
error: Failed to create directory: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “tmp” in the folder “dir1”." UserInfo={NSFilePath=dir1/tmp, NSUnderlyingError=0x6000003c0390 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

❯ swift package plugin --allow-writing-to-directory dir2 create-directory --path dir2/tmp
Directory created at dir2/tmp
```

さらに、 `--allow-writing-to-directory` は package 外に書き込むことも可能です。親ディレクトリに書き込む例が以下です。

```sh
❯ swift package plugin --allow-writing-to-directory .. create-directory --path ../tmp
Directory created at ../tmp
```

なんとなく Swift Package プラグインは Package 内部にしか書き込めないと思っていましたが、とくにそういう制限はないようです。 Package 外に書き込めることでうれしい例としてぱっと思いつくものとしては、以下のようにアプリを Xcode project と Swift Package を組み合わせて作っている際に、 Swift Package に入れたコードフォーマットのプラグインを Xcode project にも適用できるというものがあります。もちろん、これは Swift Package プラグインの仕様上は理論的に可能というだけで、実際にそれができるかはコードフォーマットのプラグインの実装に依存します。

```sh
.
├── MyApp
│   ├── MyApp
│   └── MyApp.xcodeproj
└── Packages
    └── MyAppPackage
```

いずれにしても、 Swift Package コマンドプラグインが `--allow-writing-to-directory` オプションをつけることでPackage 外にも書き込み可能になることはプラグインの作成者としても、利用者としても認識しておきたいです。