이대로만 하면 iOS 앱을 fastlane과 CircleCI을 이용하여 자동 빌드 및 배포 완성(CI/CD 파이프라인)
제대로 정리해 놓은 글이 왜 이렇게 없을까?!
Github Action으로 구성했다가... 무료 200분의 압박으로 CircleCI로 넘어갔다.
CircleCI의 장점을 간략히 정리하면
- 무료시간 빌드 사용 가능 시간이 더 길다.
- 최신 Xcode 지원 (GitHub Action에서 실행되는 MacOS 안 Xcode 14.2가 최신이라니...)
정도가 되겠다.
※ CI/CD 파이프라인이란?
지속적 통합 (Continuous Integration, CI) 및 지속적 배포 (Continuous Deployment, CD)를 지원하기 위한 자동화된 프로세스를 의미.
이 파이프라인은 소프트웨어 개발 및 배포 과정을 효율적으로 관리하고, 소프트웨어 제공 속도를 높이고 품질을 향상시키는 데 도움이 된다.
환경
- LOCAL : Mac Mini M1 16GB
1. Github에 업로드된 프로젝트를 로컬에서 내려받은 상태에서 fastlane을 연동해 주어야 한다.
fastlane는 왜 연동해 주어야 하는걸까?
- fastlane은 iOS App Build시 필요한 대다수 모든 절차를 간편하고 쉽고 연동할 수 있게 해주기 때문이다.
- 자동 빌드 배포를 구성하려고 한다면 되도록 사용을 권장한다!
// 터미널에서 아래 명령어로 설치 가능
brew install fastlane
만약 M1 칩으로 인한 오류가 발생 시 아래 명령어로 대체 해서 해보자!
arch -arm64 brew install fastlane
2. fastlane이 설치가 된 상태에서 프로젝트 폴더로 이동 후 터미널 실행해서 아래 명령어 입력
fastlane init
![](https://blog.kakaocdn.net/dn/EVztC/btsFNb0UlNC/q7oB68VzbF58nOStTWg990/img.png)
이런 형태로 출력 되면 4번을 선택하자!
(이후 무언가 절차가 더 있다면 아무렇게 해도 무관하다... 아래 파일들 직접 만들거나 내용 수정하면 동일하기 때문!)
3. fastlane init가 완료 되면 프로젝트 폴더 안 fastlane 폴더가 생성된다. 기본으로 Appfile과 Fastfile이 자동으로 생성된다.
프로젝트 디렉터리 안에 Gemfile도 생성된다.
- Appfile은 따로 내용을 적어 줄 필요는 없다.
- Fastfile 안 내용은 다음과 같이 구성한다.
# CircleCI에서 설정한 변수를 그대로 가져오지 못하는 케이스가 있어 base64 인코딩/디코딩을 활용한다.
require 'base64'
# iOS 플랫폼을 선언
default_platform(:ios)
# platform 다시한번 선언
platform :ios do
# 해당 작업에 대한 설명 작성
desc "Push a new version build to Distribution"
# lane : 다음에 오는 release 부분이 추후 do 아래 항목을 실행시키는 명령어로 사용된다.
lane :release do
# Base64.decode64를 이용해서 각 환경 변수를 가져오는 부분 환경 변수 설정은 아래 부분에서 참고하자.
# ※ 4. (1) 부분에서 설명 ※
# 그리고 strip를 사용하지 않으면 간혹 개행 공백 문자가 포함되어 러닝 오류가 발생되니 넣어주자.
# 왜 Base64를 사용하는지는 CircleCI 환경변수 관련 부분에서 참고하자.
decoded_API_KEY_CONTENT = Base64.decode64(ENV['API_KEY_CONTENT']).strip
decoded_API_KEY_NAME = Base64.decode64(ENV['API_KEY_NAME']).strip
decoded_API_KEY_ISSUER = Base64.decode64(ENV['API_KEY_ISSUER']).strip
decoded_FIREBASE_APP_ID = Base64.decode64(ENV['FIREBASE_APP_ID']).strip
decoded_FIREBASE_TOKEN = Base64.decode64(ENV['FIREBASE_TOKEN']).strip
# puts을 통해 제대로 디코딩 되었는지 확인 (이 부분은 제거해도 무관하다.)
puts "API_KEY_CONTENT: #{decoded_API_KEY_CONTENT}"
puts "API_KEY_NAME: #{decoded_API_KEY_NAME}"
puts "API_KEY_ISSUER: #{decoded_API_KEY_ISSUER}"
puts "FIREBASE_APP_ID: #{decoded_FIREBASE_APP_ID}"
puts "FIREBASE_TOKEN: #{decoded_FIREBASE_TOKEN}"
# fastlain에서 제공하는 app_store_connect_api_key 함수를 이용하여
# 앱스토어 커넥트에 로그인 할 수 있는
# api_key를 구성한다. 필요한 이유와 각 키를 구하는 방법은 아래에서 설명한다.
# ※ 4. (1) 부분에서 설명 ※
api_key = app_store_connect_api_key(
key_id: decoded_API_KEY_NAME,
issuer_id: decoded_API_KEY_ISSUER,
key_content: decoded_API_KEY_CONTENT,
duration: 1200,
in_house: false
)
# 인증서(.p12) 및 프로비저닝 프로파일(.mobileprovision) 파일의 경로 설정
# 여기서 인증 서 및 프로비저닝 프로파일은 git 프로젝트와 동일 선상에 푸시되어 있어야 한다!
cert_path = "<p12 cert 파일 경로 및 이름 지정>.p12"
cert_password = "<p12 파일 추출 시 비밀번호 입력>"
profile_path_ios = "<target 1 provision 파일 경로 및 이름 지정>.mobileprovision"
profile_path_widget = "<target 2 provision 파일 경로 및 이름 지정>.mobileprovision"
# CI/CD 파이프라인에서 사용되어 빌드 및 배포 프로세스에서 보안 인증서, 프로비저닝 프로필
# 및 기타 보안 관련 정보를 관리하는 데 사용하는 용도로 keychain을 생성한다.
create_keychain(
name: "<키체인 이름 지정>",
password: "<키체인 비밀번호 지정>",
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
# import_certificate 함수를 이용하여 특정 인증서(.p12 파일 형식)를 시스템의 키체인에
# 추가하는 데 사용한다.
import_certificate(
certificate_path: cert_path,
certificate_password: cert_password,
keychain_name: "<create_keychain으로 생성한 이름>",
keychain_password: "<create_keychain으로 생성한 비밀번호>"
)
# 프로비저닝 프로파일 설치하기 target 다중일 경우 profile_path_를 여러개 만들어
# 각 프로비저닝 프로파일을 설치한다. (Mac에서 더블 클릭과 같은 효과!?)
install_provisioning_profile(
path: profile_path_ios
)
install_provisioning_profile(
path: profile_path_widget
)
# CocoaPods 종속성 설치 아래 두 경우 중 하나만 기입하자.
# - 단순 설치 시 pod install에 해당
cocoapods
# - pod update 를 원할 경우
podfile(
update: true
)
# 실제 Git에 올라가 있는 코드를 기준으로 앱을 빌드한다.
# - export_method : "app-store" 는 배포용 빌드 옵션이다.
# - export_options 안 provisioningProfiles : Xcode 빌드 시스템에 앱 빌드 및
# 서명을 위해 사용할 프로비저닝 프로파일을 지정하는 데 사용되는 옵션이다.
build_app(
scheme: "<App Target 이름>",
export_method: "app-store", # 필요한 다른 옵션 추가
export_options: {
provisioningProfiles: {
"<Terget Identifier 1>": "<Profile Name 지정1>",
"<Terget Identifier 2>": "<Profile Name 지정2>"
}
}
)
# 내부 테스트 용으로 테스트만 할 경우 Firebase Appdistribution으로 배포 후 슬랙으로 안내하고
# 배포 용일 경우 testflight로 업로드 하도록 구성한다.
# - ONLY_TEST 변수는 CircleCI 설정 파일에서 넘겨준다.
# - skip_waiting_for_build_processing 옵션을 넣으면 TestFlight에 앱이 올라 올때 까지
# 대기하지 않는다.
if ENV['ONLY_TEST'] == 'false'
# upload_to_testflight만 해줘도 심사 요청이 가능했다.
upload_to_testflight(
skip_submission: true,
api_key: api_key,
skip_waiting_for_build_processing: true
)
# upload_to_app_store(
# skip_metadata: true,
# skip_screenshots: true,
# api_key: api_key,
# skip_waiting_for_build_processing: true
# )
else
# 가장 마지막 Git 커밋 메시지 가져오기
commit_message = last_git_commit[:message]
# firebase_app_distribution 함수를 이용하여 Firebase AppDistribution으로 자동 배포
# - release_notes 부분에 깃에서 가져온 commit_message를 입력한다.
# - 각 변수 값을 가져오는 방법은 아래에서 설명
# ※ 4. (1) 부분에서 설명 ※
firebase_app_distribution(
app: decoded_FIREBASE_APP_ID,
groups:"<그룹 이름1>, <그룹 이름2>",
release_notes: "#{commit_message}",
firebase_cli_token: decoded_FIREBASE_TOKEN
)
# 1.0.0 형태로 나오는 버전 정보를 가져온다.
version_number = get_version_number(
xcodeproj: "ios.xcodeproj",
target: "ios"
)
# Xcode - Target - General - Build 값을 가져온다.
build_number = get_build_number(
xcodeproj: "ios.xcodeproj"
)
# build_number대신 Info.plist 안 BundleVersion으로 사용을 원할 경우 아래 부분으로 사용가능
bundle_version = get_info_plist_value(
path: "ios/Info.plist",
key: "CFBundleVersion"
)
# 위에서 가져온 버전 정보 및 내용을 slack_url (slack web hooks)를 이용하여 전송한다.
# - payload에 "version" 항목을 넣게 되면 슬랙에서 버전 항목이 추가되어 별도로 표시된다.
# 아래에서 확인하자.
# ※ 4. (2) 부분에서 설명 ※
slack(
message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
slack_url: "<hooks.slack.com 해당 값 넣기>",
payload: {
"version": "v#{version_number} (#{build_number})"
}
)
end
end
Fastfile에서 추후 확인해야 할 것은 뒤에서 차차 진행하도록 하며 다음 파일내용을 설명한다.
- Gemfile 안 내용은 다음과 같이 구성한다. (만약 없다면 파일을 그냥 만들면 된다.)
source "https://rubygems.org"
gem "fastlane"
# 위 2줄은 기본으로 생성이 되었을 거다 아래 cocoapods gem 을 명시적으로 추가 하자
gem 'cocoapods'
# Firebase app distribution 플러그 인 설치하기 부분이다.
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
Gemfile 안에서 Firebase app distribution 플러그인을 설치 했기 때문에 프로젝트 폴더 안 fastlane 폴더 안에 Pluginfile을 만들어 아래와 같이 넣어주자!
- Pluginfile 안 내용은 다음과 같이 구성한다.
# Firebase app distribution 플러그 인 명시
gem 'fastlane-plugin-firebase_app_distribution'
이렇게 구성하면 프로젝트 폴더 안 fastlane 폴더 안에는 아래와 같이 구성되어 있어야 한다.
ls -al fastlane/
total 56
drwxr-xr-x@ 8 hyeonggyulim staff 256 3 6 11:13 .
drwxr-xr-x@ 20 hyeonggyulim staff 640 3 6 11:13 ..
-rw-r--r--@ 1 hyeonggyulim staff 242 3 4 16:12 Appfile
-rw-r--r--@ 1 hyeonggyulim staff 4310 3 6 15:55 Fastfile
-rw-r--r--@ 1 hyeonggyulim staff 131 3 5 14:54 Pluginfile
4. 그럼 이제 Fastfile에서 아래에서 설명한다는 부분을 하나씩 살펴보자.
(1) Fastfile에서 사용하는 각 변수를 구하는 방법을 알아보자!
※ 각 변수의 실제 할당은 CircleCI 또는 GitHub Action 이용시 각 환경에 맞는 환경변수로 등록해야 하기 때문에 뒤에서 다시 안내한다.
※ 보안을 위해 Base64 디코딩을 하였다. 이 부분은 CircleCI 환경변수 등록 부분에서 설명하며, Base64 디코딩은 하지 않아도 무관하다.
# Base64.decode64를 이용해서 각 환경 변수를 가져오는 부분 환경 변수 설정은 아래 부분에서 참고하자.
# 그리고 strip를 사용하지 않으면 간혹 개행 공백 문자가 포함되어 러닝 오류가 발생되니 넣어주자.
decoded_API_KEY_CONTENT = Base64.decode64(ENV['API_KEY_CONTENT']).strip
decoded_API_KEY_NAME = Base64.decode64(ENV['API_KEY_NAME']).strip
decoded_API_KEY_ISSUER = Base64.decode64(ENV['API_KEY_ISSUER']).strip
decoded_FIREBASE_APP_ID = Base64.decode64(ENV['FIREBASE_APP_ID']).strip
decoded_FIREBASE_TOKEN = Base64.decode64(ENV['FIREBASE_TOKEN']).strip
# puts을 통해 제대로 디코딩 되었는지 확인 (이 부분은 제거해도 무관하다.)
puts "API_KEY_CONTENT: #{decoded_API_KEY_CONTENT}"
puts "API_KEY_NAME: #{decoded_API_KEY_NAME}"
puts "API_KEY_ISSUER: #{decoded_API_KEY_ISSUER}"
puts "FIREBASE_APP_ID: #{decoded_FIREBASE_APP_ID}"
puts "FIREBASE_TOKEN: #{decoded_FIREBASE_TOKEN}"
> app_store_connect_api_key 를 구하는데 사용되는 변수 API_KEY_NAME / API_KEY_ISSUER / API_KEY_CONTENT 3가지를 알아보자.
app_store_connect_api_key가 먼저 왜 필요할까?
app_store_connect_api_key가 필요한 이유는 각 단계에서 애플에 로그인이 필요한 단계에서 기존 애플 ID / Password로 로그인을 하게 되면 이중인증이 발생하기 때문에 인중인증 없이 로그인을 하기위한 용도로 생각하면 된다.
fastlane match를 통한 인증서 관리 / Testflight 자동 배포 / AppStore 업로드 시 사용하게 된다.
※ 여기서는 match를 사용하지 않고 인증서 & 프로비저닝프로파일을 깃에 업로드 하여 사용하는 방식을 이용했다. (match 사용을 원할 경우 fastlane Matchfile을 구성해 주어도 좋다)
※ 이 방법으로 진행을 할 경우 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD를 이용한 인증 방식은 이용하지 않아도 된다!
※ 또한 app_store_connect_api_key를 이용하는 방법이 보다 안전한 방법이라고 한다!
[API_KEY_NAME / API_KEY_ISSUER / API_KEY_CONTENT 발급 절차]
Step1. https://appstoreconnect.apple.com 사이트로 이동하여 애플 계정으로 로그인 한다.
(⚠️ 앱 소유자 계정으로 해야 한다.)
Step2. 사용자 및 액세스 → 통합 → App Store Connect API 화면으로 이동하면 Issuer ID가 먼저 보인다.
이 값을 API_KEY_ISSUER 변수에 할당할 예정이니 기록!
![](https://blog.kakaocdn.net/dn/sfGpu/btsFLQ4lCVV/0XkBz6CiGgeho9EdZ0HqNK/img.png)
Step3. 위 화면 아래 + 버튼을 클릭하여 API 키를 생성한다.
![](https://blog.kakaocdn.net/dn/clqUQM/btsFJKKX5S2/Q2P1kHKZ9lNyk1jOMLWt21/img.png)
식별하기 편한 이름을 적고 액세스 권한은 관리자로 지정한다.
생서 되면 API 키 ID가 만들어지는데 이 값을 API_KEY_NAME으로 할당 예정이니 기록!
Step4. 만들고 나면 아래 화면 밑에 .p8 파일을 다운로드 할 수 있는 버튼이 있다. 이 파일을 다운받는다.
⚠️ 단, 최초1회 다운 후 더 이상 다운로드가 불가능하니 파일을 잘 관리하자!
![](https://blog.kakaocdn.net/dn/qLtuW/btsFLRoFkj2/kKDllgKC6MmR8LfYPxABNK/img.png)
Step5. 파일을 열어서 나오는 전체 문자를 API_KEY_CONTENT로 할당할 예정이니 기록!
대략 아래와 같은 행태이기 때문에 -----BEGIN PRIVATE KEY----- 부분에서 -----END PRIVATE KEY----- 부분까지 전체다 사용되어야 한다.
-----BEGIN PRIVATE KEY-----
.... 생략 ....
-----END PRIVATE KEY-----
> FIREBASE_APP_ID와 FIREBASE_TOKEN을 구하는 방법을 알아보자!
자동 빌드 후 Firebase AppDistribution으로 자동 배포를 하고 싶은 경우라면 이 두가지 값을 구해 주어야 한다.
만약, Firebase AppDistribution으로 배포하지 않고 TestFlight로 테스트 하는 경우라면 이 부분은 생략하자!
[FIREBASE_APP_ID 발급 절차]
Step1. https://console.firebase.google.com 으로 이동하여 내 프로젝트로 들어간 후 왼쪽 메뉴바에서 프로젝트 개요 옆 설정 버튼을 클릭 하자!
![](https://blog.kakaocdn.net/dn/bmhn5i/btsFMQipadi/J3wnhyvQZs0r4Ynvo1hs8K/img.png)
Step2. 설정 버튼 클릭 해서 노출되는 하위 메뉴들 중 프로젝트 설정 메뉴를 클릭 한다.
![](https://blog.kakaocdn.net/dn/cdlAAX/btsFK8Eksb7/5XVej8KINtW26uZou1NoWk/img.png)
Step3. 프로젝트 설정 화면 일반 탭 제일 하위에 보면 내 앱 부분이 있다. 여기서 자동 빌드를 구성할 타겟을 선택하자.
![](https://blog.kakaocdn.net/dn/9deh1/btsFK7rS1bC/GfqO71XIbRf8hzDIzgIbg0/img.png)
Step4. 내 앱 부분 타켓을 선택하면 SDK 설정 및 구성 부분 아래에 앱ID 라고 표시된 부분이 있다.
이 값을 FIREBASE_APP_ID 변수로 할당할 예정이다.
![](https://blog.kakaocdn.net/dn/bPMMsP/btsFMsvqcfK/lKOaD6QyRAnpGdFbfKHcRk/img.png)
[FIREBASE_TOKEN 발급 절차]
Step1. Firebase Token을 얻기 위해서는 Firebase CLI를 설치해야 한다. 터미널 실행 후 아래 명령어를 입력하자.
npm install -g firebase-tools
Step2. 설치 후 아래 명령어를 통해 firebase에 로그인 해주어야 한다.
firebase login
Step3. 위 명령어 입력 시 Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? 질문이 뜨며 n 선택 후 Waiting for authentication... 표시와 함께 GUI로 브라우저가 뜨게 된다.
브라우저에서 Firebase 프로젝트 접근 권한이 있는 계정으로 로그인 하자.
![](https://blog.kakaocdn.net/dn/emrirO/btsFKK4SWE0/XwQuO1s4knIzKcYobjbYbk/img.png)
Step4. 로그인이 완료 되면 Success! Logged in as <로그인한 이메일 주소> 로 표시가 뜨게된다.
이후 터미널에 아래 명령어를 입력하자.
cat ~/.config/configstore/firebase-tools.json
Step5. motd, usage, user, tokens 정보가 나오며 tokens 부분 refresh_token 값을 FIREBASE_TOKEN 변수로 할당할 예정이니 저장해 놓자!
![](https://blog.kakaocdn.net/dn/bOawsz/btsFLotr4M6/W5cGvHd2aIZxHiyBHGMUJ0/img.png)
(2) Fastfile에서 사용하는 slack 연동 방법을 알아보자!
# 위에서 가져온 버전 정보 및 내용을 slack_url (slack web hooks)를 이용하여 전송한다.
# - payload에 "version" 항목을 넣게 되면 슬랙에서 버전 항목이 추가되어 별도로 표시된다.
# 아래에서 확인하자.
slack(
message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
slack_url: "<hooks.slack.com 해당 값 넣기>",
payload: {
"version": "v#{version_number} (#{build_number})"
}
)
> Fastlane에서 slack 함수를 기본으로 제공하기 때문에 별도 추가 연동 없이 사용이 가능하다.
> slack 함수에서 필요한 부분은 slack_url로 보통 slack 웹훅 url 이라고 많이 이야기 한다.
[slack_url 발급 절차]
Step1. https://api.slack.com 사이트로 이동 후 로그인 한다.
![](https://blog.kakaocdn.net/dn/coCXGU/btsFM06dyBm/C6UkfjkdrdiWJkgLQCHdgk/img.png)
Step2. 오른쪽 상단 Your apps에서 앱을 하나 새로 만든다.
Step3. 앱을 만든 후 해당 앱을 선택하여 들어가면 왼쪽 메뉴가 아래 처럼 표시되며, Features 부분의 Incoming Webhooks 메뉴를 선택하자.
![](https://blog.kakaocdn.net/dn/4XBc6/btsFLqx19xQ/nnXNlUoXxIn6qdQgZ22A10/img.png)
Step4. Incoming Webhooks 로 이동 후 제일 하단 Webhook URL 부분 Add New Webhook to Workspace를 선택한다.
![](https://blog.kakaocdn.net/dn/pEVBY/btsFNuMIeOY/Mmxf6WjKkqVjoxQGVqlS6K/img.png)
Step5. Add New Webhook to Workspace 클릭 시 Slack API에 만든 앱과 Slack의 어느 채널로 연동할지 창이 뜨며, 어느 채널로 메시지를 보낼지 선택하고 허용을 누르자.
![](https://blog.kakaocdn.net/dn/ldDuk/btsFMqK6t65/1KEFrUKAnSNJOfkhAabSK1/img.png)
Step6. 허용 후 생성되는 Webook URL을 Fastfile 안 slack 부분 slack_url 에 넣어주면 된다.
# 1.0.0 형태로 나오는 버전 정보를 가져온다.
version_number = get_version_number(
xcodeproj: "ios.xcodeproj",
target: "ios"
)
# Xcode - Target - General - Build 값을 가져온다.
build_number = get_build_number(
xcodeproj: "ios.xcodeproj"
)
# build_number대신 Info.plist 안 BundleVersion으로 사용을 원할 경우 아래 부분으로 사용가능
bundle_version = get_info_plist_value(
path: "ios/Info.plist",
key: "CFBundleVersion"
)
slack(
message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
slack_url: "<hooks.slack.com 해당 값 넣기>",
payload: {
"version": "v#{version_number} (#{build_number})"
}
)
[slack 예시 화면]
iOS_Distribution 이란 이름과 그 앞 아이콘은 앞서 '[slack_url 발급 절차] Step2' 에서 생성한 앱의 이름과 아이콘이 자동으로 표시된다.
※ 이미 슬렉으로 전송된 메시지 라고 하더라도 생성한 앱 이름과 아이콘을 바꾸면 전송된 메시지에도 일괄적으로 변경된다.
- payload 배열 안에 version으로 할당해 주면 아래 이미지 처럼 버전 부분이 들어간 것을 볼 수 있다.
- Git Commit를 보여주기 때문에 push 할때 내용을 잘 작성하면 테스터들에게 별도로 메시지를 주지 않아도 될듯 하다.
![](https://blog.kakaocdn.net/dn/LeBlx/btsFJ7FWi5T/ktw6BHPGTQJycaA6El1lsK/img.png)
5. fastlane 관련 설명 부분
(1) fastlane을 이용한 iOS 프로비저닝 관리
> 자동 빌드 배포를 위해서는 Xcode 프로젝트 안 Targets -> Signing & Capabilities 부분의 Automatically manage signing 체크가 해제가 된 상태여야 하며, Git에 업로드 시 해제 된 상태로 올라가 있어야 한다.
나는 수동으로 인증서와 프로비저닝을 만든 후 Git에 업로드 하여 CI/CD 환경에서 사용하도록 구성했다.
> 왜 Automatically manage signing 체크가 해제가 된 상태여야 할까?
Automatically manage signing 옵션이 켜져 있으면 Xcode가 코드 사이닝 및 프로비저닝 프로파일을 자동으로 관리하기 때문에 Fastlane은 Fastfile에 정의된 코드 사이닝 및 프로비저닝 관련 명령을 무시하고 Xcode에서 설정한 값들을 사용하게 되어 빌드 오류가 발생되기 때문이다.
⚠️ 여기서 주의할 부분은 Distribution 배포를 자동화 할 경우 Git에 업로드 되어 있는 .xcodeproj 파일 안 Signing 부분에 Automatically manage signing 해제 후 실제 배포에 사용하는 프로비저닝으로 선택되어 있는 상태로 git에 올려야 한다! (다른 Profile이 선택된 상태로 Git에 올라갈 경우 CI/CD 빌드는 error 65가 발생)
![](https://blog.kakaocdn.net/dn/dsTWra/btsFLtO3mog/RSRFVAZqaHKG7qyn9xb8o0/img.png)
[Fastlane을 통한 프로비저닝 관리 방법]
Step1. 먼저 프로젝트 폴더 안 cert 폴더를 하나 만들자! (폴더 이름은 임의로 만들어도 무방하다)
Step2. 프로젝트 폴더 안 cert 폴더 안에 인증서와 프로비저닝 프로파일을 생성하여 넣어둔다.
.p12 파일과 .mobileprovision 파일로 구성되어 있다.
⚠️ 인증서(.p12) 파일 추출 시 지정한 비밀번호를 잘 기억했다가 앞서 작성하라고 안내된 Fastfile 안 cert_password 의 값에 넣어 주어야 한다.
![](https://blog.kakaocdn.net/dn/CMsDj/btsFK42376l/6eD4rrJgxmHzqSEHcex4W1/img.png)
※ 여기서 잠깐! 인증서와 프로비저닝프로파일 생성 방법을 모른다면 아래('인증서 및 프로비저닝 생성 방법')에서 확인하자!
Step3. 프로젝트를 Xcode로 실행하여 빌드 대상 모든 타겟에 대해 Provisioning Profile을 Step2에서 저장한 Provisioning Profile로 수동 설정한다.
![](https://blog.kakaocdn.net/dn/2PIbP/btsFM2QvctB/tfzeZOfjCwtL4BXHiGGvR0/img.png)
사진 설명을 입력하세요.
Step4. 수정 후 해당 내용을 Git에 배포하면 Fastlane을 통해 인증서 연동이 완료 된다.
[인증서 및 프로비저닝 생성 방법]
> 먼저 인증서 생성의 경우 Xcode를 통해 손쉽게 생성이 가능하다!
Step1. Xcode 실행 후 상단 Xcode 메뉴를 클릭하여 Settings 메뉴로 이동한다.
![](https://blog.kakaocdn.net/dn/Pr97I/btsFNm83GqR/a77D4SwTlFGlFFG4R5bnQK/img.png)
Step2. Settings 화면 Accounts 화면에 나의 애플 계정을 로그인 하고 오른쪽 AppID 영역 아래 Team 부분에서 인증서를 생성할 Team 이름을 더블 클릭하자!
![](https://blog.kakaocdn.net/dn/OILYj/btsFM06dyEU/IOACcDhQ8XZv8ylBshQN90/img.png)
Step3. Signing certificates for 화면이 나오면 왼쪽 하단 + 버튼을 눌르자!
![](https://blog.kakaocdn.net/dn/be2Kmg/btsFMZM0i4Z/cQ7OgaCB5Pv8yOPa8H0zVk/img.png)
Step4. + 버튼을 통해 출력되는 메뉴 중 Apple Distribution 항목을 눌러주자!
![](https://blog.kakaocdn.net/dn/bcWR6z/btsFLV5ApSH/o2lCK0juNG1lwjCsDGIKsk/img.png)
Step5. 이후 키체인 접근 앱을 실행하여 인증서 부분에 생성한 Apple Distribution을 찾은 후 화살표를 펼친다.
※ 여기서 잠깐! 키체인 접근 인증서 부분에 생성한 인증서가 없다면 https://developer.apple.com/account/resources/certificates/list 로 이동하여 생성한 인증서 파일을 수동으로 다운 받고 더블클릭 해주면 된다!
![](https://blog.kakaocdn.net/dn/C9kon/btsFNwqdtDI/YNsVnmTOAwYKmryLbgkAGK/img.png)
Step6. 펼쳐진 상태에서 두개 항목을 선택하고 마우스 우클릭 시 노출되는 2개 항목 내보내기를 통해 .p12 인증서 파일을 내려 받을 수 있다!
이때 비밀번호를 요구하게 된다.
![](https://blog.kakaocdn.net/dn/bQ1bZ2/btsFK4hKjH0/1P5mOfNMmkdjSL7X2oQOL0/img.png)
> 그 다음 프로비저닝 프로파일 생성방법을 알아보자!
Step1. Apple developer 사이트 안 프로파일 항목으로 https://developer.apple.com/account/resources/profiles/list 이동한다!
![](https://blog.kakaocdn.net/dn/dMMtSk/btsFK9ws5xV/ptbZgdEmcKYsQ1AGNpZS31/img.png)
Step2. Profiles 옆 + 버튼으로 각 타겟에 맞는 Profile을 만들고 다운받아주면 된다.
![](https://blog.kakaocdn.net/dn/pYhmP/btsFMN0gxco/wE067SnX7j38rxeDxkK1kK/img.png)
6. 사전 준비가 끝났다면 CircleCI 구성을 알아보자
(1) CircleCI 사이트 연동
> CircleCI는 지속적 통합 (Continuous Integration, CI) 및 지속적 배포 (Continuous Deployment, CD)를 지원하는 클라우드 기반의 자동화된 소프트웨어 개발 플랫폼이다.
> GitHub Action과 기능이 유사하지만 무료로 제공되는 사용시간이 더 길며, 최신 Xcode 버전을 지원하는 장점이 있어서 CircleCI로 구성했다.
[CircleCI 기본 구성 하기]
Step1. https://circleci.com 사이트에서 가입 및 기업을 생성한다.
Step2. 기업 등록 후 앱을 생성할때 소스를 가져올 저장소(GitHub)를 선택하여 레파지토리를 지정한다.
Step3. 첫 등록 시 https://app.circleci.com/pipelines/circleci/ CircleCI 대시보드에서 자동으로 config 구성을 하게 되지만 무시해도 된다.
![](https://blog.kakaocdn.net/dn/b0HUxm/btsFKaQhGJJ/QrldDqA0g9rf0r7EZZMajK/img.png)
Step4. 여기까지 했으면 기본 구성은 끝!
[CircleCI 프로젝트 안 사용 가능한 환경 변수 등록하기]
> CircleCI를 통해 Fastlane을 실행하는 구조이기 때문에 CircleCI 환경 변수를 등록하여 Fastlane에서 사용할 수 있도록 해야 한다.
Step1. CircleCI 대시보드에서 우측 상단 Project Settings로 이동한다.
![](https://blog.kakaocdn.net/dn/YB5v4/btsFM149cE4/1mlWe8gzv43NEIzNrR9GXk/img.png)
Step2. Project Settings 화면 우측 메뉴에서 Environment Variables를 선택한다.
![](https://blog.kakaocdn.net/dn/wyNWx/btsFM3IEaYf/brDjuoPsgekz9jAJZgOcuk/img.png)
Step3. Environment Variables 화면에서 앞서 구한 변수의 base64 인코딩 값을 넣어준다.
![](https://blog.kakaocdn.net/dn/TomeC/btsFJ34DMPR/RckTOgbtVP8lqI8TYKGPlk/img.png)
⚠️ 값을 그대로 넣어주어도 되지만 안전을 위해 Base64 인코딩을 권장한다.
Step4. base64 인코딩 값을 구하는 방법은 아래 명령어를 이용하자!
// 특정 파일을 인코딩 할 때 아래 명령어로 인코딩된 .txt 파일을 만들어 Step3 각 변수에 입력하자
base64 < '파일경로 및 이름.p8' > '파일경로 및 이름_base64.txt'
// 특정 문자열을 인코딩 할 때는 아래 명령어를 사용하자!
echo '문자열' | base64
(2) CircleCI 설정하기
> CircleCI 동작을 위해서는 <GitHub 레파지토리>/.circleci/config.yml 파일이 해당 위치에 존재 하여야 한다.
> 여기서는 해당 파일에 대해서 알아보자!
[config.yml 구성 하기]
Step1. <GitHub 레파지토리>/.circleci/config.yml 파일을 생성한다. (GitHub Add file 사용)
※ 자동 빌드를 구성하고자 하는 브런치 경로에서 만들어야 한다.
![](https://blog.kakaocdn.net/dn/be5HKw/btsFNlPO4pd/ekOzqNkxGlyHdffa5iNzP1/img.png)
Step2. config.yml 파일 내용은 아래와 같다.
# CircleCI 버전 2.1 사용
version: 2.1
# ruby orb: 이 orb는 Ruby 프로젝트를 위한 구성을 제공
# - circleci/ruby@1.1.2 버전의 orb를 사용하고 있으며, Ruby 프로젝트를 위한 환경 및 도구를 설정.
# - 이 orb를 사용하면 Ruby 프로젝트를 빌드하고 테스트하는 데 필요한 구성을 간편하게 사용할 수 있다.
# macos orb: 이 orb는 macOS 환경에서 작업하는 데 필요한 구성을 제공
# - circleci/macos@2.4.1 버전의 orb를 사용하고 있으며, macOS 환경에서 실행되는 작업을 설정.
# - 이 orb를 사용하면 macOS 환경에서 빌드 및 테스트를 수행하는 데 필요한 구성을 간편하게 사용할 수 있다.
orbs:
ruby: circleci/ruby@1.1.2
macos: circleci/macos@2.4.1
jobs:
build:
macos:
# 사용할 Xcode 버전 지정
xcode: "15.0.0"
environment:
# FL_OUTPUT_DIR: 이 환경 변수는 출력 디렉토리를 설정.
# - output으로 설정되어 있으므로, 프로젝트에서 생성된 출력 파일이나 결과물은 output 디렉토리에 저장.
# LC_ALL 및 LANG: 이 두 환경 변수는 로케일 설정을 지정.
# - en_US.UTF-8으로 설정되어 있으므로, 프로세스의 언어 및 문자 인코딩이 영어(미국)로 설정되고 UTF-8로 인코딩.
# - 이는 보통 다국어 프로젝트에서 특정 언어 및 문자 인코딩을 사용할 때 사용.
FL_OUTPUT_DIR: output
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
steps:
# git code checkout
- checkout
# feature/v로 시작하는 모든 브런치에서 Info.plist와 Prefix.swift 변경 여부를 확인하여
# - Firebase AppDistribtuion으로 배포할지, TestFligth로 배포 할지 결정하는 로직
- run:
name: Check if Info.plist has changed on feature/v* branches
command: |
# 가장 마지막으로 커밋한 정보를 가져온다.
PREVIOUS_COMMIT=$(git log -n 2 --pretty=format:"%H" | tail -n 1)
# 변경된 파일 리스트를 가져온다.
changed_files=$(git diff $PREVIOUS_COMMIT...$CIRCLE_BRANCH)
echo "Changed files:"
echo "$changed_files"
# feature/v로 시작하는 브랜치인지 확인
if [[ $CIRCLE_BRANCH == feature/v* ]]; then
echo "This is a feature/v* branch."
# Info.plist 파일이 마지막 커밋에서 변경되었는지 확인
if git diff --name-only $PREVIOUS_COMMIT...$CIRCLE_BRANCH | grep 'ios/Info.plist'; then
echo "ios/Info.plist has changed."
# Prefix.swift 파일이 마지막 커밋에서 변경되었는지 확인
if git diff --name-only $PREVIOUS_COMMIT...$CIRCLE_BRANCH | grep 'Prefix.swift'; then
echo "Prefix.swift has changed."
# 두개 파일 모두 변경 시 테스트 모드 false
echo 'export ONLY_TEST=false' >> $BASH_ENV
else
# ios/Info.plist 만 변경 시 테스트 모드 true
echo "Prefix.swift has not changed."
echo 'export ONLY_TEST=true' >> $BASH_ENV
fi
else
echo "ios/Info.plist has not changed. Stopping the job."
# 둘다 변경되지 않으면 circleci 종료
circleci step halt
fi
else
echo "This branch does not match the pattern 'feature/v*'. Stopping the job."
# 브런치가 feature/v 시작하지 않으면 circleci 종료
circleci step halt
fi
# restore_cache: 이 단계는 이전에 캐시된 의존성을 복원
# - Gemfile.lock 파일의 체크섬을 기반으로 이전에 캐시된 버전을 찾아 사용
- restore_cache:
key: v1-gems-{{ checksum "Gemfile.lock" }}
- run:
name: Install Bundler
command: gem install bundler
- run:
name: Bundle Install
command: bundle install --path vendor/bundle
# 설치된 종속성을 캐시로 저장. vendor/bundle 디렉토리의 내용을 캐시로 저장.
# - 이후 빌드에서 같은 종속성을 다시 설치할 필요가 없도록 캐시
- save_cache:
paths:
- vendor/bundle
key: v1-gems-{{ checksum "Gemfile.lock" }}
# 앞서 프로젝트에 연동한 fastlane 실행 부분
# - fastlane/Fastfile 안 구문 동작
- run:
name: Run Fastlane
command: bundle exec fastlane release
workflows:
version: 2
build_and_test:
jobs:
- build:
# filters 옵션을 사용하여 특정 브런치 동작 수행 가능
filters:
branches:
only:
- /^feature\/v.*/
※ workflows의 filters를 이용하여 특정 브런치로 제한 할 수 있지만 파일 변경 등에 대한 부분은 - run: 구간으로 작성해 주어야 했다.
7. CircleCI 동작 확인
![](https://blog.kakaocdn.net/dn/dx6NBJ/btsFMtHRNxN/yNaV4wkoHZFlVTlk7FrzRk/img.png)