24/7 twenty-four seven

iOS/OS X application programing topics.

Travis CIでiOSアプリのリリース作業を自動化する

この記事において利用している.travis.ymlRakefileの全体はGistにて公開しています。


↓ Rakefileの全体はこちら


↓ .travis.ymlはこちら

概要

ユビレジではiOS アプリを申請する際に発生する作業の大部分をCIで自動化しています。

申請の作業としてユビレジでは下記のワークフローを決めています。


1. リリースブランチを作る
2. リリースするバージョンのバイナリをビルドする
3. 2と同等のアプリケーションを社内に配布して最終チェックをする
4. クラッシュレポートのサービスとしてCrittercismを利用しているので、そこにデバッグシンボル(dSYM)をアップロードする
5. 2のバイナリをiTunes Connectにアップロードする


図にすると下記のようになります。
手順としては難しいものではないのですが、定期的に発生する作業としてはかなり面倒な部類のものです。

20141022124901


また、3のチェックで不具合が見つかった場合は修正して再度やり直しになるので、できるだけ簡単にしたいところです。
さらに、リリースビルドが正しい構成でビルドされているかの確認はパッと見ただけではわからないので、各自の環境でビルドしている場合、それがデバッグ構成になってないかどうかなどを気をつけるのがけっこうストレスだったりします。
そして、この作業は手作業でやるぶんには分割しにくいので(TestFlightの登録だけ手伝います、って言われても別に楽にならない)、1人の時間をけっこう取ってしまうというのも問題です。


そこで、ユビレジではこの一連の作業のうち、下記の点線で囲んだ部分、iTunes Connectにアップロードする以外の作業をすべてCIで自動化しています。

20141022130752


そうすると、実際の行われているワークフローは下記のように変わります。


1. リリースするブランチ(普通はmaster)からreleaseで始まる名前のブランチを派生する(例:release/3.0.0)
(Travis CIによって)
a. リリースビルドが作られる
b. デバッグシンボルドがCrittercismにアップロードされる
c. リリースビルドがAdHoc署名されてTestFlightで配信される
d. リリースビルドがGithub Releasesに登録される
2. 該当ののリリースをダウンロードしてXcodeから申請する


ここで実際に手作業で行う必要がある作業は太字で強調した2つだけです。

自動化の仕組み

リリースビルドの作成

ここから実現方法について説明します。
まず、リリースビルドを自動で作る仕組みは.travis.ymlとrakeのスクリプトで実現しています。

(略)
script:
  - '[ ! -z $(echo ${TRAVIS_BRANCH} | grep "^release.*$") ] && CONFIG=release || CONFIG=adhoc'
  - echo ${TRAVIS_BRANCH}
  - echo ${CONFIG}
  - bundle exec rake $(echo ${ACTION} | sed -e "s/CONFIG/${CONFIG}/g")
(略)
  matrix:
    - ACTION="profile:install certificate:add distribute:CONFIG certificate:remove"
    - ACTION=test


.travis.ymlは上記のようになっていて、ブランチ名が"release"で始まるかどうかでCONFIG変数の値が変わります。
CONFIGの値はrakeのタスクにそのまま渡されて、その値によってリリース版かベータ版のどちらの構成でビルドするかが決定されます。


Rakefileの該当部分は下記になります。

def archive(configuration: "Release")
  build_xcarchive(configuration: configuration)

  clean_ipa
  export_ipa(configuration: configuration)

  zip_artifacts

  if configuration == "Release"
    upload_artifacts
  end
end


内容を1つずつ見ていきます。
build_xcarchiveでは下記のコマンドが実行されます。
(見やすさのため絶対パスを相対パスに書き換えています。以下同様)

xcodebuild -sdk "iphoneos"
           -workspace "./Ubiregi2.xcworkspace"
           -scheme "Ubiregi2-Release"
           -configuration "Release"
           CONFIGURATION_BUILD_DIR="./build"
           CONFIGURATION_TEMP_DIR="./build/temp"
           CODE_SIGN_IDENTITY="iPhone Distribution: Ubiregi Inc. (X123456YZ)"
           archive
           -archivePath "./build/Ubiregi2.xcarchive"


これでbuildディレクトリにUbiregi2.xcarchiveが生成されます。
このとき実行されたコマンドはTravis CIのログから後で確認できるので、デバッグ構成でビルドしてないだろうか?などという不安を抱えることはなくなります。

リリースビルドを元にAdHoc版を作成する

clean_ipaは以前のipaファイルがあったら次のコマンドが失敗するので消すメソッドです。手元で実行するときのためのものです。
export_ipaで今作った「Ubiregi2.xcarchive」をipaファイルに変換します。
このipaファイルを作るのは社内確認用にTestFlightにアップロードするためなので、ここで同時にAdHocプロビジョニングプロファイルで再署名します。


export_ipaで実行される実際のコマンドは下記になります。

xcodebuild -exportArchive
           -exportFormat "IPA"
           -archivePath "./build/Ubiregi2.xcarchive"
           -exportPath "./build/Ubiregi2.ipa"
           -exportProvisioningProfile "Ubiregi Ad Hoc"


これで先ほどビルドしたものと全く同じバイナリが、署名だけ変えて作成されます。
「Ubiregi2.xcworkspace」を単にipaに再パッケージしているだけなので、実行バイナリは完全に同一です。
こうすることによって、署名以外はストアで配布されるものと全く同じアプリケーションを社内で確認することができます。

成果物をzipにまとめる

xcarchiveはディレクトリなのでそのままでは不便なので次のzip_artifactsメソッドでzipファイルにまとめられます。
ここでdSYMディレクトリもついでにzipファイルにしてしまいます。

実際のコマンドは下記です。

(cd ./build; zip -ryq Ubiregi2.app.dSYM.zip Ubiregi2.app.dSYM)
mv ./build/Ubiregi2.app.dSYM ./build/Ubiregi2.xcarchive/dSYMs/Ubiregi2.app.dSYM
(cd ./build; zip -ryq Ubiregi2.xcarchive.zip Ubiregi2.xcarchive)
成果物を外部にアップロード

TestFlightとCrittercismへのアップロードについては以前に書いたものと同様なので、リリースビルドをGithubのReleasesにアップロードする部分を紹介します。

upload_artifactsメソッドは下記のコマンドになります。

curl -sSf
     -d "{\"tag_name\":\"v3.0.0-4321_2014-10-22T06-01-18Z\",
         \"target_commitish\":\"master\",
         \"name\":\"v3.0.0-4321_2014-10-22T06-01-18Z\",
         \"body\":\"Build: 3.0.0 (4321)\\nUploaded: 2014/10/22 15:01:18\\n\",
         \"draft\":false,
         \"prerelease\":false}"
         "https://api.github.com/repos/ubiregiinc/client-releases/releases?access_token=GITHUB_ACCESS_TOKEN"

curl -sSf
     -w "%{http_code} %{url_effective}\n"
     -o /dev/null
     -X POST https://uploads.github.com/repos/OWNER/REPO/releases/RELEASE_ID/assets?name=Ubiregi2-3.0.0-4321.xcarchive.zip
     -H "Accept: application/vnd.github.v3+json"
     -H "Authorization: token "
     -H "Content-Type: application/zip"
     --data-binary @"./build/Ubiregi2.xcarchive.zip"


Releasesへのアップロードは2段階で行います。
まず、最初のリクエストで該当のバージョンのReleaseを作成します。

そのリクエストの戻り値から、作成したReleaseのIDが取れるので、そこに向けてzipファイルをアップロードします。


ここまでで、以下の手順が自動的に完了します。

a. リリースビルドが作られる
b. デバッグシンボルドがCrittercismにアップロードされる
c. リリースビルドがAdHoc署名されてTestFlightで配信される
d. リリースビルドがGithub Releasesに登録される


TestFlightで配信されたアプリケーションを社内で検証して、問題があれば、修正して同じブランチにプッシュするだけで自動的にこの手順が再実行されます。

手戻りによる追加の手間はありません。

申請作業について

最終チェックが無事にすんだら申請です。

本当ははここが最も自動化したいところなのですが、iTunes ConnectはAPIなど提供されていないので、仕方なく手作業で行います。

Github Releaseからリリースする版のxcarchiveをダウンロードします。

20141022130752


zipファイルを展開すると、Xcodeで作成したのと同様のxcarchiveパッケージになるので、それをダブルクリックするとXcodeのオーガナイザに表示されます。

20141022130752


20141022130752


あとはSubmitボタンを押して申請すれば完了です。

おまけ(リリースブランチの作成)

ここまで自動化すると、トリガーとなるリリースブランチを作る作業がだんだん面倒に思えてきます。
(ブランチを派生させてInfo.plistのバージョンを書き換えて、など)
ただ、リリースブランチは再度修正が入ることもあるので、ガチガチにしてしまうとかえって面倒になるので、リリースブランチを簡単にプッシュできるrakeタスクを用意するくらいにとどめています。

リリースブランチを作るにはリリースしたいブランチ(普通はmaster)で

$ rake release

というタスクを実行します。
バージョンの更新についての選択肢が表示されるのでどれか1つを選ぶと、Info.plistの更新、リリースブランチの作成、リモートリポジトリにプッシュ、までをやってくれます。
(するとこれをトリガーとして自動的にCIが上述のビルドを開始します)

$ rake release
Select new version [current: 3.0.0 (4146)]:
1. 4.0.0 (4321)
2. 3.1.0 (4321)
3. 3.0.1 (4321)
4. 3.0.0 (4321)
?  

まとめ

リリースを自動化するのは様々なメリットがあります。
繰り返しの手作業が減ることや品質が一定に保てることももちろんですが、誰でもその作業ができるようになるとか、作業を分割できるようになるというのも大きなメリットです。
例えばリリースするコードをFixする作業(リリースブランチの作成)と社内への告知、iTunes Connectへのアップロードはそれぞれ別のひとがやることが可能になります。

自動化しなくても担当を分けることは可能ですが、手作業でやっていると互いに待ちの時間が発生したりバイナリの受け渡しの手間など、分業することで逆に面倒になります。

ということでユビレジで行っている自動化の仕組みを紹介しました。
参考になればいいなと思います。