CircleCI 2.1 と fastlane で Flutter の iOS と Android をビルドして DeployGate で配布する


3行で

  • 会社のCI環境をCircleCIで統一しているため、BitriseやCodemagicではなくCircleCIでやりたかった
  • AndroidはDockerイメージで楽に環境構築できるが、iOSは色々対応が必要です
  • AndroidとiOSでビルドする手順に違いはあるが、CircleCI2.1の機能でうまく対応できました

config.yml と Fastfile の完成形

 今回は、リリースビルドで開発環境向け(Release-Development)のアプリを配布する処理を例にして書きます。Release-Developmentについては、 @mono0926さんのFlutterで環境ごとにビルド設定を切り替える — iOS編
を参考にしてください。

 少々長いのでGistにしました。これらのファイルを定義することで、タイトルどおりのCI環境が構築できるはずです。

~/.circleci/config.yml

~/Android/fastlane/Fastfile

~/ios/fastlane/Fastfile

~/ios/fastlane/Matchfile
# 証明書を保存するリポジトリを用意してください
git_url("[email protected]:example/example-ios-cert.git")
git_branch("master")

storage_mode("git")

type("development")

github.com/example-fastlane/util/Fastfile

 AndroidとiOSの共通変数や共通メソッドは、別リポジトリのFastfileをインポートして利用しています。実際のFastfileはもう少し分割していますが、今回は例なので一つにまとめています。

 別リポジトリのFastfileをインポートすることについては、@star__hoshiさんの Fastfile の import 機能を使い、複数のプロジェクトで共通の Fastfile を使う を参考にしてください。

 なお、flutter build コマンドはこのFastfileに定義しており、それぞれのFastfileから引数でパラメータを受け取り、ビルドモードとflavorとtargetを指定しています。iOSに限り、 --no-codesign にして、後から fastlane match で管理している証明書を差し込めるようにしています。

 ローカルとCIの両方の環境で実行できるように is_ci? で環境を判定しています。CI環境の場合に $FLUTTER_HOME/bin にしているのは、Androidで利用するDockerイメージのFlutterSDKのPATHが$FLUTTER_HOME になっているためです。

github.com/example-fastlane/util/Fastfile
# Flutter App をビルドする
private_lane :build_flutter_in_util do |options|

  codesign = options[:platform] == "ios" ? "--no-codesign" : ""

  if is_ci?
    sh("cd ../../ && $FLUTTER_HOME/bin/flutter build #{options[:platform]} --release --flavor #{options[:flavor]} --target #{options[:target]} #{codesign}")
  else
    sh("cd ../../ && flutter build #{options[:platform]} --release --flavor #{options[:flavor]} --target #{options[:target]} #{codesign}")
  end
end

CircleCI 2.1で Flutterのアプリをビルドする

 CircleCI 2.1の機能を有効にするには、CircleCIのプロジェクトの設定画面で Settings -> Advanced SettingsEnable build processing (preview)On にしてください。

 有効にすると、 executorscommandsparameters の機能が利用できます。簡単に説明すると、 executors は環境、 commands は処理、 parameters は変数を任意の組み合わせで定義できる機能です。

 他にもCircleCI 2.0の機能ですが、 working_directory: で処理ごとに実行するディレクトリを指定できます。Flutterプロジェクトはプラットフォームごとにスクリプトを実行するディレクトリが違うので、 working_directory: はとても便利です。

~/.circleci/config.yml
  setup_ios_build_setting:
    steps:
      - run:
         name: Run pod setup from cocoapods-specs.circleci.com
         command: curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
         working_directory: ~/flutterApp/ios

Androidをビルドする場合

 Androidのビルドに必要な環境は、 image: cirrusci/flutter:latest に用意されています。FlutterとAndroidのSDKがインストールされてPATHも通っているので、CircleCI側ではクローンして、 flutter doctorflutter analyze の後にビルドしてDeployGateに配信するだけです。とても楽です。

~/.circleci/config.yml
  beta_development_android:
    executor:
      name: default_android
    steps:
      - checkout
      - setup_flutter
      - setup_bundle:
          platform: android
      - flutter_build:
          platform: android
          configuration: development

iOSをビルドする場合

 iOSのビルドには macOSが必要なので、CircleCIはmacOSの環境を選択してください(有料です)。先ほど、Androidのビルドに利用したDockerイメージは、Linuxなので証明書でビルドが失敗します。したがって、iOSではFlutterのSDKをクローンしてPATHを通す必要があります。

~/.circleci/config.yml
  install_flutter:
    steps:
      - run:
          name: Install flutter SDK
          command: mkdir -p ~/sdks/flutter && git clone -b stable https://github.com/flutter/flutter.git ~/sdks/flutter
      - run:
          name: Set flutter SDK PATH in bash
          command: echo 'export FLUTTER_HOME=~/sdks/flutter' >> $BASH_ENV && source $BASH_ENV

CircleCI 2.1の新機能で実行環境を別にして処理を共通化

 AndroidとiOSの実行環境を別に定義したいので、executors を使います。 iOSでは、 executor:default_ios を指定したので、Dockerイメージではなく、CircleCIのmacOSをつかってビルドします。

 commands: の機能で、flutter doctorflutter analyzeの処理は共通化できますし、 parameters: の機能で flutter_build: で必要な引数もプラットフォームごとにわけて渡せます。

~/.circleci/config.yml
  beta_development_ios:
    executor:
      name: default_ios
    steps:
      - checkout
      - install_flutter
      - setup_flutter
      - setup_bundle:
          platform: ios
      - setup_ios_build_setting
      - flutter_build:
          platform: ios
          configuration: development

disable_automatic_code_signing で証明書を設定

 iOSでは --no-codesign でビルドしたので、 fastlaneの disable_automatic_code_signing を呼んで手動で証明書を設定します。このような手動の管理が面倒だと思う方は、 BitriseCodemagicを検討すると良いです。

 options[:app_configs] が配列なのは、全てのターゲットに一度に証明書を設定可能にするためです。iOSでは複数のターゲットをもつアプリが存在するので、このようにしておくと楽なのですが、今回の例では不要なループ処理です(消すのが面倒でした )。

github.com/example-fastlane/util/Fastfile

# Code Signing Style を Manual に変更
private_lane :change_manual_code_signing_style_in_util do |options|

  code_sign_identity = ""
  profile_name_prefix = ""

  if options[:mode] == "development"
    code_sign_identity = ENV["CERT_DEVELOPER_ID"]
    profile_name_prefix = "match Development"
  end

  options[:app_configs].each { |app_config|
    disable_automatic_code_signing(
      targets: app_config[:target],
      code_sign_identity: code_sign_identity,
      profile_name: "#{profile_name_prefix} #{app_config[:profile_name_app_id]}"
    )
  }
end

最後に

 モバイルアプリ開発でCI環境がない場合、アプリの検証サイクルを走らせるのが一気に面倒になります。できるだけ早くCI環境の構築は終わらせておき、開発に集中したいアプリエンジニアは多いと思います。特に私はそうです

 Flutterで開発するとき「CI環境どうしよう」と不安になっていた時期もありましたが、今の所は問題なく、ネイティブアプリの開発フローと変わらずに検証と開発ができています。さらに、本番・ステージング・開発など、環境別にアプリを分けて配信して、同じスマートフォンに複数のアプリをインストールさせることも可能です。

 あとは、DartとFlutterのスキルさえ上がれば・・・。Flutter開発がんばろう

参考