📄

Flutter で fastlane を使って ipa, apk, aab 作成を行う

2021/07/21に公開

目的

CI・CD を行うにあたり、iOS・Android 共に同じツールを使用しておけば何かと設定を省けたりとメリットがあると思います。Android は Gradle である程度できてしまう部分もありますが、ここでは iOS・Android 共に fastlane を使用して Flutter を使用して作成されたプロジェクト配下の iOS と Android からそれぞれ .ipa や .aab, .apk を書き出しまでを行なってみようと思います。

開発環境

  • Flutter 2.2.1
  • Xcode 12.4
  • AndroidStudio 4.1.3

手順

手順途中で rbenv が出てきます。未インストールの場合はインストールしておいてください。

  1. Flutter プロジェクトを作成します(未作成の場合)
  2. Flutter Flavorizr で開発環境を(例:Development, Staging, Production)に分けます。(以前書いたFlutter で環境を分ける方法を参考にしてください。)
  3. Flutter プロジェクト直下で Ruby のバージョンを固定します
rbenv local x.x.x
  1. Flutter プロジェクト直下で以下を実行し bundler をインストールします
gem install bundler
  1. Flutter プロジェクト直下で以下を実行し、Gemfile を作成します
bundle init
  1. 作成された Gemfile の一番下に以下を追加して fastlane を明記します。
gem 'fastlane'
  1. 以下コマンドで fastlane のインストール
# ローカル環境(≒Flutterプロジェクト内)で gem をインストールする先を指定するよう設定
bundle config --local path vendor/bundle
# Gemfile に記載された gem をインストール
bundle install

# 以下はオプション
# ./vendor/cache へ gem のロックとキャッシュを行う
bundle package
# rubygems.orgへの接続なしで作成したキャッシュ(./vendor/cache)から gem をインストールする場合
bundle install --local
  1. ios or android ディレクトリに移動し、以下で fastlane を初期化します。実行すると自動設定するか、手動設定するか選択肢が表示されるので任意で選択して下さい(本記事では手動を選択を前提に進めます)
cd ios
bundle exec fastlane init
cd android
bundle exec fastlane init
  1. fastlane の初期化を行うとそれぞれのディレクトリ配下に fastlane ディレクトリが作成されます。その中に Appfile と Fastfile が作成されているはずです。Appfile はアプリの構成情報(BundleIDやパッケージ名、AppleID, Playストア更新のための JSON など)を記述するファイルです。Fastfile が実際に fastlane の動作を定義するファイルです。

作成された ios/fastlane/Fastfile

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
  desc "Description of what the lane does"
  lane :custom_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end

作成された android/fastlane/Fastfile

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:android)

platform :android do
  desc "Runs all the tests"
  lane :test do
    gradle(task: "test")
  end

  desc "Submit a new Beta Build to Crashlytics Beta"
  lane :beta do
    gradle(task: "clean assembleRelease")
    crashlytics
  
    # sh "your_script.sh"
    # You can also use other beta testing services here
  end

  desc "Deploy a new version to the Google Play"
  lane :deploy do
    gradle(task: "clean assembleRelease")
    upload_to_play_store
  end
end
  1. Fastfile を編集します

以下の iOS の Fastfile は private 宣言している build レーン で fastlane のアクションである gym を用いて指定した環境の .ipa 作成を行う例になります。 options で宣言した build_type では Debug/Release のどちらかを指定し、scheme では分けた環境に応じたもの(以下例では development/staging/production のいずれか)を指定するようにしています。export_methodgym のパラメータそのものなので環境に応じた適切なものを指定します。

なお、Provisioning Profile の設定は Xcode 上で行うことを想定しているものとなります。 gym の方で export_optionsprovisioningProfiles で bundleID と対応する Provisioning Profile 名を指定することでも上記の設定は行えますが、うまくいかなかったので Xcode 上で設定しました。

ios/fastlane/Fastfile

default_platform(:ios)

platform :ios do
  desc "development build"
  lane :development do
    build(
      build_type: "Release",
      scheme: "development",
      export_method: "development"
    )
  end

  desc "staging build"
  lane :staging do
    build(
      build_type: "Release",
      scheme: "staging",
      export_method: "adhoc"
    )
  end

  desc "production build"
  lane :production do
    build(
      build_type: "Release",
      scheme: "production",
      export_method: "app-store"
    )
  end

  desc "build lane"
  private_lane :build do |options|
    configuration = [
      options[:build_type].capitalize,
      options[:scheme]
    ].join("-")
    output_name = [
      options[:scheme],
      options[:build_type].capitalize
    ].join

    gym(
      workspace: "Runner.xcworkspace",
      configuration: configuration,
      scheme: options[:scheme],
      clean: true,
      export_method: options[:export_method],
      output_directory: "output",
      output_name: output_name,
    )
  end
end

Android の Fastfile は Flutter のビルドコマンドを直接実行するようにしました。(経緯は補足を参照してください。)この .apk と .aab の作成ぐらいでは fastlane を使用するメリットがあまりないですが、例えば fastlane アクションの supply を使用することで作成された .apk や .aab を Google Playストアへアップロードするまでを行うことも可能となります。なお、以下 Fastfile を使用するにあたり署名周りの設定で app/build.gradle を多少なり変更しています。こちらも詳細は補足を参照してください。

android/fastlane/Fastfile

default_platform(:android)

platform :android do

  desc "development build apk and aab"
  lane :development do
    flutter_build(
      build_type: "release",
      flavor: "development",
      target: "lib/main-development.dart"
    )
  end

  desc "staging build apk and aab"
  lane :staging do
    flutter_build(
      build_type: "release",
      flavor: "staging",
      target: "lib/main-staging.dart"
    )
  end

  desc "production build apk and aab"
  lane :production do
    flutter_build(
      build_type: "release",
      flavor: "production",
      target: "lib/main-production.dart"
    )
  end

  desc "flutter build lane"
  private_lane :flutter_build do |options|
    target = options[:target]
    build_type = ["--", options[:build_type].downcase].join
    flavor = options[:flavor].downcase

    gradle(task: "clean")
    sh("fvm", "flutter", "build", "apk", build_type, "--flavor", flavor, "--target", target)
    sh("fvm", "flutter", "build", "appbundle", build_type, "--flavor", flavor, "--target", target)
  end
end
  1. Flutter でビルドします(iOSのみ)
flutter build ios --release --no-codesign --flavor [flavor名]

Flutter Flavorizr を使用して環境分けをしている場合、iOS では Flutter Flavorizr で作成された各環境の .xcconfig の FLUTTER_TARGETmain-[flavor名].dart が設定されているため、build コマンドでの target 指定は不要です(ビルドコマンド自体を Fastfile に追記して自動化しても良いかもしれません)。Android は fastlane で Flutter のビルドコマンドを直接実行しているため、ビルドは不要です。

  1. fastlane を実行します
cd ios
bundle exec fastlane [lane名]

cd android
bundle exec fastlane [lane名]
  1. iOS であれば Fastfile で出力先に指定したディレクトリに .ipa が作成されていれば成功です。Android であれば、Flutter プロジェクト配下に build ディレクトリがあり、その中の app/outputs/apk と app/outputs/bundle に .apk と .aab が作成されていれば成功です。

補足

1. fastlane で gradle を実行した時に Flutter の target が正しく設定されない

Flutter Flavorizr を使用して環境分けを行なった Flutter プロジェクト配下の Android プロジェクトをまずは以下コマンドでビルドしました。

flutter build apk --release --flavor [flavor名] --target lib/main-[flavor名].dart

その後、以下 Fastfile(一部)を用いて fastlane で ビルドしました。その結果 target が正しく設定されず、デフォルトの lib/main.dart が使用された .apk が作成されていました。flutter buildtarget を指定して作成された .apk は正しい lib/main-[flavor名].dart が使用されていました。また、app/build.gradle の方で Flutter Flavorizr で作成された productFlavors のそれぞれの環境に project.flutter.target = "lib/main-[flavor名].dart" を追記し、target の指定をしても全て lin/main-production.dart が使用されるといった結果でした。

default_platform(:android)

platform :android do

  desc "development build apk and aab"
  lane :development do
    build(
      flavor: "development",
      build_type: "Release"
    )
  end

  ...

  desc "build lane"
  private_lane :build do |options|
    assembleTaskName = [
      "assemble",
      options[:flavor].capitalize,
      options[:build_type].capitalize
    ].join
    bundleTaskName = [
      "app:bundle",
      options[:flavor].capitalize,
      options[:build_type].capitalize
    ].join
    gradle(task: "clean")
    gradle(
      tasks: [assembleTaskName, bundleTaskName]
    )
  end
end

2. fastlane から Android の署名を書き換えができない

上記に関連して Android の Fastfile で properties パラメータの android.injected.signing.store.file などを使用して署名を書き換えようとすると /android/app と /android のどちらにも .jks を配置しないと .jks が存在しないというエラーが発生し、ビルドできなかったです。(以下に Issue としても挙げられていました。)そのため、Android Developers のビルドファイルから署名情報を削除する を参考に署名設定をするにあたり build.gradle の変更を行いました。

https://github.com/fastlane/fastlane/issues/15726

app/build.gradle(一部)

...
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

// ---追記----
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {

    // ----- BEGIN flavorDimensions (autogenerated by flutter_flavorizr) -----
    flavorDimensions "flavor-type"

    productFlavors {
        development {
            ...
        }
        staging {
            ...
        }
        production {
            ...
        }
    }

    // ----- END flavorDimensions (autogenerated by flutter_flavorizr) -----

    compileSdkVersion 30

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        ...
    }

    // ---追記---
    signingConfigs {
        debug {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }

        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            signingConfig signingConfigs.release
        }
    }

    // apk ビルドで Execution failed for task ':app:lintVitalRelease' が発生するので追記
    // https://github.com/flutter/flutter/issues/30598#issuecomment-622926226
    lintOptions {
        disable 'InvalidPackage'
        checkReleaseBuilds false
    }
}

まとめ

fastlane で iOS・Android それぞれの .ipa, .apk, .aab の作成までの手順を紹介しました。これらの作成までであれば効果は薄いかもしれませんが、fastlane をもう少し踏み込んで使用すると各ストアへのアップロードまでも自動化できます。また、公式にもあるように fastlane と既存のツール(GitHub Actions や CircleCI など)と組み合わせて CI・CD のセットアップも可能です。活用して自動化してみてはいかかでしょうか。

参考

https://flutter.dev/docs/deployment/cd
https://docs.fastlane.tools/
https://qiita.com/sekitaka_1214/items/9af55223086f29a7d127

Discussion