受託開発でもfastlaneとCircleCIでiOSアプリを継続的デリバリー

この記事は[CircleCI Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/circleci)の14日目の記事です。

13日目は[nofrmm](http://qiita.com/nofrmm)さんですが、まだ記事が無いようです。

## 余談

本当は、CircleCIでのiOSビルドの有料化に伴って、個人アプリのビルドをTravisCIに移したという話をしようと思いましたが、今でもCircleCIでビルド出来てる（金は払ってない）っぽいので、一旦移行はやめました。
なので、社内で実際に構築した別のことをまとめました。

## 背景

iOSアプリを受託で作る場合、いろいろ面倒ですよね。主に証明書周りが。

弊社でよくあるパターンとしては、

- iTunes connectなどはお客さんしか入れない。
- 証明書はもらえる。
- UDIDが増えたら、お客さんに連絡→provisioningを発行してもらう。
- ipaファイルを納品する。

という感じです。
そのため、証明書やprovisioning profileを動的に取得することは不可能で、Gitのリポジトリに含めることが多いです。

また、弊社ではすでにCircleCIのiOS Planに課金をしており、CircleCI上でiOSのビルドをシェルスクリプトベースで実行していました。

今回、[fastlane](https://fastlane.tools/)というものが、どうやら便利らしいという噂を聞きつけ、試してみました。

## 実現すること

CircleCI上で動作させる`beta`というlaneを作成し、下記の手順を実行します。

- 証明書のインストール
- ipaファイルの作成
- CircleCIのArtifactsとしてipaを登録
- DeployGateにサブミット
- 証明書のアンインストール

## 導入手順

一通り設定したものが、[noboru-i/kyouen-ios at bcbca4f21be08d56242e23a44f04986496515bd3](https://github.com/noboru-i/kyouen-ios/tree/bcbca4f21be08d56242e23a44f04986496515bd3)こちらになります。
こちらでは、SwiftLintの実行なども入っていますが、それはまた別の機会に。

### Gemfileの作成・設定

```
source 'https://rubygems.org'

gem 'fastlane'
```

上記内容で`Gemfile`を作成し、`bundle install --path vendor/bundle`でインストールを実行します。

### fastlaneの初期化

`bundle exec fastlane init`というコマンドがありますが、実体としては後述の`Appfile`や`Fastfile`などを質問に答えていくことで作ってくれる、というものなので、今回は実行せずに進みます。

手動で、下記のようなディレクトリ構成を作成します。
プロジェクトのルートディレクトリに`fastlane`ディレクトリを作成し、fastlaneで必要なものを配置していきます。

```
project_root
├── Gemfile
├── circle.yml
└── fastlane
    ├── Appfile
    ├── Fastfile
    ├── actions
    │   └── swiftlint.rb
    └── cert
        ├── 02b06950-dd9a-4a2f-8f74-097ed2dd2db7.mobileprovision
        ├── apple.cer
        └── dist.p12
```

`cert`ディレクトリ内のファイルについては、下記のように配置します。

- ファイル名がUUIDとなっているprovisioning profileを配置（インストール済みのものは、`~/Library/MobileDevice/Provisioning Profiles`に格納されているはず。）
- Apple Worldwide Developer Relations Certification Authorityと呼ばれるものを、`apple.cer`というファイル名で配置
- `p12`ファイルはパスワードを設定の上、配置（キーチェーンアクセスより、パスワード付きで書き出せる。p12ファイルが最悪流出しても、パスワードがわからなければ利用できない。）


### fastlane/Appfileの設定

こちらは、Bundle Identifierなどのツール全般の設定を行うファイルです。

詳しい説明は、[こちら](https://github.com/fastlane/fastlane/blob/master/docs/Appfile.md)を確認ください。

今回は、プロジェクトの設定ファイルにある値をそのまま利用するので、ファイルだけ作成しておき、空にしています。

### fastlane/Fastfileの設定

こちらは、laneと呼ばれる実行する処理を記述するファイルです。

詳しい説明は[こちら](https://github.com/fastlane/fastlane/blob/master/docs/README.md)、各アクションのパラメータなどは[こちら](https://github.com/fastlane/fastlane/blob/master/docs/Actions.md)に記載がありますが、[こちら](https://github.com/fastlane/fastlane/tree/master/lib/fastlane/actions)にあるコードを直接見たほうが早いかもしれません。

今回は、前述の`beta`というlaneを定義するために、下記のように設定します。

各アプリ毎に変更が必要な箇所は、先頭にまとめてあります。（You need to set your project values.の部分）
環境に合わせて書き換えて頂ければと思います。

```ruby
fastlane_version '1.47.0'

default_platform :ios

# You need to set your project values.
workspace_name = 'Sample.xcworkspace'
scheme_name = 'Sample_adhoc'
deploygate_user_name = 'sample-user'
project_name = 'Sample'

# And you need to set environment variables.
# DEPLOY_GATE_KEY : DeployGate API key, using when upload to DeployGate.
# GITHUB_ACCESS_TOKEN : GitHub access token, using when comment to Pull Request.
# KEY_PASSWORD : p12 password, using when import *.p12 files.

platform :ios do
  before_all do
    add_keychain if is_ci?
  end

  after_all do
    remove_keychain if is_ci?
  end

  desc 'Submit a new Beta Build to DeployGate'
  lane :beta do
    gym(
      workspace: workspace_name,
      scheme: scheme_name,
      output_name: "#{project_name}.ipa"
    )

    Helper.log.info 'Save Artifacts'
    sh "cp #{lane_context[SharedValues::IPA_OUTPUT_PATH]} $CIRCLE_ARTIFACTS"

    message = "Commit: #{ENV['CIRCLE_SHA1']} / #{last_git_commit[:message]}, Build: #{ENV['CIRCLE_BUILD_NUM']}"
    deploygate(
      api_token: ENV['DEPLOY_GATE_KEY'],
      user: deploygate_user_name,
      ipa: lane_context[SharedValues::IPA_OUTPUT_PATH],
      message: message
    )
  end

  private_lane :add_keychain do
    create_keychain(
      name: 'ios-build.keychain',
      password: SecureRandom.uuid,
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: true
    )
    import_certificate keychain_name: 'ios-build.keychain', certificate_path: 'fastlane/cert/apple.cer'
    Dir.glob('cert/*.p12').each do |cert_file|
      import_certificate keychain_name: 'ios-build.keychain', certificate_path: 'fastlane/' + cert_file, certificate_password: ENV['KEY_PASSWORD']
    end

    Dir.glob('cert/*.mobileprovision').each do |provisioning_file|
      FastlaneCore::ProvisioningProfile.install(provisioning_file)
    end
  end

  private_lane :remove_keychain do
    delete_keychain(
      name: 'ios-build.keychain'
    )
    sh "rm -f \"#{FastlaneCore::ProvisioningProfile.profiles_path}*\""
  end
end
```

外部公開用のlaneとして`beta`があり、それ以外はprivate_laneとしてメソッドを切り出しているイメージです。

### CircleCIの設定

上記までで、`bundle exec fastlane beta`を実行することで当初の目的を果たせるようになりました。

あとは、これをCircleCI上で実行するように、`circle.yml`を下記のようにします。

```yaml
machine:
  timezone: Asia/Tokyo
  xcode:
    version: "7.1"
test:
  override:
    - echo 'no test'
deployment:
  master:
    branch: master
    commands:
      - bundle exec fastlane beta
```

ここでは、`master`ブランチの変更があった場合に、`beta`を実行するようにしています。

最後に、CircleCI上でenvironment variablesの設定を行います。設定が必要な項目はFastfileに記載してあります。（And you need to set environment variables.の部分）

あ、CircleCIのProject setting -> Experimental SettingsでBuild iOS projectをOnにすることもお忘れなく。（CircleCIへの課金が必要）

## 完成

この設定を行うことで、「XXさんが手が離せないのでアプリの配布が出来ません！」だったり、「XXさんが作ったipaとYYさんがつくったipaで動作が違うんだけど」とか、「ipaの作成作業が面倒なので、DeployGateでの配布は1日1回までとさせて頂きます」といったことから開放されます。

今回は、fastlaneの中でも`gym`や`deploygate`といったアクションぐらいしか使っていないですが、provisioning profileの更新や、iTunes connectとの連携など、様々なものが定義されています。

実際やってみて、パス周りだったり、パラメータの意味を調べるのだったりに時間がかかってしまいましたが、今まで慣れないシェルスクリプトで頑張っていたことを考えると、Rubyのコードで拡張していけるようになっただけでも、良かったと思います。