iOSアプリの GitLab CI/CD 環境を整える（コードレビュー編 SwiftLint, Fastlane, Danger）

# はじめに

## 本記事のゴール
iOSアプリのGitLab CI/CD環境を整えたい！

ということで、第一弾はコードレビュー編です。

以下のキャプチャのようにMerge Request作成時に自動でSwiftLintを実行＋実行結果をコメントに残すことで、コードレビューを円滑にし品質向上に繋げることを目的としています。

![スクリーンショット 2020-05-26 15.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/a041f6ff-f71d-55d4-c422-d8b642006aab.png)


## 環境
- macOS（ビルド用）：macOS Catalina 10.15.4
- Xcode：11.4.1
- Swift：5.2.2
- SwiftLint：0.30.1
- Danger：8.0.0
- Danger Gitlab：8.0.0
- Danger Swiftlint：0.24.2
- Fastlane：2.148.1
- GitLab CE（セルフホスティング）：12.4.0
- GitLab CI：11.1.0

## SwiftLint とは？
**SwiftLint** は、Swift用の静的解析ツールです。
コードレビュー時に静的解析ツールを挟むことで、品質向上に繋げるのが目的です。

詳しくは公式ドキュメントを読んで下さい。
https://realm.github.io/SwiftLint/

## Fastlane とは？
**Fastlane**は、モバイルアプリ開発で必要な様々な作業（テスト／ビルド／リリース等）の自動化ツールです。

`.gitlab-ci.yml`にCIプロセスを記述するのも有りですが、CI環境に依存しない＋リリースプロセスまで考えると、iOSアプリ開発ではFastlaneを使う方が良いと思います。

また、Fastlane標準でSwiftLintに対応しているのですが、MRコメントにLint結果を残すまではできないので、少し歯がゆさが残ります。
これを解決するために、次のDangerを使用します。

詳しくは公式ドキュメントを読んで下さい。
https://fastlane.tools/

## Danger とは？
**Danger**は、コードレビューの自動化ツールです。

FastlaneだけでもCIプロセス中にSwiftLintを実行することは出来るのですが、Lint結果を確認するために一々ジョブの中身のログだったりアーティファクトを確認しないとダメなので面倒です。（※やりようによってはGitLab PagesでLint結果を公開することもできますが、ここでは割愛）

ですので、Lint結果をMRコメントに残す目的で使用します。

詳しくは公式ドキュメントを読んで下さい。
https://danger.systems/swift/

# 1. SwiftLint を導入する
## 1-1. SwiftLint のインストール
チームで共有しやすいようにCocoapodsでインストールします

```diff:Podfile
target 'MyApp' do
  use_frameworks!

+  pod 'SwiftLint'
end
```
```terminal
$ pod install
```

※Homebrewでもインストールできるので、必要に応じて公式ドキュメントを確認して下さい

## 1-2. Xcode での SwiftLint 実行スクリプトの設定
Xcodeでのビルド実行時に、WarningsとErrorsメッセージを表示するために、実行スクリプトの設定をします

1. Xcode でプロジェクトを開く
2. [Project Navigator] でプロジェクトを選択する
3. [Targets] からターゲットを選択する
4. [Build Phase]タブ -> [+]ボタン -> [New Run Script Phase] を選択する
5. 追加された『Run Script』項目にスクリプトを書く

<img width="1439" alt="OtherViews_と_PencilKitDraw_xcodeproj.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/0c2a724f-7600-962c-3256-5f1d48900d75.png">

```terminal
if which ${PODS_ROOT}/SwiftLint/swiftlint >/dev/null; then
  ${PODS_ROOT}/SwiftLint/swiftlint
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
```

<img width="1440" alt="PencilKitDraw_xcodeproj.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/f4495030-fbec-20c1-f1bc-88cb7610fe83.png">


## 1-3. 静的解析の実行 
ビルド実行（⌘＋B）で静的解析を実行する
<img width="1440" alt="DrawingViewController_swift.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/4b423810-61e4-74dc-cb36-c99b5847463c.png">


## 1-4. ルールの詳細設定
プロジェクトのルートディレクトリに `.swiftlint.yml` を作成し、その中にルールの詳細を記述します

```terminal
$ touch .swiftlint.yml
```

ファイルの中身は [Swiftの静的解析ツール「SwiftLint」のセットアップ方法 ＞ ルールの詳細設定
](https://qiita.com/uhooi/items/bf888b53b4b8210108aa#%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E8%A9%B3%E7%B4%B0%E8%A8%AD%E5%AE%9A) を参考にさせて頂きました

---

ここまでで、ローカルではSwiftLintが実行できるようになりました :thumbsup:  
GitLab CIで実行するための設定に続きます :point_right:


# 2. Fastlane と Danger を導入する
## 2-1. Fastlane と Danger のGemインストール
`bundle init`でGemfileを作成してインストールします

```terminal
$ bundle init
```

```:Gemfile
source "https://rubygems.org"

gem "fastlane"
gem "cocoapods"

gem "danger"
gem "danger-gitlab"
gem "danger-swiftlint" 
```

```terminal
$ bundle install --path vendor/bundle
```

## 2-2. Fastlane のセットアップ
`fastlane init`を実行して「4.マニュアルセットアップ」でセットアップする

```terminal
$ bundle exec fastlane init
[✔] 🚀 
[23:15:49]: Sending anonymous analytics information
[23:15:49]: Learn more at https://docs.fastlane.tools/#metrics
[23:15:49]: No personal or sensitive data is sent.
[23:15:49]: You can disable this by adding `opt_out_usage` at the top of your Fastfile
[✔] Looking for iOS and Android projects in current directory...
[23:15:49]: Created new folder './fastlane'.
[23:15:49]: Detected an iOS/macOS project in the current directory: 'PencilKitDraw.xcodeproj'
[23:15:49]: -----------------------------
[23:15:49]: --- Welcome to fastlane 🚀 ---
[23:15:49]: -----------------------------
[23:15:49]: fastlane can help you with all kinds of automation for your mobile app
[23:15:49]: We recommend automating one task first, and then gradually automating more over time
[23:15:49]: What would you like to use fastlane for?
1. 📸  Automate screenshots
2. 👩‍✈️  Automate beta distribution to TestFlight
3. 🚀  Automate App Store distribution
4. 🛠  Manual setup - manually setup your project to automate your tasks
?  4
```

### Fastfileの設定
実行後、ルートディレクトリにfastlaneフォルダが作成されているので、FastfileにSwiftLint実行用のレーンを作成します
※Fastlaneでは、アクションと呼ばれる機能を組み合わせをレーンと呼びます

前述のとおり、Fastlane標準でSwiftLintをサポートしていますが、MR上でLint結果の確認が大変なので、こちらは使用しません。
https://docs.fastlane.tools/actions/swiftlint/

```ruby:fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Does a static analysis of the project. Configure the options in .swiftlint.yml"
  lane :lint do
    # Runs pod install for the project
    # https://docs.fastlane.tools/actions/cocoapods/
    cocoapods

    # Runs danger for the project
    # https://docs.fastlane.tools/actions/danger/
    danger
  end
end
```
dangerアクションがSwiftLintの実行を内包しているので、記述はこれだけで大丈夫です

## 2-3. Danger のセットアップ
`danger init`を実行してセットアップ（チュートリアルは全てEnterで飛ばしてOK）

```terminal
$ bundle exec danger init
```

### Dangerfileの設定
実行後、ルートディレクトリにDangerfileが作成されているので、以下の設定を記述します

```ruby:Dangerfile
# Make it more obvious that a MR is a work in progress and shouldn't be merged yet
warn("MR is classed as Work in Progress") if gitlab.mr_title.include? "WIP"

# Warn when there is a big MR
warn("Big MR") if git.lines_of_code > 500

# Swiftlint
swiftlint.config_file = '.swiftlint.yml'
# swiftlint.lint_files inline_mode: true
swiftlint.lint_files fail_on_error: true
```
`swiftlint.lint_files inline_mode: true`はLint結果をMRにインラインでコメントする設定です。
Warningが多いと逆にコメントが追えなくなるので、コメントアウトしています。

#### Dangerプラグイン
##### danger-gitlab
https://github.com/danger/danger/blob/master/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb

##### danger-swiftlint
https://github.com/ashfurrow/danger-ruby-swiftlint/blob/master/lib/danger_plugin.rb

---
FastlaneとDangerの設定が完了です :thumbsup: 
最後にGitlab CI/CD環境に組み込んでいきます :point_right:

# 3. GitLab CI/CD 環境を構築する
## 3-1. `.gitlab-ci.yml` ファイルの作成
```terminal
$ touch .gitlab-ci.yml
```

```yaml:.gitlab-ci.yml
stages:
  - build
  
variables:
  LC_ALL: "en_US.UTF-8"
  LANG: "en_US.UTF-8"
  GIT_STRATEGY: clone

before_script:
  - export CI_MERGE_REQUEST_ID=$(curl -s "https://${DANGER_GITLAB_HOST}/api/v4/projects/${CI_PROJECT_ID}/merge_requests?private_token=${OAUTH_TOKEN}&state=opened" | jq -r ".[]|select(.sha == \"$CI_COMMIT_SHA\")|.iid")
  - bundle install

lint:
  stage: build
  script:
    - fastlane lint
  tags:
    - ios
  only:
    - merge_requests
```

## 3-2. GitLab CI Runner のセットアップ（ビルド用maxOS）
残念ながら、GitLab CI/CD環境でiOSアプリをビルドするにはmacOSでビルドすることが唯一の現実的な方法です。（2020.05.26時点）
なので、今回は自宅で眠っていたMacbook ProにGitLab CI Runnerしました。

```terminal
$ sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
$ sudo chmod +x /usr/local/bin/gitlab-runner
$ cd ~
$ gitlab-runner install
$ gitlab-runner start
```
※gitlab-runnerコマンドは絶対にsudoを付けないようにして下さい

```terminal
$ gitlab-runner status
Runtime platform                                    arch=amd64 os=darwin pid=18242 revision=c127439c version=13.0.0
gitlab-runner: Service is running!
```
無事起動していることを確認

## 3-3. GitLab CI Runner の登録（ビルド用maxOS）
続いて、CI Runnerを登録します。

GitLabプロジェクト管理画面のSettings > CI/CD > Specific Runners > Set up a specific Runner manuallyからURLとトークンを取得します。
<img width="1439" alt="CI___CD_Settings_·_CI___CD_·_zyyx-projects___wvvu-pencil-product___DrawingWithPencilKit_·_GitLab_と_Macからデバイス間でコピー_ペーストする_-_Apple_サポート.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/3b1c79f3-a692-1839-403b-91b9d3ea6477.png">

そして、ビルド用MacbookにGitLab CI Runnerをシェル実行として登録します。

```terminal
$ gitlab-runner register
Runtime platform                                    arch=amd64 os=darwin pid=3501 revision=c127439c version=13.0.0
Running in system-mode.                            
                                                   
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
-> Enter URL

Please enter the gitlab-ci token for this runner:
-> Enter Token

Please enter the gitlab-ci description for this runner:
-> Enter Description

Please enter the gitlab-ci tags for this runner (comma separated):
-> Enter Tag (e.g. ios)

Registering runner... succeeded                     runner=Rk3cH_Lx

Please enter the executor: ssh, docker+machine, kubernetes, virtualbox, docker-ssh+machine, custom, docker, docker-ssh, parallels, shell:
-> shell

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 
```

GitLabプロジェクト管理画面から正常にCI Runnerが登録できていることを確認
<img width="1439" alt="2CI___CD_·_wvvu-pencil-product_·_GitLab_と_Slack___prj_limeboard_dev_cicd___ZYYX.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/7e435d66-dc6f-eace-9fc9-0660884c623e.png">

## 3-4. Danger用のGitLab APIトークン生成
DangerはGitLab APIを使用してMRコメントを投稿するので、GitLab APIトークンを生成します

GitLabプロフィール管理画面のUser Settings > Access Tokens > Personal Access TokensにScopes > api にチェックを入れてトークンを生成する（NameとExpires atは任意）
<img width="1438" alt="Personal_Access_Tokens_·_User_Settings_·_GitLab_と__gitlab-ci_yml.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/433c1e60-9b63-bd8d-dbe5-58784f613da3.png">

## 3-5. Danger用の環境変数の登録

GitLabプロジェクト管理画面のSettings > CI/CD > Variablesに以下の環境変数を設定します

| Key | Value | Masked |
|:-----------------|:------------------|:------------------|
| DANGER_GITLAB_API_BASE_URL | e.g. https://gitlab.com/api/v4 | |
| DANGER_GITLAB_API_TOKEN | 3-4で生成したAPIトークン  | on |
| DANGER_GITLAB_HOST | e.g. gitlab.com | |

<img width="1439" alt="CI___CD_·_wvvu-pencil-product_·_GitLab_と_Slack___prj_limeboard_dev_cicd___ZYYX.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/2b116cc0-5611-fdde-f235-81ac0ed2217f.png">

---
以上で全ての設定が完了です :tada: :tada: 
MRを作成して、SwiftLintの実行結果がMRコメントに追加されていることを確認してみましょう！

![スクリーンショット 2020-05-26 15.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121942/9cea253e-6186-0b18-3da5-d2b7162b5c36.png)


# ハマったポイント
- GitLab CEを社内サーバーにオレオレ証明書で立てていた影響でハマる
    - CI Runnerが有効にならない
        - 解決策 https://docs.gitlab.com/runner/configuration/tls-self-signed.html
    - DangerでGitLab API実行エラー
        - 解決策 Danger用の環境変数の追加


| Key | Value |
|:-----------------|:------------------|
| SSL_CERT_FILE | e.g. /etc/gitlab/certs/gitlab.hogehoge.com.crt |