読者です 読者をやめる 読者になる 読者になる

コネヒト開発者ブログ

コネヒト開発者ブログ

fastlaneとTravisでいい感じのタイミングでアプリを受け取る

お久しぶりです、3/31, 4/1は仕事ですが、4/2はバルト9へ行く田村(@Utmrer)です。 今回は弊社でのfastlaneによるアプリ配布についてお話します。

アプリ配布の通知メールの回数を減らしたい

開発段階のアプリを社内やテスターに配布する処理を何かしらのScriptで書いてCIで実行し、自動化している人は多いかと思います。 弊社も一時期配布を自動化していましたが、2つの問題がありました。

  1. RC, AdHoc, Development等、複数の環境のビルドが必要である
  2. PushしてCIが回るだびに通知メールがくると煩わしい

という感じで毎回複数環境のBuildを実行するのは時間が掛かり過ぎる、メールを送ってアップデートを促したいが「頻繁にメールが来ることによって、メールを無視することが普通になる」とアップデートしてもらえない、と問題を抱えていたので都度手動で配布してましたが、「fastlaneが便利らしいぞ」という話を聞いたので、Travisで自動化することにしました。

証明書をリポジトリに含める

Travis上でアプリをビルドするために証明書を書き出します。 f:id:Utmrer:20160320223115p:plain キーチェーンからiPhone Distribution: Your Name (Team ID)、そして今回はDevelopmentのBuildも配布するのでiPhone Developer: Your Name (Team ID)p12を書き出します。 WWDRCAはTravisなら最初から入ってます。

各環境のlane等をFastfileに定義する

最終目的なFastfileはこんな感じになります。

fastlane_version "1.61.0"

default_platform :ios

platform :ios do
  before_all do
    ENV['KEYCHAIN_NAME'] = "TempKeychain.keychain"
  end

  desc "Build for AdHoc"
  lane :build_adhoc do
    sigh(
      app_identifier: "com.example.app.adhoc",
      adhoc: true,
      skip_certificate_verification: true,
    )
    ENV["PROFILE_UDID"] = lane_context[SharedValues::SIGH_UDID]
    gym(
      clean: true,
      workspace: "App.xcworkspace",
      scheme: "Adhoc",
      use_legacy_build_api: true
    )
    deploy_crashlytics
  end

  desc "Build for Debug"
  lane :build_debug do
    sigh(
      app_identifier: "com.example.app.dev",
      development: true,
      provisioning_name: "Debug",
      skip_certificate_verification: true
    )
    ENV["PROFILE_UDID"] = lane_context[SharedValues::SIGH_UDID]
    gym(
      clean: true,
      workspace: "App.xcworkspace",
      scheme: "Debug",
      use_legacy_build_api: true
    )
    deploy_crashlytics
  end

  desc "Build for Release"
  lane :build_release do
    sigh(
      app_identifier: "com.example.app",
      adhoc: false,
      provisioning_name: "Release",
    )
    ENV["PROFILE_UDID"] = lane_context[SharedValues::SIGH_UDID]
    gym(
      clean: true,
      workspace: "App.xcworkspace",
      scheme: "Release",
      use_legacy_build_api: true
    )
    deliver(
      force: true,
      skip_screenshots: true,
      skip_metadata: true
    )
  end

  desc "Import Certificates"
  lane :import_certificates do
    return unless Helper.is_ci?

    create_keychain(
      name: ENV["KEYCHAIN_NAME"],
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: true,
      password: ENV["PASSWORD"]
    )

    import_certificate(
      certificate_path: "./certificates/distribution.p12",
      certificate_password: ENV['PASSWORD'],
      keychain_name: ENV["KEYCHAIN_NAME"]
    )

    import_certificate(
      certificate_path: "./certificates/development.p12",
      certificate_password: ENV['PASSWORD'],
      keychain_name: ENV["KEYCHAIN_NAME"]
    )
  end

  after_all do |lane|
  end

  error do |lane, exception|
  end

  def deploy_crashlytics
    crashlytics(
      api_token: "YOUR_API_TOKEN",
      build_secret: "YOUR_BUILD_SECRET",
      groups: 'tester_group',
      notifications: true
    )
  end

end

build_adhoc/build_debug/build_releaseがそれぞれの環境別のBuildを作成し、Crashlyticsへアップロードするlaneです。
ENV['PASSWORD']は「ちょっとパスワードとかcommitに含めるの嫌だなぁ〜」って時に、コマンドで暗号化して.travis.ymlで定義した環境変数です。
fastlaneでiOS Dev CenterやiTune Connectに接続するために必要なFASTLANE_USERFASTLANE_PASSWORDと合わせてこのようなコマンドで暗号化します。
travis encrypt PASSWORD=CERT_PASSWORD FASTLANE_USER=APPLE_EMAIL FASTLANE_PASSWORD=APPLE_PASSWORD
詳しくはこちらをご覧ください。
Encryption keys - Travis CI

ちょっとハマったところはprovisioning_nameを指定するところです。ここでprovisioning_nameを指定しないと、新しいProvisioning Profileを次々に生成し、iOS Dev Centerがかなりカオスになりました。

Travisでいい感じのタイミングでBuildする

f:id:Utmrer:20160321115901p:plain さて、それぞれの環境のlaneが出来たので、それぞれをいい感じのタイミングで実行しましょう。
弊社のiOSプロジェクトはgit-flowで開発しており、新しい機能はfeaturebranchを作り、実装が終わるとdevelopbranchへmerge、リリースのタイミングになるとそこからreleasebranchを作って全体的な機能の確認をし、問題が無ければmaterへmergeされるというflowをとっています。

  1. 新しい機能の実装が一旦完了した段階でみんなに開発環境アプリをインストールしてもらいたい
  2. 新しいリリースを申請する段階でみんなにAdHoc環境アプリをインストールしてもらいたい

という2つのタイミングがアプリを配布したいタイミングなので「featurebranchをdevelopbranchへmergeしたタイミングで開発環境アプリ」「masterbranchへreleasebranchをmergeしたタイミングでAdHoc環境アプリ」を配布することにしました。あとmasterへmergeされたタイミングでiTunesConnectにもアップロードしちゃいましょう。
その仕様を満たす.travis.ymlはこんな感じになりました。

language: objective-c
osx_image: xcode7.2
env:
  global:
  - LANG=en_US.UTF-8
  - LC_ALL=en_US.UTF-8
  - secure: COMMAND_DE_ANGO_KA_SHITA_PASSWORD_TOKA
script:
  # YOUR_TEST_SCRIPT
after_success:
  # Deploy Release/AdHoc App
  - "[[ $TRAVIS_PULL_REQUEST == 'false' ]] && [[ $TRAVIS_BRANCH == 'master' ]] && fastlane import_certificates && fastlane build_release && fastlane build_adhoc"
  # Deploy Debug App
  - "[[ $TRAVIS_PULL_REQUEST == 'false' ]] && [[ $TRAVIS_BRANCH == 'develop' ]] && fastlane import_certificates && fastlane build_debug"

本当はDeploymentを使ってDeployしたかったのですが、Deploymentではうまくfastlaneが動かなかったのでafter_successで実行しています。
$TRAVIS_PULL_REQUESTの判定でPushした時だけ実行するようにし、$TRAVIS_BRANCHの判定で特定のbranchでlaneを実行するようにしています。
ここの処理はもうちょっと綺麗に出来そうですね、Circle CIならもうちょっと綺麗だった気がします。
これでdevelopへmergeした時に開発環境アプリが、masterへmergeした時にAdHoc環境アプリがテスターに届くようになりました!

おわりに

この機能の実装に50 commit以上かかってしまって、完成した時の達成感がかなり大きかったです!(証明書辛い…)
fastlaneは社内でテスター募集をする時にboardingを使ったりしてるのですが、もうちょっと色々触ってみたいですね。(dg add-devices的なのをSlackからやったり)
それと今回はCrashlyticsから新しいアプリがメールで行きますが、出来ればSlackに何かしら通知したいですね。弊社の人はメールクライアントよりSlackにいる時間が長いのでSlackに通知した方がアップデートしてくれそうです。
もっと良い配布方法や.travis.ymlの書き方をご存じの方は是非教えて下さい!ではでは👋