요즘 Github Actions 가 그렇게 맛집이라고 하더군요! 그래서 해 봤습니다: Github Actions 로 Flutter 앱 빌드하기. 이 글에서는 Github Actions에서 Flutter 앱을 Android APK 또는 AppBundle 로 빌드하는 방법을 소개하며, (작동하는지는 불확실하지만,) IOS ipa로 빌드하는 방법도 함께 소개합니다.

Secret 등록

안드로이드 앱을 스토어에 등록하기 위해서는(=릴리스모드로 빌드하기 위해서는) Android Keystore 가 필요합니다.

Settings > Secrets > Actions secrets 에서 Secrets 들을 등록해야 합니다.

  1. ANDROID_KEYSTORE_BASE64 등록: key.jksbase64 로 인코딩한 문자열을 등록해야 합니다. base64 key.jks 명령어로 인코딩된 문자열을 등록합니다.
  2. ANDROID_KEYSTORE_PASSWORD 등록: key.properties 파일에 등록된 키스토어 비밀번호를 등록합니다.
  3. ANDROID_KEY_ALIAS 등록: key.properties 파일에 등록된 키스토어 Alias 를 등록합니다.
  4. ANDROID_KEY_PASSWORD 등록: key.properties 파일에 등록된 키 비밀번호를 등록합니다.

Workflow 파일 작성

Secret 들을 등록 했다면, Workflow 파일을 생성해 CI 스크립트를 작성해 작업을 마무리할 수 있습니다. 우선, 저장소에 .github / workflows / flutter.yml 파일을 생성해 workflow 파일을 생성합니다.

name: Flutter CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

master branch 의 push와 pull request 이벤트에 실행되는 Flutter CI Action 을 정의합니다.

Android 빌드

jobs:
  build_android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Flutter
        uses: subosito/flutter-action@v1.4.0
        with:
          channel: 'stable'

Flutter를 사용하기 위해, ubuntu-latest 에서 Flutter Actions 을 사용해 환경을 갖춥니다.

      - name: Download Android keystore
        id: android_keystore
        uses: timheuer/base64-to-file@v1.0.3
        with:
          fileName: key.jks
          encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
      - name: Create key.properties
        run: |
          echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
          echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties
          echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
          echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties          

안드로이드 앱 빌드를 위해, 앞서 업로드 했던 Android Keystore를 다운로드하고 배치합니다. key.jks 파일을 배치하고, 앞서 업로드한 ANDROID_KEYSTORE_PASSWORD , ANDROID_KEY_PASSWORD , ANDROID_KEY_ALIASkey.properties 파일에 작성합니다. 이 부분의 출처는 Albert221의 flutter-release.yml 스크립트입니다.

      - name: Install dependencies
        run: flutter pub get
      - name: Build APK
        run: flutter build apk --release

의존 패키지들을 설치하고, 릴리즈 모드로 apk 파일을 빌드합니다. apk 대신 appbundle 을 사용하면 appbundle 을 빌드할 수 있습니다.

      - name: Rename APK
        run: mv build/app/outputs/flutter-apk/app-release.apk ./ExampleApp-SNAPSHOT.apk
      - name: Archive APK
        uses: actions/upload-artifact@v1
        with:
          name: android-build
          path: ./ExampleApp-SNAPSHOT.apk

빌드된 apk 파일의 이름을 바꾸고, artifact 로 업로드 합니다. 이 부분을 플레이 스토어에 publish 하는 기능으로 바꿔 Continuous Delivery를 구현할 수도 있습니다!

IOS 빌드

IOS 빌드를 원한다면, 이 코드를 추가로 삽입하면 됩니다. 이 코드는 ipa 추출 전 단계의 폴더를 압축해 artifact에 업로드하는 코드입니다. 하지만 안타깝게도 전 Apple Developer Program 이 없기에, ipa 빌드는 실험 해볼 수 없었습니다. 언제가 될지는 모르겠지만 Apple Developer Program 을 얻게 된다면 글을 업데이트 하도록 하겠습니다!

  build_ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Flutter
        uses: subosito/flutter-action@v1.4.0
        with:
          channel: 'stable'
      - name: Install dependencies
        run: flutter pub get
      - name: Build IOS
        run: flutter build ios --release --no-codesign

#      - name: Export IPA
#        run: |
#          cd ios
#          xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive
#          xcodebuild -exportArchive -archivePath $PWD/build/Runner.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath $PWD/build/Runner.ipa
#      - name: Rename IPA
#        run: mv ios/build/Runner.ipa/Runner.ipa ./ExampleApp-SNAPSHOT.ipa
#      - name: Archive IPA
#        uses: actions/upload-artifact@v1
#        with:
#          name: ios-build
#          path: ./ExampleApp-SNAPSHOT.ipa

      - name: Archive IOS
        uses: actions/upload-artifact@v1
        with:
          name: ios-build
          path: build/ios/iphoneos

Flutter.yml

완성된 Flutter.yml 은 다음과 같습니다:

name: Flutter CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
jobs:
  build_android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Flutter
        uses: subosito/flutter-action@v1.4.0
        with:
          channel: 'stable'
      - name: Download Android keystore
        id: android_keystore
        uses: timheuer/base64-to-file@v1.0.3
        with:
          fileName: key.jks
          encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
      - name: Create key.properties
        run: |
          echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
          echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties
          echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
          echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties          
      - name: Install dependencies
        run: flutter pub get
      - name: Build APK
        run: flutter build apk --release
      - name: Rename APK
        run: mv build/app/outputs/flutter-apk/app-release.apk ./ExampleApp-SNAPSHOT.apk
      - name: Archive APK
        uses: actions/upload-artifact@v1
        with:
          name: android-build
          path: ./ExampleApp-SNAPSHOT.apk
  build_ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Flutter
        uses: subosito/flutter-action@v1.4.0
        with:
          channel: 'stable'
      - name: Install dependencies
        run: flutter pub get
      - name: Build IOS
        run: flutter build ios --release --no-codesign

#      - name: Export IPA
#        run: |
#          cd ios
#          xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive
#          xcodebuild -exportArchive -archivePath $PWD/build/Runner.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath $PWD/build/Runner.ipa
#      - name: Rename IPA
#        run: mv ios/build/Runner.ipa/Runner.ipa ./ExampleApp-SNAPSHOT.ipa
#      - name: Archive IPA
#        uses: actions/upload-artifact@v1
#        with:
#          name: ios-build
#          path: ./ExampleApp-SNAPSHOT.ipa

      - name: Archive IOS
        uses: actions/upload-artifact@v1
        with:
          name: ios-build
          path: build/ios/iphoneos

이게 끝입니다! 이제 무료로 (빌드) 해주는 Github Actions을 즐겨 봅시다. :)