つばくろぐ @takamii228

知は力なり

App Store Connectアップロード時のipaファイルのvalidateをcliで実行する #iOS #flutter

本記事は主にFlutterの話題ですが、iOS向けの内容でFlutterを使っていなくても参考になると考えたため iOS #2 Advent Calendar 2020 として投稿しようと思います。

App Store Connetへのアップロード時のipaファイルのValidate

XcodeApp Storeの配布資材を作成した後、Xcode上ではApp Store ConnectにアップロードするDistribute Appボタンの下にValidate Appというボタンがあります。

これを押すと配布しようとしているipaファイルがApp Store Connectへのアップロード要件を見たしているバイナリかどうかを簡易的にチェックすることができます。

https://help.apple.com/xcode/mac/current/#/dev37441e273

今回はこれを自動化するにあたって試行錯誤したことをまとめます。

f:id:takamii228:20201205181831p:plain

Flutter 1.22.0にアップデートしたアプリがApp Store Connectにアップロードできない

運用していたFlutterアプリのFluterのバージョンを1.22.0にバージョンアップを実施し、いつも通りにCDパイプラインでipaファイルを作成した後にApp Store Connectにアップロードしようとしたら以下のエラーが発生しました。

f:id:takamii228:20201205182051p:plain

ipaのExportには成功します。ipaファイルをTransporter経由でアップロードしても同様のエラーが発生しました。

f:id:takamii228:20201205182150p:plain

おかしいなと思ってググってみるとどうやらFlutterのバージョンアップに起因するもののようです。

qiita.com

github.com

ios/Flutter/AppframeworkInfo.plist の MinimumOSVersion を 9.0 にすることで解消するようです。

Validate Appを自動化する

以前紹介したFlutterのCDパイプラインではipaの作成にとどまっており、今回のエラーはApp Store Connectにアップロードするまで気づくことができませんでした。

takamii.hatenablog.com

そこで今回はXcode上で実行可能なValidate AppをCDパイプラインに組み込んで同様のエラーが発生しないように改善します。

Validate AppをCLIで実行するには、xcodebuildコマンドのexportArchive時に指定しているexportOptionPlistファイルのdestinationuploadに、methodとしてvalidationとして以下のように指定すればよいようです。

// ExportOptionsProdValidate.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>destination</key>
    <string>upload</string> // destinationはupload
    <key>method</key>
    <string>validation</string> // methodはvalidationにする
    <key>provisioningProfiles</key>
    <dict>
        <key>com.takamiii.xcodevalidate</key>
        <string>xcodebuildvalidate-prod</string>
    </dict>
    <key>signingCertificate</key>
    <string>xxxxxx</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>xxxxxx</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

exportOptionPlistの説明はこちらがよくまとまっていました。

qiita.com

flutterの場合は flutter build ios --release コマンドの後に実行すればValidate AppをCLIで実行することができそうです。validate appを実行するコマンド例は以下のようになります。

$ xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist

exportOptionのplistファイルに upload とあるため、実際にアップロードされてしまうのではないかと少し不安に思いましたが、コマンド実行後にApp Store Connectを実際に確認してみたら特にアップロードされた形跡はありませんでした。安心してValidateの処理をパイプラインに組み込めそうです。

なおxcodebuildコマンド内で実行されるApp Store Connectへのアップロードの認証情報はXcodeに設定した認証情報が利用されます。そのため、該当のApp IDに対するApp Store Connectのアップロード権限をもつApple IDの認証情報がないと以下のようなエラーが発生します。

$ xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist
2020-12-05 19:01:32.639 xcodebuild[43990:6477163] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2020-12-05_19-01-32.639.xcdistributionlogs'.
error: exportArchive: Failed to log in.

Error Domain=IDEDistributionErrorDomain Code=9 "Failed to log in." UserInfo={IDEDistributionErrorsAccountIssues=(
), NSLocalizedDescription=Failed to log in., NSLocalizedRecoverySuggestion=App Store Connect access for “BLAG9TXR3M” is required. Ensure that your Apple ID account usernames and passwords are correct in the Accounts preference pane.}

** EXPORT FAILED **

またApp Store Connect側にアップロードの受け口となるAppsを作成しておく必要があります。App Store Connect側の設定無しに実行するとアップロードエラーになるため検証時には注意しましょう。

話題はそれますが、xcodebuildコマンドのオプションって把握するの結構大変ですよね...

再現確認

ここからは元の事象の再現確認と修正の手順を順に追ってみます。

まずFlutterのバージョンを1.17.5にした状態でAndroid StudioでFlutterのプロジェクトを作成します。そのあと、Flutterのバージョンを1.22.0に上げます。

次にXcodeでRunner.xcworkspaceを開いてBundle Identiferや証明書関連の情報を設定します。この後の手順の実行にはApp Store Connectへのアップロードに必要なApp IDや配布証明書、プロビジョニングプロファイルが必要手順は省略します。

また上の例で示したexportOptionPlistファイルを作成します。

最後に以下のコマンドを順番に実行してvalidateを実行し、エラーが再現することを確認します。

$ flutter clean
$ flutter pub get
$ flutter build ios --release
$ cd ios
$ xcodebuild -workspace Runner.xcworkspace \
     -scheme Runner \
     -configuration Release archive \
     -archivePath ../build/Runner.xcarchive
$ xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist

順に実行すると想定通り、冒頭で表示されたのと同じエラー出力がされました。

$  xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist
2020-12-05 19:20:21.170 xcodebuild[48219:6514151] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2020-12-05_19-20-21.169.xcdistributionlogs'.
error: exportArchive: App Store Connect Operation Error. Invalid Bundle. The bundle Runner.app/Frameworks/App.framework does not support the minimum OS Version specified in the Info.plist.

Error Domain=ITunesConnectionOperationErrorDomain Code=1091 "error: App Store Connect Operation Error. Invalid Bundle. The bundle Runner.app/Frameworks/App.framework does not support the minimum OS Version specified in the Info.plist." UserInfo={NSLocalizedDescription=error: App Store Connect Operation Error. Invalid Bundle. The bundle Runner.app/Frameworks/App.framework does not support the minimum OS Version specified in the Info.plist.}

** EXPORT FAILED **

Issueの対処通り、ios/Flutter/AppFrameworkInfo.plistMinimumOSVersionを8.0から9.0に変更します。

...
  <key>MinimumOSVersion</key>
  <string>9.0</string>
...

再度Validateコマンドを実行すると、Validated Runnerとなりエラーは解消されました。

$ xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist
2020-12-05 19:25:38.546 xcodebuild[49681:6526812] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2020-12-05_19-25-38.546.xcdistributionlogs'.
Validated Runner
** EXPORT SUCCEEDED **

しかし** EXPORT SUCCEEDED ** というログメッセージは微妙ですね...。

この状態で作成したipaファイルはApp Store Connectへのアップロードも成功しました!

f:id:takamii228:20201205193405p:plain

このValifatedeは今回のようなバイナリ設定のエラーの他に、バージョン番号の間違いも検出できます。

$ xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist
2020-12-05 17:02:54.174 xcodebuild[21868:6252939] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2020-12-05_17-02-54.173.xcdistributionlogs'.
error: exportArchive: App Store Connect Operation Error. Redundant Binary Upload. You've already uploaded a build with build number '3' for version number '1.0.0'. Make sure you increment the build string before you upload your app to App Store Connect. Learn more in Xcode Help (http://help.apple.com/xcode/mac/current/#/devba7f53ad4).

Error Domain=ITunesConnectionOperationErrorDomain Code=1091 "error: App Store Connect Operation Error. Redundant Binary Upload. You've already uploaded a build with build number '1' for version number '1.0.0'. Make sure you increment the build string before you upload your app to App Store Connect. Learn more in Xcode Help (http://help.apple.com/xcode/mac/current/#/devba7f53ad4)." UserInfo={NSLocalizedDescription=error: App Store Connect Operation Error. Redundant Binary Upload. You've already uploaded a build with build number '1' for version number '1.0.0'. Make sure you increment the build string before you upload your app to App Store Connect. Learn more in Xcode Help (http://help.apple.com/xcode/mac/current/#/devba7f53ad4).}

** EXPORT FAILED **

なお今回のエラーの発生条件は1.17.5から1.22.0へのバージョンアップです。1.22.0の状態でプロジェクトを新規に作成すると、AppFramework.plistのMinimumOSVersionの値は9.0となっており元のエラーは発生しませんでした。

また最新の1.22.4では関連するエラーに関するパッチがあたっており、1.17.5から1.22.4へのバージョンアップでは上記のエラーは発生しないようになっていました。

該当のPRは以下になるようです。

github.com

github.com

また、Flutterのドキュメント側にも修正がはいったようで、Deployment Targetを修正する場合はAppFramework.plistのMinimumOSVersionの値と揃えるようにするように追記されました。

Build and release an iOS app - Flutter

validate処理を加えたFlutterアプリのipa作成スクリプト

以前作成したFlutterでipaファイルを作成するスクリプトにvalidate処理を足すと以下のようになります。

#!/bin/sh
set -x
set -e

# Artifacts Path
OUTPUT_APK_PATH="./artifacts/ios"

# TARGET_APP_VERSION
TARGET_APP_VERSION=${1}

# TARGET_BUILD_NO
TARGET_BUILD_NO=${2}

# cleanを実行
flutter clean

# 依存関係を解決
flutter pub get

# flutter buildを実行
flutter build ios --release \
  --build-name=${TARGET_APP_VERSION} --build-number=${TARGET_BUILD_NO}

cd ios

# archive を作成
xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos \
  -configuration Release archive -archivePath ../build/Runner.xcarchive

# 作成するipaファイルをvalidateする
xcodebuild -exportArchive -archivePath ../build/Runner.xcarchive \
       -exportOptionsPlist ExportOptionsProdValidate.plist

# ipa fileを作成
xcodebuild -allowProvisioningUpdates -exportArchive \
  -archivePath ../build/Runner.xcarchive \
  -exportOptionsPlist ExportOptionsProd.plist -exportPath ../build/ios-release

# 成果物をコピー
cp ./build/ios-release/Runner.ipa ${OUTPUT_APK_PATH}/app-release-${TARGET_APP_VERSION}.${TARGET_BUILD_NO}.ipa

まとめ

CDパイプラインの中での利用を想定してxcodebuildコマンド経由でValidate Appする方法をFlutter製のアプリを例にまとめました。

XcodeApp Store Connectへのアップロードを実行しているプロジェクトではあまり恩恵はないですが、ipaの作成とApp Store Connectへのアップロードの運用が別れているようなプロジェクトでは資材を引き渡した後のアップロードエラーの手戻りを未然に防げるのではないでしょうか。

フレームワークのバージョンアップや開発ツールの更新を行った時は今まで動いていた仕組みが変わって思わぬところでエラーが発生するケースがあるため、それを事前に検知できるような仕組みづくりもトライアンドエラーにはなりますが、次は同じ轍を踏まないように毎回改善を繰り返しておくとよいでしょう。