Flutterによる個人androidアプリ開発にGitHub ActionsでCI/CDを導入した話
TL;DR
GitHub ActionsとFlutterを使用して、Google Playへのリリースの自動化をしたため、ご紹介させてください。
現在、CI/CDを導入しているアプリはこちらのクソアプリになります。
なお、本記事上では、使用したツールやactionsについて、用途は書きますがどういうツールかは説明する予定はございません。
なお作成にあたり、以下の記事を参考にさせていただきました。特にテストリポートのところは、qiitaのkasa_leさんの記事を参考にさせていただきました。
お急ぎの方は以下のyamlファイル
を.github/workflows
下に、test.sh
をリポジトリ直下
に、リリースノート系のテキストファイル
をdistribution/whatsnew
に置き、各secretsを登録すれば動作すると思います。
登録するsecrets
- GH_TOKEN・・・GitHubのトークン。releaseを作成する際に使用
- KEYSTORE_PASS・・・ビルド時に使用
- KEYSTORE_KEY_PASS・・・ビルド時に使用
- PLAYSTORE_ACCOUNT_KEY・・・Google Playにデプロイ時に使用
- PACKAGE_NAME・・・Google Play上の一意のアプリ名
# .github/workflows/ci.yml
name: Flutter CI
on:
push:
branches: [ dev ]
env:
JAVA_VERSION: 12.x
FLUTTER_VERSION: 1.22.4
TEST_OUTPUT_DIR: test-reports
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Set up flutter ${{ env.FLUTTER_VERSION }}
uses: subosito/flutter-action@v1
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Set up test environment
run: |
flutter pub get
flutter pub global activate dart_dot_reporter
flutter pub global activate junitreport
pip3 install junit2html
- name: Analyze
if: always()
run: |
flutter analyze
- name: Test
if: always()
run: |
export PATH="$PATH":"${FLUTTER_HOME}/bin/cache/dart-sdk/bin"
export PATH="$PATH":"${FLUTTER_HOME}/.pub-cache/bin"
type flutter
./test.sh ${TEST_OUTPUT_DIR}
- name: Arcive test results
if: always()
uses: actions/upload-artifact@v2
with:
name: ${{ env.TEST_OUTPUT_DIR }}
path: ${{ env.TEST_OUTPUT_DIR }}
# .github/workflows/cd.yml
name: Google Play production release
on:
push:
branches: [ main ]
env:
JAVA_VERSION: 12.x
FLUTTER_VERSION: 1.22.4
TEST_OUTPUT_DIR: test-reports
GITVERSION_VERSION: '5.x'
jobs:
version:
name: Create version number
runs-on: ubuntu-latest
steps:
- name: Allow unsecure commands
id: ACTIONS_ALLOW_UNSECURE_COMMANDS
run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV
- uses: actions/checkout@v1
- name: Fetch all history for all tags and branches
run: |
git config remote.origin.url https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}
git fetch --prune --depth=10000
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.3
with:
versionSpec: ${{ env.GITVERSION_VERSION }}
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v0.9.3
- name: Create version.txt with nuGetVersion
run: echo ${{ steps.gitversion.outputs.nuGetVersion }} > version.txt
- name: Upload version.txt
uses: actions/upload-artifact@v2
with:
name: gitversion
path: version.txt
build:
name: Build APK and Create release
needs: [ version ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get version.txt
uses: actions/download-artifact@v2
with:
name: gitversion
- name: Create new file without newline char from version.txt
run: tr -d '\n' < version.txt > version1.txt
- name: Read version
id: version
uses: juliangruber/read-file-action@v1
with:
path: version1.txt
- name: Update version in YAML
run: sed -i 's/99.99.99+99/${{ steps.version.outputs.content }}+${{ github.run_number }}/g' pubspec.yaml
- name: Update KeyStore password in gradle properties
run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties
- name: Update KeyStore key password in gradle properties
run: sed -i 's/#{KEYSTORE_KEY_PASS}#/${{ secrets.KEYSTORE_KEY_PASS }}/g' android/key.properties
- uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- uses: subosito/flutter-action@v1
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Set up test environment
run: |
flutter pub get
flutter pub global activate dart_dot_reporter
flutter pub global activate junitreport
pip3 install junit2html
- name: Analyze
if: always()
run: |
flutter analyze
- name: Test
if: always()
run: |
export PATH="$PATH":"${FLUTTER_HOME}/bin/cache/dart-sdk/bin"
export PATH="$PATH":"${FLUTTER_HOME}/.pub-cache/bin"
type flutter
./test.sh ${TEST_OUTPUT_DIR}
- name: Arcive test results
if: always()
uses: actions/upload-artifact@v2
with:
name: ${{ env.TEST_OUTPUT_DIR }}
path: ${{ env.TEST_OUTPUT_DIR }}
- name: Build aab
run: flutter build appbundle
- name: Create a Release in GitHub
uses: ncipollo/release-action@v1
with:
artifacts: "build/app/outputs/bundle/release/app-release.aab"
token: ${{ secrets.GH_TOKEN }}
tag: ${{ steps.version.outputs.content }}
commit: ${{ github.sha }}
- name: Upload app bundle
uses: actions/upload-artifact@v2
with:
name: appbundle
path: build/app/outputs/bundle/release/app-release.aab
release:
name: Release app to google play production track
needs: [ build ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get appbundle from artifacts
uses: actions/download-artifact@v2
with:
name: appbundle
- name: Release app to production track
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: ${{ secrets.PACKAGE_NAME }}
releaseFiles: app-release.aab
track: production
whatsNewDirectory: distribution/whatsnew
#!/bin/sh
mkdir -p ./test-reports/ 2>/dev/null
flutter test --machine > machine.log
cat machine.log | tojunit --output ./test-reports/TEST-results.xml
flutter pub global run dart_dot_reporter machine.log
python3 -m junit2htmlreport ./test-reports/TEST-results.xml
導入の背景
趣味で書いていたandroidアプリをリリースするのに億劫になっていたところ、
Git Hub Actionsで便利にできそうだと知ったため、導入しました。
実装時の要件
実装時にはざっくりと以下の要件を叶える感じにしたいなと思い、以下を要件としました。
- devブランチにpushすると自動テスト(analyze + test)が実行される
- テストの結果は配布可能な状態である
- mainブランチにマージした際、テストに問題なければ、Google Playにデプロイされる
- リリースするものは、GitHub上でreleaseが作成される
CDまでに必要な準備
必要なもの
- Google Playのアカウント
- GitHubのトークン
- Google Play Developer APIのアクセスキー
必要なものを取得する方法
Google Playのアカウント
Google Playにリリースするために必要となります。
以下の記事とかがご参考になると思います。
GitHubのトークン
CI/CDの中で、必要となります。以下が公式の手順になります。
Google Play Developer APIのアクセスキー
CDを実現するために、Google Play Developer APIへのアクセスキーを作成する必要があります。
私はGCPを利用していたため、Google Play Developer UIからGoogle Play用のProjectを作成、そのプロジェクト上でサービスアカウントとキーの発行、ロールの割当を実施しました。
ここでの注意点として、サービスアカウントを作成するだけでは、Google Play Developer UIに表示されません。必ず作成したサービスアカウント をIAMのメンバーに追加してください。
使用させていただいたサービス/ツールなど
GitHub Actions
GitHubが提供しているCI/CDツールです。無料ユーザーでも1日約66時間は利用できるため、かなり使いたい放題です。
action系
使わせていただいたアクションの一覧になります。強い感謝の気持ちしかないです。
- actions/checkout@v1・・・workflow内でリポジトリにアクセスする必要がある時に利用
- gittools/actions/gitversion/setup@v0.9.3・・・GitVersionのセットアップ
- gittools/actions/gitversion/execute@v0.9.3・・・GitVersionの実行
- actions/upload-artifact@v2・・・特定のファイルをGitHub UIからダウンロード可能にする処理
- actions/download-artifact@v2・・・ワークフロー内で一度アップロードしたファイルをワークフローの内にダウンロード
- juliangruber/read-file-action@v1・・・ファイル読み込み処理
- actions/setup-java@v1・・・Javaのセットアップ
- subosito/flutter-action@v1・・・flutterのセットアップ
- ncipollo/release-action@v1・・・GitHub上にリリースの作成
- r0adkll/upload-google-play@v1・・・Google Playにアップロードする処理。リリース先はbetaなど可能
flutter
Googleが作成したDartのライブラリです。使いやすくて大好きです。
dart_dot_reporter
Dart/Flutterに関するテストリポートを作成してくれるDartのライブラリです。テスト結果をいい感じにするために使用しています。
junitreport
xml 形式のレポートを生成するツールです。テスト結果をいい感じにするために使用しています。
junit2html
junit xml ファイルから単一の html ファイルを生成するツールです。
テスト結果をいい感じにしてくれます。Python2系はEOLを2020年に迎えたので、python3を指定する必要があります。
所感
CI/CDを導入してから、デプロイの億劫さもなくなり、だいぶ楽になりました。
正直、業務でも使いたいです。
Discussion