지난 게시글에서는 Android에서 Firebase App Distribution 의 도입을 통해 배포 파이프라인을 알파 테스트를 추가함으로써 배포 채널의 분리(알파 테스트-Firebase, 베타 테스트-내부 테스트, 프로덕션 출시)하였고, 이번엔 iOS 에 대해 적용해보겠다.
기존의 Build Scheme 은 다음과 같다.
▲ 그림 1. 기존의 Build Scheme
이는 Android Studio에서 Flutter 앱을 디버그 혹은 실행할 때 선언한 Build Configuration
과 동일한데, Android 배포 파이프라인을 개선하면서 (기존) dev, prod
-> (변경) development, hotfix, sandbox, production
으로 변경되었기 때문에, 마찬가지로 Xcode 내에서 해당 Build Scheme 들을 변경해줘야 한다.
Build Scheme 을 추가하기 전에, Build Scheme에 대응하는 Build Configuration 이 실행되도록 하기 위해 Configuration들을 추가해주었다.[Xcode > Project > Runner > Info > Configurations]
▲ 그림 2. Build Configuration 추가
Flutter에서 --flavor와 --debug|--release 옵션을 통해 실행되는 iOS 빌드는 내부적으로 Build Configuration을 [BuildType]-[Flavor] 형식으로 구성하여 Xcode 프로젝트를 참조한다. 따라서 Xcode 내에는 해당 이름의 Build Configuration과 이를 참조하는 Scheme이 구성돼 있어야 Flutter 빌드가 정상적으로 수행된다.
▲ 그림 3. Android Studio 에서 Build Configuration
다시 말해, 위 이미지처럼 Android Studio에서 "production" 이라는 Run Configuration을 만들고 --dart-define=FLAVOR=prod --release
옵션과 --flavor=production
을 지정하면, Flutter는 내부적으로 Release-production이라는 Build Configuration 이름을 Xcode에 전달한다.
이때 Xcode 프로젝트 내에는 "Release-production" 이라는 이름의 Build Configuration이 반드시 존재해야 하며, 동시에 이를 참조하는 Xcode Scheme (production) 도 구성돼 있어야 한다.
즉, Flutter는 Android Studio에서 해당 Run Configuration을 실행할 때 자동으로 xcodebuild -scheme production -configuration Release-production
과 같은 명령을 실행하게 되며, 이때 대응되는 Xcode 설정이 누락되어 있다면 빌드가 실패한다.
따라서 Android Studio와 Xcode 양쪽 모두에서 Flavor 이름과 Build Configuration 네이밍이 일치하도록 관리해야 Flutter의 iOS 빌드가 원활하게 동작한다.
따라서 4가지의 Build Scheme 을 설정하면 다음과 같다.
▲ 그림 4. 변경된 Build Scheme
각 Build Scheme 에 따라 위에서 설정했던 Build Configuration 을 1:1 매핑 시켜야 한다.
▲ 그림 5. 각 Build Scheme 별 Configuration 매핑
Android 에서 Fastfile 에서 앱을 빌드할 때 사용하는 함수는 다음과 같다.
def build_app(flavor)
if flavor == "sandbox"
sh "flutter build apk --flavor sandbox --release --dart-define=FLAVOR=dev"
return "../build/app/outputs/flutter-apk/app-sandbox-release.apk"
elsif flavor == "production-firebase"
sh "flutter build apk --flavor production --release --dart-define=FLAVOR=prod"
return "../build/app/outputs/flutter-apk/app-production-release.apk"
elsif flavor == "production"
sh "flutter build appbundle --flavor production --release --dart-define=FLAVOR=prod"
return "../build/app/outputs/bundle/productionRelease/app-production-release.aab"
else
UI.user_error!("Unknown flavor: #{flavor}")
end
end
여기서 빌드 시 --dart-define=FLAVOR=[dev or prod]
이를 추가주었는데, 이러한 이유는 FLAVOR
변수를 main.dart
에 전달하여 해당 값에 따라 Environment
를 설정하기 때문이다. 해당 이유는 다음 게시글 에서 확인할 수 있다.
따라서 CI 단계에서도 해당 변수를 전달하기 위해 Android 의 경우 위의 방법으로 해결하였고, iOS 에서는
- xcconfig 를 통한 전달
- 명령어에 포함시켜 직접 빌드
다음 두가지를 시도해보았고, 첫번째 방법에서 빌드 오류 로 인해 이러한 문제를 해결하지 못해 두번째 방법으로 해결하였다. 만약 첫번째 방법을 시도했는데 오류가 나지 않는다면 첫번째 방법으로 적용하는 것을 적극 권장한다. 이러한 이유는 아래에서 다루도록 하겠다.
xcconfig 파일은 Xcode 프로젝트 내에서 빌드 설정을 외부 파일로 분리하여 구성할 수 있게 해주는 구성 파일이다.
환경별 설정(예: 디버그/릴리즈, 개발/운영 등)을 명확하게 구분하여 관리할 수 있고, 같은 프로젝트 내에서도 다양한 빌드 구성을 손쉽게 제어할 수 있다는 장점이 있다.
xcconfig 파일을 활용하면 DART_DEFINES와 같은 변수를 설정하여 Dart 레이어에 값을 넘길 수 있다. 이를 통해 런타임 시점에서 Dart 코드 내에서 정의된 환경 값(const String.fromEnvironment('FLAVOR'))을 사용할 수 있게 된다.
#include "Debug.xcconfig"
DART_DEFINES=FLAVOR%3Ddev
#include "Debug.xcconfig"
DART_DEFINES=FLAVOR%3Dprod
#include "Release.xcconfig"
DART_DEFINES=FLAVOR%3Ddev
#include "Release.xcconfig"
DART_DEFINES=FLAVOR%3Dprod
위의 4개 .xcconfig
를 만들었다면 이는 ios/Flutter 내부에 넣어주면 되고,
ios > Flutter > Add Files to "Runner" > 파일 선택 > Action: Reference files in place 선택한다.
▲ 그림 6. Xcode에 File 추가
이 때 .xcconfig 파일은 빌드 설정용이므로 앱 번들에 포함되면 안 되므로 Targets 에는 모두 해제해주었다.
▲ 그림 7. Action 설정
각 Configuration 마다 위에서 생성한 xcconfig 를 매핑시켜줘야 한다.
▲ 그림 8. 각 Build Configuration 마다 .xcconfig 매핑
그러나 다음과 같은 빌드 실패 에러가 발생하였고, 빌드 캐시 삭제, pod 초기화 등 시도해보았으나 해당 에러를 해결하지 못해 결국 2번 방법으로 우회하였다.
Failed to build iOS app
Error (Xcode): Error parsing assemble command: your generated configuration may be out of date. Try re-running 'flutter build ios' or the appropriate build command.
Encountered error while building for device.
flutter
명령어를 이용하여 --dart-define=FLAVOR=
를 포함시켜서 빌드하는 방법이다.
flutter build ios --flavor sandbox --dart-define=FLAVOR=dev --release
이 방법으로 진행할 경우 Fastfile
에서 flavor 에 따라 다르게 명령어를 실행해주면 된다. 해당 작업은 추후 Fastfile 작성 파트에서 상세 내용을 다루도록 하겠다.
Build Configuration 이 추가되었기 때문에 실행 시 Configuration 에 따라(운영 환경인지, 개발 환경인지) Firebase 연동에 필요한 Info.plist
를 런타임 단계에서 복사하는 스크립트도 수정해야 한다.[Xcode > Targets > Runner > Build Phase]
▲ 그림 9. 기존 Run Script
기존에는 dev 와 prod 만 이용했었기 때문에, 이제는 4가지 환경에 맞게 다시 설정해줘야 한다.
▲ 그림 10. 변경된 Run Script
ios 디렉토리에서 해당 명령어를 실행 시 ios/Gemfile 이 수정되며 ios/fastlane/Pluginfile 이 생긴다.
fastlane add_plugin firebase_app_distribution
이 후 GOOGLE_APPLICATION_CREDENTIALS
환경 변수를 설정해야 한다.
export GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/credentials/file.json
이는 Android 에서 수행했던 작업과 동일하다.
firebase_app_distribution으로 iOS 앱을 배포하려면, 테스터 기기에서 앱을 설치할 수 있어야 한다. 이를 위해서는 해당 기기들의 UDID가 포함된 Ad-Hoc 프로비저닝 프로필과
앱 서명을 위한 Ad-Hoc 배포 인증서가 필요하다.
추후 Fastfile 에서 export_method: "ad-hoc"
으로 .ipa를 생성할 때 이 프로파일과 인증서가 포함되어야 Firebase를 통해 배포된 앱이 실제 기기에서 설치 가능하다.
지금은 appstore 용 인증서만 발급한 상태기 때문에 match를 통해 App Store 용 인증서(App Store Distribution)와 프로비저닝 프로필만 있는 경우, export_method: "app-store"
(따로 설정 안하면 default로 설정됨)로 빌드된 .ipa는 Firebase App Distribution에서는 설치 불가하다. 이 프로파일은 실제 App Store 제출 전용으로, 테스터 기기 UDID가 포함되지 않기 때문이다.
따라서 Firebase를 통한 테스트 배포에는 Ad-Hoc 인증서 및 프로비저닝 프로필이 필수이다.
Apple Developer Portal > Certificates, Identifiers & Profiles > Devices
fastlane match adhoc --git_branch [브랜치명]
해당 명령어를 수행한 뒤 git_url 로 들어가보면 profiles/adhoc
에 프로비저닝 프로필이 생성된 것을 확인할 수 있다.
▲ 그림 11. Ad-Hoc 프로비저닝 프로필
이미 Ad-hoc 인증서 발급했는데, UUID 를 추가해야 되는 경우 ?
인증서 발급 이후 테스트 기기의 UUID 를 추가해야 하는 경우 인증서를 새로 업데이트 해줘야 한다. 따라서 위에서 언급한 Device List 에서 UUID 추가한 후 match 인증서 발급을 덮어쓰기 하면 된다.
fastlane match adhoc --force --git_branch [브랜치명]
iOS 의 Build Scheme 과 Configuration 은 다음과 같다.
- Build Scheme
- development
- hotfix
- sandbox
- production
- Build Configuration
- Debug-development
- Debug-hotfix
- Release-sandbox
- Release-production
lane 2, 3, 4의 경우 Build Scheme: production && Build Configuration: Release-production
을 공통으로 사용하기 때문에 scheme 과 configuration 은 해당 값대로 할당해주었고, parameter 로 받는 export_method
의 경우 default 는 "app-store"
이고 Firebase App Distribution 에 프로덕션 앱을 올리는 경우를 고려하여 이 경우는 export_method = "ad-hoc"
을 명시하도록 했다.
## 공통 빌드 함수 - lane 2, lane 3, lane 4
def build_ios_app(export_method = "app-store")
build_app(
clean: true,
workspace: "Runner.xcworkspace",
output_directory: "./ipa",
scheme: "production",
configuration: "Release-production",
export_method: export_method
)
return "./ipa/Runner.ipa"
end
lane 구성은 Android 와 동일하게 4개로 다시 언급하면,
Lane 1: 알파 테스트 -> Firebase 에 sandbox ipa 업로드
Lane 2: 알파 테스트 -> Firebase 에 production ipa 업로드
Lane 3: 베타 테스트 -> TestFlight에 production ipa 업로드
Lane 4: 제품 출시 및 버전 업데이트 > App Store 에 production ipa 업로드
Lane 1과 Lane 2 가 추가 되었으므로 이에 맞는 lane 을 추가해주고, 나머지 두 lane 은 기존 게시글에서 build_app
함수만 별도로 빼고 나머지는 크게 변경된 점은 없다.
위에서 --dart-define=FLAVOR=
를 main.dart
에 전달하기 위한 방법으로 2가지를 소개했고, 결국 방법 2(명령어에 포함시켜 직접 빌드)를 이용하여 Fastfile 을 작성한다고 언급했었다.
주석처리가 된 부분은 방법 1(xcconfig 를 통한 전달)으로 빌드 오류가 발생하지 않으면 해당 스크립트를 사용하면 된다. 다음은 방법 2로 구현했을 경우 다음 스크립트를 수행하는데,
sandbox 환경의 iOS 앱을 Flutter CLI와 Xcode를 통해 빌드하고 .xcarchive 파일을 생성한 후, 이를 기반으로 .ipa
파일을 export하여 Firebase App Distribution에 배포한다. .xcarchive
는 Xcode에서 .ipa를 만들기 위한 중간 산출물로, 서명 및 export 설정에 필요한 메타데이터를 포함하고 있다. 이 과정을 통해 Ad-Hoc
방식의 .ipa
를 생성하고, QA 그룹에 내부 테스트용으로 안정적으로 배포할 수 있다.
...
# Lane 1: [sandbox] 환경 - 알파 테스트용 ipa -> Firebase 배포
# desc "Deploy iOS [sandbox] to Firebase App Distribution"
# lane :deploy_sandbox_to_firebase do |options|
# do_signing(
# key_id: options[:key_id],
# issuer_id: options[:issuer_id],
# key_content: options[:key_content],
# app_identifier: options[:app_identifier],
# signing_type: "adhoc"
# )
#
# ipa_path = build_ios_app("sandbox", "Release-sandbox", "ad-hoc")
# version, build_number = get_app_version()
#
# firebase_app_distribution(
# app: ENV['FIREBASE_IOS_APP_ID_DEV'],
# ipa_path: ipa_path,
# groups: "qa",
# release_notes: "[sandbox] Version: #{version}+#{build_number} 내부 테스트)"
# )
# end
# Lane 1: [sandbox] 환경 - 알파 테스트용 ipa -> Firebase 배포
desc "Deploy iOS [sandbox] to Firebase App Distribution"
lane :deploy_sandbox_to_firebase do |options|
do_signing(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
app_identifier: options[:app_identifier],
signing_type: "adhoc"
)
# 1. Flutter CLI로 빌드 (FLAVOR=dev 반영)
sh("flutter build ios --flavor sandbox --dart-define=FLAVOR=dev --release")
# 2. 생성된 .ipa 경로 지정 (기본 생성 위치는 `build/ios/iphoneos/Runner.app`)
# Fastlane에서 ipa 파일로 export 하기 위해 .xcarchive 경로 지정
archive_path = "../../build/ios/archive/Runner.xcarchive"
ipa_output_path = "./ipa"
# 3. .xcarchive가 존재하지 않으면, 생성 (선택)
sh("xcodebuild -workspace ../Runner.xcworkspace -scheme sandbox -configuration Release-sandbox -archivePath #{archive_path} archive")
# 4. .ipa export (Ad-Hoc 방식으로)
ipa_path = gym(
export_method: "ad-hoc",
archive_path: archive_path,
output_directory: ipa_output_path,
output_name: "Runner.ipa",
scheme: "sandbox"
)
# 5. Version info 추출
version, build_number = get_app_version()
# 6. Firebase에 업로드
firebase_app_distribution(
app: ENV['FIREBASE_IOS_APP_ID_DEV'],
ipa_path: ipa_path,
groups: "qa",
release_notes: "[sandbox] Version: #{version}+#{build_number} 내부 테스트)"
)
end
default_platform(:ios)
platform :ios do
## iOS Signing 설정 (API Key & Match)
desc "Do Signing for Deploy iOS APP"
lane :do_signing do |options|
api_key = app_store_connect_api_key(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
is_key_content_base64: true
)
match(
type: options[:signing_type] || "appstore", # default: appstore
app_identifier: options[:app_identifier],
api_key: api_key,
git_branch: 'release',
readonly: true
)
end
## Version 정보 추출
def get_app_version
pubspec = File.read("../../pubspec.yaml")
version_line = pubspec.lines.find { |line| line.start_with?("version:") }
version, build_number = version_line.split(":").last.strip.split("+")
return version, build_number
end
def build_ios_app(export_method = "app-store")
build_app(
clean: true,
workspace: "Runner.xcworkspace",
output_directory: "./ipa",
scheme: "production",
configuration: "Release-production",
export_method: export_method
)
return "./ipa/Runner.ipa"
end
# Lane 1: [sandbox] 환경 - 알파 테스트용 ipa -> Firebase 배포
# desc "Deploy iOS [sandbox] to Firebase App Distribution"
# lane :deploy_sandbox_to_firebase do |options|
# do_signing(
# key_id: options[:key_id],
# issuer_id: options[:issuer_id],
# key_content: options[:key_content],
# app_identifier: options[:app_identifier],
# signing_type: "adhoc"
# )
#
# ipa_path = build_ios_app("sandbox", "Release-sandbox", "ad-hoc")
# version, build_number = get_app_version()
#
# firebase_app_distribution(
# app: ENV['FIREBASE_IOS_APP_ID_DEV'],
# ipa_path: ipa_path,
# groups: "qa",
# release_notes: "[sandbox] Version: #{version}+#{build_number} 내부 테스트)"
# )
# end
# Lane 1: [sandbox] 환경 - 알파 테스트용 ipa -> Firebase 배포
desc "Deploy iOS [sandbox] to Firebase App Distribution"
lane :deploy_sandbox_to_firebase do |options|
do_signing(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
app_identifier: options[:app_identifier],
signing_type: "adhoc"
)
# 1. Flutter CLI로 빌드 (FLAVOR=dev 반영)
sh("flutter build ios --flavor sandbox --dart-define=FLAVOR=dev --release")
# 2. 생성된 .ipa 경로 지정 (기본 생성 위치는 `build/ios/iphoneos/Runner.app`)
# Fastlane에서 ipa 파일로 export 하기 위해 .xcarchive 경로 지정
archive_path = "../../build/ios/archive/Runner.xcarchive"
ipa_output_path = "./ipa"
# 3. .xcarchive가 존재하지 않으면, 생성 (선택)
sh("xcodebuild -workspace ../Runner.xcworkspace -scheme sandbox -configuration Release-sandbox -archivePath #{archive_path} archive")
# 4. .ipa export (Ad-Hoc 방식으로)
ipa_path = gym(
export_method: "ad-hoc",
archive_path: archive_path,
output_directory: ipa_output_path,
output_name: "Runner.ipa",
scheme: "sandbox"
)
# 5. Version info 추출
version, build_number = get_app_version()
# 6. Firebase에 업로드
firebase_app_distribution(
app: ENV['FIREBASE_IOS_APP_ID_DEV'],
ipa_path: ipa_path,
groups: "qa",
release_notes: "[sandbox] Version: #{version}+#{build_number} 내부 테스트)"
)
end
# Lane 2: [production] 환경 - 알파 테스트용 ipa -> Firebase 배포
desc "Deploy iOS [production] to Firebase App Distribution"
lane :deploy_prod_to_firebase do |options|
do_signing(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
app_identifier: options[:app_identifier],
signing_type: "adhoc"
)
ipa_path = build_ios_app("ad-hoc")
version, build_number = get_app_version()
firebase_app_distribution(
app: ENV['FIREBASE_IOS_APP_ID_PRODUCTION'],
ipa_path: ipa_path,
groups: "qa",
release_notes: "[production] Version: #{version}+#{build_number} 배포 전 내부 테스트)"
)
end
# Lane 3: [production] 환경 - 베타 테스트용 ipa -> TestFlight 배포
desc "Deploy iOS [production] to TestFlight"
lane :deploy_to_testflight do |options|
do_signing(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
app_identifier: options[:app_identifier],
signing_type: "appstore"
)
build_ios_app()
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
# Lane 4: [production] 환경 - App Store 배포
desc "Deploy iOS [production] to App Store"
lane :deploy_to_app_store do |options|
do_signing(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
app_identifier: options[:app_identifier],
signing_type: "appstore"
)
build_ios_app()
upload_to_app_store(
automatic_release: false, # 자동 출시 비활성화
submit_for_review: false, # 자동 심사 제출 비활성화
skip_screenshots: true, # 스크린샷 업로드 건너뛰기
skip_metadata: true, # 메타데이터 업데이트 건너뛰기
skip_app_version_update: true, # App Store의 버전 자동 업데이트 비활성화
force: true, # Preview 확인 건너뛰기
precheck_include_in_app_purchases: false # In-App Purchase 검사 건너뛰기
)
end
end
Run script 에서 Build Configuration 에 따라서 연동되어 있는 Firebase 프로젝트와 관련 Info.plist 를 복사하는 스크립트를 추가했었고, runner 안에 프로젝트에서 복사가 잘 되었음을 확인하였다.
▲ 그림 12. GoogleService-Info.plist 찾을 수 없음
그러나 위의 문제가 발생했었고 이러한 원인으로는 Xcode는 GoogleService-Info.plist를 빌드 시작 전부터 존재해야 하는 정적 파일로 간주하는데, 기존에는 빌드 도중에 Run Script에서 복사하고 있으니, Xcode는 이를 빌드 입력 파일로 인식하지 못하는 것이다.
따라서 옵션에 따른 배포 실행할 때 정적으로 해당 파일을 생성해주는 로직이 필요하여 다음과 같이 수정하였다.
나머지 lane 을 실행하는 것은 Android 에서 옵션에 따른 배포 실행과 동일하다.
- name: 옵션에 따른 배포 실행
working-directory: ios
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
KEY_ID: ${{ secrets.ASC_KEY_ID }}
ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
KEY_CONTENT: ${{ secrets.ASC_KEY_P8 }}
APP_IDENTIFIER: ${{ secrets.APP_IDENTIFIER }}
FIREBASE_APP_ID_SANDBOX: ${{ secrets.FIREBASE_APP_ID_SANDBOX }}
FIREBASE_APP_ID_PRODUCTION: ${{ secrets.FIREBASE_APP_ID_PRODUCTION }}
run: |
COMMIT_MSG=$(git log -1 --pretty=%B)
if [[ "$COMMIT_MSG" =~ deploy:[1-4] ]]; then
DEPLOY_OPTION=$(echo "$COMMIT_MSG" | grep -o 'deploy:[1-4]' | cut -d':' -f2)
else
echo "배포 옵션이 지정되지 않았습니다. 기본 옵션(1) 사용"
DEPLOY_OPTION="1"
fi
echo "🔍 선택된 배포 옵션: $DEPLOY_OPTION"
case "$DEPLOY_OPTION" in
"1")
# 개발 환경의 Firebase 연동 관련 Info.plist 생성
echo "${{ secrets.IOS_GOOGLE_SERVICE_DEV_PLIST }}" > Runner/GoogleService-Info.plist
echo "[sandbox] Firebase App Distribution 배포 시작"
fastlane deploy_sandbox_to_firebase key_id:$KEY_ID issuer_id:$ISSUER_ID key_content:$KEY_CONTENT app_identifier:$APP_IDENTIFIER
;;
"2")
# 운영 환경의 Firebase 연동 관련 Info.plist 생성
echo "${{ secrets.IOS_GOOGLE_SERVICE_PROD_PLIST }}" > Runner/GoogleService-Info.plist
echo "[production] Firebase App Distribution 배포 시작"
fastlane deploy_prod_to_firebase key_id:$KEY_ID issuer_id:$ISSUER_ID key_content:$KEY_CONTENT app_identifier:$APP_IDENTIFIER
;;
"3")
# 운영 환경의 Firebase 연동 관련 Info.plist 생성
echo "${{ secrets.IOS_GOOGLE_SERVICE_PROD_PLIST }}" > Runner/GoogleService-Info.plist
echo "[production] TestFlight 배포 시작"
fastlane deploy_to_testflight key_id:$KEY_ID issuer_id:$ISSUER_ID key_content:$KEY_CONTENT app_identifier:$APP_IDENTIFIER
;;
"4")
# 운영 환경의 Firebase 연동 관련 Info.plist 생성
echo "${{ secrets.IOS_GOOGLE_SERVICE_PROD_PLIST }}" > Runner/GoogleService-Info.plist
echo "[production] App Store 배포 시작"
fastlane deploy_to_app_store key_id:$KEY_ID issuer_id:$ISSUER_ID key_content:$KEY_CONTENT app_identifier:$APP_IDENTIFIER
;;
*)
echo "❌ Invalid deployment option selected: $DEPLOY_OPTION"
exit 1
;;
esac
아무래도 .ipa
와 .xcarchive
를 Flutter CLI 를 이용하여 만들다보니 다른 Lane 에 비해 로그 출력도 2배 이상 많고, 시간 또한 더 오래걸렸다(15분). .xcconfig
로 --dart-define=FLAVOR
를 전달하는 첫번째 방법을 이용하고 Fastfile에서 fastlane core 에서 지원하는 build_app
을 사용했다면 시간이 더 단축되었을 것이다.
따라서 이러한 이유로 인해 방법 2(명령어에 포함시켜 직접 빌드) 보다 방법 1(xcconfig 를 통한 전달)을 권장했던 것이다.
▲ 그림 13. [Option 1] 실행 결과
▲ 그림 14. 'sandbox' 용 Firebase 업로드 결과
▲ 그림 15. [Option 2] 실행 결과
▲ 그림 16. 'sandbox' 용 Firebase 업로드 결과
다음은 기존의 배포 파이프라인에서 Firebase App Distribution
도입을 통한 알파 테스트 구축 및 개선한 결과이다.
▲ 그림 17. 배포 파이프라인 개선
이를 통해 도입된 Firebase App Distribution 기반의 테스트 배포는 다음과 같은 실질적 이점을 제공한다:
- 배포 속도 향상 및 테스트 피드백 루프 단축
- Play Console의 내부 테스트 채널은 빌드 반영까지 평균 30분~1시간 가량의 지연이 발생하지만, Firebase App Distribution을 활용할 경우 업로드 즉시 배포가 가능하여 알파 테스터의 피드백을 더 빠르게 수집할 수 있다.
- 결과적으로 QA 반복 주기를 단축하고, 기능 안정화 기간을 앞당길 수 있다.
- 테스트 대상에 따른 배포 채널의 전략적 분리
- 내부 검증(Firebase): 개발자, QA 인력 등 조직 내부 인원이 주도하는 테스트는 Firebase를 통해 즉각적인 배포와 회수, 버전 전환이 가능하며, Slack 등 협업 도구와도 쉽게 연동 가능하다.
- 외부 검증(Play Console): 실 사용자 기반의 베타 테스트나 외부 파트너 대상 검증은 Play Console을 통해 안정적으로 운영함으로써 테스트 안정성과 정책 준수, 사용자 경험 측면에서 유리하다.
- 테스트 단계별 품질 게이트 확보
- 알파(Firebase) → 베타(Play Console) → 프로덕션 배포로 이어지는 명확한 릴리즈 파이프라인을 구축함으로써, 각 단계마다 품질 검증 기준을 수립하고 이탈 없는 배포 전략을 운용할 수 있다.