Swift Package Managerで簡単なコマンドラインツールを作って試す

## Swift Package Manager

[公式ページ](https://github.com/apple/swift-package-manager)

### 環境

```sh
$ swift package --version
Swift Package Manager - Swift 5.2.0
```

今回は新たにコマンドラインツールを作る想定で環境を作っていきたいと思います。

```sh
$ mkdir spm-sample
$ cd spm-sample
$ swift package init --type=executable
Creating executable package: spm-sample
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/spm-sample/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/spm-sampleTests/
Creating Tests/spm-sampleTests/spm_sampleTests.swift
Creating Tests/spm-sampleTests/XCTestManifests.swift
```
`--type` に関しては以下の種類があります。
- empty
- library
- system-module
- executable
- manifest

一旦ビルドして実行してみます。

```sh
$ swift build
$ ./.build/debug/spm-sample
Hello, world!
```
### ちょっとしたツールの作成

#### 作るもの
折角なので、iOS開発を行っていたらいつの間にか色んなファイルで
容量を食ってしまうものを一斉に削除するツールを作ってみたいと思います。
プロジェクト名は `iCleaner` として作成します。

#### 必要なパッケージを導入
↓のパスを簡単に扱えるようにする `PathKit` を導入したいと思います。
<a href="https://github.com/kylef/PathKit"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/64a56cc7-dc0d-6bb6-0b46-18b87ad76d7e.png" width="460px"></a>

`Package.swift` に以下を追加

```swift
import PackageDescription

let package = Package(
    name: "iCleaner",
    dependencies: [
        .package(url: "https://github.com/kylef/PathKit.git", from: "1.0.0") // ☆追加
    ],
    targets: [
        .target(
            name: "iCleaner",
            dependencies: ["PathKit"]), // ☆追加
        .testTarget(
            name: "iCleanerTests",
            dependencies: ["iCleaner"]),
    ]
)
```

試しに以下を `main.swift` に書いて実行してみます。

```swift
import Foundation
import PathKit

let path = FileManager.default.currentDirectoryPath
let currentPath = Path(path)
print(currentPath.glob("*.swift"))
```

```sh
$ swift run
[XXX/iCleaner/Package.swift]
```
:+1:

そしてツールの完成形はこちら

```swift:Cleaner.swift
import Foundation
import PathKit

class Cleaner {
  static let DEVICE_SUPPORT_PATH = "Library/Developer/Xcode/iOS DeviceSupport"
  static let DEVICE_LOGS_PATH = "Library/Developer/Xcode/iOS Device Logs"
  static let ARCHIVES_PATH = "Library/Developer/Xcode/Archives"
  static let DERIVEDDATA_PATH = "Library/Developer/Xcode/DerivedData"

  let homePath: Path
  init() {
    let path = NSHomeDirectory()
    homePath = Path(path)
    print("HOME: \(homePath)")
  }

  func run() {
    // Device support
    let deviceSupportPath = homePath + Path(Cleaner.DEVICE_SUPPORT_PATH)
    if deviceSupportPath.exists {
      deviceSupportPath.glob("*").forEach { try? $0.delete() }
    }

    // Device logs
    let devicelogsPath = homePath + Path(Cleaner.DEVICE_LOGS_PATH)
    if devicelogsPath.exists {
      devicelogsPath.glob("*.db").forEach { try? $0.delete() }
      devicelogsPath.glob("*.db-shm").forEach { try? $0.delete() }
      devicelogsPath.glob("*.db-wal").forEach { try? $0.delete() }
    }

    // Archives
    let archivesPath = homePath + Path(Cleaner.ARCHIVES_PATH)
    if archivesPath.exists {
      archivesPath.glob("*").forEach { try? $0.delete() }
    }

    // DerivedData
    let derivedDataPath = homePath + Path(Cleaner.DERIVEDDATA_PATH)
    if derivedDataPath.exists {
      derivedDataPath.glob("*").forEach { try? $0.delete() }
    }
  }
}
```
```swift:main.swift
let cleaner = Cleaner()
cleaner.run()
```

`swift run` で実行する事ができます。

#### オプションを指定できるようにする

`Device Support` に関しては使用中のものもあると思うので試しにオプションでスキップできるように
してみたいと思います。

PathKitと同じ作者の `Commander` を使用します。
<a href="https://github.com/kylef/Commander"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/8b53046c-4481-2518-7975-881bfe62acdc.png" width="460px"></a>


`Package.swift` に `Commander` を追加

```swift
let package = Package(
    name: "iCleaner",
    dependencies: [
        .package(url: "https://github.com/kylef/PathKit.git", from: "1.0.0"),
        .package(url: "https://github.com/kylef/Commander.git", from: "0.9.1") // 追加
    ],
    targets: [
        .target(
            name: "iCleaner",
            dependencies: ["PathKit", "Commander"]), // Commander追加
        .testTarget(
            name: "iCleanerTests",
            dependencies: ["iCleaner"]),
    ]
)
```

`main.swift` をオプションが受け付けれるように修正

```swift:main.swift
import Commander

command(
  Option("skipds", default: 1, description: "Skip delete Device Support file's.(0: no skip/1: skip)")
) { skipds in
  let cleaner = Cleaner()
  cleaner.run(skipDeviceSupport: skipds == 1)
}.run()
```

`Cleaner` クラスにスキップフラグを追加

```swift
  func run(skipDeviceSupport: Bool) {
    if !skipDeviceSupport {
      // Device support
      let deviceSupportPath = homePath + Path(Cleaner.DEVICE_SUPPORT_PATH)
      if deviceSupportPath.exists {
        deviceSupportPath.glob("*").forEach { try? $0.delete() }
      }
    }
    ...
  }
```

リリースビルドして確認してみます。

```sh
$ swift build -c release
$ ./.build/release/iCleaner --help 
Usage:

    $ ./.build/release/iCleaner

Options:
    --skipds [default: 1] - Skip delete Device Support file's.(0: no skip/1: skip)

$ ./.build/release/iCleaner --skipds 0
#=> スキップされずに実行
```
うまくスキップのON/OFFが切り替えれたらOKです。✨

### 参考にさせて頂いたURL
- [Swift Package Managerの使い方 - Qiita](https://qiita.com/_tid_/items/c26fcd1b95eaed2dedb2)
- [iOSアプリ開発にSwift Package Managerを使おう - Qiita](https://qiita.com/hironytic/items/09a4c16857b409c17d2c)
- [Swift Package Manager (SwiftPM) で作るコマンドラインツール - Qiita](https://qiita.com/mono0926/items/e8fdd97115780204f797)