안녕하세요. iOSangBong 입니다.
3탄으로 간단하게 CI/CD 적용기가 끝날 줄 몰랐지만
여러 문제가 있더라구요...
그래서 4탄으로 돌아올지 몰랐는데.. 다시 돌아왔습니다...
https://iosangbong.tistory.com/37
[iOS] GitLab CI + Fastlane CI/CD 적용기 - 3
안녕하세요. iOSangBong입니다.바로 이어서 GitLab Runner 설정을 해보겠습니다.https://iosangbong.tistory.com/36 [iOS] GitLab CI + Fastlane CI/CD 적용기 - 2안녕하세요. iOSangBong입니다.출근하자마자 블로그를 작성하
iosangbong.tistory.com
1️⃣ 현 상황
GitLab CI + Fastlane을 적용해보니
실제로는 원하는데로 작동하지 않더라구요.
문제 1. fastlane으로 increment_build_number를 했지만
info.plist의 CFBundleShortVersionString에 값이 하드코딩으로 고정되어
특정 빌드버전으로만 업로드되고 있었습니다...
ㄴ project.pbxproj 내에 CURRENT_PROJECT_VERSION 값이 Target마다 달랐기 때문입니다.
ㄴ agvtool 은 첫번째 값 기준으로 동작하기 때문에 항상 낮은 버전에서 +1 됐습니다.
2. SPM 라이브러리의 태그버전을 Resolve한 후에도
새로 업로드한 버전으로 업데이트 되는게 아닌
기존 버전으로 적용되서 TestFlight 업로드 되었으며
ㄴ Exact version으로 고정되어 있어 resolvePackageDependencies 실행시 원래 버전으로 돌아갔습니다.
3. (추가적으로) readme를 수정한 다음 빌드버전을 +1한 다음
GitLab에 커밋 푸시까지 해줘야 했습니다.
2️⃣ 원인 파악
1. 구조 파악
현 상황을 해결하기 위해서
CI/CD 구조를 파악해보겠습니다..

SPM 태그를 푸시하게 되면 트리거가 발생
↓
A앱과 B앱에서 연동하여 (readme 수정, 빌드번호 +1, Commit&Push, SPM 태그 버전업)
↓
Fastlane 으로 TestFlight 배포
이러한 과정으로 구성을 하였는데 아래와 같은 상황이였습니다.
- readme 수정, Commit & Push(미구현)
- SPM 태그 버전업(GitLab CI)
- 빌드번호 +1(fastlane)
.
.
.
난감하긴 하네요ㅋㅋㅋ
.
.
.
2. 역할 분석
CI/CD 역할을 잘못 분배하고 있었습니다.
처음 구현할때 CI와 CD의 역할을 명확히 구분하지 않았습니다.
CI : 코드 push/trigger가 감지되면 실행되며 빌드 및 테스트를 진행
- 트리거 감지(태그, Push)
- 환경변수 관리
- 빌드번호 변경
- Commit & Push
- readme 업데이트
- SPM 버전 업데이트
CD : 앱 아카이브를 진행하고 TestFlight/App Store 업로드
- 코드 서명
- 아카이브(.ipa 생성)
- TestFlight 업로드
처음에 구현할때는 역할도 잘못 구현하였었네요...
3. 현재 구조
1. SPM 라이브러리 yml 파일(SUCCESS!)
ㄴ 트리거 발동
2. A앱, B앱 yml 파일(ERROR!)
ㄴ SPM 버전업만 실행하고 있었음 <- 역할 부족
3. Fastlane(ERROR!)
ㄴ 버전업 및 TestFlight 배포 <- 역할 과부하
빌드번호 변경, readme 업데이트, commit & push는 GitLab CI 가 담당해야하는데
Fastlane에서 일부 처리하려다 보니 문제가 생겼습니다.
그러면 2번 3번의 구조를 재구성해서
다시 구현 해볼까요!
3️⃣ A앱 yml 파일 before
stages:
- deploy
variables:
LANG: "en_US.UTF-8"
LC_ALL: "en_US.UTF-8"
deploy_testflight:
stage: deploy
tags:
- iOS
- mac
only:
- tags
- triggers
script:
- cd \(A앱)
- |
if [ -n "$KLAGO_COMMON_VERSION" ]; then
echo "KLAGO_Common 버전 업데이트: $KLAGO_COMMON_VERSION"
sed -i '' '/klago_common/{n; s/"version" : "[^"]*"/"version" : "'"$KLAGO_COMMON_VERSION"'"/g}' BcMullen.xcworkspace/xcshareddata/swiftpm/Package.resolved
fi
- xcodebuild -resolvePackageDependencies -workspace \(A앱).xcworkspace -scheme \(A앱 scheme)
- export FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120
- fastlane custom_lane
단순히 SPM 버전을 Package.resolved 에서 수정하고 resolve 후 업로드 했지만
실제로는 버전이 반영되지 않았습니다.
4️⃣ 구조 재설계
일단 필요한 요구 사항으로는
1. 기존 SPM의 태그가 발행됐을때 A앱, B앱에서 태그가 적용은 되어야 하지만
내부 검수를 진행해야되기 때문에 실제로 developer 브랜치에 Commit 되진 않아야 한다.
2. readme에 히스토리를 기록하기 위해 앱버전(빌드버전)을 기록한다..
위 조건들을 맞추기 위해서 아래의 구성으로 파이프라인 구조를 구현해보겠습니다.
SPM 라이브러리 태크 발행
↓
GitLab CI 트리거
- SPM 태그 버전 체크
- Git 설정
- 빌드번호 + 1 적용
- Readme 업데이트
- Commit & Push
- SPM 태그 버전으로 Package.swift 수정
- SPM 의존성 갱신
↓
Fastlane
- TestFlight 배포
5️⃣ A앱 yml 파일 After
script:
##################################
# 1. 버전 체크
##################################
- echo "📦 COMMON_VERSION = $COMMON_VERSION"
- |
if [ -z "$COMMON_VERSION" ]; then
echo "❌ COMMON_VERSION이 비어있습니다."
exit 1
fi
##################################
# 2. Git 설정
##################################
- git config user.name "CI Bot"
- git config user.email "cibot@example.com"
- git remote set-url origin "http://oauth2:${CI_PUSH_TOKEN}@your-gitlab.com/${CI_PROJECT_PATH}.git"
- git fetch origin
- git checkout -B developer origin/developer
- git pull --rebase origin developer
##################################
# 3. 빌드번호 (CI 기준)
##################################
- |
CURRENT_BUILD=$(grep "CURRENT_PROJECT_VERSION" MyApp/MyApp.xcodeproj/project.pbxproj | grep -o '[0-9]*' | sort -n | tail -1)
NEW_BUILD=$((CURRENT_BUILD + 1))
sed -i '' \
"s/CURRENT_PROJECT_VERSION = [0-9]*/CURRENT_PROJECT_VERSION = ${NEW_BUILD}/g" \
MyApp/MyApp.xcodeproj/project.pbxproj
echo "✅ 빌드번호: $CURRENT_BUILD → $NEW_BUILD"
echo $NEW_BUILD > .new_build
##################################
# 4. README 업데이트
##################################
- |
NEW_BUILD=$(cat .new_build)
TODAY=$(date "+%Y. %m. %d")
APP_VERSION=$(grep -A5 'LIBRARY_SEARCH_PATHS' MyApp/MyApp.xcodeproj/project.pbxproj | grep "MARKETING_VERSION" | sed -n '3p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
sed -i '' "18a\\
\\
[${APP_VERSION}(${NEW_BUILD})] - MyApp TestFlight 업로드\\
\\
${TODAY} GitLab_CI Bot\\
- Common [${COMMON_LIBRARY_VERSION}] 임시 적용\\
" README.md
##################################
# 5. commit & push (빌드번호만)
##################################
- git add MyApp/MyApp.xcodeproj/project.pbxproj
- git add README.md
- |
NEW_BUILD=$(cat .new_build)
APP_VERSION=$(grep -A5 'LIBRARY_SEARCH_PATHS' MyApp/MyApp.xcodeproj/project.pbxproj | grep "MARKETING_VERSION" | sed -n '3p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if git diff --cached --quiet; then
echo "⚠️ 변경사항 없음"
else
git commit -m "[developer] - COMMON 태그 ${COMMON_LIBRARY_VERSION} 으로 MyApp ${APP_VERSION}(${NEW_BUILD}) TestFlight 업로드"
git pull --rebase origin developer
git push origin HEAD:developer
fi
##################################
# 6. SPM 버전 수정 (exact version 적용)
##################################
- echo "🔄 common_library version → $COMMON_LIBRARY_VERSION"
- |
sed -i '' \
"/XCRemoteSwiftPackageReference \"common_library\"/{n;n;n;n;n;s/version = [0-9]*\.[0-9]*\.[0-9]*/version = ${COMMON_LIBRARY_VERSION}/;}" \
MyApp/MyApp.xcodeproj/project.pbxproj
- echo "✅ 버전 수정 완료"
- grep -A6 "XCRemoteSwiftPackageReference \"common_library\"" MyApp/MyApp.xcodeproj/project.pbxproj
##################################
# 7. SPM 의존성 갱신
##################################
- echo "🔄 Package.resolved 갱신..."
- rm -f MyApp/MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
- rm -rf ~/Library/Caches/org.swift.swiftpm/repositories/common_library-*
- rm -f ~/Library/Caches/org.swift.swiftpm/repositories/common_library-*.lock
- |
xcodebuild -resolvePackageDependencies \
-workspace MyApp/MyApp.xcworkspace \
-scheme MyApp
- |
grep "$COMMON_LIBRARY_VERSION" MyApp/MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved || {
echo "❌ SPM 버전 반영 실패"
exit 1
}
- echo "✅ SPM 갱신 & 검증 완료"
##################################
# 8. TestFlight 배포
##################################
- echo "🚀 TestFlight 배포 시작..."
- export FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120
- cd MyApp
- fastlane deploy_lane
- echo "✅ TestFlight 배포 완료"
빌드번호를 가져오는 문제를 해결하기 위해
agvtool 대신 grep + sort로 최댓값을 가져오도록 변경했고
SPM 버전 문제를 해결하기 위해
XCRemoteSwiftPackageReference 버전도 함께 수정했습니다.
6️⃣ 마무리
CI/CD를 처음 도입하다 보니 역할 분배가 명확하지 않았기 때문에 이런 문제가 생긴 것 같습니다.
수정 후 태그 발행해서 업로드 테스트 해보니 정상적으로 작동했고,
불필요한 반복작업을 줄이는데 많이 도움이 된것 같습니다.
Before
SPM 수정 -> 커밋 & 푸시 & 태그 발행 -> A앱 태그 적용 및 빌드 & 아카이브 -> B앱 태그 적용 및 빌드 & 아카이브
After
SPM 수정 -> 커밋 & 푸시 & 태그 발행 -> 점심을 먹거나 카페 갔다오기ㅋㅋ
CI/CD의 핵심은 사람이 할일을 줄여주는 것이라는걸 다시 한번 느꼈습니다.
긴 글이지만 읽어주셔서 감사합니다~
그럼 다들 즐코딩하세요. 그럼 이만~
'Swift' 카테고리의 다른 글
| [iOS] GitLab CI + Fastlane CI/CD 적용기 - 3 (0) | 2026.03.13 |
|---|---|
| [iOS] GitLab CI + Fastlane CI/CD 적용기 - 2 (0) | 2026.03.13 |
| [iOS] GitLab CI + Fastlane CI/CD 적용기 - 1 (0) | 2026.03.12 |
| [iOS][Swift] LocalAuthentication Face ID & Touch ID 생체인식 간단하게 알아보기 (0) | 2024.08.03 |
| [iOS][Swift] shake motion 알아보기 (0) | 2023.08.22 |