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

酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Xcodeでのビルドを自動化するxcodebuildコマンドとIPAファイルを作成してiTunes Connect(Testflight)に投げる方法

開発 開発-iOS

2014年にAppleがTestflightを買収してから数年経ちました。Androidのサポートが打ち切られたりして対応に追われたこともありましたが、数年経ち公式にiTunes Connectのアプリ申請プロセスに組み込まれるようになりました。

iOS 8を公開した時期から1つのバージョンに対してバイナリを複数投げることができるようになりました。

Jenkinsを使ってビルドしたiOSアプリのバイナリを自動でアップロードできるように、xcodebuildと延々戦っていたので、Xcode 6.1で実行した結果をメモしておきたいと思います。僕がJenkinsビルドマンをやっている限りこの記事は随時更新されていきます。

環境について

僕の環境での結果なので、他の環境で適用できなかったらごめんなさい。プロジェクトの構成によっては、xcodebuildがエラーを吐くと思うので適時良い感じに読みかえてください

下記のメモには「Example.xcodeproj」というプロジェクトファイルの名前が沢山登場します。

プロジェクトが格納されている{Project directory}には、「Example.xcodeproj」と「Example2.xcodeproj」の2つが格納されており、exampleスキーマにexampleターゲットが存在していることを前提としています。これはうちのアプリのプロジェクトの都合ですので良い感じに読みかえてください。

基本的に、-projectでプロジェクトファイルを指定する必要はありません。xcodebuildは、同一ディレクトリにxcodeprojが2つあるとどちらを使えば良いのかわからなくてエラーを吐くので、下記のメモでは.xcodeprojファイルを明示的に指定しています。

目次

xcodebuildを使ってビルド作業をコマンドラインでおこなう

ビルドに使うXcodeのバージョンを使い分ける

プロジェクトの関係上、 1台のPCに複数のXcodeがインストールされている場合があります。そういった場合には適切なバージョンのXcodeを使い分ける必要があります。

どのバージョンのxcodebuildが使われているか調べる

$ xcodebuild -version
Xcode 7.0.1
Build version 7A1001

Xcode 6.4のxcodebuildに切り替える

Applicationsに「Xcode」と「Xcode 6.4」があった場合を想定しています。「Xcode 6.4」の方はXcodeを共存させるためにリネームしていますので、自分の環境にあった名前に読み替えてください。

$ export DEVELOPER_DIR=/Applications/Xcode\ 6.4.app

$ xcodebuild -version
Xcode 6.4
Build version 6E35b

クリーン

ビルドをクリーンします。中間ファイルや出力ファイルが削除されます。中間ファイルを削除してしまうため、次回ビルドした際にはすべてのファイルをコンパイルしなおす必要がありコンパイル時間がかかってしまいます。

しかし、ビルドをクリーンすることで、例えば画像の差し替えなどの変更がビルド結果に反映されないことを防ぐことができます。何故かうちのプロジェクトではこの現象が頻発するのでビルドをクリーンしています。

# 全部のビルドをクリーンする
xcodebuild clean -project Example.xcodeproj

# exampleスキームのビルドをクリーンする
xcodebuild clean -project Example.xcodeproj -scheme example

ビルド

指定されたプロジェクトのターゲットをビルドします。

# シミュレータ向けにビルドする
xcodebuild -project Example.xcodeproj -target example \
  -sdk iphonesimulator -configuration Debug build

# 実機向けにビルドする
xcodebuild -project Example.xcodeproj -target example \
  -sdk iphoneos -configuration Release build

# 実機向けにビルドする(-sdkを指定していないと実機ビルドになる)
xcodebuild -project Example.xcodeproj -target example \
  -configuration Release build

# CocoaPodsを使っていてxcworkspaceを指定してビルドする
xcodebuild -workspace Example.xcworkspace  \
  -scheme example -sdk iphonesimulator

プロジェクトに指定されているものと異なるCode sign(証明書)とプロビジョニングを指定したい場合には以下のように指定することが可能です。

# 実機向けにビルドする(証明書をEnterpriseに変更)
xcodebuild -sdk iphoneos \
  -project Example.xcodeproj -target example \
  -configuration Release build \
  CODE_SIGN_IDENTITY='iPhone Distribution: Sakusan CO.,LTD.' \
  PROVISIONING_PROFILE='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

スキームを調べる/ターゲットを調べる

ビルドのためには-targetまたは-schemeを指定する必要があります。.xcodeprojを使ってビルドするためにはターゲット(-target)を指定する必要があります。.xcworkspaceを使ってビルドするためにはスキーム(-scheme)を指定する必要があります。

それぞれ-targetまたは-schemeを取得します。

.xcodeprojからターゲットの一覧を取得する

$ xcodebuild -project Example.xcodeproj -list


Information about project "Example":
    Targets:
        Example Staging
        Example Production

    Build Configurations:
        Debug
        Release

    If no build configuration is specified and 
        -scheme is not passed then "Release" is used.

    Schemes:
        Example Production
        Example Staging

.xcworkspaceからスキームの一覧を取得する

$ xcodebuild -workspace Example.xcworkspace -list


Information about workspace "Example":
    Schemes:
        Pods-Example Production
        Pods-Example Staging
        Example Production
        Example Staging

テスト

指定されたプロジェクトのターゲットをテストします。

# iPhone 5s シミュレータでテストする
xcodebuild -project Example.xcodeproj -scheme example \
  -sdk iphonesimulator  \
  -destination 'platform=iOS Simulator,name=iPhone 5s' clean test

# iOS 8.1のiPhone 6シミュレータでテストする
xcodebuild -project Example.xcodeproj -scheme example \
  -sdk iphonesimulator  \
  -destination 'platform=iOS Simulator,OS=8.1,name=iPhone 6' clean test

使用しているxcodebuildで利用可能なSDKを調べる

$ xcodebuild -showsdks  

OS X SDKs:  
    OS X 10.9                       -sdk macosx10.9  
    OS X 10.10                      -sdk macosx10.10  
  
iOS SDKs:  
    iOS 8.1                         -sdk iphoneos8.1  
  
iOS Simulator SDKs:  
    Simulator - iOS 7.1             -sdk iphonesimulator7.1  
    Simulator - iOS 8.1             -sdk iphonesimulator8.1  

インストールされている証明書を調べる

$ security find-identity -v -p codesigning


  1) XXXXXXXXXX "iPhone Distribution: CH3COOH INC."
  2) XXXXXXXXXX "iPhone Distribution: Sakusan,INC."
     2 valid identities found

再署名する

現在出力しているipaファイルを新しいProvisioning Profileを使って再署名することができます。利用用途としては、新しいIPAファイルは作成したくないがProvisioning Profileが失効してしまったり対応するUDIDが増えた時に、Provisioning Profileのみを入れ替えたいという時によく使います。

詳しい方法については下記の記事にまとめました。

IPAファイルを作成してiTunes Connectに投げる

実機向けにビルドした.appファイルを署名して、.ipaファイルを作成してからiTunes Connectにぶん投げます。

注意しておかないといけないのは、必須項目であるアイコン画像やビルドバージョンの更新*1をしておかないといけないということです。

# 実機ビルド
xcodebuild -project Example.xcodeproj -target example \
  -configuration Release build

appファイルが、{Project directory}/build/Release-iphoneos/example.app にできていると思います。example.appは既にAppStoreで署名済みとします。

# .appをパッケージして.ipaファイルを作る
xcrun -sdk iphoneos PackageApplication \
  build/Release-iphoneos/example.app \
  -o /Users/ch3cooh/Documents/example.ipa

作成したIPAの検証をおこなう

作成したexample.ipaファイルの検証を行います。-uで指定しているMAILADDRESSと-pで指定しているPASSWORDは、それぞれApple IDのものを指します。検証するためには「altool」を利用します。altoolのパスが思った以上に長くて表示されていません……

'/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool' \
  --validate-app -f /Users/ch3cooh/Documents/example.ipa  \
  -u MAILADDRESS -p PASSWORD

以下のようにエラーが出なかったよと教えてくれます(そこそこ時間はかかる模様)。

2015-02-10 22:12:49.661 altool[29899:706733] No errors validating archive at /Users/ch3cooh/Documents/example.ipa

作成したIPAをiTunes Connectへアップロードする

問題がなかったので、iTunes Connectへ--validate-app--upload-appに変えてアップロードします。

'/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool'  \
  --upload-app -f /Users/ch3cooh/Documents/example.ipa  \
  -u MAILADDRESS -p PASSWORD

iTunes Connect(Testflight)へのアップロードには検証とは比にならないくらい時間がかかります。辛抱強く待ちましょう。アップロードができました!!

2015-02-10 22:26:18.567 altool[32637:810145] No errors uploading /Users/ch3cooh/Documents/example.ipa

iTunes Connectのプレリリース上でも先ほどアップロードしたバイナリを確認することができます。

f:id:ch3cooh393:20150210223340p:plain

繰り返しになりますが、アップロードにはものすごい時間とネットワークリソースを使用しますので、開発マシンではなく専用のビルドマシンでJenkinsなどのCIツールを使う方が良いでしょう*2

App Extension(Widget)を含むiOSアプリをビルドする

App Extension(Widget)と本体側のiOSアプリはプロビジョニングプロファイルが異なります。コマンドラインから異なるプロビジョニングプロファイルを指定します。

追記:Xcode 6.3でiTunes Connectにアップロードできなくなってしまった(2015/4/9)

altoolの--validate-appオプションをつけてバイナリの検証をするのは問題がないのですが、Xcode 6.3をインストールした影響か、--upload-appオプションでiTunes ConnectへのSubmitに失敗するようになってしまいました。

2015-04-09 15:10:27.019 altool[19173:77108] *** Error: Errors uploading ‘/Users/ch3cooh/Documents/example.ipa’: ( “Error Domain=WorkerErrorDomain Code=-10001 \"Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.\” UserInfo=0x7fc522511550 {MZUnderlyingException=Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application., NSLocalizedDescription=Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application., NSLocalizedFailureReason=Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. You should reinstall the application.}“ )

重要な文章は、Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.の部分です。

altoolさん曰く、/usr/local/itms/bin/iTMSTransporterが見つからないと言っています。確かに「/usr/local」自体が存在していませんでした。

iTMSTransporterが実際に存在するのは以下のフォルダ(?)になります。

  • /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms/bin/

/usr/local/itms/bin/iTMSTransporterを参照してしまうaltoolのためにシンボリックリンクを貼ってパスを通してあげましょう。ひょっとすると /usr/localは権限の関係で作れないかもしれないので適切な方法に読み替えてください。

mkdir /usr/local
ln -s "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms" /usr/local/

今まで通りatoolコマンドでipaファイルをアップロードしてみます。数分後、以下のようなメッセージが表示されました。

2015-04-09 16:04:37.073 altool[19527:89225] No errors uploading ‘/Users/ch3cooh/Documents/example.ipa’

WatchKitアプリが含まれていないバイナリであれば問題なくTestFlightの配信まで確認できたが、WatchKitアプリが含まれているプロダクトはバイナリが無効と言われてしまった。

f:id:ch3cooh393:20150409162312p:plain

追記:Xcode 6.4でビルドしていたプロジェクトをXcode 7.2にするとビルドが通らなくなってしまった(2016/3/11)

Xcode 6.4でビルドしていたプロジェクトをXcode 7.2(Xcode 7.2.1)にした途端ビルドが通らなくなってしまった。ビルドスクリプトは以下の通りです。

xcodebuild -sdk iphoneos \
  -workspace ${XCWORKSPACE_NAME} -scheme "${SCHEME_NAME}" \
  -configuration Release build \
  CODE_SIGN_IDENTITY=${CODE_SIGN_ID} \
  PROVISIONING_PROFILE=${PROVISIONING_ID} \
  SYMROOT=${BUILD_NUMBER_PATH}

xcrun -sdk iphoneos PackageApplication "${SOURCE_PATH}" -o ${APP_PATH} \
  --embed ~/Library/MobileDevice/Provisioning\ Profiles/${PROVISIONING_FILENAME}

原因を探ってみるとSYMROOTの位置に中間ファイルや.appファイルが生成されていないのが原因で、後続のアプリパッケージで失敗しているのがわかりました。

xcodebuildのCODE_SIGN_IDENTITYに今までは証明書のハッシュ値を指定していましたが、CODE_SIGN_IDENTITY"iPhone Distribution: SAKUSAN,INC. (XXXXXXXXX)"のような名前で指定することでビルドが通るのを確認しました。

xcodebuild -sdk iphoneos \
  -workspace ${XCWORKSPACE_NAME} -scheme "${SCHEME_NAME}" \
  -configuration Release build \
  SYMROOT=${BUILD_NUMBER_PATH} \
  CODE_SIGN_IDENTITY="${CODE_SIGN_NAME}"

追記:Xcode 7でビルドしたApp Store向けバイナリを申請に出すとAppleからInvalid Swift Supportメールが送られてくる(2016/4/5)

Xcode 7でビルドしたApp Store向けバイナリをiTunes Connectにアップロードすると機械的にリジェクトされてしまい、AppleからInvalid Swift Supportと書かれたメールが送られてきます。

追記:Xcode 8.3でビルドするとPackageApplicationが見つからないとエラーが発生する (2017/4/7)

Xcode 8.2.1まではビルドエラーにならなかったのですが、Xcode 8.3にアップグレードすると下記のようにPackageApplicationが見つからないエラーが発生してしまいます。

xcrun: error: unable to find utility “PackageApplication”, not a developer tool or in PATH Build step ‘シェルの実行’ marked build as failure

解決方法の詳細については以下の記事に書きました。

参考

*1:ビルドバージョンは、前回iTunes Connectにアップロードしたバージョンより大きい数字を指定しておく必要があります

*2:テザリングでネットに繋げている時にしたらリソース全部持って行かれた