① 배운 것
//workflows.yml
name: IOS CD Release
on:
push:
tags:
- "v_dev_*"
- "v_prod_*"
jobs:
release:
runs-on: macos-latest
steps:
# 1. 코드 체크아웃
- name: Checkout repository
uses: actions/checkout@v3
# 2. SSH 키 설정 - fastLane match에서 사용
- name: Setup SSH
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
known_hosts: ${{ secrets.KNOWN_HOSTS }}
# 3. Ruby 설정 및 캐싱
- name: Setup Ruby with caching
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0
bundler-cache: true
# 4. Fastlane 캐싱
- name: Cache Fastlane
uses: actions/cache@v3
with:
path: |
~/.bundle
~/.fastlane
key: ${{ runner.os }}-fastlane-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-fastlane-
# 5. Install Fastlane
- name: Install Fastlane
run: |
cd ios
gem install bundler
bundle install
- name: Cache Flutter dependencies
uses: actions/cache@v3
with:
path: ${{ matrix.flutter_path }}
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-flutter-
# 6. Flutter 설치
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
channel: 'stable'
#7. github에서 가져오는 dependency에 접근하기위한 설정
- name: Configure Git
run: |
git config --global url."https://${{ vars.USER_NAME }}:${{ secrets.TOKEN_GITHUB }}@github.com/".insteadOf "https://github.com/"
# 8. Flutter 의존성 설치
- name: Install Dependencies
run: flutter pub get
# 9. config.dart 생성
- name: Generate Config
run: |
echo '${{ secrets.CONFIG }}' | base64 --d >> ./lib/common/config.dart
# 10. Fastlane 실행
- name: Build and Upload to TestFlight
run: |
cd ios
if [[ "${GITHUB_REF}" == refs/tags/v_dev_* ]]; then
echo "APP_IDENTIFIER=co.example.example.dev" >> $GITHUB_ENV
echo "MATCH_TYPE=adhoc" >> $GITHUB_ENV
echo "GIT_URL=https://github.com/..." >> $GITHUB_ENV
fastlane release environment:dev
elif [[ "${GITHUB_REF}" == refs/tags/v_prod_* ]]; then
echo "APP_IDENTIFIER=co.example.example" >> $GITHUB_ENV
echo "MATCH_TYPE=appstore" >> $GITHUB_ENV
echo "GIT_URL=https://github.com/..." >> $GITHUB_ENV
fastlane release environment:prod
else
echo "Tag does not match any pattern. Exiting..."
exit 1
fi
env:
CI: true
APPLE_ID: ${{ secrets.APPLE_ID }}
TEAM_ID: ${{ secrets.TEAM_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
FIREBASE_CLI_TOKEN: ${{ secrets.FIREBASE_CLI_TOKEN }}
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID_IOS_DEV }}
APP_DISTRIBUTION_GROUPS: ${{ vars.APP_DISTRIBUTION_GROUPS }}
달라진점은, 'Build and Upload to TestFlight'에서 tag에 따라 필요한 환경변수를 설정하고 어떤 fastlane을 실행시킬지 결정한다.
#fastfile
default_platform(:ios)
platform :ios do
desc "Deploy to App Distribution or TestFlight"
lane :release do |options|
# 옵션 처리
environment = options[:environment] || "dev" # 기본값: dev
flavor = environment == "prod" ? "prod" : "dev"
export_method = environment == "prod" ? "app-store" : "ad-hoc"
provisioning_profile_name = environment == "prod" ? "AppStore" : "AdHoc"
app_id = environment == "prod" ? "co.example.example" : "co.example.example.dev"
# CI 환경 설정
setup_ci if ENV['CI']
# 인증서 및 프로비저닝 프로파일 설정
match(
git_url: environment == "prod" ? "git@github.com:..." : "git@github.com:...",
app_identifier: [
app_id,
"#{app_id}.NotificationServiceExtension"
],
type: export_method == "app-store" ? "appstore" : "adhoc",
readonly: true
)
# 버전 관리
pubspec = YAML.load_file("../../pubspec.yaml")
version = pubspec["version"].split('+')
increment_version_number(
version_number: version[0],
xcodeproj: "Runner.xcodeproj"
)
increment_build_number(
build_number: version[1],
xcodeproj: "Runner.xcodeproj"
)
# 코코아팟 업데이트 및 빌드
cocoapods(
repo_update: true,
clean_install: true,
use_bundle_exec: false
)
# Flutter 빌드
sh("flutter build ios --release --flavor #{flavor} --no-codesign")
# 앱 빌드
build_app(
workspace: "Runner.xcworkspace",
scheme: flavor,
export_method: export_method,
export_options: {
provisioningProfiles: {
app_id => "match #{provisioning_profile_name} #{app_id}",
"#{app_id}.NotificationServiceExtension" => "match #{provisioning_profile_name} #{app_id}.NotificationServiceExtension"
}
}
)
# 배포 처리
if environment == "prod"
# TestFlight 업로드
upload_to_testflight(
skip_waiting_for_build_processing: true,
apple_id: ENV["APPLE_ID"],
team_id: ENV["TEAM_ID"]
)
slack(message: "TestFlight 배포 완료 🎉", slack_url: ENV["SLACK_WEBHOOK_URL"])
else
# Firebase App Distribution 업로드
firebase_app_distribution(
app: ENV["FIREBASE_APP_ID"],
firebase_cli_token: ENV["FIREBASE_CLI_TOKEN"],
groups: ENV["APP_DISTRIBUTION_GROUPS"]
)
slack(message: "IOS Dev App Distribution 배포 완료 🎉", slack_url: ENV["SLACK_WEBHOOK_URL"])
end
end
error do |lane, exception, options|
# 배포 실패 시 Slack 알림
slack(
message: "IOS 자동배포 실패 🥲\n #{exception}",
success: false,
slack_url: ENV["SLACK_WEBHOOK_URL"]
)
end
end
여기도 달라진점은, lane 이름으로 들어온 options에 따라 환경변수를 설정하고 options에 따라 testflight에 올릴지 app distribution에 올릴지 결정한다.
appfile과 matchfile에서도 환경에 따라 구분되어야 하는 변수들은 workflow에서 설정해서 받음
#appFile
app_identifier(ENV['APP_IDENTIFIER']) # The bundle identifier of your app
apple_id("...") # Your Apple Developer Portal username
itc_team_id("...") # App Store Connect Team ID
team_id("...") # Developer Portal Team ID
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
#matchFile
git_url(ENV['GIT_URL'])
storage_mode("git")
type(ENV['MATCH_TYPE'])
# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
# username("user@fastlane.tools") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
② 회고 (restropective)
ios는 플러터 빌드 후 아카이브를 따로 해줘야하는 부분이 너무너무 귀찮았는데 이것을 자동으로 할 수 있어서 완전 편하다.
그런데 github에서 제공하는 Ruuner가 너무 느리다. 거의 20분 넘게 걸림. 특히 build하고 testflight에 올리는 부분이 너무 느린데, 흠..🤔 찾아보니까 젠킨스를 이용하면 훨씬 빠르게 할 수 있다는 후기가 있어서 나중에 백앤드 분들한테 부탁해서 젠킨스로 옮겨봐야겠다!!
③ 개선을 위한 방법
이제 안드로이드 자동배포까지 가보자고
캐시해두니까 두번째 실행부터는 빨라진거같기도하고? 전체 워크프로우 시작~끝 까지 12분 정도 걸림