Xcode
iOS
CircleCI
OriginalトレタDay 17

iOS project を CircleCI 2.0 に移行してみた

はじめに

トレタでは、2年ほど前から CI 環境として CircleCI を利用していますが、この度めでたく CircleCI 2.0 が iOS プロジェクトのビルドにも対応したということで、試しに移行してみました。

CircleCI 1.0 と 2.0 の違いをまとめつつ、移行時に調べたことやはまったことをまとめます。

ジョブを定義できるように

CircleCI 1.0 時代のビルドには、3つのジョブ dependencies test deployment があらかじめ用意されており、各ジョブに手順を定義することでビルドの設定を行うというものでした。

1.0
machine:
  xcode:
    version: 8.3.3
dependencies:
  override:
    - (commands)
test:
  override:
    - (commands)
deployment:
  development:
    commands:
      - (commands)

CircleCI 2.0 では、あらかじめ用意されたジョブを利用するのではなく、ジョブ自体を自由に定義できるようになりました。また、ジョブ毎に環境を変更することもできます。

設定ファイルは circle.yml ではなく .circleci/config.yml に記述します。

2.0
version: 2
jobs:
  job1:
    macos:
      xcode: 8.3.3
    steps:
      - (commands)
  job2:
    macos:
      xcode: 8.3.3
    steps:
      - (commands)
  job3:
    macos:
      xcode: 8.3.3
    steps:
      - (commands)
  job4:
    macos:
      xcode: 8.3.3
    steps:
      - (commands)
  job5:
    macos:
      xcode: 8.3.3
    steps:
      - (commands)

ジョブを並列実行できるように

CircleCI 1.0 では、3つのジョブ dependencies test deployment が直列に実行されますが、2.0 では自分で定義したジョブを並列で実行することもできるようになりました。

「ジョブCを実行するためにはジョブAとジョブBが完了している必要がある」などジョブ同士の依存関係を定義することで、ジョブが並列に実行されるようになります。

トレタでは

  • キャッシュが存在しない場合は RubyGems, CocoaPods をインストールしてキャッシュを作成(setup
  • ↑が終わったら、3つのテストを並列実行
    • lint SwiftLintによる構文チェック
    • scan ユニットテスト
    • snapshot UI テスト
  • ↑が終わったら、2つのアプリのビルドを並列実行
    • deploy_staging : ステージング環境向けのアプリ作成
    • deploy_prodtest : 本番環境向けのアプリ作成

という風に移行してみました(サンプルで試して見たときのものなのでビルド時間は気にしないでください)。

31801826-9f032da2-b585-11e7-8cd0-045203f6f1a2.png

CircleCI ではこのビルドのパイプラインを workflow と呼んでいます。設定ファイルでは workflows の下に登場人物となるジョブと、彼らの依存関係を puppet のようなフォーマットで定義していきます。

2.0
version: 2
jobs:
  setup:
    ...
  lint:
    ...
  scan:
    ...
  snapshot:
    ...
  deploy_staging:
    ...
  deploy_prodtest:
    ...
workflows:
  version: 2
  build-and-deploy:
    jobs:
      - setup
      - lint:
          requires:
            - setup
      - scan:
          requires:
            - setup
      - snapshot:
          requires:
            - setup
      - deploy_staging:
          requires:
            - lint
            - scan
            - snapshot
      - deploy_prodtest:
          requires:
            - lint
            - scan
            - snapshot

アプリのテストもビルドもそれなりに時間がかかるので、CircleCI 2.0 に移行して並列実行するだけでも時間をかなり節約できるはずです。

キャッシュを自分でコントロールできるように

1.0 ではサービス側でよしなにキャッシュコントロールしてくれていましたが、2.0 ではユーザー自身で管理する必要があります。

キャッシュの保存 save_cache は、cache key に任意の文字列を指定して行います。キャッシュを復元 restore_cache する時は、指定した cache key で呼び出せばOKです。

キャッシュはプロジェクト単位で利用可能なため、各 lock ファイルの checksum や、ブランチごとにキャッシュを作っておくなどしておくと効率がよさそうです。

先ほど紹介した setup ジョブでは、下記のような設定で RubyGems と CocoaPods のキャッシュを必要に応じて作成するようにしています。

version: 2
jobs:
  setup:
    steps:
      - checkout
      - restore_cache:
          keys:
            - gems-{{ checksum "Gemfile.lock" }}
            - gems-
      - run:
          name: Running bundle install
          command: bundle check || bundle install
          environment:
            BUNDLE_JOBS: 4
            BUNDLE_RETRY: 3
      - save_cache:
          key: gems-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - restore_cache:
          keys:
            - pods-{{ checksum "Podfile.lock" }}
            - pods-
      - run:
          name: Running pod install
          command: |
            curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
            bundle exec pod install
      - save_cache:
          key: pods-{{ checksum "Podfile.lock" }}
          paths:
            - Pods

リストア時の secondary cache key として gems-pods- を設定しておくと、 cache key にマッチするキャッシュが存在しない場合に最後保存されたキャッシュをリストアしてくれるため、 bundle install pod install の時短が期待できます。

保存しておいたキャッシュを別のジョブで利用する場合も、同様に restore_cache で復元できます。

  scan:
    macos:
      xcode: 8.3.3
    steps:
      - checkout
      - restore_cache:
          keys:
            - gems-{{ checksum "Gemfile.lock" }}
            - gems-
      - restore_cache:
          keys:
            - pods-{{ checksum "Podfile.lock" }}
            - pods-
      - run:
          name: Running fastlane scan
          command: bundle exec fastlane scan

Artifacts 保存方法の変更

CircleCI 1.0 では、テストのログやUIテストのスクリーンショットなどのファイルを後から参照するためには、$CIRCLE_ARTIFACTS ディレクトリに格納しておく必要がありました。

CircleCI 2.0 では、ジョブのステップ定義の中に store_artifacts を記述し、保存しておきたいファイル/ディレクトリの名前と、保存先のパスを指定します。

  snapshot:
    steps:
      - checkout
      - restore_cache:
          ...
      - run:
          name: Running fastlane snapshot
          command: bundle exec fastlane snapshot
      - store_artifacts:
          path: fastlane/screenshots
          destination: screenshots

ruby バージョンを変える

デフォルトでは ruby 2.0.0 が利用されるため、もっと新しいバージョンの ruby を利用したい場面があると思います。 CircleCI 2.0 では ruby-installchruby がインストールされているようで、それらを利用すれば別のバージョンの ruby が使えそうです。

(1) ジョブの定義に下記のように shell の設定を追加

circleci/config.yml
setup: 
  macos:
    xcode: 8.3.3
  shell: /bin/bash --login -eo pipefail

(2) .ruby-version をファイルをプロジェクトルートに追加

ruby-2.4

circle.keychain がなくなった

CircleCI 1.0 では circle.keychain という名前のキーチェーンがありましたが、 CircleCI 2.0 のドキュメントからは消えているので、どうやらなくなっているぽいです。

Fastlane の create_keychain で keychain を作成してから、証明書のインポートを行う必要があります。

lane :certificates do
  create_keychain(
    name: ENV["KEYCHAIN_NAME"],
    password: ENV["KEYCHAIN_PASSWORD"],
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: true
  )

  import_certificate(
    certificate_path: "certificates/development.p12",
    keychain_password: ENV["KEYCHAIN_PASSWORD"]
  )
  ...
end

thanks to @horimislime

さいごに

まだまだ機能を使いきれておらず、最適化されていない感じが満載ですが、なんとなく移行してジョブを並列実行するだけでも、十分なリターンが得られるので試してみる価値は十分にありました。もっとよい方法や tips などあれば教えてもらえると嬉しいです。

そんな CircleCI 2.0 の気になるところとしては、なぜかビルドが失敗することと、Workflow API がまだないこと(Hubot から API 叩きたいので欲しいところ)。

以上、現場よりお伝えしました。Happy CI-ing!