作っているアプリで実機検証をやりやすくすためにCIで自動でデプロイできるようにしたの記事化
今回はiOSアプリのみ、次回Androidアプリのデプロイ方法も記事にする予定
PR
使用
- アプリ: Flutter
- ビルド: fastlane
- CI: GitHub Actions
- デプロイ先: Firebase App Distribution
実装
①. Apple Developer で証明書を作成
まず、iOSアプリを配布するためにApple Developerでアプリの証明書を作成する必要があるので、以下のコマンドでprivate.key
とcsr.pem
を作成
$ openssl genrsa -out private.key 2048 $ openssl req -new -key private.key -out csr.pem
key作成時の入力は以下みたいな感じで自身の設定を入力
Country Name (2 letter code) [AU]:JP State or Province Name (full name) [Some-State]:Tokyo Locality Name (eg, city) []:Tokyo Organization Name (eg, company) [Internet Widgits Pty Ltd]:Your Company Name Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:Your Name Email Address []:your-email@example.com Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
Apple DeveloperにログインしてCertificatesを画面を開いて、+
をクリックして以下の通りに操作
- Apple Distributionを選択
- 以下の画像の画面で
csr.pem
をアップロード - 証明書をダウンロードして
distribution.cer
の名前で保存
②. p12 ファイルを作成 & インポート
以下のコマンドで①でダウンロードしたdistribution.cer
を元にp12ファイルを作成
$ openssl x509 -in distribution.cer -inform der -out distribution.pem $ openssl pkcs12 -legacy -export -out distribution.p12 -inkey private.key -in distribution.pem
※ 以下の記事の通り、legacy
のオプションを付けないと mac でパスワード認証ができないので注意
以下でパスワードを入力
Enter Export Password: Verifying - Enter Export Password:
作成されたdistribution.p12
をダブルクリックし、mac のキーチェーンにインストール
パスワードを求められるので、上記で入力したパスワードを入力、これで証明書をインポートできる
③. 配信用の証明書をプロビジョニングプロファイルを作成
Apple Developer にログインして、配信用の証明書をプロビジョニングプロファイルを作成
- Profiles にアクセスして、
+
をクリック - Ad Hocを選択
- App ID を選択
- Certificates で①で作成した証明書を選択
- プロビジョニングプロファイルをダウンロードして
adhok.mobileprovision
の名前で保存
④. Xcodeでプロビジョニングプロファイルが正しく作成されたか確認
以下のコマンドでFlutterアプリをXcodeで開く
$ open ios/Runner.xcworkspace
- Xcode でプロジェクトを開き、
Signing & Capabilities
を選択 - Automatically manage signingにチェックがついている場合は外す
- Provisioning Profileで③でダウンロードした
adhok.mobileprovision
を選択 - 以下の画像の通り表示されていればOK(認証できていない場合はエラーが表示される)
⑤. 作成したファイルをGitHub Actions の secrets に設定
②と③で作成した証明書とプロビジョニングプロファイルはデプロイ時に使用するので以下のコマンドでbase 64に変換してGitHub Actions の secrets に設定
$ base64 -i distribution.p12 | pbcopy $ base64 -i adhok.mobileprovision | pbcopy
⑥. fastlaneで配布用のビルドを作成 & Firebase App Distribution にデプロイ
以下のfastlaneのファイルを作成
■ dart/app/ios/fastlane/Fastfile
# Fastfile default_platform(:ios) platform :ios do desc "Build and distribute iOS app for Adhoc" lane :adhoc do p12_base64 = ENV['CERTIFICATES'] p12_password = ENV['CERTIFICATES_PASSWORD'] File.write("certificate.p12", Base64.decode64(p12_base64)) create_keychain( name: "build.keychain", password: "keeper", default_keychain: true, unlock: true, timeout: 36000, lock_when_sleeps: false ) `curl -OL https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer` import_certificate( keychain_name: "build.keychain", keychain_password: "keeper", certificate_path: "fastlane/AppleWWDRCAG3.cer", ) import_certificate( keychain_name: "build.keychain", keychain_password: "keeper", certificate_path: "fastlane/certificate.p12", certificate_password: p12_password ) current_time = Time.now.to_i increment_build_number( build_number: current_time, xcodeproj: "Runner.xcodeproj" ) build_app( workspace: "Runner.xcworkspace", scheme: "Runner", export_options: { method: "ad-hoc", provisioningProfiles: { "com.unicorn.stockkeeper" => "keeper-adhoc" }, signingStyle: "manual" }, clean: true, output_name: "app.ipa", output_directory: "build" ) # Firebase App Distributionにアップロード firebase_app_distribution( app: ENV['REVIEW_IOS_FIREBASE_APP_ID'], firebase_cli_token: ENV['REVIEW_FIREBASE_CLI_TOKEN'], ipa_path: "build/app.ipa", groups: "testers" ) delete_keychain(name: "build.keychain") end end
以下の部分で証明書作成 & キーチェーンにインポート
p12_base64 = ENV['CERTIFICATES'] p12_password = ENV['CERTIFICATES_PASSWORD'] File.write("certificate.p12", Base64.decode64(p12_base64)) create_keychain( name: "build.keychain", password: "keeper", default_keychain: true, unlock: true, timeout: 36000, lock_when_sleeps: false ) `curl -OL https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer` import_certificate( keychain_name: "build.keychain", keychain_password: "keeper", certificate_path: "fastlane/AppleWWDRCAG3.cer", ) import_certificate( keychain_name: "build.keychain", keychain_password: "keeper", certificate_path: "fastlane/certificate.p12", certificate_password: p12_password )
同じbuild_numberのアプリはデプロイできないので以下のコードで現在日時で更新
current_time = Time.now.to_i increment_build_number( build_number: current_time, xcodeproj: "Runner.xcodeproj" )
以下のコードでプロビジョニングプロファイルを指定してiOSアプリをビルド
build_app( workspace: "Runner.xcworkspace", scheme: "Runner", export_options: { method: "ad-hoc", provisioningProfiles: { "com.unicorn.stockkeeper" => "keeper-adhoc" }, signingStyle: "manual" }, clean: true, output_name: "app.ipa", output_directory: "build" )
最後に以下のコードでFirebase App Distributionにアップロード
firebase_app_distribution( app: ENV['REVIEW_IOS_FIREBASE_APP_ID'], firebase_cli_token: ENV['REVIEW_FIREBASE_CLI_TOKEN'], ipa_path: "build/app.ipa", groups: "testers" )
app
とfirebase_cli_token
の値は以下のページを参考に取得
⑥. GitHub Actionsでデプロイできるようにする
以下のyamlファイルを作成することでCI上でデプロイできる
■ .github/workflows/depoly_ios_app_review.yaml
name: レビュー環境のiOSアプリをFirebase App Distributionにデプロイする on: push: branches: - main paths: - "dart/app/**" env: REVIEW_IOS_FIREBASE_APP_ID: ${{ secrets.REVIEW_IOS_FIREBASE_APP_ID }} REVIEW_FIREBASE_CLI_TOKEN: ${{ secrets.REVIEW_FIREBASE_CLI_TOKEN }} CERTIFICATES: ${{ secrets.CERTIFICATES }} CERTIFICATES_PASSWORD: ${{ secrets.CERTIFICATES_PASSWORD }} REVIEW_PROVISIONING_PROFILE: ${{ secrets.REVIEW_PROVISIONING_PROFILE }} REVIEW_GOOGLE_SERVICE_INFO_PLIST: ${{ secrets.REVIEW_GOOGLE_SERVICE_INFO_PLIST }} REVIEW_DART_FIREBASE_OPTIONS: ${{ secrets.REVIEW_DART_FIREBASE_OPTIONS }} REVIEW_RUNNER_INFOPLIST: ${{ secrets.REVIEW_RUNNER_INFOPLIST }} jobs: deploy: runs-on: macos-latest defaults: run: working-directory: dart/app steps: - name: Checkout repository uses: "actions/checkout@v4" - name: Create GoogleService-Info.plist run: echo $REVIEW_GOOGLE_SERVICE_INFO_PLIST | base64 --decode > ./ios/Runner/GoogleService-Info.plist - name: Create Provisioning File run: | mkdir -pv ~/Library/MobileDevice/Provisioning\ Profiles/ cd ~/Library/MobileDevice/Provisioning\ Profiles/ echo $REVIEW_PROVISIONING_PROFILE | base64 --decode > ./keeper-adhoc.mobileprovision - name: Create lib/firebase_options.dart run: echo $REVIEW_DART_FIREBASE_OPTIONS | base64 --decode > ./lib/firebase_options.dart - name: Create dart/app/ios/Runner/Info.plist run: echo $REVIEW_RUNNER_INFOPLIST | base64 --decode > ./ios/Runner/Info.plist - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "2.7" - name: Cache Flutter dependencies uses: actions/cache@v4 with: path: | ${{ env.FLUTTER_HOME }}/.pub-cache **/.flutter-plugins **/.flutter-plugin-dependencies **/.dart_tool/package_config.json key: ${{ runner.os }}-flutter-${{ hashFiles('dart/app/pubspec.lock') }} restore-keys: | ${{ runner.os }}-flutter- - name: Cache Bundler dependencies uses: actions/cache@v4 with: path: vendor/bundle key: ${{ runner.os }}-bundler-${{ hashFiles('dart/app/ios/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-bundler- - name: Cache CocoaPods uses: actions/cache@v4 with: path: | dart/app/ios/Pods key: ${{ runner.os }}-pods-${{ hashFiles('dart/app/ios/Podfile.lock') }} restore-keys: | ${{ runner.os }}-pods- - name: Set up Flutter uses: subosito/flutter-action@v2 with: channel: stable cache: true - name: Install Flutter dependencies run: flutter pub get - name: Install dependencies run: | cd ios bundle install - name: Install CocoaPods dependencies run: | cd ios pod install - name: Build and distribute with Fastlane run: | cd ios bundle exec fastlane adhoc
.gitignoreしているファイルが結構あるので、除外しているファイルをGitHub Actions の secrets 経由で生成してCIで実行
CIが正常に動作すれば以下みたいな感じでFirebase App Distributionからアプリをダウンロードできるようになる