뚝딱뚝딱 모바일

[Flutter] Code Push를 해보라고요? (2) - 맨땅에 헤딩하는 법 본문

실무 이야기

[Flutter] Code Push를 해보라고요? (2) - 맨땅에 헤딩하는 법

규석 2024. 11. 22. 18:37

아래 글에서 이어집니다.

 

[Flutter] Code Push를 해보라고요? (1) - 지피지기

오늘도 열심히 앱을 개발하고, QA를 거치고, 구글 플레이 스토어와 애플 앱스토어 심사를 거친 후 앱을 업데이트했습니다.어?? 앱 배포 후 미처 발견 못한 오류를 발견하였습니다.큰일 났습니다.

nkstar-ios.tistory.com


Codemagic의 악몽

기존 Codemagic 빌드가 있기에 간단하게 붙이면 되겠다~ 생각하고 호기롭게 Codemagic 연계 문서를 클릭하였습니다.

어? 이게 뭐지? 이런 건 처음 보는데? 싶은 yaml 파일이 쫜! 하고 앞에 있었습니다.

 

그렇습니다. Codemagic은 GUI 형식의 Workflow Editor와 yaml 파일, 두 가지 방식의 빌드 flow를 지원하고 있었고, Shorebird를 적용하기 위해서는 yaml 파일을 새로 만들어야 했습니다!

그냥 앱 CI/CD를 새로 만들어야 되는거잖아요 ㅠㅠ

슬퍼할 시간도 없이 바로 Docs를 정독하기 시작하였습니다.

 

Codemagic Integration

Integrate Shorebird into your Codemagic workflow

docs.shorebird.dev

 

 

How to Set Up Flutter Code Push With Shorebird and Codemagic | Codemagic Blog

Learn how to set up Flutter code push with Shorebird and Codemagic

blog.codemagic.io

 

 

Using codemagic.yaml

Configure all your workflows in a single file

docs.codemagic.io

자료가 없다 보니 그냥 공식 Docs 위주로 닥치는 대로 봤던 것 같습니다. 그중에서 가장 도움이 되었다고 생각하는 문서들입니다. 쭉 읽어보시면 좋을 것 같습니다.

Codemagic 세팅

shorebird 로그인 후 나온 토큰을 Codemagic에 넣어주어야 사용이 가능해집니다.

Codemagic 프로젝트를 yaml 파일로 변경 후 좌측 탭의 Environment variables에 진입해 줍니다.

빨간 박스의 문구를 클릭하여 변경합니다.

 

SHOREBIRD_TOKEN 키값으로 토큰을 넣어주고 group명은 shorebird로 적어줍니다. Secure 항목도 체크해주어야 합니다.

 

스토어 관련 환경변수 세팅

이번엔 각 스토어에 자동 배포를 위한 환경변수를 세팅해 보겠습니다.

위 첨부한 링크들을 보시면 더욱 도움 되실 겁니다.

 

Codemagic 좌측 탭의 Teams -> codemagic.yaml settings -> Code signing identites에 각각의 인증서를 포함하여 주면 됩니다. 각각 이름은 codemagic.yaml에 사용할 것이기에 명확한 이름으로 지어주시면 좋습니다.

 

  • iOS : p12파일 및 provisioning profiles
  • AOS : key store

구글은 API 권한이 필요하므로, 구글 API 권한 json을 발급받아 SHOREBIRD_TOKEN을 넣어둔 Environment variables에 넣어주시면 됩니다. 여기 키값도 codemagic.yaml에서 사용할 것입니다.

 

애플도 연결해 줍시다. Codemagic 좌측 탭의 Teams -> General settings -> Integrations의 Developer Portal을 클릭하여 key를 추가해 주시면 됩니다. 물론 여기 키값도 codemagic.yaml에서 사용할 것입니다.

codemagic.yaml 원문

매우 매우 많은 시행착오와 오류를 겪고...

아무 일도 없었다기엔 너무 오래 걸렸어


성공적인 codemagic.yaml 파일을 작성할 수 있었습니다.

아래 설명과 함께 한 번씩 보시면 좋을 것 같습니다!

definitions:
# Reusable definitions go in this section
scripts:

  - &shorebird_install
    name: Install Shorebird CLI
    script: |
      # Install the Shorebird CLI
      curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash
      # Set Shorebird PATH
      echo PATH="/Users/builder/.shorebird/bin:$PATH" >> $CM_ENV

  - &setup_local_properties
    name: Set up local.properties
    script: echo "flutter.sdk=$HOME/programs/flutter" > "$CM_BUILD_DIR/android/local.properties"

  - &use_provisioning_profiles
    name: Use provisioning profiles
    script: xcode-project use-profiles

  - &flutter_setting
    name: Flutter setting
    script: |
      flutter pub get
      dart run build_runner build

  - &release_android
    name: Release Android
    script: shorebird release android --flutter-version=3.22.2 --target lib/main_prod.dart

  - &release_ios
    name: Release iOS
    script: shorebird release ios --flutter-version=3.22.2 --target lib/main_prod.dart -- --export-options-plist=/Users/builder/export_options.plist

# patch should behind get version
  - &patch_android
    name: Patch Android
    script: |
      VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
      echo "Version: $VERSION"
      shorebird patch android --target lib/main_prod.dart --release-version=$VERSION

  - &patch_ios
    name: Patch iOS
    script: |
      VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
      echo "Version: $VERSION" 
      shorebird patch ios --target lib/main_prod.dart --release-version=$VERSION -- --export-options-plist=/Users/builder/export_options.plist

  - &sentry_debug_file_upload
    name: Sentry Debug File Upload
    script:  flutter packages pub run sentry_dart_plugin

workflows:
  app-deploy-workflow:
    name: Deploy
    instance_type: mac_mini_m1
    environment:
      ios_signing:
        provisioning_profiles:
          - ios-profile
        certificates:
          - ios-certificate
      android_signing:
        - android-keystore
      groups:
        - shorebird
        - google_credentials
      flutter: 3.22.2
      xcode: 15.4
    integrations:
      app_store_connect: app-store-connect
    scripts:
      - *setup_local_properties
      - *shorebird_install
      - *flutter_setting
      - *release_android
      - *use_provisioning_profiles
      - *release_ios
      - *sentry_debug_file_upload
    artifacts:
      - build/**/outputs/**/*.aab
      - build/**/outputs/apk/**/*.apk
      - build/ios/ipa/*.ipa
    publishing:
      app_store_connect:
        auth: integration
      google_play:
        track: production
        credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
      slack:
        channel: 'your_channel'
        notify_on_build_start: true # To receive a notification when a build starts
        notify:
          success: true # To not receive a notification when a build succeeds
          failure: true # To not receive a notification when a build fails

  patch-workflow:
    name: Hotfix Patch
    instance_type: mac_mini_m1
    environment:
      android_signing:
        - android-keystore
      ios_signing:
        provisioning_profiles:
          - ios-profile
        certificates:
          - ios-certificate
      groups:
        - shorebird
      flutter: 3.22.2
      xcode: 15.4
    scripts:
      - *setup_local_properties
      - *shorebird_install
      - *flutter_setting
      - *patch_android
      - *use_provisioning_profiles
      - *patch_ios
      - *sentry_debug_file_upload
    publishing:
      slack:
        channel: 'your_channel'
        notify_on_build_start: true # To receive a notification when a build starts
        notify:
          success: true # To not receive a notification when a build succeeds
          failure: true # To not receive a notification when a build fails

각 Script와 workflow에 대해 설명드리겠습니다. (저희 회사 기준이기에 입맛에 맞게 수정하시면 됩니다.)

각각의 Script 설명

shorebird_install

  - &shorebird_install
    name: Install Shorebird CLI
    script: |
      # Install the Shorebird CLI
      curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash
      # Set Shorebird PATH
      echo PATH="/Users/builder/.shorebird/bin:$PATH" >> $CM_ENV

Shorebird를 install 하는 Script입니다. flow를 시작할 때 실행해 주시면 됩니다.

setup_local_properties

  - &setup_local_properties
    name: Set up local.properties
    script: echo "flutter.sdk=$HOME/programs/flutter" > "$CM_BUILD_DIR/android/local.properties"

안드로이드 빌드할 때 사용하는 Script로, Flutter SDK 경로를 설정할 때 사용합니다.

use_provisioning_profiles

  - &use_provisioning_profiles
    name: Use provisioning profiles
    script: xcode-project use-profiles

iOS 빌드할 때 사용하는 Script로, Xcode가 빌드할 때 프로비저닝 프로필을 사용하도록 합니다.

flutter_setting

  - &flutter_setting
    name: Flutter setting
    script: |
      flutter pub get
      dart run build_runner build

프로젝트의 pubspec.yaml에 적혀있는 라이브러리를 가져오고, build_runner를 통해 추가적인 파일을 만들어냅니다. (build_runner를 사용하지 않으면 아래 명령어는 빼도 무방합니다.)

Android 관련 Scripts

  - &release_android
    name: Release Android
    script: shorebird release android --flutter-version=3.22.2 --target lib/main_prod.dart
    
  - &patch_android
    name: Patch Android
    script: |
      VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
      echo "Version: $VERSION"
      shorebird patch android --target lib/main_prod.dart --release-version=$VERSION
  • release_andoird : Shorebird를 통해 android를 빌드합니다.
  • patch_android : pubspec.yaml의 version을 가져와 Shorebird를 통해 patch를 진행합니다.

iOS 관련 Scripts

  - &release_ios
    name: Release iOS
    script: shorebird release ios --flutter-version=3.22.2 --target lib/main_prod.dart -- --export-options-plist=/Users/builder/export_options.plist
    
  - &patch_ios
    name: Patch iOS
    script: |
      VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
      echo "Version: $VERSION" 
      shorebird patch ios --target lib/main_prod.dart --release-version=$VERSION -- --export-options-plist=/Users/builder/export_options.plist
  • release_ios : Shorebird를 통해 iOS를 빌드합니다.
  • patch_ios : pubspec.yaml의 version을 가져와 Shorebird를 통해 patch를 진행합니다.

sentry_debug_file_upload

  - &sentry_debug_file_upload
    name: Sentry Debug File Upload
    script: flutter packages pub run sentry_dart_plugin

센트리 디버그 파일을 업로드하여, 오류가 발생한 위치를 알 수 있게 해 줍니다. 센트리를 사용하지 않다면 빼셔도 무방합니다.

Workflow 설명

앱을 스토어에 심사 직전까지 올려주는 app-deploy-workflow,

Shorebird를 통해 코드푸시를 진행하는 patch-workflow가 있습니다.

 

Workflow에 사용되는 인자에 대해 간단히 설명드리면

  • name : Codemagic에서 표기되는 Workflow의 이름입니다. 명확한 이름으로 해주시면 좋습니다.
  • instance_type : 가상 환경을 구동할 기기를 뜻합니다. iOS까지 빌드하여야 하기에 무조건 맥으로 설정하여야 합니다. (iOS를 안 하실 거면 Window나 Linux로 하여도 무방합니다.)
  • environment : 환경변수들을 지정해 주는 필드입니다.
    • ios_signing
      • provisioning_profiles : 위에서 설정해 둔 프로비저닝 프로필의 이름을 넣어주시면 됩니다.
      • certificates : 위에서 설정해 둔 인증서의 이름을 넣어주시면 됩니다.
    • android_signing : 위에서 설정해둔 keystore의 이름을 넣어주시면 됩니다.
    • groups : Environment variables에 설정해둔 group의 이름을 넣어주시면 됩니다. shorebird와 구글 API json 그룹명을 넣어주었습니다.
    • flutter : flutter 버전을 뜻합니다. 저희 같은 경우는 3.22.2를 사용 중이라 3.22.2를 지정해 주었습니다.
    • xcode : xcode 버전입니다. 저희는 xcode 16.0 마이그레이션을 진행하지 않아 15.4를 사용 중입니다.
  • integrations
    • app_store_connect : 위에서 설정해 둔 Developer Portal의 이름을 적어주시면 됩니다.
  • scripts : 본인이 빌드하고자 하는 순서대로 넣어주시면 됩니다.
  • artifacts : 빌드가 완료되면 Codemagic을 통해 받으려고 하는 파일의 경로들입니다. 빌드가 끝나면 다운로드 링크가 제공됩니다.
  • publishing : 퍼블리싱 관련 필드입니다.
    • app_store_connect : App Store Connect로 업로드합니다.
      • auth : 위에서 설정해둔 integration에서 가져오도록 해주었습니다.
    • google_play : Google Play Console로 업로드합니다.
      • track : Google Play Console에서 설정하신 track명을 적어주시면 됩니다.
      • credentials : 위에서 설정한 구글 API의 키값(그룹명 아님!)을 적어주시면 됩니다.
    • slack (참고 링크) : 슬랙 메시지를 보내줍니다.

Shorebird 통해서 Code Push 하기

이렇게 Codemagic을 설정하고, deploy flow를 통해서 배포 진행 후 코드 수정 후 patch flow를 실행하시면 됩니다.

배포 후에는 콘솔을 확인해 보시면 아래 사진과 같이 버전이 추가됩니다.

Code Push를 진행하였으면 2.8.14+420 버전처럼 표기됩니다!

버전명을 클릭하여 아래 화면에 들어올 수 있습니다.

Code Push를 했다면, 이렇게 Patch가 추가됩니다. 이 탭에서 각각의 Patch들을 활성화 / 비활성화할 수 있고, Install 여부를 볼 수 있습니다.

주의사항

현재 배포된 버전과 patch 버전이 무조건 같아야 한다는 것입니다. 예시로 배포 버전 1.1.2+13이면 patch 버전 또한 1.1.2+13이어야 합니다. 메이저, 마이너, 패치, 빌드 넘버까지 모두 같아야 합니다.

 

또한, 전 글에서 말했듯 Shorebird는 Dart파일의 변경만을 감지하고 적용합니다. 그렇기에 asset을 변경했다거나, 다른 라이브러리를 추가하여 yaml 파일이 달라졌다면, 이는 적용이 되지 않습니다.

Code Push 후기

4~5번 정도의 Code Push를 진행하였는데요, 딱 임시방편의 느낌이 강했습니다.

 

Shorebird는 앱을 껐다 켜게 되면 적용이 되는데요. 이것이 사람마다 적용 시간이 달랐습니다.

저는 Code Push 후 30분 내로 적용이 되었고, 회사 분들은 1시간, 2시간 등등 편차가 다양했습니다.

 

그리고 모두가 적용이 되는 것이 아닌 것 같습니다. 한 분이 유독 Code Push가 적용이 안되었습니다. 추측하기로는 자동 업데이트 설정을 끄면 적용이 안 되는 것이 아닐까?라는 생각으로 자동 업데이트 설정을 켜고 앱을 재설치하니 적용이 되었습니다. 정확한 원인인지는 모르겠으나, Code Push 변경 사항이 적용이 되지 않았다면 이를 의심해 볼 수 있습니다.

 

이러한 점 때문에, 크리티컬 한 이슈가 터졌을 때 약간의 유저라도 불편을 덜 겪게 하자!라고 생각하신다면 Shorebird 사용을 추천드립니다. 그게 아니라면 스토어를 통한 정식 업데이트가 더욱 좋을 수 있습니다.


뭐야 되게 간단하네?라고 생각하실 겁니다. 저도 지금 보니 되게 간단한 내용 같습니다.

하지만 이렇게 구성하기까지 정말 많은 오류와 시행착오를 겪었습니다. 개발 당시 자료가 너무 없었고, 공식 Docs도 지금처럼 세세하게 작성이 안되어 있어, 그냥 제 맘대로 넣어보고, 실패하면 고치고를 수십 수백 번 반복했었습니다. 진짜 맨땅에 헤딩을 하듯 시도했고, 다행히 저는 머리가 깨지지 않고 머리가 단단해졌습니다.

 

이걸 통해서 자료가 없을 때 개발 하는 법과 가끔은 무식하게 맨땅에 헤딩하는 편이 자료를 찾고 분석하고 진행하는 것보다 좋을 때가 있다를 깨달았습니다. 이 Code Push를 적용하면서 좀 더 한 단계 성장한 기분이 듭니다.

 

다음 편은 제가 만났던 오류와 제 실수들을 적어보려고 합니다. 많은 도움이 되길 바라겠습니다.