つばくろぐ @takamii228

知は力なり

flutter build ipaコマンドを使ってみた

flutter 2.0からflutter build ipaコマンドが利用できるようになったそうなので今更ながら試してみました。

1.22以前のipaのビルド方法

flutterの1.22以前の場合はflutter build iosコマンドを実行後に、xcodebuildコマンドでarchiveビルドを実行してexport ipaを実行する必要がありました。

flutterコマンドにもflutter build ipaはありません。

$ flutter --version
Flutter 1.22.6 • channel unknown • unknown source
Framework • revision 9b2d32b605 (5 months ago) • 2021-01-22 14:36:39 -0800
Engine • revision 2f0af37152
Tools • Dart 2.10.5
$ flutter build --help
Flutter build commands.

Global options:
-h, --help                  Print this usage information.
-v, --verbose               Noisy logging, including all shell commands executed.
                            If used with --help, shows hidden options.
-d, --device-id             Target device id or name (prefixes allowed).
    --version               Reports the version of this tool.
    --suppress-analytics    Suppress analytics reporting when this command runs.
    --packages              Path to your ".packages" file.
                            (required, since the current directory does not contain a ".packages"
                            file)

Usage: flutter build <subcommand> [arguments]
-h, --help    Print this usage information.

Available subcommands:
  aar             Build a repository containing an AAR and a POM file.
  apk             Build an Android APK file from your app.
  appbundle       Build an Android App Bundle file from your app.
  bundle          Build the Flutter assets directory from your app.
  ios             Build an iOS application bundle (Mac OS X host only).
  ios-framework   Produces a .framework directory for a Flutter module and its plugins for
                  integration into existing, plain Xcode projects.

Run "flutter help" to see global options.

ipaを作成するコマンドは以前はiosフォルダへ移動してxcodebuildコマンドを実行する必要がありました。

$ flutter build ios --release
$ cd ios
$ xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos \
  -configuration Release archive -archivePath ../build/Runner.xcarchive
$ xcodebuild -allowProvisioningUpdates -exportArchive \
  -archivePath ../build/Runner.xcarchive \
  -exportOptionsPlist ExportOptions.plist -exportPath ../build/ios-release

こらのコマンドの詳細はこちらの記事にまとめています。

takamii.hatenablog.com

flutter build ipaコマンドを試してみる

flutterのバージョンを最新化して新しいコマンドを試してみましょう。

まずはflutterを最新のstableに更新します。

$ flutter --version
Flutter 2.2.3 • channel unknown • unknown source
Framework • revision f4abaa0735 (9 hours ago) • 2021-07-01 12:46:11 -0700
Engine • revision 241c87ad80
Tools • Dart 2.13.4

flutter buildを見てみるとipaオプションがあることがわかります。

$ flutter build --help
Build an executable app or install bundle.

Global options:
-h, --help                  Print this usage information.
-v, --verbose               Noisy logging, including all shell commands executed.
                            If used with "--help", shows hidden options. If used with "flutter
                            doctor", shows additional diagnostic information. (Use "-vv" to force
                            verbose logging in those cases.)
-d, --device-id             Target device id or name (prefixes allowed).
    --version               Reports the version of this tool.
    --suppress-analytics    Suppress analytics reporting when this command runs.

Usage: flutter build <subcommand> [arguments]
-h, --help    Print this usage information.

Available subcommands:
  aar             Build a repository containing an AAR and a POM file.
  apk             Build an Android APK file from your app.
  appbundle       Build an Android App Bundle file from your app.
  bundle          Build the Flutter assets directory from your app.
  ios             Build an iOS application bundle (Mac OS X host only).
  ios-framework   Produces .xcframeworks for a Flutter project and its plugins for integration into
                  existing, plain Xcode projects.
  ipa             Build an iOS archive bundle (Mac OS X host only).
  web             Build a web application bundle.

Run "flutter help" to see global options.

また公式ドキュメントを見ると、--export-options-plistオプションでExportOptionを指定できるようです。

flutter.dev

次にflutterの新規プロジェクトを作ってBundle Identiferや証明書、プロビジョニングプロファイルを設定して開発用と配布用両方でビルドコマンドを試してみましょう。

開発用証明書でipaファイルをビルドする

まずは開発用のExportOptions.plistを用意します。ExportOptions.plistはipaを作成するときに毎回作成されているのでそれを参考にするとよいでしょう。

<?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>compileBitcode</key>
    <true/>
    <key>destination</key>
    <string>export</string>
    <key>method</key>
    <string>development</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>com.takamiii.flutter.ipa</key>
        <string>flutteripa</string>
    </dict>
    <key>signingCertificate</key>
    <string>xxxxxxxxxxxxxx</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>xxxxxxxxxx</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

flutter buildコマンドを実行してみます。

$ flutter build ipa --release --export-options-plist="ios/ExportOptionsDev.plist" 
Archiving com.takamiii.flutter.ipa...
Automatically signing iOS for device deployment using specified development team in Xcode project: xxxxxxxxxx
Running Xcode build...                                                  
 └─Compiling, linking and signing...                         4.6s
Xcode archive done.                                         30.5s
Built /Users/takami228/git/flutteripa/build/ios/archive/Runner.xcarchive.

💪 Building with sound null safety 💪

Building IPA...                                                     7.2s
Built IPA to /Users/takami228/git/flutteripa/build/ios/ipa.

無事build配下にipaファイルが作成されました。実機へのインストールや動作確認も問題ありません。

$ tree build/ios/ipa
build/ios/ipa
├── DistributionSummary.plist
├── ExportOptions.plist
├── Packaging.log
└── flutteripa.ipa

0 directories, 4 files

具体的に何をやっているか--verboseオプションで詳しく見てみましょう。

$ flutter build ipa --release --export-options-plist="ios/ExportOptionsDev.plist" --verbose
[  +92 ms] executing: sysctl hw.optional.arm64
[  +17 ms] Exit code 1 from: sysctl hw.optional.arm64
[        ] sysctl: unknown oid 'hw.optional.arm64'
[   +6 ms] executing: [/Users/takami228/development/flutter/] git -c log.showSignature=false log -n 1 --pretty=format:%H
[  +14 ms] Exit code 0 from: git -c log.showSignature=false log -n 1 --pretty=format:%H
...
[   +4 ms] executing: [/Users/takami228/git/flutteripa/ios/Runner.xcodeproj/] xcrun xcodebuild -project /Users/takami228/git/flutteripa/ios/Runner.xcodeproj -scheme Runner -showBuildSettings
...
[   +1 ms] executing: [/Users/takami228/git/flutteripa/ios/] xcrun xcodebuild -configuration Release VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos SCRIPT_OUTPUT_STREAM_FILE=/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/flutter_tools.soPA6m/flutter_build_log_pipe.ylKQtK/pipe_to_stdout FLUTTER_SUPPRESS_ANALYTICS=true COMPILER_INDEX_STORE_ENABLE=NO -archivePath /Users/takami228/git/flutteripa/build/ios/archive/Runner archive
...
[   +4 ms] Built /Users/takami228/git/flutteripa/build/ios/archive/Runner.xcarchive.
[   +3 ms] 💪 Building with sound null safety 💪
[        ] Building IPA...
[        ] executing: xcrun xcodebuild -exportArchive -archivePath /Users/takami228/git/flutteripa/build/ios/archive/Runner.xcarchive -exportPath /Users/takami228/git/flutteripa/build/ios/ipa -exportOptionsPlist /Users/takami228/git/flutteripa/ios/ExportOptionsDev.plist
[+7685 ms] Exported Runner to: /Users/takami228/git/flutteripa/build/ios/ipa
           ** EXPORT SUCCEEDED **


           2021-07-02 14:59:27.762 xcodebuild[16660:579191] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2021-07-02_14-59-27.762.xcdistributionlogs'.
...

中ではxcodebuildのarchiveとexportArchiveを実行してますね、納得です。

配布用証明書でipaファイルをビルドする

続いて配布用のExportOptionでも試してみます。

<?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>export</string>
    <key>method</key>
    <string>app-store</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>com.takamiii.flutter.ipa</key>
        <string>flutteripaprod</string>
    </dict>
    <key>signingCertificate</key>
    <string>xxxxxxxxxxxxxx</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>xxxxxxxxxx</string>
    <key>uploadBitcode</key>
    <false/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

こちらも無事ビルドできました。

$ flutter build ipa --release --export-options-plist="ios/ExportOptionsProd.plist"
Archiving com.takamiii.flutter.ipa...
Automatically signing iOS for device deployment using specified development team in Xcode project: xxxxxxxxxx
Running Xcode build...                                                  
 └─Compiling, linking and signing...                         4.4s
Xcode archive done.                                         31.2s
Built /Users/takami228/git/flutteripa/build/ios/archive/Runner.xcarchive.

💪 Building with sound null safety 💪

Building IPA...                                                    13.2s
Built IPA to /Users/takami228/git/flutteripa/build/ios/ipa.

App Store Connectへそのままアップロードする

最後に配布用ビルドでそのままApp Store Connectへアップロードするようにdestinationをupload`にして実行してみましょう。

<?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>
    <key>method</key>
...

なおExportOptionの値の詳細については以下の記事を参考にするとよいです。

qiita.com

変更したplistファイルで実行してみます。

$ flutter build ipa --release --export-options-plist="ios/ExportOptionsProd.plist"
Archiving com.takamiii.flutter.ipa...
Automatically signing iOS for device deployment using specified development team in Xcode project: xxxxxxxxxx
Running Xcode build...                                                  
 └─Compiling, linking and signing...                         4.8s
Xcode archive done.                                         30.8s
Built /Users/takami228/git/flutteripa/build/ios/archive/Runner.xcarchive.

💪 Building with sound null safety 💪

Building IPA...                                                    82.1s
Built IPA to /Users/takami228/git/flutteripa/build/ios/ipa.

無事アップロードが成功しました。

しばらく時間をおいてApp Store Connectを見てみるとアップロードできていることがわかります。

f:id:takamii228:20210702145120p:plain

TestFlightで内部テスト配信を設定すると無事実機端末で動作確認ができました。

f:id:takamii228:20210702145336p:plainf:id:takamii228:20210702145347p:plain

まとめ

flutter build ipaコマンドでipaファイルの作成やApp Store Connectへのアップロードまでが自動化できることを確認できました。今までxcodebuildコマンドを実行していた部分が省略できるのはメリットだと思います。一方で証明書やxcodebuild周りの知識がないとエラーになったときに対処できないので、中の仕組みを理解した上で利用するとよいでしょう。

補足

公式ドキュメントからはflutter build iosコマンドとxcodebuildコマンドを組み合わせるやり方は削除されていますが、flutter 2.0以降でも以前の手順で問題なく利用できます。

Firebase App DistributionがApp Bundleに対応したらしいので試してみた(失敗)

先日のGoogle I/OでFirebase App DistributionでのAndoirdアプリの配信でApp Bundle形式での配信に対応したとの発表がありました。今まではapkファイル形式での配信に限られていました。

firebase.googleblog.com

ちなみにApp Bundleへの対応は2021年の8月以降の新規アプリでは必須の対応となっています。すでにapk形式で配信しているアプリはapk形式での配信は継続できます。

android-developers.googleblog.com

これからはApp Bundleが主流になっていくと思われるので、早速試してみます。

Firebaseのプロジェクトを作成する

Firebase App Distributionを使うFirebaseプロジェクトを作成します。

詳細については以前のブログへの参照としてスキップします。

takamii.hatenablog.com

App Bundleファイルを作成する

今回はAndroid Studioで新規プロジェクトをBasic Activityで作成したものをそのまま使います。

App Bundleファイルの作成はGradle taskのbundleタスクを実行すればよいです。

f:id:takamii228:20210626140835p:plain

なおAndroid Studio 4.2からGradle TaskがGradleのビューに出なくなってますが、設定で表示するように変更ができます。

stackoverflow.com

ターミナルで実行する場合は以下のコマンドを実行すればよいです。自分でproduct flavorを定義している場合はbunlde${xxxx}の形になります。

$ ./gradlew bundle  

実行するとapp-debug.aabapp-release.aabというファイルがapp/build配下に生成されます。

$ tree app/build/outputs/bundle 
app/build/outputs/bundle
├── debug
│   └── app-debug.aab
└── release
    └── app-release.aab

2 directories, 2 files

Firebase App Distributionにアップロードする

生成したファイルをFirebase App Distributionへアップロードしてみます。

アップロード画面に行くと以下の注意書きが出てきました。

f:id:takamii228:20210626145303p:plain

どうやら開発者登録済みのアカウントでのGoogle Playへのリンクが必要なようです。

f:id:takamii228:20210626145413p:plain

開発者登録したアカウントで連携すると無事リンクができましたが、下の方のApp Linkのstatusがオンになりません。

f:id:takamii228:20210626145554p:plain

ドキュメントをよく読んでみると、以下の記述がありました。

・The app you upload to Google Play and Firebase must share the same package name.

・The app in Google Play must be in a published state. An app is published when its app store listing has been completed and approved, and the app has been distributed to a following Google Play track: internal, closed, production, or open-testing.

日本語版は以下です。

Google Play と Firebase にアップロードするアプリのパッケージ名が同じであること。

Google Play のアプリが公開中であること。「公開中」とは、そのアプリのストアの掲載情報が完成して承認されており、かつそのアプリが Google Play のトラック(内部テスト、クローズド テスト、本番環境テスト、オープンテスト)に配布されていることを言います。

firebase.google.com

どうやらGoogle Playで公開済みのアプリでないと利用できないようです。ぐぬぬー。

残念ながら私は配信しているアプリを持っていないので検証はここまでしかできませんでした。

Google Play周りの機能を利用するのでGoogle Play側でいろいろ整ってる必要があるようです。なので未公開のアプリを開発版でテスト配信に使う場合はまだまだapkでやる必要がありそうです。

もし業務で利用できる機会があったらまた試してみようと思います。

AndroidのCIパイプラインでAndroid StudioのJDKを使う

AndroidのCIパイプラインでgradleタスク実行する場合にはJDKが必要になります。

Android Studio経由でgradleタスク実行する場合はAndroid Studioに内包されたJDKが用いられます。

一方でmacOSのTerminalでプロジェクト作成時にimportされるgralde wrapper経由でgradleタスクを実行すると、利用するJDKmacOSのものになります。

例えばCIパイプラインをmacOSのCI Runnerで実行する場合、何も設定しないとmacOS側のJDKが用いられてします。macOS側ではJava14、15を入れていた場合にはAndroid Studioで実行した結果と変わってしまう可能性があります。

例えばmacOSのTerminalでの./gradlew --versionの結果とAndroid Studio側での実行結果が微妙に異なっています。

# macOSでの./gradlew --versionの結果
$ ./gradlew --version

------------------------------------------------------------
Gradle 6.7.1
------------------------------------------------------------

Build time:   2020-11-16 17:09:24 UTC
Revision:     2972ff02f3210d2ceed2f1ea880f026acfbab5c0

Kotlin:       1.3.72
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM:          11.0.10 (Oracle Corporation 11.0.10+9)
OS:           Mac OS X 11.3.1 x86_64

# Android StudioのJREでの./gradlew --versionの結果
$ ./gradlew --version                                                        

------------------------------------------------------------
Gradle 6.7.1
------------------------------------------------------------

Build time:   2020-11-16 17:09:24 UTC
Revision:     2972ff02f3210d2ceed2f1ea880f026acfbab5c0

Kotlin:       1.3.72
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM:          11.0.8 (JetBrains s.r.o 11.0.8+10-b944.6916264)
OS:           Mac OS X 10.16 x86_64

ちなみにAndroid Studio 4.2からBundleされるJDKが11系に変わったようです。

developer.android.com

build.gradleで利用するバージョンをきちんと指定していれば問題ないのですが、気になる場合はAndroid StudioJDKでgralde タスクを実行するようにしておきましょう。

具体的にはgradleタスクを実行する前にJAVA_HOMEの設定をAndroid Studioが内包しているものに変更すればよいです。

例えばCIパイプラインを実行するシェルスクリプトの最初にJAVA_HOMEを入れ替えるようにするとよいでしょう。

macOSの場合、Android StudioJDK/Applications/Android Studio.app/Contents/jre/jdk/Contents/Homeにあります。

!/bin/sh

set -e
set -x

...
JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"

./gradlew --version

./gradlew lint

./gradlew test

./gradlew connectedAndroidTest