Xcode8のAutomatically manage signingを有効にしつつCI環境でも動作するようにする


Xcode8になってから、今まで使っていたコマンドラインからのipaのエクスポート方法がdeprecatedになってしまったので、Appleも推奨のAutomatically manage signingを使ってみることにしました。

やったこと

Automatically manage signingを有効にした状態で以下をできるようにしました。

  • 普段開発に使っている端末のターミナルからコマンドラインでipa書き出し
  • CI環境上でコマンドラインでipa書き出し

コマンドラインビルド時に何が変わるのか

詳しくは他の記事で解説されているので書きませんが、Automatically manage signingの対応で問題になるのは証明書とプロファイルの選択方法で、以下のような違いがあります。

手動でsigningを行っていたとき

ビルド時に使う証明書とプロファイルは、その都度引数で指定。(Xcodeの設定に依存しない)

Automatically manage signingを使った場合

Xcode側でインストールされている証明書とプロファイルから必要なものを自動で判断するため、証明書とプロファイルは指定できない。(後述しますがTeamIDは指定できます。)

以下に、どうやって対応したかを書いていきます。

普段開発に使っている端末のターミナルからのipa書き出し

これはXcodeからArchiveができていればコマンドラインからも同じ設定で動作するので、特にハマるところはないと思います。

流れとしてはxcodebuildでArchiveした後に、このArchiveを使ってxcodebuild-exportOptionsPlistを指定してipaをエクスポートします。(自分用ですがサンプルを下に貼ってあります。)
exportOptionsPlistに、AppStore提出用なのか、Enterprise用なのか、adhoc用なのか、などを指定すると、それに合ったプロファイルでsignされます。

plistについては、こちらの記事が参考になりました。
http://qiita.com/roworks/items/7ef12acabf9679561d84

アーカイブとipa出力の自分用のサンプルを貼っておきます。

archive
xcodebuild -sdk iphoneos \
-workspace ".xcworkspaceへのパス" \
-archivePath "出力する.xcarchiveへのパス" \
-scheme "スキーム名" \
-configuration "BuildConfig名(省略可)" \
-IDEBuildOperationMaxNumberOfConcurrentCompileTasks=2 \ #ビルド高速化のための設定(省略可)
archive \
DEVELOPMENT_TEAM="チームID(省略可)"
archiveからipaのエクスポート
xcodebuild -exportArchive \
-archivePath "入力元の.xcarchiveがあるパス" \
-exportPath ".ipaの出力先のパス" \
-exportOptionsPlist "exportOptions.plistののパス" \
DEVELOPMENT_TEAM="チームID(省略可)"

自分の環境の場合は、XcodeではAppStore提出用のデベロッパーアカウントを使い、ターミナルからipa書き出しを行う際にはEnterprise用のデベロッパーアカウントを使いたかったので、Xcodeの設定ではAppStore提出用のデベロッパーアカウントのチームIDを指定しておき、ターミナルから実行時のxcodebuildの引数にDEVELOP_TEAM=<アカウントのチームID>をつけて切り替えるようにしました。

余談ですが、deprecatedになってしまった、自分でプロファイルを指定する方法だと、メインのターゲットとは別でプロファイルが必要になるWatchやTodayのExtensionを含んだバイナリを書き出す時に困ります。よく知られている(と思われる)解決法として、XcodeのBuildSettingsでExtensionのターゲットのProvisioning Profileに変数を設定しておき、コマンドラインの引数からプロファイル名をセットして置き換えるという方法がありましたが、Automatically manage signingを使えばそれも気にしなくて良くなります。

CI環境上でのipa書き出し

やることは上に書いたターミナルからのipa書き出しと同じなのですが、環境が全く違うので厄介でした。
やり方としては以下のどちらかになると思います。

  1. CI環境のときだけ手動signingに切り替えるようにする
  2. 自分の端末のXcodeが自動生成する証明書とプロファイルをCI環境にもインストールする

1の方法はメンテナンスが後々大変になりそうなので、2の方法で進めました。

Xcodeが自動生成する証明書とプロファイルをどうやって抜き出すか

手作業でやると結構大変です。証明書の方は良いのですが、プロファイルの方は特定ができないので一度全部プロファイルを消して、自動生成されたタイミングで抜き出して...という作業になりそうでした。
そこで、Bitriseが出しているこのツールを使わせてもらいました。

codesigndoc
https://github.com/bitrise-tools/codesigndoc

これを使うと必要なプロファイルと証明書を抜き出してフォルダにエクスポートしてくれます。(本当にありがたいです...)
あとは、抜き出されたプロファイルと証明書をCI環境にインストールすれば、自分の端末のターミナルから実行するときと同じようにipaが書き出せます。

※codesigndocを実行するときにはXcodeのArchive時のBuildConfigや、TeamIDの設定をCI環境上で実行したいものと同じにしてから実行してください。

その他ハマったこと困ったこと

codesigndocがDistribution用の証明書をexportしてくれない

自分の環境だけかもしれないですが、必要な証明書が全部揃っているつもりでCI環境にインストールして実行してみたところexportArchiveで証明書が無いと言われてコケました。結局、Distribution用の証明書は自分で書き出して追加しました。

CI上でのsigning時にハングする

TravisCIを使っているのですが、signing(codesign)に入ったまま止まってしまう現象が発生ました。調べていくとSierraからの挙動変更のようで、Keychainのプロンプトが表示されて止まっているようです。
解決法
1. 証明書のインポート時(security import)時に-Tオプションを使っているのであれば-Aに置き換え
2. 証明書のインポート後に以下のコマンドを追加

security set-key-partition-list -S apple-tool:,apple: -s -k keychainPass keychainName

(検索用 codesign stuck hang signing)

exportArchive時にerror: exportArchive: No applicable devices found.

TravisCI上の環境だとRubyのバージョンがMacのSystemのものと異なるらしく、それでコケているようです。
手元で試したわけじゃないですが、-exportOptionsPlistを指定すると発生する模様。

解決法
単純に rvm use system では変更できないようです。
こちらに投稿されているスクリプトをコピーし、それを経由させるよう、xcodebuildxcbuild-safe.sh に置き換えます。
https://gist.github.com/claybridges/cea5d4afd24eda268164

archive
xcbuild-safe.sh -sdk iphoneos -workspace "${WORKSPACENAME}.xcworkspace" -archivePath "$(PWD)/build/latest/${WORKSPACENAME}.xcarchive" -scheme "${SCHEME}" \
 -configuration "${CONFIG}" -IDEBuildOperationMaxNumberOfConcurrentCompileTasks=2 archive DEVELOPMENT_TEAM="${TEAM_ID}"
archiveからipaのエクスポート
xcbuild-safe.sh -exportArchive -archivePath "$(PWD)/build/latest/${WORKSPACENAME}.xcarchive" -exportPath "$(PWD)/build/${DIRNAME}" -exportOptionsPlist "$(PWD)/script/exportOptions.plist" DEVELOPMENT_TEAM="${TEAM_ID}"

**ARCHIVE FAILED**と表示されてコケるがエラーが表示されない

原因は色々あるみたいですが、RunScriptでコケてもこうなるみたいです。
一度RunScriptを消して試してみたらできました。


以上です!まだ情報も少ない上にハマりどころも多く、思った以上に時間がかかりました。
特に、リポジトリにpushしてCIを動かす必要があるので、これで時間をかなり使います。
TravisCIを使っているのですが、Debug job(ビルド用の環境にsshで入れる)がハマったときの原因究明に役立ちました。
また、Bitriseが提供しているCIサービスでは、簡単にローカルに仮想環境を構築して試せるようです。