fastlaneを使って継続的デリバリーを実践する

# モバイルDevOps Advent Calendar 2015 4日目
寒さもひとしお身にしみるころ、皆様いかがお過ごしでしょうか。

モバイルDevOps Advent Calendarということで、fastlaneを使って継続的デリバリーを実践する的な内容を書きたいと思います。

継続的デリバリーの正しい定義、探したけどコレという感じのが無かったので、この記事では「いつでもリリース可能な状態にして、リリース時のコストを下げておくこと」という定義とします。

[iOSアプリの継続的デリバリーに便利なfastlaneのご紹介 - Qiita](http://qiita.com/gin0606/items/162d756dfda7b84e97d4)

内容的には、以前書いた↑ものと被っているのですが、書いた当時からfastlaneがめちゃくちゃ便利になっているので、改めて書き直した次第です。

# 条件
## Require
- バージョン管理している
    - git(github)前提で話をしています
- リモートリポジトリは非公開な状態
    - 公開したくない情報の暗号化などには触れてません
- Rubyの基礎文法くらいは把握してる事

## Optional
- Bundler
- CIサーバー
    - Jenkins
    - CircleCI
    - TravisCI

Gitを使ってる人は[.gitignore](https://gist.github.com/gin0606/f607dfa495eed6f2a791)と同じ`.gitignore`を作っておいてください。

commitは自分のペースで適宜よろしくお願いします。

## 筆者の作業環境
- Git 
- Xcode Version 7.1.1 (7B1005)
- ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
- Bundler version 1.10.6

# 説明の流れ
一旦、ローカルで配信が出来る状態にしてから、自動ビルド・自動配信を出来るようにします。

# プロジェクトの作成と必要な設定
## schemaを共有する
[[Xcode] 作成したSchemeを開発メンバーに共有する ｜ Developers.IO](http://dev.classmethod.jp/smartphone/iphone/xcode-scheme-shared/)

設定してない人居ない気がするけど一応

# fastlaneのinstall
## Bundlerを使う場合
```sh
$ gem install bundler # bundlerをまだ入れてない場合。必要に応じて、`sudo`つけたりしてください。
$ bundle init
$ vi Gemfile
source "https://rubygems.org"

gem "fastlane" # Gemfileに追記する
$ bundle install --path .bundle
```

`bundle init`実行すると、Gemfileが生成されるので、その中に`gem "fastlane"`と追記します。

その後、Gemfileに書いたgemを`bundle install --path .bundle`でインストールします。
`--path .bundle`を書かないとシステム領域にインストールされます。

`.bundle/`は`.gitignore`でバージョン管理しないようにしましょう。チームのメンバーにもBundlerを導入してもらって`bundle install --path .bundle`すればOKです。

[【初心者向け】Mac(OSX Lion)でRuby on Railsを動かすための5ステップ « pplog](http://pplog.org/?p=1845)

RubyやBundler関連の環境構築は↑これを見たりしてください。少し古い記事だけどそんなに変わらないです。

### Bundlerとは
RubyのCocoapods/Carthageのようなもので、[Bundler](http://bundler.io/)というのがあります。

これを使うと、他の作業者と使用するGem(fastlaneやcocoapods)のVersionを合わせる事が出来るのでお薦めです。

また、gemのinstall時にpathを指定すると、システム領域とは別の場所にgemが入るので、「システム領域に入れたfastlaneが定期的に`gem cleanup`しないと遅くなる」といった問題も起こりにくくなると思います。

## Bundlerを使わない場合
```sh
$ gem install fastlane # 必要に応じて、`sudo`つけたりしてください。
```

Bundlerは必須ではありませんが、私がBundler使っているので、以後はBundler使ってる前提でコマンドなど書きます。

Bundler導入してない場合でも`bundle exec`を抜かして実行すれば同じ結果を得られます。

https://github.com/fastlane/fastlane#installation

# fastlaneのsetup
```sh
$ bundle exec fastlane init
```

fastlane initを実行すると色々聞かれるので答えていきます

* Do you have everything commited in version control? If not please do so now! (y/n)
    * バージョン管理してなかったら今直ぐ導入して`y`
* App Identifier (com.krausefx.app)
    * 対象のアプリのApp Identifierを入力する
* Your Apple ID (fastlane@krausefx.com)
    * 自分のApple IDを入力する
* Do you want to setup 'deliver', which is used to upload app screenshots, app metadata and app updates to the App Store? This requires the app to be in the App Store already. (y/n)
    * fastlaneをストア申請に使うかどうか
    * 今回は`n`
* Do you want to setup 'snapshot', which will help you to automatically take screenshots of your iOS app in all languages/devices? (y/n)
    * アプリのスクリーンショットを自動で撮るか
    * 今回は`n`
* Do you want to use 'sigh', which will maintain and download the provisioning profile for your app? (y/n)
    * provisioning profileのダウンロードにsighを使うかどうか
    * 生成されるコードの状態が変わるだけなので、今回は`n`

*fastlane/Appfile*と*fastlane/Fastfile*が作られるので*fastlane/Fastfile*を以下のような状態にします。

```ruby:fastlane/Fastfile
fastlane_version "1.42.0"
default_platform :ios

ENV["GYM_USE_LEGACY_BUILD_API"] = true

platform :ios do
end
```

全て消しました。

消すところは、消す前によく読んでおきましょう。

ここまでやると以下の様なディレクトリ状態になると思います。

```
├── ExampleApp
├── ExampleApp.xcodeproj
├── ExampleAppTests
├── Gemfile
├── Gemfile.lock
└── fastlane
    ├── Appfile
    ├── Fastfile
    └── actions
```

# iOSアプリのテスト
fastlaneからテストを実行するためにFastfileに下記の内容を追記しましょう。以後は、`lane`だけ書くので、同じような感じで追加してください。

```ruby:fastlane/Fastfile
platform :ios do
  desc "テストを実行する"
  lane :test do
    scan(scheme: "ExampleApp")
  end
end
```

これだけで、シェルで`bundle exec fastlane test`実行するとテストされます。最高。

# iOSアプリのビルド
```ruby:fastlane/Fastfile
desc "ipaを作る"
lane :build do
  gym(scheme: "ExampleApp")
end
```

これだけで、シェルで`bundle exec fastlane build`実行するとReleaseビルドのipaが生成されます。最高。

ビルド出来ない場合はDistribution証明書を入れたり、プロビジョニングプロファイルを入れたりしましょう。

setupで書いた`ENV["GYM_USE_LEGACY_BUILD_API"] = true`はおまじないみたいなものだと思ってください。詳しく知りたい人は`bundle exec fastlane action gym`を見る。

# iOSアプリの配信
* crashlytics
* deploygate
* hockey
* pilot

テスト配信用のactionは上記の4つ存在していて、pilotはTestFlight用です。

4つ全て説明するのも辛いので、`fastlane action [name]`でactionに必要な設定などが見られるので、あとは頑張ってください。

私がcrashlyticsを使っているので、例としてcrashlyticsを使った場合のlaneを載せておきます。

```ruby:fastlane/Fastfile
desc "crashlyticsに配信する"
lane :crashlytics do
  gym(scheme: "ExampleApp")
  crashlytics(
    crashlytics_path: "./Pods/Crashlytics/iOS/Crashlytics.framework",
    api_token: "your api_token",
    build_secret: "your build_secret"
  )
end
```

# CIサーバーの設定
## Jenkins
Jenkinsの場合は、ローカルの場合と同じように証明書やプロビジョニングプロファイルを入れて、作ったlaneを呼び出すだけでOKだと思います。

## CI as a Service
### 署名関係
CircleCIやTravisCIのようなCIサービスを使う場合は、ビルド毎に証明書の設定をする必要があるので、リポジトリに証明書(p12ファイル)を追加します

次に、CIサービス上で署名できるようにします

```ruby:fastlane/Fastfile
desc "CircleCIなどにkeychainを作成して証明書を追加する"
lane :keychain do
  ENV["KEYCHAIN_NAME"] = "ios-build.keychain"
  ENV["KEYCHAIN_PASSWORD"] = "password"
  create_keychain(
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: true
  )
  import_certificate certificate_path: "certs/dist.p12", certificate_password: "password"
end
```

これで`bundle exec fastlane keychain`で証明書が登録されるようになりました。これをローカルで実行すると良くないことが起こるので、よく分かってない人は気をつけましょう。

説明しやすさを重視して、パスワードなどは直接Fastfileに書いているけど、適宜CIサービスの環境変数にしたほうが安全です。

他社が絡むプロジェクトだったり、public repositoryの場合は[Travis CI for iOS · objc.io](https://www.objc.io/issues/6-build-tools/travis-ci/)の内容を参考にして証明書自体の暗号化・復号しましょう。

後は、今作ったlaneと、ベータ配信用のlaneを呼べばOKです。

### プロビジョニングプロファイルについて
個々人の作業環境によって、プロビジョニングプロファイルの扱いが変わってくると思うので、サラッと触れます。

#### CIサービス上からプロビジョニングプロファイルをダウンロード出来る権限がある場合
```ruby:fastlane/Fastfile
desc "crashlyticsに配信する"
lane :crashlytics do
  sigh(adhoc: true) # この行を追加
  gym(scheme: "ExampleApp")
  crashlytics(
    crashlytics_path: "./Pods/Crashlytics/iOS/Crashlytics.framework",
    api_token: "your api_token",
    build_secret: "your build_secret"
  )
end
```

sighを使いましょう

CIサービスの環境変数に`FASTLANE_USER`と`FASTLANE_PASSWORD`を追加して、プロビジョニングプロファイルをダウンロード出来るアカウントの情報を入れると、あとは最初に指定したapp_identifierと`lane :keychain`で登録した証明書に合致するプロビジョニングプロファイルをダウンロード＆インストールしてくれます。

#### 権限がない場合
https://github.com/fastlane/fastlane_core/blob/e44ac3ce3e1d16e5094453dccbe16c8e709393ad/lib/fastlane_core/provisioning_profile.rb#L41-L66

を参考に、プロビジョニングプロファイルをインストールしましょう。

試してませんが`FastlaneCore::ProvisioningProfile.install(profile_path)`でインストール出来るかもしれません。

### 配信する
``` sh
$ bundle exec fastlane keychain
$ bundle exec fastlane crashlytics
```

上記２つがCIサービス上で実行されるようにすれば終了です。

# おまけ
この記事で書いた説明だけじゃ、実際に運用するのに必要な要件を満たせないと思うので、所属先のプロジェクトで実際に運用しているFastfileとcircle.ymlをおまけとして載せておきます。アプリ名とかapp_identifier以外はそのままです。

他社が開発しているものなので、この記事のディレクトリ構造とは違ってます。あと、fastlaneのVersionが少し古い。あと、~~テストが書かれていないので~~諸事情により、`lane :test`ではビルド通るかの確認をしています。

APIサーバーと繋ぐアプリなので、edge・stagingサーバーそれぞれに繋ぐアプリを配信しています。サーバーの切り替えなどはUser-Defined Settingを使っています。

[iOS - XCode設定Tips 1: 独自のプロジェクト設定やマクロを活用する - Qiita](http://qiita.com/appwatcher/items/5e5462458ea4dd30c38a)

それぞれのbuild configで共通でfastlaneに設定する値などは、ほとんど環境変数で設定しているので、これをこのまま使いたい場合は、`fastlane action [action_name]`などを使って、必要な環境変数を探してみてください :ghost: 

```ruby:fastlane/Fastfile
fastlane_version "1.39.0"

default_platform :ios

platform :ios do
  before_all do
  end

  desc "Runs all the tests"
  lane :test do
    build(
      env: :edge,
      configuration: "Debug",
    )
  end

  lane :keychain do
    create_keychain(
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: true
    )
    import_certificate certificate_path: "certs/dist.p12", certificate_password: ENV['DIST_CERT_PASSWORD']
  end

  desc "edgeサーバーに繋ぐアプリ"
  lane :edge do |options|
    increment_build_number unless options[:build_only]

    build(
      env: :edge,
      configuration: "Debug",
    )
    crashlytics(notifications: false) unless options[:build_only]
  end

  desc "stagingサーバーに繋ぐアプリ"
  lane :staging do |options|
    increment_build_number unless options[:build_only]

    build(
      env: :staging,
      configuration: "Staging",
    )
    crashlytics unless options[:build_only]
  end

  private_lane :build do |options|
    app_identifier = "jp.app_name.#{options[:env].to_s}"
    display_name = "#{options[:env].to_s}_app_name"

    update_app_identifier(app_identifier: app_identifier)
    update_info_plist(display_name: display_name)
    sigh(adhoc: true)
    update_project_provisioning(build_configuration: options[:configuration])
    gym(
      configuration: options[:configuration],
      use_legacy_build_api: true,
    )
  end

  after_all do |lane|
  end

  error do |lane, exception|
  end
end
```

```yaml:circle.yml
machine:
  environment:
    FL_BUILD_NUMBER_BUILD_NUMBER: $CIRCLE_BUILD_NUM
  xcode:
    version: "7.0"

dependencies:
  post:
    - openssl aes-256-cbc -k "$DIST_P12_ENC_KEY" -in certs/dist.p12.enc -d -a -out certs/dist.p12

test:
  override:
    - bundle exec fastlane keychain
    - bundle exec fastlane edge build_only:true

deployment:
  edge:
    branch: master
    commands:
      - bundle exec fastlane edge
  staging:
    branch: deployment/staging
    commands:
      - bundle exec fastlane staging
```

# おわり
という感じで割りと駆け足になってしまいましたが、モバイルDevOps Advent Calendar 2015 4日目でした。

[冒頭で紹介した記事](http://qiita.com/gin0606/items/162d756dfda7b84e97d4)も参考になればと思います。

CIサーバーと連携して、社内や社外にベータ配信するだけで色々なコストが大分減るので、是非やってみてください。

明日は @toshi0383 が *iOSアプリ受託開発での署名付け替えの技術* について話してくれるそうです。