JenkinsとfastlaneでiOSビルド環境を構築@Mac mini

# 前置き
AndroidをEC2インスタンス(Linux)のJenkinsでビルドしていたので、同じようにiOSもビルドしたい。
しかしAndroidはソース書いてビルドもしているのでなんとかJenkinsの設定もできるが、iOS開発はほとんどわからない。自分の開発環境にiOS開発環境をセットアップしたぐらいだ。

- 参考([iOS開発環境セットアップ@会社 ～配布用ipa作成まで](https://qiita.com/higekick/items/5ca1dc12dac7da52519b))

そんな状態のほとんどAndroid開発しかしたことない自分でもfastlaneを使えばiOSも簡単にビルド自動化ができたので、Jenkins × fastlane × Mac miniの構成をご紹介します。
 
# 構成
iOSのビルドにはMacが必要なので、そのままLinuxのインスタンス上のJenkinsでは出来ない。
そのため、JNLPというJavaのサービスを利用してEC2のJenkinsとローカルの机の上のMac miniちゃんを連携してビルドできるようにする。  

![jenkins_ios_build_mod_for_qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152641/d918d486-0d52-e8c2-9223-36a2c086ea00.png)

- iOSをビルドするMac miniにはJenkinsはセットアップする必要は無い。fastlaneを使ってiOSのビルドに詳しくなくても楽チン
- ビルド自体の他に以下も行ってCI/CD(CDだけかな？)を回して、社内でipaの検証サイクルを効率化できた。
    - ビルドしたらipaをS3にアップロードしてリンクをBacklogにコメントする
    - ビルド中に何かエラーになったらslackのチャンネルに通知してすぐ気づけるように


# Macへfastlaneのセットアップ
- iOS/Androidのビルドツール fastlane でコマンドラインからビルドしてipaを作成する
    - https://docs.fastlane.tools/

## Rubyのセットアップ
- fastlaneはRubyのgemで提供されているのでRubyが必要
- rbenvでRuby2.5.1をシステム全体にセット (バージョンは適当)

## fastlaneのセットアップ
上記ドキュメントを参照してプロジェクトのルート(xxxxx.xcodeprojが存在するディレクトリ)で

```
$ fastfile init
```

によりFastfile, Appfileを作成してソース管理する

- gymというアクションでiOSをビルドする
    - https://docs.fastlane.tools/actions/gym/#gym

## fastlaneのgemインストール

```
$ bundle install —path vendor/bundle
```

でGemfileに記述してプロジェクト毎にセットしていると毎回のビルドでfastlaneをインストールしないといけないので、以下でシステムに直接インストールする

```
$ gem install fastlane
```

## ビルド時のタイムアウトとリトライを調整する
デフォルトの設定だと以下ビルド情報を表示するコマンドでタイムアウトとリトライの上限を超えてたまにエラーになってしまう場合がある。

```
xcodebuild -showBuildSettings -workspace ./xxxxx.xcworkspace -scheme xxxxx
```

デフォルトだと10秒と4回の上限のようなので、Fastfileに以下のように設定しエラーとならないように

```
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10"
```

- 参考：
    - https://github.com/fastlane/fastlane/issues/10131

# JenkinsへのMac miniのslave登録
EC2インスタンスで起動しているJenkinsへローカルの机の上にあるMac miniをslaveとして登録する。  

- Jenkinsの管理 -> ノードの管理 -> 新規ノード作成 -> Permanent Agentを選択してOK
- 以下必要な項目を記入して保存  

|                  |       値          |    メモ    |
|:-----------------|:-----------------:|:------------------:|
| 同時ビルド数|1|2にすると並行して２つ同時にビルドできる？ |
| リモートFSルート|Users/jenkins/jobs/ |Mac側のルートディレクトになる場所を指定する|
| ラベル|mac-mini(識別するラベル)|ビルドするジョブを作った時にこのラベル名を指定してビルドするマシンを指定する|
| 用途|「このマシンを特定ジョブ専用にする」を選択| iOSのビルドしかしないのでこの設定にする|
| 起動方法|「 Launch agent via Java Web Start 」を選択| この方法がJavaのJNLPという通信で↓のjarファイルを実行して通信を開始する|
| 可用性|「keep this agent online as much as possible 」を選択| |

登録後にagent.jarがダウンロードできるのでMac miniにセットして、
表示される以下コマンド(Jenkinsが作成してくれる)を実行し、Jenkinsと同期状態(ビルド実行状態が待機中)となれば成功

```
java -jar /Users/jenkins/jobs/agent.jar -jnlpUrl http://xxx.xxx.xxx/computer/mac-mini/slave-agent.jnlp -secret [シークレットコード] -workDir "/Users/jenkins/jobs" -failIfWorkDirIsMissing
```

※ 設定で上記の項目を変更した場合、agent.jarとコマンドも変更になるので都度ダウンロードとコマンドの変更を行う
# Jenkinsマスター(EC2インスタンスで起動している)へのポート設定
このJNLPのサービスがJenkinsと通信を行うため、JenkinsのJNLPに使用するポートの固定とEC2インスタンスに設定しているセキュリティグループのポート設定を行う

- ポート固定
    - Jenkinsの管理 -> グローバルセキュリティの設定 -> Agents
        - -> TCP port for JNLP agents を固定、40790 を指定
        - -> Agent protocols で Java Web Start Agent Protocol/4 (TLS encryption) を指定
- セキュリティグループ
Mac miniのIPアドレス(インターネット向け)のJNLPのポート(40790)を通すようにする。  

| タイプ | プロトコル | ポート範囲 | ソース | 説明 |
|:-------|:----------:|:----------:|:------:|:----:|
| カスタム TCP ルール | TCP | 40790 | xxx.xxx.xxx.xxx/32 | port for iOS build MacMini to Master Jenkins |

# MacMiniの自動起動設定
MacMini起動時にJNLP起動コマンドを自動で実行するようにlaunchctlに登録する
Fastlaneやaws s3コマンドへパスが通っていないと実行できないので、plistへPATHもセットしておく
- 設定ファイル

```
/Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist
```

システム環境設定 -> 省エネルギー -> スケジュールでMacの自動起動、終了を行うようにする
平日の09:30起動、21:00終了としている

- 参考:
    - https://altarf.net/computer/mac%E3%81%A7%E3%81%AE%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E7%9A%84%E3%81%AA%E4%BD%95%E3%81%8B/4472

## メモ
JenkinsでMacMiniの状態がオフラインになることがある。  
Jenkinsのビルド実行状態 -> mac-mini がオフラインになり以下のエラーが表示される  

```
コネクションが切断されました。java.nio.channels.ClosedChannelException
```

回避策として
Macのシステム環境設定 -> 省エネルギー -> ディスプレイがオフのときにコンピュータを自動でスリープさせないのチェックを入れているが、またオフラインになる場合があるかもしれないので、
その場合はターミナルで以下、launchctlを再起動する

- launchctlを止める方法

```
launchctl unload  /Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist
```

- launchctlを起動する方法

```
launchctl load  /Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist
```

- Jenkins本体が落ちた場合
    - Jenkins本体のEC2がスポットインスタンスの価格高騰などで落ちた場合、Jenkins本体が再び上がったら自動的に接続するのでMac mini側は何もしなくてよい
    - Jenkins本体が上がってしばらくしてもオフラインの場合は、Mac mini側を再起動してみる。

# aws S3へのアップロード

- python3とawscliのインストール
`aws s3` のコマンドでビルドしたipaをs3へアップロードする
awsアカウントのcredentialをセットしてawsコマンドを使えるようにする(省略)

#  backlogへのリンクのポスト
BacklogのAPIを実行できるRubyのgemをシステムにインストールしてbacklogのチケットへコメントするスクリプトを実装
https://github.com/emsk/backlog_kit

```
$ gem install backlog_kit
```

# slack通知
ビルドエラーの際、slackのチャンネルへ通知する
Fastfileのslackアクションにエラー時に通知するように設定できる

# プロビジョニングが更新されたら
Mac miniのプロビジョニングファイルはfastfileに記載した自動更新オプションにより自動で更新されるはずだが、念の為以下のプロビジョニングファイルを削除しておく。(ビルド時に新しく取得しなおすように)

```
~/Library/MobileDevice/Provisioning Profiles/
```

- fastfileのプロビジョニング自動更新オプション

```
xcargs: "-allowProvisioningUpdates",
```

- キャッシュが原因でビルドが失敗していると思ったら
以下パス直下のファイルを全て消してみる。

```
/Users/jenkins/Library/Developer/Xcode/DerivedData
```

fastfileで以下のようにしてクリアしていれば、毎回のビルドで消えているはずだが、ビルド失敗するようなら自分で消してみる

```
  # delete Xcode Derrived Data
  clear_derived_data
```

※↑のプロビジョニングファイルも含めて毎回ビルド毎に rm するようにしたらいいかもしれない

# fastlaneのアップデート
ひんぱんにアップデートされ、iOSの新バージョンリリース後にビルドがうまくいかなくなった場合は、リリースノートを見ると対応されていることがあったので、バージョンアップ手順は確認しておきたい。

- リリースノート
    - https://github.com/fastlane/fastlane/releases

- 現在のバージョン確認 -> 2.126.0 (commander-fastlaneは何かは知らない)

```
$ gem list | grep 'fastlane'
commander-fastlane (4.4.6)
fastlane (2.126.0)
```

- 以下コマンドでアップデート

```
$ sudo gem install fastlane
```

- アップデート後のバージョン確認 -> 2.134.0になった

```
$ gem list | grep 'fastlane'
commander-fastlane (4.4.6)
fastlane (2.134.0, 2.126.0)
```

- 元のバージョンに戻したい場合、以下でいけるはず

```
$ gem install fastlane -v “2.126.0”
```