Xcode用のコード生成ツール「Generamba」のセットアップ&操作方法

## はじめに

私はコーディングが遅いです。
少しでも速くVIPERアーキテクチャでiOSアプリを開発したいため、コード生成ツールを導入することにします。

## 「Generamba」とは？

Ruby製のコード生成ツールです。
VIPERモジュールの生成用に設計されていますが、他のクラスも生成できます。
Objective-CとSwiftの両方に対応しています。

## 環境

- OS：macOS Catalina 10.15.2
- Swift：5.1.3
- Xcode：11.3.1 (11C504)
- Generamba：1.5.0

## セットアップ

### Generambaのインストール

Gemfileに以下を追加し、Bundlerからインストールします。

```diff:Gemfile
+ gem "generamba"
```

```shell-session
$ bundle install
```

### 設定ファイルの作成

プロジェクトのルートフォルダで `generamba setup` コマンドを実行し、ウィザードに沿って設定ファイルを作成します。

```shell-session
$ bundle exec generamba setup
```

```:著者名が設定されていません！Gitでの名前は「uhooi」と設定されています。コードヘッダーで使いますか？
The author name is not configured!
Your name in git is configured as uhooi. Do you want to use it in code headers? (yes/no)
```

私は `yes` を選択しました。

```:ヘッダーで使う会社名：
The company name which will be used in the headers:
```

私は `THE Uhooi` と入力しました。

```:プロジェクト名は「Foo」です。使いますか？
The name of your project is Foo. Do you want to use it? (yes/no)
```

私は `yes` を選択しました。

```:プロジェクトのプリフィクス（あるなら）：
The project prefix (if any):
```

Objective-C用の設定です。
私はSwiftを使っているので、何も入力せずに <kbd>Enter</kbd> キーを押下しました。

```:.xcodeprojファイルのパス：（カレントフォルダにプロジェクトファイルがない場合）
The path to a .xcodeproj file of the project:
```

プロジェクトファイルが存在しないと以下のエラーが発生します。

```
bundler: failed to load command: generamba (…/bin/generamba)
RuntimeError: [Xcodeproj] Unable to open `…Foo/Foo.xcodeproj` because it doesn't exist
  …/gems/xcodeproj-1.14.0/lib/xcodeproj/project.rb:109:in `open'
  …/gems/generamba-1.5.0/lib/generamba/cli/setup_command.rb:31:in `setup'
  …/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
  …/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
  …/gems/thor-0.19.1/lib/thor.rb:359:in `dispatch'
  …/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
  …/gems/generamba-1.5.0/bin/generamba:5:in `<top (required)>'
  …/bin/generamba:23:in `load'
  …/bin/generamba:23:in `<top (required)>'
```

XcodeGenを使っている場合、予め `xcodegen generate` コマンドを実行し、プロジェクトファイルを生成する必要があります。

```:.xcodeprojファイルのパスは「Foo.xcodeproj」です。使いますか？（カレントフォルダにプロジェクトファイルがある場合）
The path to a .xcodeproj file of the project is 'Foo.xcodeproj'. Do you want to use it? (yes/no)
```

私は `yes` を選択しました。

```:「モジュール」を追加するための適切なターゲットを選択します。（インデックスを入力します）
Select the appropriate target for adding your MODULES (type the index):
0. Foo
1. FooTests
2. FooUITests
```

私は `0` を選択しました。

```:このプロジェクトでユニットテストを使っていますか？
Are you using unit-tests in this project? (yes/no)
```

私は `yes` を選択しました。

```:「テスト」を追加するための適切なターゲットを選択します。（インデックスを入力します）
Select the appropriate target for adding your TESTS (type the index):
0. Foo
1. FooTests
2. FooUITests
```

私は `1` を選択しました。

```:すべてのモジュールを1つのパスで追加しますか？
Do you want to add all your modules by one path? (yes/no)
```

私は `yes` を選択しました。

```:Xcodeとファイルシステムの両方で同じパスを使いますか？
Do you want to use the same paths for your files both in Xcode and the filesystem? (yes/no)
```

私は `yes` を選択しました。

```:新しいモジュールを作成するためのデフォルトのパス：
The default path for creating new modules:
```

私は `./Foo/Modules` と入力しました。

```:テストを作成するためのデフォルトのパス：
The default path for creating tests:
```

私は `./FooTests/Modules` と入力しました。

```:CocoaPodsを使っていますか？
Are you using Cocoapods? (yes/no)
```

私は `yes` を選択しました。

```:Podfileのパスは「Podfile」です。使いますか？
The path to a Podfile is 'Podfile'. Do you want to use it? (yes/no)
```

私は `yes` を選択しました。

```:Carthageを使っていますか？
Are you using Carthage? (yes/no) 
```

私は `yes` を選択しました。

```:Cartfileのパスは「Cartfile」です。使いますか？
The path to a Cartfile is 'Cartfile'. Do you want to use it? (yes/no)
```

私は `yes` を選択しました。

```:よく知られているテンプレートをRambafileに追加しますか？
Do you want to add some well known templates to the Rambafile? (yes/no) 
```

私は `yes` を選択しました。

これでウィザードが完了です。
最終的な出力を載せます。

```shell-session
$ bundle exec generamba setup
The company name which will be used in the headers: THE Uhooi
The name of your project is Foo. Do you want to use it? (yes/no) yes
The project prefix (if any): 
The path to a .xcodeproj file of the project is 'Foo.xcodeproj'. Do you want to use it? (yes/no) yesSelect the appropriate target for adding your MODULES (type the index):
0. Foo
1. FooTests
2. FooUITests
 0
Are you using unit-tests in this project? (yes/no) yes
Select the appropriate target for adding your TESTS (type the index):
0. Foo
1. FooTests
2. FooUITests
 1
Do you want to add all your modules by one path? (yes/no) yes
Do you want to use the same paths for your files both in Xcode and the filesystem? (yes/no) yes
The default path for creating new modules: ./Foo/Modules
The default path for creating tests: ./FooTests/Modules
Are you using Cocoapods? (yes/no) yes
The path to a Podfile is 'Podfile'. Do you want to use it? (yes/no) yes
Are you using Carthage? (yes/no) yes
The path to a Cartfile is 'Cartfile'. Do you want to use it? (yes/no) yes
Do you want to add some well known templates to the Rambafile? (yes/no) yes

+--------------------+----------------------------------------------------------------------------------+
|                                      Summary for generamba setup                                      |
+--------------------+----------------------------------------------------------------------------------+
| company            | THE Uhooi                                                                        |
| project_name       | Foo                                                                              |
| prefix             |                                                                                  |
| xcodeproj_path     | Foo.xcodeproj                                                                    |
| podfile_path       | Podfile                                                                          |
| cartfile_path      | Cartfile                                                                         |
| templates          | ["{name: rviper_controller}", "{name: mvvm_controller}", "{name: swifty_viper}"] |
| project_target     | Foo                                                                              |
| project_file_path  | ./Foo/Modules                                                                    |
| project_group_path | ./Foo/Modules                                                                    |
| test_target        | FooTests                                                                         |
| test_file_path     | ./FooTests/Modules                                                               |
| test_group_path    | ./FooTests/Modules                                                               |
+--------------------+----------------------------------------------------------------------------------+

Rambafile successfully created! Now run `generamba template install`.
```

作成された設定ファイルも載せます。

```yaml:Rambafile
### Headers settings
company: THE Uhooi

### Xcode project settings
project_name: Foo
xcodeproj_path: Foo.xcodeproj

### Code generation settings section
# The main project target name
project_target: Foo

# The file path for new modules
project_file_path: ./Foo/Modules

# The Xcode group path to new modules
project_group_path: ./Foo/Modules

### Tests generation settings section
# The tests target name
test_target: FooTests

# The file path for new tests
test_file_path: ./FooTests/Modules

# The Xcode group path to new tests
test_group_path: ./FooTests/Modules

### Dependencies settings section
podfile_path: Podfile
cartfile_path: Cartfile

### Templates
templates:
- {name: rviper_controller}
- {name: mvvm_controller}
- {name: swifty_viper}
```

### 設定ファイルの編集

[自作したテンプレート](https://github.com/uhooi/generamba-catalog/blob/master/uhooi_viper/uhooi_viper.rambaspec)を使いたいため、設定ファイルを手動で編集します。

```yaml:Rambafile
### Templates
+ catalogs:
+ - 'https://github.com/uhooi/generamba-catalog'
templates:
- {name: rviper_controller}
- {name: mvvm_controller}
- {name: swifty_viper}
+ - {name: uhooi_viper}
```

自作テンプレートを使わない場合、この手順は不要です。

### テンプレートのインストール

`generamba template install` コマンドを実行し、テンプレートをインストールします。

```
$ bundle exec generamba template install
```

「Templates」フォルダにテンプレートが格納されます。

### テンプレート一覧の出力

`generamba template list` コマンドで、テンプレートの一覧を出力します。

```

$ bundle exec generamba template list
mvvm_controller
rviper_controller
adkrviper_controller
championat_viper
swifty_viper
swifty_viper_OSX
uhooi_viper
```

### テンプレートをバージョン管理から無視する

テンプレートをバージョン管理の対象外にします。
Gitを使っている場合、以下を「.gitignore」に追加するのみでOKです。

```diff:.gitignore
+ Templates/
```

## 操作方法

### モジュールの生成

`generamba gen` コマンドで、モジュールを生成します。

```
$ bundle exec generamba gen {モジュール名} {テンプレート名}
```

XcodeGenを使っている場合、プロジェクトファイルを再生成した方がいいと思います。

私はMakefileを作成し、 `make generate-module MODULE_NAME={モジュール名}` を実行すると「モジュールの生成→プロジェクトファイルの生成→CocoaPods用ワークスペースの生成→ワークスペースをXcodeで開く」を一括で行うようにしています。

```makefile:Makefile
PRODUCT_NAME := Foo
MODULE_TEMPLATE_NAME ?= uhooi_viper

.PHONY: generate-module
generate-module:
	bundle exec generamba gen ${MODULE_NAME} ${MODULE_TEMPLATE_NAME}
	make generate-xcodeproj

.PHONY: generate-xcodeproj
generate-xcodeproj:
	mint run xcodegen xcodegen generate
	bundle exec pod install
	make open

.PHONY: open
open:
	open ./${PRODUCT_NAME}.xcworkspace
```

## 注意: ライブラリ管理ツールを使わなくなった場合

CocoaPodsやCarthageを使わなくなった場合、以下を削除しないとエラーになることがあります。

```diff:Rambafile
- ### Dependencies settings section
- podfile_path: Podfile
- cartfile_path: Cartfile
```

`Podfile` がなくてもエラーにならず、 `Cartfile` がないとエラーになる気がします。

## おわりに

これでVIPERのモジュールを爆速で実装できます💪

## 参考リンク

- [strongself/Generamba: This codegenerator is too brilliant to be real!](https://github.com/strongself/Generamba)
- [Rambafile Structure · strongself/Generamba Wiki](https://github.com/strongself/Generamba/wiki/Rambafile-Structure)